diff --git a/MIGRATION_SWAGGER_TODO.md b/MIGRATION_SWAGGER_TODO.md new file mode 100644 index 0000000..837d47c --- /dev/null +++ b/MIGRATION_SWAGGER_TODO.md @@ -0,0 +1,19 @@ + +# Swagger Path Updates Required + +The following Swagger YAML files need path updates: + +## skydive.yaml +- Change: /api/v1/ → /api/skydive/ +- Affected routes: /jumps, /aeronefs, /dropzones, /canopies, /pages, /qcm, /applications + +## cms.yaml +- Change: /api/v2/ → /api/cms/ +- Affected routes: /articles, /comments, /products, /tags, /profiles, /user + +## herowars.yaml +- Change: /api/v3/ → /api/herowars/ +- Affected routes: /clans, /members +- Note: Add herowars endpoint from former /api/v2/herowars + +Update in: src/config/swagger.js if paths are hardcoded. diff --git a/scripts/migrate-to-domain-structure.js b/scripts/migrate-to-domain-structure.js new file mode 100644 index 0000000..17cd868 --- /dev/null +++ b/scripts/migrate-to-domain-structure.js @@ -0,0 +1,481 @@ +#!/usr/bin/env node + +/** + * Migration Script: Restructure API from v1/v2/v3 to Domain-Based (skydive/cms/herowars) + * + * This script automates the transformation: + * - /api/v1/* → /api/skydive/* (Skydive domain) + * - /api/v2/* → /api/cms/* (CMS domain) + * - /api/v3/* → /api/herowars/* (Hero Wars domain) + * + * Usage: node scripts/migrate-to-domain-structure.js [--dry-run] [--skip-git] + */ + +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +// Colors for console output +const colors = { + reset: '\x1b[0m', + green: '\x1b[32m', + yellow: '\x1b[33m', + red: '\x1b[31m', + cyan: '\x1b[36m', + blue: '\x1b[34m', +}; + +const log = { + info: (msg) => console.log(`${colors.cyan}ℹ${colors.reset} ${msg}`), + success: (msg) => console.log(`${colors.green}✓${colors.reset} ${msg}`), + warn: (msg) => console.log(`${colors.yellow}⚠${colors.reset} ${msg}`), + error: (msg) => console.log(`${colors.red}✗${colors.reset} ${msg}`), + section: (msg) => console.log(`\n${colors.blue}━━━ ${msg} ━━━${colors.reset}\n`), +}; + +// Parse command line arguments +const args = process.argv.slice(2); +const dryRun = args.includes('--dry-run'); +const skipGit = args.includes('--skip-git'); + +const projectRoot = path.resolve(__dirname, '..'); +const routesDir = path.join(projectRoot, 'src/routes/api'); + +// Domain mapping +const domainMap = { + v1: { + domain: 'skydive', + routes: { + '/applications': 'applications', + '/aeronefs': 'aeronefs', + '/canopies': 'canopies', + '/dropzones': 'dropzones', + '/jumps': 'jumps', + '/pages': 'pages', + '/profiles': 'profiles', + '/qcm': 'qcm', + }, + }, + v2: { + domain: 'cms', + routes: { + '/user': 'user.routes', + '/articles': 'articles.routes', + '/comments': 'comments.routes', + '/product': 'product.routes', + '/products': 'products.routes', + '/profiles': 'profiles.routes', + '/tags': 'tags.routes', + }, + // herowars will be moved to v3 + }, + v3: { + domain: 'herowars', + routes: { + '/clans': 'clans.routes', + '/members': 'members.routes', + '/herowars': 'herowars.routes', // to be moved from v2 + }, + }, +}; + +/** + * Phase 1: Create new domain directories + */ +function createDomainDirectories() { + log.section('Phase 1: Creating domain directories'); + + Object.values(domainMap).forEach(({ domain }) => { + const domainPath = path.join(routesDir, domain); + if (!fs.existsSync(domainPath)) { + if (!dryRun) { + fs.mkdirSync(domainPath, { recursive: true }); + } + log.success(`Created directory: ${domain}/`); + } else { + log.warn(`Directory already exists: ${domain}/`); + } + }); +} + +/** + * Phase 2: Copy/move route files to new domain directories + */ +function migrateRouteFiles() { + log.section('Phase 2: Migrating route files'); + + // Handle v1 → skydive and v2 → cms + ['v1', 'v2', 'v3'].forEach((version) => { + const { domain, routes } = domainMap[version]; + const oldVersionDir = path.join(routesDir, version); + const newDomainDir = path.join(routesDir, domain); + + log.info(`\nMigrating ${version} → ${domain}:`); + + Object.entries(routes).forEach(([routePath, fileName]) => { + const extensions = ['', '.js']; + let fileFound = false; + + for (const ext of extensions) { + const oldFilePath = path.join(oldVersionDir, fileName + ext); + if (fs.existsSync(oldFilePath)) { + const newFilePath = path.join(newDomainDir, path.basename(oldFilePath)); + + if (!dryRun) { + const content = fs.readFileSync(oldFilePath, 'utf8'); + fs.writeFileSync(newFilePath, content); + } + + log.success(` Copied: ${fileName + ext} → ${domain}/`); + fileFound = true; + break; + } + } + + if (!fileFound) { + log.warn(` File not found: ${fileName}(.js)?`); + } + }); + }); + + // Handle herowars.routes.js move from v2 to v3 + const herowarsFile = path.join(routesDir, 'v2', 'herowars.routes.js'); + if (fs.existsSync(herowarsFile)) { + const newPath = path.join(routesDir, 'herowars', 'herowars.routes.js'); + if (!dryRun) { + const content = fs.readFileSync(herowarsFile, 'utf8'); + fs.writeFileSync(newPath, content); + } + log.success(` Moved herowars.routes.js from v2 → herowars/`); + } +} + +/** + * Phase 3: Create new index.js files for each domain + */ +function createIndexFiles() { + log.section('Phase 3: Creating index files for domains'); + + Object.entries(domainMap).forEach(([version, { domain, routes }]) => { + const indexPath = path.join(routesDir, domain, 'index.js'); + + // Filter out herowars from v2 routes if it's the cms domain + const filteredRoutes = + domain === 'cms' + ? Object.entries(routes).filter(([, fileName]) => !fileName.includes('herowars')) + : Object.entries(routes); + + const routeImports = filteredRoutes + .map(([routePath, fileName]) => `routes.use('${routePath}', require('./${fileName}'));`) + .join('\n'); + + const indexContent = `var routes = require('express').Router(); + +${routeImports} + +routes.use(function (err, req, res, next) { + if (err.name === 'ValidationError') { + return res.status(422).json({ + errors: Object.keys(err.errors).reduce(function (errors, key) { + errors[key] = err.errors[key].message; + return errors; + }, {}) + }); + } + return next(err); +}); + +module.exports = routes; +`; + + if (!dryRun) { + fs.writeFileSync(indexPath, indexContent); + } + + log.success(`Created: ${domain}/index.js`); + }); +} + +/** + * Phase 4: Update src/routes/api/index.js + */ +function updateApiIndex() { + log.section('Phase 4: Updating src/routes/api/index.js'); + + const apiIndexPath = path.join(routesDir, 'index.js'); + const newContent = `var routes = require('express').Router(); + +routes.use('/skydive', require('./skydive')); +routes.use('/cms', require('./cms')); +routes.use('/herowars', require('./herowars')); + +module.exports = routes; +`; + + if (!dryRun) { + fs.writeFileSync(apiIndexPath, newContent); + } + + log.success(`Updated routes/api/index.js`); +} + +/** + * Phase 5: Update app.js for Swagger documentation + */ +function updateSwaggerPaths() { + log.section('Phase 5: Creating Swagger path update instructions'); + + const swaggerUpdateInstructions = ` +# Swagger Path Updates Required + +The following Swagger YAML files need path updates: + +## skydive.yaml +- Change: /api/v1/ → /api/skydive/ +- Affected routes: /jumps, /aeronefs, /dropzones, /canopies, /pages, /qcm, /applications + +## cms.yaml +- Change: /api/v2/ → /api/cms/ +- Affected routes: /articles, /comments, /products, /tags, /profiles, /user + +## herowars.yaml +- Change: /api/v3/ → /api/herowars/ +- Affected routes: /clans, /members +- Note: Add herowars endpoint from former /api/v2/herowars + +Update in: src/config/swagger.js if paths are hardcoded. +`; + + if (!dryRun) { + fs.writeFileSync(path.join(projectRoot, 'MIGRATION_SWAGGER_TODO.md'), swaggerUpdateInstructions); + } + + log.success(`Created MIGRATION_SWAGGER_TODO.md`); +} + +/** + * Phase 6: Create Swagger YAML files for new structure + */ +function createSwaggerYamlFiles() { + log.section('Phase 6: Updating Swagger YAML structure'); + + const swaggerDir = path.join(projectRoot, 'src/swagger'); + + // Read existing YAML files and update paths + const updateSwaggerFile = (oldName, newName, newBasePath) => { + const oldPath = path.join(swaggerDir, oldName); + if (fs.existsSync(oldPath)) { + let content = fs.readFileSync(oldPath, 'utf8'); + + // Update paths in YAML + content = content.replace(/\/api\/v\d+/g, `/api/${newBasePath}`); + + const newPath = path.join(swaggerDir, newName); + if (!dryRun) { + fs.writeFileSync(newPath, content); + } + log.success(`Created/Updated: ${newName}`); + } + }; + + // Note: This depends on what swagger files exist + log.warn(`Manual Swagger file updates may be needed`); +} + +/** + * Phase 7: Git operations + */ +function gitOperations() { + log.section('Phase 7: Git operations'); + + if (skipGit) { + log.warn('Skipping git operations (--skip-git flag)'); + return; + } + + try { + if (!dryRun) { + // Stage new files + execSync('git add -A', { cwd: projectRoot, stdio: 'pipe' }); + log.success(`Staged changes with git`); + + // Create commit + const commitMessage = 'refactor: restructure API routes from v1/v2/v3 to domain-based (skydive/cms/herowars)'; + execSync(`git commit -m "${commitMessage}"`, { cwd: projectRoot, stdio: 'pipe' }); + log.success(`Created git commit`); + } else { + log.info(`Would stage and commit changes to git`); + } + } catch (err) { + log.error(`Git operation failed: ${err.message}`); + } +} + +/** + * Phase 8: Post-migration validation + */ +function validateMigration() { + log.section('Phase 8: Validation'); + + Object.entries(domainMap).forEach(([version, { domain, routes }]) => { + const domainPath = path.join(routesDir, domain); + const indexPath = path.join(domainPath, 'index.js'); + + // Check domain directory exists + if (fs.existsSync(domainPath)) { + log.success(`✓ Domain directory exists: ${domain}/`); + } else { + log.error(`✗ Domain directory missing: ${domain}/`); + } + + // Check index.js exists + if (fs.existsSync(indexPath)) { + log.success(`✓ Index file exists: ${domain}/index.js`); + } else { + log.error(`✗ Index file missing: ${domain}/index.js`); + } + + // Check route files + const routeFiles = Object.values(routes); + routeFiles.forEach((fileName) => { + const filePath = path.join(domainPath, fileName + '.js'); + if (fs.existsSync(filePath)) { + log.success(`✓ Route file exists: ${domain}/${fileName}.js`); + } else { + log.warn(`⚠ Route file not found: ${domain}/${fileName}.js`); + } + }); + }); + + // Check main api index + const apiIndexPath = path.join(routesDir, 'index.js'); + if (fs.existsSync(apiIndexPath)) { + log.success(`✓ Main API index exists: routes/api/index.js`); + } else { + log.error(`✗ Main API index missing: routes/api/index.js`); + } +} + +/** + * Phase 9: Generate migration report + */ +function generateReport() { + log.section('Phase 9: Migration Summary'); + + const report = ` +═══════════════════════════════════════════════════════════════════════════════ + API STRUCTURE MIGRATION REPORT +═══════════════════════════════════════════════════════════════════════════════ + +MIGRATION: v1/v2/v3 → Domain-Based (skydive/cms/herowars) + +OLD STRUCTURE: + └── routes/api/ + ├── v1/ (Skydive routes) + ├── v2/ (CMS + misc routes) + └── v3/ (Hero Wars routes) + +NEW STRUCTURE: + └── routes/api/ + ├── skydive/ + ├── cms/ + └── herowars/ + +ROUTE MAPPINGS: + ✓ /api/v1/* → /api/skydive/* + - applications, aeronefs, canopies, dropzones, jumps, pages, profiles, qcm + + ✓ /api/v2/* → /api/cms/* + - user, articles, comments, product, products, profiles, tags + + ✓ /api/v3/* → /api/herowars/* + - clans, members, herowars (moved from v2) + +FILES CREATED/UPDATED: + ✓ src/routes/api/skydive/index.js + ✓ src/routes/api/cms/index.js + ✓ src/routes/api/herowars/index.js + ✓ src/routes/api/index.js + ✓ All route files copied to appropriate domains + +MANUAL STEPS REQUIRED: + + 1. Update Swagger/OpenAPI documentation: + - Review MIGRATION_SWAGGER_TODO.md + - Update path prefixes in src/swagger/*.yaml files + - Update paths in src/config/swagger.js if needed + + 2. Update app.js (if direct version references exist): + - Search for 'v1', 'v2', 'v3' strings + - Replace with domain names where appropriate + + 3. Update tests and client calls: + - Replace /api/v1/ with /api/skydive/ + - Replace /api/v2/ with /api/cms/ + - Replace /api/v3/ with /api/herowars/ + + 4. Update any documentation: + - README.md API endpoints section + - API documentation and guides + + 5. Test all endpoints: + - Verify each domain routes work correctly + - Check authentication/authorization still functions + - Validate error handling + +OPTIONAL: Old v1/v2/v3 directories can be removed after verification: + rm -rf src/routes/api/v1 + rm -rf src/routes/api/v2 + rm -rf src/routes/api/v3 + +═══════════════════════════════════════════════════════════════════════════════ +`; + + console.log(report); + + if (!dryRun) { + fs.writeFileSync(path.join(projectRoot, 'MIGRATION_REPORT.md'), report); + } +} + +/** + * Main execution + */ +function main() { + console.clear(); + console.log(` +${colors.cyan}╔══════════════════════════════════════════════════════════╗ +║ API STRUCTURE MIGRATION: v1/v2/v3 → Domain-Based ║ +║ (skydive / cms / herowars) ║ +╚══════════════════════════════════════════════════════════════╝${colors.reset} + `); + + if (dryRun) { + log.warn(`DRY RUN MODE - No changes will be made`); + } + + try { + createDomainDirectories(); + migrateRouteFiles(); + createIndexFiles(); + updateApiIndex(); + updateSwaggerPaths(); + createSwaggerYamlFiles(); + gitOperations(); + validateMigration(); + generateReport(); + + log.success(`\n✓ Migration script completed successfully!\n`); + + if (dryRun) { + log.info(`To apply changes, run: node scripts/migrate-to-domain-structure.js\n`); + } else { + log.info(`To rollback changes, run: git reset --soft HEAD~1 && git restore .\n`); + } + } catch (error) { + log.error(`Migration failed: ${error.message}`); + process.exit(1); + } +} + +main(); diff --git a/src/database/models/mongo/Aeronef.js b/src/database/models/mongo/Aeronef.js index 7f261b1..c658bde 100644 --- a/src/database/models/mongo/Aeronef.js +++ b/src/database/models/mongo/Aeronef.js @@ -5,7 +5,7 @@ var AeronefSchema = new mongoose.Schema({ slug: { type: String, lowercase: true, unique: true }, aeronef: String, imat: String, - author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } + author: { type: mongoose.Schema.Types.UUID, ref: 'User' } }, {timestamps: true, toJSON: {virtuals: true}}); AeronefSchema.pre('validate', function (next) { diff --git a/src/database/models/mongo/Application.js b/src/database/models/mongo/Application.js index 794e04c..bf6b568 100644 --- a/src/database/models/mongo/Application.js +++ b/src/database/models/mongo/Application.js @@ -8,7 +8,7 @@ var ApplicationSchema = new mongoose.Schema({ slug: { type: String, lowercase: true, unique: true }, title: String, description: String, - author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } + author: { type: mongoose.Schema.Types.UUID, ref: 'User' } }, { timestamps: true, toJSON: { virtuals: true } }); ApplicationSchema.plugin(uniqueValidator, { message: 'is already taken' }); diff --git a/src/database/models/mongo/Article.js b/src/database/models/mongo/Article.js index c7e8ce3..dd10f31 100755 --- a/src/database/models/mongo/Article.js +++ b/src/database/models/mongo/Article.js @@ -12,7 +12,7 @@ var ArticleSchema = new mongoose.Schema({ membre: String, // membre: Membre.schema, // membre: { type: mongoose.Schema.Types.ObjectId, ref: 'Membre' }, - author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, + author: { type: mongoose.Schema.Types.UUID, ref: 'User' }, comments: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Comment' }], favoritesCount: { type: Number, default: 0 }, tagList: [{ type: String }] diff --git a/src/database/models/mongo/Canopy.js b/src/database/models/mongo/Canopy.js index 312fb6c..4ec8498 100644 --- a/src/database/models/mongo/Canopy.js +++ b/src/database/models/mongo/Canopy.js @@ -5,7 +5,7 @@ var CanopySchema = new mongoose.Schema({ slug: { type: String, lowercase: true, unique: true }, voile: String, taille: Number, - author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } + author: { type: mongoose.Schema.Types.UUID, ref: 'User' } }, {timestamps: true, toJSON: {virtuals: true}}); CanopySchema.pre('validate', function (next) { diff --git a/src/database/models/mongo/Comment.js b/src/database/models/mongo/Comment.js index 10db85d..cfabe8b 100755 --- a/src/database/models/mongo/Comment.js +++ b/src/database/models/mongo/Comment.js @@ -2,7 +2,7 @@ var mongoose = require('mongoose'); var CommentSchema = new mongoose.Schema({ body: String, - author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, + author: { type: mongoose.Schema.Types.UUID, ref: 'User' }, article: { type: mongoose.Schema.Types.ObjectId, ref: 'Article' } }, { timestamps: true, toJSON: { virtuals: true } }); diff --git a/src/database/models/mongo/Dropzone.js b/src/database/models/mongo/Dropzone.js index 2591fa2..123b893 100644 --- a/src/database/models/mongo/Dropzone.js +++ b/src/database/models/mongo/Dropzone.js @@ -5,7 +5,7 @@ var DropzoneSchema = new mongoose.Schema({ slug: { type: String, lowercase: true, unique: true }, lieu: String, oaci: String, - author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } + author: { type: mongoose.Schema.Types.UUID, ref: 'User' } }, {timestamps: true, toJSON: {virtuals: true}}); DropzoneSchema.pre('validate', function (next) { diff --git a/src/database/models/mongo/Jump.js b/src/database/models/mongo/Jump.js index 714c709..6e5d518 100644 --- a/src/database/models/mongo/Jump.js +++ b/src/database/models/mongo/Jump.js @@ -25,7 +25,7 @@ var JumpSchema = new mongoose.Schema({ video: String, files: [{ type: mongoose.Schema.Types.ObjectId, ref: 'JumpFile' }], x2data: { type: mongoose.Schema.Types.ObjectId, ref: 'X2DataLog' }, - author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } + author: { type: mongoose.Schema.Types.UUID, ref: 'User' } }, {timestamps: true, toJSON: {virtuals: true}}); JumpSchema.plugin(uniqueValidator, { message: 'is already taken' }); diff --git a/src/database/models/mongo/JumpFile.js b/src/database/models/mongo/JumpFile.js index 7985a89..05ffea9 100644 --- a/src/database/models/mongo/JumpFile.js +++ b/src/database/models/mongo/JumpFile.js @@ -11,7 +11,7 @@ var JumpFileSchema = new mongoose.Schema({ enum : ['csv', 'image', 'kml', 'video'], default: 'video' }, - author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } + author: { type: mongoose.Schema.Types.UUID, ref: 'User' } }, {timestamps: true, toJSON: {virtuals: true}}); JumpFileSchema.plugin(uniqueValidator, { message: 'is already taken' }); diff --git a/src/database/models/mongo/User.js b/src/database/models/mongo/User.js index edd5315..8efb8b3 100644 --- a/src/database/models/mongo/User.js +++ b/src/database/models/mongo/User.js @@ -1,10 +1,13 @@ var mongoose = require('mongoose'); var uniqueValidator = require('mongoose-unique-validator'); var crypto = require('crypto'); +const { v4: uuidv4 } = require('uuid'); var jwt = require('jsonwebtoken'), config = require('../../../config'); var UserSchema = new mongoose.Schema({ + //_id: { type: String, default: uuidv4() }, + _id: { type: mongoose.Schema.Types.UUID }, email: { type: String, lowercase: true, unique: true, required: [true, "email can't be blank"], match: [/\S+@\S+\.\S+/, 'is invalid'], index: true }, username: { type: String, lowercase: true, unique: true, required: [true, "username can't be blank"], match: [/^[a-zA-Z0-9_]+$/, 'is invalid'], index: true }, role: String, @@ -16,7 +19,7 @@ var UserSchema = new mongoose.Schema({ image: String, bg_image: String, favorites: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Product' }], - following: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }], + following: [{ type: mongoose.Schema.Types.UUID, ref: 'User' }], hash: String, salt: String }, { timestamps: true, toJSON: { virtuals: true } }); diff --git a/src/database/models/mongo/X2DataLog.js b/src/database/models/mongo/X2DataLog.js index 7a626d9..f498b32 100644 --- a/src/database/models/mongo/X2DataLog.js +++ b/src/database/models/mongo/X2DataLog.js @@ -60,7 +60,7 @@ var X2DataLogSchema = new mongoose.Schema({ ], date: String, createdOn: String, - author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } + author: { type: mongoose.Schema.Types.UUID, ref: 'User' } }, {timestamps: true, toJSON: {virtuals: true}}); X2DataLogSchema.methods.toJSONFor = function() { diff --git a/src/database/models/mongo/herowars/Member.js b/src/database/models/mongo/herowars/Member.js index 12e31c6..fa60cce 100644 --- a/src/database/models/mongo/herowars/Member.js +++ b/src/database/models/mongo/herowars/Member.js @@ -47,7 +47,7 @@ var HWMemberSchemaOld = new mongoose.Schema({ scoreGifts: Number, warGifts: Number, rewards: Number, - author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } + author: { type: mongoose.Schema.Types.UUID, ref: 'User' } }, {timestamps: true, toJSON: {virtuals: true}}); */ var HWMemberSchema = new mongoose.Schema({ diff --git a/src/routes/api/cms/articles.routes.js b/src/routes/api/cms/articles.routes.js new file mode 100644 index 0000000..7b670ca --- /dev/null +++ b/src/routes/api/cms/articles.routes.js @@ -0,0 +1,25 @@ +const express = require('express'); +const { + articlesFeed, + createArticle, + deleteArticle, + getArticle, + getArticles, + updateArticle, + addFavoriteArticle, + deleteFavoriteArticle +} = require('../../../controllers/articles.controller'); +const auth = require('../../../middlewares/auth'); + +const articleRouter = express.Router(); + +articleRouter.get('/', auth.optional, getArticles); +articleRouter.post('/', auth.required, createArticle); +articleRouter.get('/feed', auth.required, articlesFeed); +articleRouter.get('/:slug', auth.optional, getArticle); +articleRouter.put('/:slug', auth.required, updateArticle); +articleRouter.delete('/:slug', auth.required, deleteArticle); +articleRouter.post('/:slug/favorite', auth.required, addFavoriteArticle); +articleRouter.delete('/:slug/favorite', auth.required, deleteFavoriteArticle); + +module.exports = articleRouter; diff --git a/src/routes/api/cms/comments.routes.js b/src/routes/api/cms/comments.routes.js new file mode 100644 index 0000000..0c5ca16 --- /dev/null +++ b/src/routes/api/cms/comments.routes.js @@ -0,0 +1,10 @@ +const express = require('express'); +const { createComment, getAllComments } = require('../../../controllers/comments.controller'); +const auth = require('../../../middlewares/auth'); + +const commentRouter = express.Router(); + +commentRouter.post('/', auth.required, createComment); +commentRouter.get('/', auth.optional, getAllComments); + +module.exports = commentRouter; diff --git a/src/routes/api/cms/index.js b/src/routes/api/cms/index.js new file mode 100644 index 0000000..4f0c270 --- /dev/null +++ b/src/routes/api/cms/index.js @@ -0,0 +1,23 @@ +var routes = require('express').Router(); + +routes.use('/user', require('./user.routes')); +routes.use('/articles', require('./articles.routes')); +routes.use('/comments', require('./comments.routes')); +routes.use('/product', require('./product.routes')); +routes.use('/products', require('./products.routes')); +routes.use('/profiles', require('./profiles.routes')); +routes.use('/tags', require('./tags.routes')); + +routes.use(function (err, req, res, next) { + if (err.name === 'ValidationError') { + return res.status(422).json({ + errors: Object.keys(err.errors).reduce(function (errors, key) { + errors[key] = err.errors[key].message; + return errors; + }, {}) + }); + } + return next(err); +}); + +module.exports = routes; diff --git a/src/routes/api/cms/product.routes.js b/src/routes/api/cms/product.routes.js new file mode 100644 index 0000000..997aed2 --- /dev/null +++ b/src/routes/api/cms/product.routes.js @@ -0,0 +1,23 @@ +const express = require('express'); +const { + getProduct, + getProducts, + createProduct, + addFavoriteProduct, + deleteFavoriteProduct, + deleteProduct, + updateProduct, +} = require('../../../controllers/product.controller'); +const auth = require('../../../middlewares/auth'); + +const productRouter = express.Router(); + +productRouter.get('/', auth.optional, getProducts); +productRouter.post('/', auth.required, createProduct); +productRouter.get('/:slug', auth.optional, getProduct); +productRouter.put('/:slug', auth.required, updateProduct); +productRouter.delete('/:slug', auth.required, deleteProduct); +productRouter.post('/:slug/favorite', auth.required, addFavoriteProduct); +productRouter.delete('/:slug/favorite', auth.required, deleteFavoriteProduct); + +module.exports = productRouter; diff --git a/src/routes/api/cms/products.routes.js b/src/routes/api/cms/products.routes.js new file mode 100644 index 0000000..7603457 --- /dev/null +++ b/src/routes/api/cms/products.routes.js @@ -0,0 +1,15 @@ +const express = require('express'); +const { + getProducts, + getProductsByCategory, + productsFeed +} = require('../../../controllers/products.controller'); +const auth = require('../../../middlewares/auth'); + +const productsRouter = express.Router(); + +productsRouter.get('/', auth.optional, getProducts); +productsRouter.get('/feed', auth.required, productsFeed); +productsRouter.get('/:category', auth.optional, getProductsByCategory); + +module.exports = productsRouter; diff --git a/src/routes/api/cms/profiles.routes.js b/src/routes/api/cms/profiles.routes.js new file mode 100644 index 0000000..17c8346 --- /dev/null +++ b/src/routes/api/cms/profiles.routes.js @@ -0,0 +1,12 @@ +const express = require('express'); +const {addFollowUser, deleteFollowUser, getParamUsername, getProfile} = require('../../../controllers/profiles.controller'); +const auth = require('../../../middlewares/auth'); + +const profilesRouter = express.Router(); + +profilesRouter.param('username', getParamUsername); +profilesRouter.get('/:username', auth.required, getProfile); +profilesRouter.post('/:username/follow', auth.required, addFollowUser); +profilesRouter.delete('/:username/follow', auth.required, deleteFollowUser); + +module.exports = profilesRouter; \ No newline at end of file diff --git a/src/routes/api/cms/tags.routes.js b/src/routes/api/cms/tags.routes.js new file mode 100644 index 0000000..4b26e24 --- /dev/null +++ b/src/routes/api/cms/tags.routes.js @@ -0,0 +1,11 @@ +const express = require('express'); +const { createTag, deleteTag, getAllTags } = require('../../../controllers/tags.controller'); +const auth = require('../../../middlewares/auth'); + +const tagRouter = express.Router(); + +tagRouter.post('/', auth.required, createTag); +tagRouter.get('/', auth.optional, getAllTags); +tagRouter.delete('/:name', auth.required, deleteTag); + +module.exports = tagRouter; diff --git a/src/routes/api/cms/user.routes.js b/src/routes/api/cms/user.routes.js new file mode 100644 index 0000000..2e3743f --- /dev/null +++ b/src/routes/api/cms/user.routes.js @@ -0,0 +1,22 @@ +const express = require('express'); +const { + authenticate, createUser, getUser, loginAsUser, + updateUser, updateUserEmail, updateUserPassword, updateUserRole, updateUserUsername +} = require('../../../controllers/user.controller'); +const auth = require('../../../middlewares/auth'); + +const userRouter = express.Router(); + +userRouter.get('/', auth.required, getUser); +userRouter.put('/', auth.required, updateUser); +userRouter.post('/', auth.optional, createUser); +userRouter.get('/authenticate', auth.optional, authenticate); +userRouter.post('/login', auth.optional, loginAsUser); +//userRouter.post('/register', auth.optional, createUser); +//userRouter.get('/users', auth.required, getAllUsers); +userRouter.put('/update/email', auth.required, updateUserEmail); +userRouter.put('/update/password', auth.required, updateUserPassword); +userRouter.put('/update/role', auth.required, updateUserRole); +userRouter.put('/update/username', auth.required, updateUserUsername); + +module.exports = userRouter; diff --git a/src/routes/api/herowars/clans.routes.js b/src/routes/api/herowars/clans.routes.js new file mode 100644 index 0000000..eb7b35f --- /dev/null +++ b/src/routes/api/herowars/clans.routes.js @@ -0,0 +1,14 @@ +const express = require('express'); +const { createClan, deleteClan, getAllClans, getClan, updateClan } = require('../../../controllers/clans.controller'); +const auth = require('../../../middlewares/auth'); + +const clanRouter = express.Router(); + +//clanRouter.param('clanId', getClan); +clanRouter.get('/', auth.optional, getAllClans); +clanRouter.post('/', auth.optional, createClan); +clanRouter.get('/:clanId', auth.required, getClan); +clanRouter.put('/:clanId', auth.required, updateClan); +clanRouter.delete('/:clanId', auth.required, deleteClan); + +module.exports = clanRouter; diff --git a/src/routes/api/herowars/herowars.routes.js b/src/routes/api/herowars/herowars.routes.js new file mode 100644 index 0000000..e027e70 --- /dev/null +++ b/src/routes/api/herowars/herowars.routes.js @@ -0,0 +1,13 @@ +const express = require('express'); +const { createMember, getAllMembers, getMember, updateMember } = require('../../../controllers/herowars.controller'); +const auth = require('../../../middlewares/auth'); + +const herowarsRouter = express.Router(); + +herowarsRouter.param('member', getMember); +herowarsRouter.get('/:member', auth.required, getMember); +herowarsRouter.get('/', auth.required, getAllMembers); +herowarsRouter.post('/', auth.required, createMember); +herowarsRouter.put('/', auth.required, updateMember); + +module.exports = herowarsRouter; diff --git a/src/routes/api/herowars/index.js b/src/routes/api/herowars/index.js new file mode 100644 index 0000000..48eb572 --- /dev/null +++ b/src/routes/api/herowars/index.js @@ -0,0 +1,19 @@ +var routes = require('express').Router(); + +routes.use('/clans', require('./clans.routes')); +routes.use('/members', require('./members.routes')); +routes.use('/herowars', require('./herowars.routes')); + +routes.use(function (err, req, res, next) { + if (err.name === 'ValidationError') { + return res.status(422).json({ + errors: Object.keys(err.errors).reduce(function (errors, key) { + errors[key] = err.errors[key].message; + return errors; + }, {}) + }); + } + return next(err); +}); + +module.exports = routes; diff --git a/src/routes/api/herowars/members.routes.js b/src/routes/api/herowars/members.routes.js new file mode 100644 index 0000000..0e04f3c --- /dev/null +++ b/src/routes/api/herowars/members.routes.js @@ -0,0 +1,14 @@ +const express = require('express'); +const { createMember, deleteMember, getAllMembers, getMember, updateMember } = require('../../../controllers/members.controller'); +const auth = require('../../../middlewares/auth'); + +const memberRouter = express.Router(); + +//memberRouter.param('memberId', getMember); +memberRouter.get('/', auth.required, getAllMembers); +memberRouter.post('/', auth.required, createMember); +memberRouter.get('/:memberId', auth.required, getMember); +memberRouter.put('/:memberId', auth.required, updateMember); +memberRouter.delete('/:memberId', auth.required, deleteMember); + +module.exports = memberRouter; diff --git a/src/routes/api/index.js b/src/routes/api/index.js index 938053a..0514d09 100644 --- a/src/routes/api/index.js +++ b/src/routes/api/index.js @@ -1,40 +1,7 @@ var routes = require('express').Router(); -routes.use('/v1', require('./v1')); -routes.use('/v2', require('./v2')); -routes.use('/v3', require('./v3')); -/* -//routes.use('/profiles', require('./v1/profiles')); -routes.use('/applications', require('./v1/applications')); -routes.use('/aeronefs', require('./v1/aeronefs')); -routes.use('/canopies', require('./v1/canopies')); -routes.use('/dropzones', require('./v1/dropzones')); -routes.use('/jumps', require('./v1/jumps')); -routes.use('/pages', require('./v1/pages')); -routes.use('/qcm', require('./v1/qcm')); +routes.use('/skydive', require('./skydive')); +routes.use('/cms', require('./cms')); +routes.use('/herowars', require('./herowars')); -routes.use('/user', require('./v2/user.routes')); -routes.use('/articles', require('./v2/articles.routes')); -routes.use('/comments', require('./v2/comments.routes')); -routes.use('/product', require('./v2/product.routes')); -routes.use('/products', require('./v2/products.routes')); -routes.use('/profiles', require('./v2/profiles.routes')); -routes.use('/tags', require('./v2/tags.routes')); - -routes.use('/clans', require('./v3/clans.routes')); -routes.use('/members', require('./v3/members.routes')); - -routes.use(function (err, req, res, next) { - if (err.name === 'ValidationError') { - return res.status(422).json({ - errors: Object.keys(err.errors).reduce(function (errors, key) { - errors[key] = err.errors[key].message; - return errors; - }, {}) - }); - } - return next(err); -}); -*/ - -module.exports = routes; \ No newline at end of file +module.exports = routes; diff --git a/src/routes/api/skydive/aeronefs.js b/src/routes/api/skydive/aeronefs.js new file mode 100644 index 0000000..7db47c7 --- /dev/null +++ b/src/routes/api/skydive/aeronefs.js @@ -0,0 +1,143 @@ +var router = require('express').Router(), + mongoose = require('mongoose'), + Jump = mongoose.model('Jump'), + User = mongoose.model('User'); +const auth = require('../../../middlewares/auth'), + queries = require('../../queries'); + + +router.get('/allByImat', auth.required, function (req, res, next) { + let limit = 25; + let offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + let query = queries.getAeronefsByImatQuery(user._id.toString()); + Promise.all([ + Jump.aggregate(query) + .skip(Number(offset)) + .limit(Number(limit)) + .exec() + //Aeronef.count(query) + ]).then(function (results) { + let aeronefs = results[0]; + //let aeronefsCount = results[1]; + return res.json({ + aeronefs: aeronefs.map(function (data) { + return { aeronef: data._id.aeronef, imat: data._id.imat, count: data.count }; + }), + aeronefsCount: aeronefs.length + }); + /* + return res.json({ + aeronefs: aeronefs.map(function (aeronef) { + return aeronef.toJSONFor(user); + }), + aeronefsCount: aeronefsCount + }); + */ + }).catch(next); + }).catch(next); +}); + +router.get('/allByImatByYear', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + let limit = 50; + let offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + let query = queries.getAeronefsByImatByYearQuery(user._id.toString()); + + Jump.aggregate(query) + .skip(Number(offset)) + .limit(Number(limit)) + .exec() + .then(function (result) { + let aeronefs = result.map(function (data) { + return { aeronef: data._id.aeronef, imat: data._id.imat, year: data._id.year, count: data.count }; + }); + return res.json({ + aeronefs: aeronefs, + aeronefsCount: aeronefs.length + }); + }).catch(next); + }).catch(next); + /* + let limit = 50; + let offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + let query = [ + { + '$match': { + '$expr': { + '$eq': [ '$author' , { '$toObjectId': user._id.toString() } ] + } + } + }, { + '$project': { + '_id': 0, + 'aeronef': 1, + 'imat': 1, + 'year': { + '$year': '$date' + } + } + }, { + '$group': { + '_id': { + 'aeronef': '$aeronef', + 'imat': '$imat', + 'year': '$year' + }, + 'count': { + '$sum': 1 + } + } + }, { + '$sort': { + '_id': 1 + } + } + ]; + Promise.all([ + req.payload ? User.findById(req.payload.id) : null, + Jump.aggregate(query) + .skip(Number(offset)) + .limit(Number(limit)) + .exec() + ]).then(function (results) { + let user = results[0]; + let aeronefs = results[1]; + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + return res.json({ + aeronefs: aeronefs, + aeronefsCount: aeronefs.length + }); + }).catch(next); + */ + +}); + +module.exports = router; diff --git a/src/routes/api/skydive/applications.js b/src/routes/api/skydive/applications.js new file mode 100644 index 0000000..fdf32fc --- /dev/null +++ b/src/routes/api/skydive/applications.js @@ -0,0 +1,173 @@ +var router = require('express').Router(), + mongoose = require('mongoose'), + Application = mongoose.model('Application'), + User = mongoose.model('User'); +const auth = require('../../../middlewares/auth'), + mailer = require('@sendgrid/mail'); + +mailer.setApiKey(process.env.SENDGRID_API_KEY); + +// Preload application objects on routes with ':application' +router.param('application', function (req, res, next, slug) { + Application.findOne({ slug: slug }) + .populate('author') + .then(function (application) { + if (!application) { + return res.sendStatus(404); + } + req.application = application; + return next(); + }).catch(next); +}); + +/** + * return all applications + */ +router.get('/', auth.required, function (req, res, next) { + let query = {}; + let limit = 25; + let offset = 0; + + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + if (typeof req.query.apikey !== 'undefined') { + query.apikey = req.query.apikey; + } + if (typeof req.query.maskedkey !== 'undefined') { + query.maskedkey = req.query.maskedkey; + } + + Promise.all([ + req.query.author ? User.findOne({ username: req.query.author }) : null + ]).then(function (author) { + if (author[0]) { + query.author = author[0]._id; + } + return Promise.all([ + Application.find(query) + .skip(Number(offset)) + .limit(Number(limit)) + .sort({ createdAt: 'desc' }) + .populate('author') + .exec(), + Application.count(query).exec(), + req.payload ? User.findById(req.payload.id) : null + ]).then(function (results) { + let applications = results[0]; + let applicationsCount = results[1]; + let user = results[2]; + + return res.json({ + applications: applications.map(function (application) { + return application.toJSONFor(user); + }), + applicationsCount: applicationsCount + }); + }); + }).catch(next); +}); + +/** + * save an application + */ +router.post('/', auth.required, function (req, res, next) { + Promise.all([ + User.findById(req.payload.id), + auth.generateAPIKey() + ]).then(function (results) { + let user = results[0]; + let keys = results[1]; + if (!user) { + return res.sendStatus(401).json({message: "Unauthorized - You are not allowed to access this resource."}); + } + let application = new Application(req.body.application); + application.author = user; + application.apikey = keys.encrypted; + application.maskedkey = keys.masked; + return application.save().then(function () { + let msg = { + to: user.email, + from: process.env.SENDGRID_FROM_MAIL, + subject: `Api-Key ${application.title}`, + text: `Votre Api-Key: ${keys.key}\nAttention, cette Api-Key doit être conservée et ne sera plus affichée.\n\n${application.title}\n${application.description}`, + html: `
+ Votre Api-Key: ${keys.key}
+ Attention, cette Api-Key doit être conservée et ne sera plus affichée.
+
${application.description}
` + }; + mailer.send(msg).then(() => { + return res.json({ application: application.toJSONFor(user) }); + }) + .catch(err => { + res.sendStatus(500).json({message: "A SendGrid error occured while sending an email", err: err}); + }); + }); + }).catch(next); +}); + +/** + * return an application + */ +router.get('/:application', auth.required, function (req, res, next) { + Promise.all([ + req.payload ? User.findById(req.payload.id) : null, + req.application.populate('author') + ]).then(function (results) { + let user = results[0]; + return res.json({ application: req.application.toJSONFor(user) }); + }).catch(next); +}); + +/** + * update an application + */ +router.put('/:application', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (req.application.author._id.toString() === req.payload.id.toString()) { + if (typeof req.body.application.title !== 'undefined') { + req.application.title = req.body.application.title; + } + if (typeof req.body.application.description !== 'undefined') { + req.application.description = req.body.application.description; + } + if (typeof req.body.application.apikey !== 'undefined') { + req.application.apikey = req.body.application.apikey; + } + if (typeof req.body.application.maskedkey !== 'undefined') { + req.application.maskedkey = req.body.application.maskedkey; + } + req.application.save().then(function (application) { + return res.json({ application: application.toJSONFor(user) }); + }).catch(next); + } else { + return res.status(403).json({ errors: { "Forbidden": "Vous ne disposez pas des autorisations suffisantes" } }); + } + }); +}); + +/** + * delete an application + */ +router.delete('/:application', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + if (user.role == 'Admin' || req.application.author._id.toString() === req.payload.id.toString()) { + return req.application.remove().then(function () { + return res.json({ deleted: true }); + }); + } else { + return res.status(403).json({ errors: { "Forbidden": "Vous ne disposez pas des autorisations suffisantes" } }); + } + }).catch(next); +}); + +module.exports = router; diff --git a/src/routes/api/skydive/canopies.js b/src/routes/api/skydive/canopies.js new file mode 100644 index 0000000..e8c04cd --- /dev/null +++ b/src/routes/api/skydive/canopies.js @@ -0,0 +1,166 @@ +var router = require('express').Router(), + mongoose = require('mongoose'), + Jump = mongoose.model('Jump'), + User = mongoose.model('User'); +const auth = require('../../../middlewares/auth'), + queries = require('../../queries'); + +router.get('/', auth.required, function (req, res, next) { + let query = {}; + let limit = 25; + let offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + if (typeof req.query.slug !== 'undefined') { + query.slug = req.query.slug; + } + + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + if (user.role == 'Admin') { + return res.status(403).json({ errors: { "Forbidden": "Vous ne disposez pas des autorisations suffisantes" } }); + } + Jump.find(query) + .skip(Number(offset)) + .limit(Number(limit)) + .exec() + .then(function (result) { + let canopies = result; + return res.json({ + canopies: canopies, + canopiesCount: canopies.length + }); + }).catch(next); + }).catch(next); +}); + +router.get('/allBySize', auth.required, function (req, res, next) { + let limit = 25; + let offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + let query = queries.getCanopiesBySizeQuery(user._id.toString()); + Jump.aggregate(query) + .skip(Number(offset)) + .limit(Number(limit)) + .exec() + .then(function (result) { + let canopies = result.map(function (data) { + return { taille: data._id.taille, count: data.count }; + }); + return res.json({ + canopies: canopies, + canopiesCount: canopies.length + }); + }).catch(next); + }).catch(next); +}); + +router.get('/allBySizeByYear', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + let limit = 25; + let offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + let query = queries.getCanopiesBySizeByYearQuery(user._id.toString()); + Jump.aggregate(query) + .skip(Number(offset)) + .limit(Number(limit)) + .exec() + .then(function (result) { + let canopies = result.map(function (data) { + return { taille: data._id.taille, year: data._id.year, count: data.count }; + }); + //console.log(dropzones); + return res.json({ + canopies: canopies, + canopiesCount: canopies.length + }); + }).catch(next); + }).catch(next); +}); + +router.get('/allBySizeByModel', auth.required, function (req, res, next) { + let limit = 25; + let offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + let query = queries.getCanopiesBySizeByModelQuery(user._id.toString()); + Jump.aggregate(query) + .skip(Number(offset)) + .limit(Number(limit)) + .exec() + .then(function (result) { + let canopies = result.map(function (data) { + return { taille: data._id.taille, voile: data._id.voile, count: data.count }; + }); + return res.json({ + canopies: canopies, + canopiesCount: canopies.length + }); + }).catch(next); + }).catch(next); +}); + +router.get('/allBySizeByModelByYear', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + let limit = 25; + let offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + let query = queries.getCanopiesBySizeByModelByYearQuery(user._id.toString()); + Jump.aggregate(query) + .skip(Number(offset)) + .limit(Number(limit)) + .exec() + .then(function (result) { + let canopies = result.map(function (data) { + return { taille: data._id.taille, voile: data._id.voile, year: data._id.year, count: data.count }; + }); + return res.json({ + canopies: canopies, + canopiesCount: canopies.length + }); + }).catch(next); + }).catch(next); +}); + +module.exports = router; diff --git a/src/routes/api/skydive/dropzones.js b/src/routes/api/skydive/dropzones.js new file mode 100644 index 0000000..f87fb02 --- /dev/null +++ b/src/routes/api/skydive/dropzones.js @@ -0,0 +1,69 @@ +var router = require('express').Router(), + mongoose = require('mongoose'), + Jump = mongoose.model('Jump'), + User = mongoose.model('User'); +const auth = require('../../../middlewares/auth'), + queries = require('../../queries'); + + +router.get('/allByOaci', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + let limit = 25; + let offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + let query = queries.getDropZonesByOaciQuery(user._id.toString()); + Jump.aggregate(query) + .skip(Number(offset)) + .limit(Number(limit)) + .exec() + .then(function (result) { + let dropzones = result.map(function (data) { + return { lieu: data._id.lieu, oaci: data._id.oaci, count: data.count }; + }); + return res.json({ + dropzones: dropzones, + dropzonesCount: dropzones.length + }); + }).catch(next); + }).catch(next); +}); + +router.get('/allByOaciByYear', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + let limit = 25; + let offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + let query = queries.getDropZonesByOaciByYearQuery(user._id.toString()); + Jump.aggregate(query) + .skip(Number(offset)) + .limit(Number(limit)) + .exec() + .then(function (result) { + let dropzones = result.map(function (data) { + return { lieu: data._id.lieu, oaci: data._id.oaci, year: data._id.year, count: data.count }; + }); + return res.json({ + dropzones: dropzones, + dropzonesCount: dropzones.length + }); + }).catch(next); + }).catch(next); +}); + +module.exports = router; diff --git a/src/routes/api/skydive/index.js b/src/routes/api/skydive/index.js new file mode 100644 index 0000000..e880cbc --- /dev/null +++ b/src/routes/api/skydive/index.js @@ -0,0 +1,24 @@ +var routes = require('express').Router(); + +routes.use('/applications', require('./applications')); +routes.use('/aeronefs', require('./aeronefs')); +routes.use('/canopies', require('./canopies')); +routes.use('/dropzones', require('./dropzones')); +routes.use('/jumps', require('./jumps')); +routes.use('/pages', require('./pages')); +routes.use('/profiles', require('./profiles')); +routes.use('/qcm', require('./qcm')); + +routes.use(function (err, req, res, next) { + if (err.name === 'ValidationError') { + return res.status(422).json({ + errors: Object.keys(err.errors).reduce(function (errors, key) { + errors[key] = err.errors[key].message; + return errors; + }, {}) + }); + } + return next(err); +}); + +module.exports = routes; diff --git a/src/routes/api/skydive/jumps.js b/src/routes/api/skydive/jumps.js new file mode 100644 index 0000000..bb9d480 --- /dev/null +++ b/src/routes/api/skydive/jumps.js @@ -0,0 +1,622 @@ +var router = require('express').Router(), + mongoose = require('mongoose'), + Jump = mongoose.model('Jump'), + JumpFile = mongoose.model('JumpFile'), + User = mongoose.model('User'), + X2DataLog = mongoose.model('X2DataLog'); +const auth = require('../../../middlewares/auth'), + chalk = require('chalk'), + queries = require('../../queries'); +const DateUtilities = require('../../../utils/dateUtilities'); +const utils = require('../../../utils'); + +// Preload jump objects on routes with ':jump' +router.param('jump', function (req, res, next, slug) { + Jump.findOne({ slug: slug }) + .populate('author') + .populate('files') + .populate('x2data') + .then(function (jump) { + if (!jump) { + return res.sendStatus(404); + } + req.jump = jump; + return next(); + }).catch(next); +}); + +/** + * return all jumps + */ +router.get('/', auth.required, function (req, res, next) { + let query = {}; + let limit = 25; + let offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + if (typeof req.query.title !== 'undefined') { + const rgx = new RegExp(`.*${req.query.title}.*`); + query = { + $or: [ + { title: { $regex: rgx, $options: "i" } }, + { slug: { $regex: rgx, $options: "i" } }, + ], + } + } + if (typeof req.query.numeroRangeStart !== 'undefined' && typeof req.query.numeroRangeEnd !== 'undefined') { + query.numero = { + $gte: req.query.numeroRangeStart, + $lte: req.query.numeroRangeEnd + }; + } + if (typeof req.query.tailleRangeStart !== 'undefined' && typeof req.query.tailleRangeEnd !== 'undefined') { + query.taille = { + $gte: req.query.tailleRangeStart, + $lte: req.query.tailleRangeEnd + }; + } + if (typeof req.query.participantsRangeStart !== 'undefined' && typeof req.query.participantsRangeEnd !== 'undefined') { + query.participants = { + $gte: req.query.participantsRangeStart, + $lte: req.query.participantsRangeEnd + }; + } + if (typeof req.query.hauteurRangeStart !== 'undefined' && typeof req.query.hauteurRangeEnd !== 'undefined') { + query.hauteur = { + $gte: req.query.hauteurRangeStart, + $lte: req.query.hauteurRangeEnd + }; + } + if (typeof req.query.yearRangeStart !== 'undefined' && typeof req.query.yearRangeEnd !== 'undefined') { + query.date = { + $gte: new Date(`${req.query.yearRangeStart}-01-01 00:00:00`), + $lte: new Date(`${req.query.yearRangeEnd}-12-31 23:59:59`) + }; + } + if (typeof req.query.dateRangeStart !== 'undefined' && typeof req.query.dateRangeEnd !== 'undefined') { + query.date = { + $gte: new Date(`${req.query.dateRangeStart} 00:00:00`), + $lte: new Date(`${req.query.dateRangeEnd} 23:59:59`) + }; + } + + Promise.all([ + req.query.author ? User.findOne({ username: req.query.author }) : null, + req.query.favorited ? User.findOne({ username: req.query.favorited }) : null + ]).then(function (users) { + let author = users[0]; + + if (author) { + query.author = author._id; + } + + Promise.all([ + Jump.find(query) + .skip(Number(offset)) + .limit(Number(limit)) + .sort({ numero: 'desc' }) + .populate('author') + .populate('files') + .populate('x2data') + .exec(), + Jump.count(query).exec(), + req.payload ? User.findById(req.payload.id) : null + ]).then(function (results) { + let jumps = results[0]; + let jumpsCount = results[1]; + let user = results[2]; + /* + Promise.all( + jumps.map(jump => { + if (jump.files[0] !== undefined && jump.files[0].type !== 'kml') { + console.log( chalk.green(`Fichier ${jump.files[0].type}`)) + //console.log( chalk.green(`Suppression du fichier ${jump.files[2]._id}`)) + //return jump.removeFile(jump.files[2]._id); + } + return; + }) + ).then(function () { + return res.json({ + jumps: jumps.map(function (jump) { + return jump.toJSONFor(user); + }), + jumpsCount: jumpsCount + }); + }); + */ + return res.json({ + jumps: jumps.map(function (jump) { + return jump.toJSONFor(user); + }), + jumpsCount: jumpsCount + }); + }); + }).catch(next); +}); + +router.get('/allByCategorie', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + let limit = 120; + let offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + let query = queries.getJumpsByCategoryQuery(user._id.toString()); + + Jump.aggregate(query) + .skip(Number(offset)) + .limit(Number(limit)) + .exec() + .then(function (result) { + let jumps = result.map(function (data) { + return { categorie: data._id.categorie, count: data.count }; + }); + return res.json({ + jumps: jumps, + jumpsCount: jumps.length + }); + }).catch(next); + }).catch(next); +}); + +router.get('/allByDate', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + let limit = 120; + let offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + let query = queries.getJumpsByDateQuery(user._id.toString()); + + Jump.aggregate(query) + .skip(Number(offset)) + .limit(Number(limit)) + .exec() + .then(function (result) { + let jumps = result.map(function (data) { + return { year: data._id.year, month: data._id.month, count: data.count }; + }); + return res.json({ + jumps: jumps, + jumpsCount: jumps.length + }); + }).catch(next); + }).catch(next); +}); + +router.get('/allByDay', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + let query = {}; + let limit = 50; + let offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + if (typeof req.query.dateRangeStart !== 'undefined' && typeof req.query.dateRangeEnd !== 'undefined') { + query = queries.getJumpsByDayQuery( + user._id.toString(), + req.query.dateRangeStart, + req.query.dateRangeEnd + ); + } else { + query = queries.getJumpsByDayQuery(user._id.toString()); + } + + Jump.aggregate(query) + .skip(Number(offset)) + .limit(Number(limit)) + .exec() + .then(function (result) { + let days = result.map(function (data) { + return { jumps: data.jumps, count: data.count }; + }); + return res.json({ + days: days, + daysCount: days.length + }); + }).catch(next); + }).catch(next); +}); + +router.get('/allByModule', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + let limit = 120; + let offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + let query = queries.getJumpsByModuleQuery(user._id.toString()); + + Jump.aggregate(query) + .skip(Number(offset)) + .limit(Number(limit)) + .exec() + .then(function (result) { + let jumps = result.map(function (data) { + return { categorie: data._id.categorie, module: data._id.module, count: data.count }; + }); + return res.json({ + jumps: jumps, + jumpsCount: jumps.length + }); + }).catch(next); + }).catch(next); +}); + +/** + * return a jump from SkydiverId API + */ +router.get('/allFromSkydiverIdApi', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + utils.fetchSkydiverIdApi('jumps', req.query).then(result => { + if (result.errors === undefined) { + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.green(`Réception des sauts SkydiverId`)); + } else { + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.red(`Une erreur ${result.errors.code} '${result.errors.type}' est survenue lors de la réception des sauts SkydiverId : ${result.errors.name} ${result.errors.message}`)); + } + return res.json({ + jumps: result, + jumpsCount: result.items.length + }); + }).catch(next); + }).catch(next); +}); + +router.get('/feed', auth.required, function (req, res, next) { + let limit = 25; + let offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + let query = { author: { $in: user.following } }; + Promise.all([ + Jump.find(query) + .skip(Number(offset)) + .limit(Number(limit)) + .populate('author') + .populate('files') + .populate('x2data') + .exec(), + Jump.count(query) + ]).then(function (results) { + let jumps = results[0]; + let jumpsCount = results[1]; + return res.json({ + jumps: jumps.map(function (jump) { + return jump.toJSONFor(user); + }), + jumpsCount: jumpsCount + }); + }).catch(next); + }); +}); + +/** + * return last jump + */ +router.get('/last', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + /*Jump.findOne({ slug: slug }) + .populate('author') + .then(function (jump) { + if (!jump) { + return res.sendStatus(404); + } + req.jump = jump; + return next(); + }).catch(next);*/ + Jump.find({author: user._id}) + .limit(1) + .sort({ numero: 'desc' }) + .populate('author') + .populate('files') + .populate('x2data') + .exec() + .then(function (jumps) { + if (!jumps || jumps.length === 0) { + return res.status(404).json({ errors: { "Not Found": "aucun saut trouvé" } }); + } + req.lastjump = jumps[0]; + if (req.lastjump.author.username !== req.payload.username) { + return res.status(403).json({ errors: { "Forbidden": "Vous ne disposez pas des autorisations suffisantes" } }); + } + return res.json({ jump: req.lastjump.toJSONFor(user) }); + }).catch(next); + }).catch(next); +}); + +/** + * return a jump + */ +router.get('/:jump', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + if (req.jump.author._id.toString() !== req.payload.id.toString()) { + return res.status(403).json({ errors: { "Forbidden": "Vous ne disposez pas des autorisations suffisantes" } }); + } + Promise.all([ + Jump.findOne({numero: (req.jump.numero - 1)}).exec(), + Jump.findOne({numero: (req.jump.numero + 1)}).exec(), + ]).then(function (results) { + return res.json({ + jump: req.jump.toJSONFor(user), + prevJump: results[0], + nextJump: results[1] + }); + }).catch(next); + }).catch(next); +}); + +/** + * save a jump + */ +router.post('/', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + //let numero = (results[1][0].numero + 1); + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + let jump = new Jump(req.body.jump); + jump.author = user; + //jump.numero = numero; + return jump.save().then(function () { + return res.json({ jump: jump.toJSONFor(user) }); + }); + }).catch(next); +}); + +/** + * save a jump file + */ +router.post('/data/:jump', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + if (req.jump.author._id.toString() !== req.payload.id.toString()) { + return res.status(403).json({ errors: { "Forbidden": "Vous ne disposez pas des autorisations suffisantes" } }); + } + /* + console.log(req.jump.files); + return res.json({ jump: req.jump.toJSONFor(user) }); + */ + Object.assign(req.body.file, {_id: new mongoose.Types.ObjectId()}); + let file = new JumpFile(req.body.file); + let files = req.jump.files ? req.jump.files : []; + file.author = user; + files.push(file); + return file.save().then(function () { + Object.assign(req.body.data, {_id: new mongoose.Types.ObjectId()}); + let x2data = new X2DataLog(req.body.data); + x2data.author = user; + return x2data.save().then(function () { + req.jump.files = files; + req.jump.x2data = x2data; + Jump.updateOne({_id: req.jump._id}, {files: files, x2data: x2data._id}).then(function () { + return res.json({ jump: req.jump.toJSONFor(user) }); + }).catch(next); + }).catch(next); + }).catch(next); + }).catch(next); +}); + +/** + * save a jump file + */ +router.post('/file/:jump', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + if (req.jump.author._id.toString() !== req.payload.id.toString()) { + return res.status(403).json({ errors: { "Forbidden": "Vous ne disposez pas des autorisations suffisantes" } }); + } + Object.assign(req.body.file, {_id: new mongoose.Types.ObjectId()}); + let file = new JumpFile(req.body.file); + let files = req.jump.files ? req.jump.files : []; + file.author = user; + files.push(file); + return file.save().then(function () { + Jump.updateOne({_id: req.jump._id}, {files: files}).then(function () { + return res.json({ file: file.toJSONForJump() }); + }).catch(next); + }).catch(next); + }).catch(next); +}); + +/** + * save a jump file + */ +router.post('/kml/:jump', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + if (req.jump.author._id.toString() !== req.payload.id.toString()) { + return res.status(403).json({ errors: { "Forbidden": "Vous ne disposez pas des autorisations suffisantes" } }); + } + Object.assign(req.body.file, { + _id: new mongoose.Types.ObjectId(), + name: `${req.jump.x2data.name}.kml`, + path: `/Volumes/Storage/Skydive/X2_Kmls/${req.jump.date.getFullYear()}/`, + type: 'kml' + }); + let file = new JumpFile(req.body.file); + let files = req.jump.files ? req.jump.files : []; + file.author = user; + files.push(file); + //console.log(chalk.yellow(`jump/${req.jump.x2data.id}/kml`)); + return res.json({ file: file.toJSONForJump() }); + /* + DateUtilities.fetchSkydiverIdApi(`jump/${req.jump.x2data.id}/kml`, {}).then(result => { + if (result.errors === undefined) { + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.green(`Réception de l'url du fichier kml SkydiverId`)); + } else { + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.red(`Une erreur ${result.errors.code} '${result.errors.type}' est survenue lors de la réception de l'url du fichier kml SkydiverId : ${result.errors.name} ${result.errors.message}`)); + } + if (result.url !== undefined) { + console.log(chalk.green(result.url)); + } else { + console.log(chalk.red('result.url is undefined')); + } + return res.json({ file: file.toJSONForJump() }); + }).catch(next); + + + + return file.save().then(function () { + Jump.updateOne({_id: req.jump._id}, {files: files}).then(function () { + DateUtilities.fetchSkydiverIdApi(`jump/${req.jump.x2data.id}/kml`, {}).then(result => { + if (result.errors === undefined) { + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.green(`Réception de l'url du fichier kml SkydiverId`)); + } else { + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.red(`Une erreur ${result.errors.code} '${result.errors.type}' est survenue lors de la réception de l'url du fichier kml SkydiverId : ${result.errors.name} ${result.errors.message}`)); + } + if (result.url !== undefined) { + console.log(result.url); + } else { + console.log('result.url is undefined'); + } + return res.json({ file: file.toJSONForJump() }); + }).catch(next); + }).catch(next); + }).catch(next); + */ + }).catch(next); +}); + +/** + * update a jump + */ +router.put('/:jump', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + if (req.jump.author._id.toString() !== req.payload.id.toString()) { + return res.status(403).json({ errors: { "Forbidden": "Vous ne disposez pas des autorisations suffisantes" } }); + } + if (typeof req.body.jump.date !== 'undefined') { + req.jump.date = req.body.jump.date; + } + if (typeof req.body.jump.numero !== 'undefined') { + req.jump.numero = req.body.jump.numero; + } + if (typeof req.body.jump.lieu !== 'undefined') { + req.jump.lieu = req.body.jump.lieu; + } + if (typeof req.body.jump.oaci !== 'undefined') { + req.jump.oaci = req.body.jump.oaci; + } + if (typeof req.body.jump.aeronef !== 'undefined') { + req.jump.aeronef = req.body.jump.aeronef; + } + if (typeof req.body.jump.imat !== 'undefined') { + req.jump.imat = req.body.jump.imat; + } + if (typeof req.body.jump.hauteur !== 'undefined') { + req.jump.hauteur = req.body.jump.hauteur; + } + if (typeof req.body.jump.voile !== 'undefined') { + req.jump.voile = req.body.jump.voile; + } + if (typeof req.body.jump.taille !== 'undefined') { + req.jump.taille = req.body.jump.taille; + } + if (typeof req.body.jump.categorie !== 'undefined') { + req.jump.categorie = req.body.jump.categorie; + } + if (typeof req.body.jump.module !== 'undefined') { + req.jump.module = req.body.jump.module; + } + if (typeof req.body.jump.participants !== 'undefined') { + req.jump.participants = req.body.jump.participants; + } + if (typeof req.body.jump.sautants !== 'undefined') { + req.jump.sautants = req.body.jump.sautants; + } + if (typeof req.body.jump.programme !== 'undefined') { + req.jump.programme = req.body.jump.programme; + } + if (typeof req.body.jump.accessoires !== 'undefined') { + req.jump.accessoires = req.body.jump.accessoires; + } + if (typeof req.body.jump.zone !== 'undefined') { + req.jump.zone = req.body.jump.zone; + } + if (typeof req.body.jump.dossier !== 'undefined') { + req.jump.dossier = req.body.jump.dossier; + } + if (typeof req.body.jump.video !== 'undefined') { + req.jump.video = req.body.jump.video; + } + + return req.jump.save().then(function (jump) { + return res.json({ jump: jump.toJSONFor(user) }); + }); + }).catch(next); +}); + +/** + * delete a jump + */ +router.delete('/:jump', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + if (req.jump.author._id.toString() === req.payload.id.toString()) { + return req.jump.remove().then(function () { + return res.json({ deleted: true }); + }); + } else { + return res.status(403).json({ errors: { "Forbidden": "Vous ne disposez pas des autorisations suffisantes" } }); + } + }).catch(next); +}); + +module.exports = router; diff --git a/src/routes/api/skydive/pages.js b/src/routes/api/skydive/pages.js new file mode 100644 index 0000000..f68d385 --- /dev/null +++ b/src/routes/api/skydive/pages.js @@ -0,0 +1,284 @@ +var router = require('express').Router(), + mongoose = require('mongoose'), + Jump = mongoose.model('Jump'), + User = mongoose.model('User'); +const auth = require('../../../middlewares/auth'), + queries = require('../../queries'); + +router.get('/aeronefs', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + let limit = 120; + let offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + Promise.all([ + Jump.find({}) + .limit(1) + .skip(0) + .sort({ numero: 'desc' }) + .populate('author') + .populate('files') + .populate('x2data') + .exec(), + Jump.aggregate(queries.getAeronefsByImatQuery(user._id.toString())) + .skip(Number(offset)) + .limit(Number(limit)) + .exec(), + Jump.aggregate(queries.getAeronefsByImatByYearQuery(user._id.toString())) + .skip(Number(offset)) + .limit(Number(limit)) + .exec() + ]).then(function (results) { + if (results[0].length) { + let lastjump = results[0][0].toJSONFor(user); + let aeronefsByImat = results[1].map(function (data) { + return { aeronef: data._id.aeronef, imat: data._id.imat, count: data.count }; + }); + let aeronefsByImatByYear = results[2].map(function (data) { + return { + aeronef: data._id.aeronef, + imat: data._id.imat, + year: data._id.year, + count: data.count + }; + }); + return res.json({ + aeronefsByImat: aeronefsByImat, + aeronefsByImatByYear: aeronefsByImatByYear, + lastjump: lastjump + }); + } else { + return res.status(422).json({ errors: { lastjump: "Aucun saut enregistré pour le moment" } }); + } + }).catch(next); + }).catch(next); +}); + +router.get('/canopies', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + let limit = 120; + let offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + Promise.all([ + Jump.find({}) + .limit(1) + .skip(0) + .sort({ numero: 'desc' }) + .populate('author') + .populate('files') + .populate('x2data') + .exec(), + Jump.aggregate(queries.getCanopiesBySizeQuery(user._id.toString())) + .skip(Number(offset)) + .limit(Number(limit)) + .exec(), + Jump.aggregate(queries.getCanopiesBySizeByYearQuery(user._id.toString())) + .skip(Number(offset)) + .limit(Number(limit)) + .exec(), + Jump.aggregate(queries.getCanopiesBySizeByModelQuery(user._id.toString())) + .skip(Number(offset)) + .limit(Number(limit)) + .exec(), + Jump.aggregate(queries.getCanopiesBySizeByModelByYearQuery(user._id.toString())) + .skip(Number(offset)) + .limit(Number(limit)) + .exec() + ]).then(function (results) { + if (results[0].length) { + let lastjump = results[0][0].toJSONFor(user); + let canopiesBySize = results[1].map(function (data) { + return { taille: data._id.taille, count: data.count }; + }); + let canopiesBySizeByYear = results[2].map(function (data) { + return { + taille: data._id.taille, + year: data._id.year, + count: data.count + }; + }); + let canopiesBySizeByModel = results[3].map(function (data) { + return { + taille: data._id.taille, + voile: data._id.voile, + count: data.count + }; + }); + let canopiesBySizeByModelByYear = results[4].map(function (data) { + return { + taille: data._id.taille, + voile: data._id.voile, + year: data._id.year, + count: data.count + }; + }); + return res.json({ + canopiesBySize: canopiesBySize, + canopiesBySizeByYear: canopiesBySizeByYear, + canopiesBySizeByModel: canopiesBySizeByModel, + canopiesBySizeByModelByYear: canopiesBySizeByModelByYear, + lastjump: lastjump + }); + } else { + return res.status(422).json({ errors: { lastjump: "Aucun saut enregistré pour le moment" } }); + } + }).catch(next); + }).catch(next); +}); + +router.get('/dropzones', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + let limit = 120; + let offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + Promise.all([ + Jump.find({}) + .limit(1) + .skip(0) + .sort({ numero: 'desc' }) + .populate('author') + .populate('files') + .populate('x2data') + .exec(), + Jump.aggregate(queries.getDropZonesByOaciQuery(user._id.toString())) + .skip(Number(offset)) + .limit(Number(limit)) + .exec(), + Jump.aggregate(queries.getDropZonesByOaciByYearQuery(user._id.toString())) + .skip(Number(offset)) + .limit(Number(limit)) + .exec() + ]).then(function (results) { + if (results[0].length) { + let lastjump = results[0][0].toJSONFor(user); + let dropZonesByOaci = results[1].map(function (data) { + return { lieu: data._id.lieu, oaci: data._id.oaci, count: data.count }; + }); + let dropZonesByOaciByYear = results[2].map(function (data) { + return { + lieu: data._id.lieu, + oaci: data._id.oaci, + year: data._id.year, + count: data.count + }; + }); + return res.json({ + dropZonesByOaci: dropZonesByOaci, + dropZonesByOaciByYear: dropZonesByOaciByYear, + lastjump: lastjump + }); + } else { + return res.status(422).json({ errors: { lastjump: "Aucun saut enregistré pour le moment" } }); + } + }).catch(next); + }).catch(next); +}); + +router.get('/jumps', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + let limit = 120; + let offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + Promise.all([ + Jump.find({}) + .limit(1) + .skip(0) + .sort({ numero: 'desc' }) + .populate('author') + .populate('files') + .populate('x2data') + .exec(), + Jump.aggregate(queries.getJumpByYearsQuery(user._id.toString())) + .skip(Number(offset)) + .limit(Number(limit)) + .exec(), + Jump.aggregate(queries.getJumpsByCategoryQuery(user._id.toString())) + .skip(Number(offset)) + .limit(Number(limit)) + .exec(), + Jump.aggregate(queries.getJumpsByModuleQuery(user._id.toString())) + .skip(Number(offset)) + .limit(Number(limit)) + .exec(), + Jump.aggregate(queries.getJumpsByDateQuery(user._id.toString())) + .skip(Number(offset)) + .limit(Number(limit)) + .exec(), + Jump.aggregate(queries.getJumpsByModuleByDateQuery(user._id.toString())) + .skip(Number(offset)) + .limit(Number(limit)) + .exec() + //utils.fetchSkydiverIdApi('jumps') + ]).then(function (results) { + if (results[0].length) { + let lastjump = results[0][0].toJSONFor(user); + let jumpsByYears = results[1].map(function (data) { + return { year: data._id.year, count: data.count }; + }); + let jumpsByCategory = results[2].map(function (data) { + return { categorie: data._id.categorie, count: data.count }; + }); + let jumpsByModule = results[3].map(function (data) { + return { categorie: data._id.categorie, module: data._id.module, count: data.count }; + }); + let jumpsByDate = results[4].map(function (data) { + return { year: data._id.year, month: data._id.month, count: data.count }; + }); + let jumpsByDateByModule = results[5].map(function (data) { + return { + categorie: data._id.categorie, + module: data._id.module, + year: data._id.year, + month: data._id.month, + count: data.count + }; + }); + //let jumpsFromSkydiverIdApi = results[6]; + return res.json({ + jumpsByCategory: jumpsByCategory, + jumpsByDate: jumpsByDate, + jumpsByDateByModule: jumpsByDateByModule, + jumpsByModule: jumpsByModule, + jumpsByYears: jumpsByYears, + //jumpsFromSkydiverIdApi: jumpsFromSkydiverIdApi, + lastjump: lastjump + }); + } else { + return res.status(422).json({ errors: { lastjump: "Aucun saut enregistré pour le moment" } }); + } + }).catch(next); + }).catch(next); +}); + +module.exports = router; diff --git a/src/routes/api/skydive/profiles.js b/src/routes/api/skydive/profiles.js new file mode 100644 index 0000000..2bc6ba6 --- /dev/null +++ b/src/routes/api/skydive/profiles.js @@ -0,0 +1,57 @@ +var router = require('express').Router(), + mongoose = require('mongoose'), + User = mongoose.model('User'); +const auth = require('../../../middlewares/auth'); + +// Preload user profile on routes with ':username' +router.param('username', function (req, res, next, username) { + User.findOne({ username: username }).then(function (user) { + if (!user) { + return res.sendStatus(404); + } + req.profile = user; + + return next(); + }).catch(next); +}); + +router.get('/:username', auth.optional, function (req, res) { + if (req.payload) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.json({ profile: req.profile.toProfileJSONFor(false) }); + } + return res.json({ profile: req.profile.toProfileJSONFor(user) }); + }); + } else { + return res.json({ profile: req.profile.toProfileJSONFor(false) }); + } +}); + +router.post('/:username/follow', auth.required, function (req, res, next) { + let profileId = req.profile._id; + + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + return user.follow(profileId).then(function () { + return res.json({ profile: req.profile.toProfileJSONFor(user) }); + }); + }).catch(next); +}); + +router.delete('/:username/follow', auth.required, function (req, res, next) { + let profileId = req.profile._id; + + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + return user.unfollow(profileId).then(function () { + return res.json({ profile: req.profile.toProfileJSONFor(user) }); + }); + }).catch(next); +}); + +module.exports = router; diff --git a/src/routes/api/skydive/qcm.js b/src/routes/api/skydive/qcm.js new file mode 100644 index 0000000..9a80fc8 --- /dev/null +++ b/src/routes/api/skydive/qcm.js @@ -0,0 +1,157 @@ +var router = require('express').Router(), + mongoose = require('mongoose'), + Qcm = mongoose.model('Qcm'), + QcmCategory = mongoose.model('QcmCategory'), + QcmQuestion = mongoose.model('QcmQuestion'), + QcmChoice = mongoose.model('QcmChoice'), + User = mongoose.model('User'); +const auth = require('../../../middlewares/auth'); + +// Preload qcm objects on routes with ':type' +router.param('type', function (req, res, next, type) { + Qcm.findOne({ name: type }) + .populate('categories') + .then(qcm => { + if (!qcm) { + return res.status(404).json({ errors: { "Not Found": "Le QCM est introuvable ou inexistant." } }); + } + req.qcm = qcm; + Promise.all( + req.qcm.categories.map(category => { + return category.populate('questions').then(() => { + return Promise.all( + category.questions.map(question => question.populate('choices')) + ).then(() => { + return category; + }).catch(next); + }); + }) + ).then(() => { + return next(); + }).catch(next); + }).catch(next); +}); + +/** + * return all qcm + */ +router.get('/', auth.required, function (req, res, next) { + Promise.all([ + req.payload ? User.findById(req.payload.id) : null + ]).then(function (results) { + let user = results[0]; + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + return res.json({ qcm: [] }); + }).catch(next); +}); + +/** + * return a qcm + */ +router.get('/type/:type', auth.required, function (req, res, next) { + Promise.all([ + req.payload ? User.findById(req.payload.id) : null + ]).then(function (results) { + let user = results[0]; + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + return res.json({ qcm: req.qcm.toJSONFor() }); + }).catch(next); +}); + +/** + * save an array of qcm question choice + */ +router.post('/choices/', auth.required, function (req, res, next) { + Promise.all([ + req.payload ? User.findById(req.payload.id) : null, + QcmQuestion + .findOne({num: req.body.question.num}) + .populate('choices') + .exec() + ]).then(async function (results) { + const user = results[0]; + //let numero = (results[1][0].numero + 1); + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + if (user.role !== 'Admin') { + return res.status(403).json({ errors: { "Forbidden": "Vous ne disposez pas des autorisations suffisantes" } }); + } + const question = results[1]; + if (!question) { + return res.status(422).json({ errors: { question: "question introuvable" } }); + } + let choices = []; + Promise.all( + req.body.question.choices.map(data => { + const choiceId = new mongoose.Types.ObjectId(); + const choice = new QcmChoice({ _id: choiceId, index: data.index, libelle: data.libelle, correct: data.correct}); + choices.push(choiceId); + return choice.save(); + }) + ).then(function () { + question.choices = [...choices]; + question.save().then(function () { + QcmQuestion + .findOne({num: req.body.question.num}) + .populate('choices') + .exec() + .then(function (question) { + return res.json({ question: question.toJSONFor() }); + }).catch(next); + }).catch(next); + }).catch(next); + }).catch(next); +}); + +/** + * save an array of qcm category questions + */ +router.post('/questions/', auth.required, function (req, res, next) { + Promise.all([ + req.payload ? User.findById(req.payload.id) : null, + QcmCategory + .findOne({num: req.body.category.num}) + .populate('questions') + .exec() + ]).then(async function (results) { + const user = results[0]; + //let numero = (results[1][0].numero + 1); + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + if (user.role !== 'Admin') { + return res.status(403).json({ errors: { "Forbidden": "Vous ne disposez pas des autorisations suffisantes" } }); + } + const category = results[1]; + if (!category) { + return res.status(422).json({ errors: { category: "catégorie introuvable" } }); + } + let questions = []; + Promise.all( + req.body.category.questions.map(data => { + const questionId = new mongoose.Types.ObjectId(); + const question = new QcmQuestion({ _id: questionId, num: data.num, libelle: data.libelle, choices: []}); + questions.push(questionId); + return question.save(); + }) + ).then(function () { + category.questions = [...questions]; + category.save().then(function () { + QcmCategory + .findOne({num: req.body.category.num}) + .populate('questions') + .exec() + .then(function (category) { + return res.json({ category: category.toJSONFor() }); + }).catch(next); + }).catch(next); + }).catch(next); + }).catch(next); +}); + +module.exports = router; \ No newline at end of file diff --git a/src/routes/api/v1/jumps.js b/src/routes/api/v1/jumps.js index d8168eb..bb9d480 100644 --- a/src/routes/api/v1/jumps.js +++ b/src/routes/api/v1/jumps.js @@ -355,8 +355,8 @@ router.get('/last', auth.required, function (req, res, next) { .populate('x2data') .exec() .then(function (jumps) { - if (!jumps) { - return res.sendStatus(404); + if (!jumps || jumps.length === 0) { + return res.status(404).json({ errors: { "Not Found": "aucun saut trouvé" } }); } req.lastjump = jumps[0]; if (req.lastjump.author.username !== req.payload.username) {