From 66821427be8937d7f55e3eb09daa0be76743c807 Mon Sep 17 00:00:00 2001 From: Julien Gautier Date: Sun, 26 Apr 2026 04:27:47 +0200 Subject: [PATCH] fix(skydive): migrate author field from ObjectId to UUID string - Register SkydiverProfile model in mysql.js (fixes startup crash on association) - Replace $toObjectId with direct string match in all 12 aggregation queries - Guard toJSONFor on all Mongo models to fall back to MySQL user.toProfileJSONFor() when author is a UUID string (restores username/image in API responses) - Pass user to toJSONFor() everywhere instead of null - Remove dead .populate('author') calls (author: String has no ref) - Fix ownership check in /last route: compare author string to req.payload.id instead of the now-absent author.username property --- src/config/passport-headerapikey.js | 1 - src/database/models/mongo/Aeronef.js | 4 +- src/database/models/mongo/Application.js | 8 +- src/database/models/mongo/Canopy.js | 4 +- src/database/models/mongo/Dropzone.js | 4 +- src/database/models/mongo/Jump.js | 2 +- src/database/models/mongo/JumpFile.js | 4 +- src/database/models/mongo/X2DataLog.js | 4 +- src/database/models/mongo/herowars/Clan.js | 4 +- src/database/models/mongo/herowars/Member.js | 4 +- src/database/mysql.js | 2 + src/routes/api/skydive/applications.routes.js | 5 +- src/routes/api/skydive/jumps.routes.js | 18 ++-- src/routes/api/skydive/pages.routes.js | 12 +-- src/routes/queries.js | 102 ++++-------------- src/services/mongo/clan.service.js | 3 - src/services/mongo/member.service.js | 3 - 17 files changed, 64 insertions(+), 120 deletions(-) diff --git a/src/config/passport-headerapikey.js b/src/config/passport-headerapikey.js index f555fc8..2b1f46a 100644 --- a/src/config/passport-headerapikey.js +++ b/src/config/passport-headerapikey.js @@ -13,7 +13,6 @@ passport.use('headerapikey', new HeaderAPIKeyStrategy( const salt = secret; Promise.resolve(bcrypt.hash(apikey, salt)).then(function (encryptedKey) { Application.findOne({ encryptedKey: encryptedKey }) - .populate('author') .then(function (application) { if (!application) { return done({message: "Application can't be found.", status: 404}, false, false); diff --git a/src/database/models/mongo/Aeronef.js b/src/database/models/mongo/Aeronef.js index 7819cc6..bd7a42b 100644 --- a/src/database/models/mongo/Aeronef.js +++ b/src/database/models/mongo/Aeronef.js @@ -26,7 +26,9 @@ AeronefSchema.methods.toJSONFor = function(user){ imat: this.imat, createdAt: this.createdAt, updatedAt: this.updatedAt, - author: this.author.toProfileJSONFor(user) + author: this.author && typeof this.author.toProfileJSONFor === 'function' + ? this.author.toProfileJSONFor(user) + : (user && typeof user.toProfileJSONFor === 'function' ? user.toProfileJSONFor() : this.author ?? null) }; }; diff --git a/src/database/models/mongo/Application.js b/src/database/models/mongo/Application.js index b55a326..53d1453 100644 --- a/src/database/models/mongo/Application.js +++ b/src/database/models/mongo/Application.js @@ -32,7 +32,9 @@ ApplicationSchema.methods.toJSONFor = function (user) { description: this.description, createdAt: this.createdAt, updatedAt: this.updatedAt, - author: this.author.toProfileJSONFor(user) + author: this.author && typeof this.author.toProfileJSONFor === 'function' + ? this.author.toProfileJSONFor(user) + : (user && typeof user.toProfileJSONFor === 'function' ? user.toProfileJSONFor() : this.author ?? null) }; }; @@ -44,7 +46,9 @@ ApplicationSchema.methods.toAuthJSON = function () { description: this.description, createdAt: this.createdAt, updatedAt: this.updatedAt, - author: this.author.toJSONFor() + author: this.author && typeof this.author.toJSONFor === 'function' + ? this.author.toJSONFor() + : this.author ?? null }; }; diff --git a/src/database/models/mongo/Canopy.js b/src/database/models/mongo/Canopy.js index 2e8c393..9195ef8 100644 --- a/src/database/models/mongo/Canopy.js +++ b/src/database/models/mongo/Canopy.js @@ -26,7 +26,9 @@ CanopySchema.methods.toJSONFor = function(user){ taille: this.taille, createdAt: this.createdAt, updatedAt: this.updatedAt, - author: this.author.toProfileJSONFor(user) + author: this.author && typeof this.author.toProfileJSONFor === 'function' + ? this.author.toProfileJSONFor(user) + : (user && typeof user.toProfileJSONFor === 'function' ? user.toProfileJSONFor() : this.author ?? null) }; }; diff --git a/src/database/models/mongo/Dropzone.js b/src/database/models/mongo/Dropzone.js index 72e2b82..e5206e5 100644 --- a/src/database/models/mongo/Dropzone.js +++ b/src/database/models/mongo/Dropzone.js @@ -26,7 +26,9 @@ DropzoneSchema.methods.toJSONFor = function(user){ oaci: this.oaci, createdAt: this.createdAt, updatedAt: this.updatedAt, - author: this.author.toProfileJSONFor(user) + author: this.author && typeof this.author.toProfileJSONFor === 'function' + ? this.author.toProfileJSONFor(user) + : (user && typeof user.toProfileJSONFor === 'function' ? user.toProfileJSONFor() : this.author ?? null) }; }; diff --git a/src/database/models/mongo/Jump.js b/src/database/models/mongo/Jump.js index b97e4ce..a1b214d 100644 --- a/src/database/models/mongo/Jump.js +++ b/src/database/models/mongo/Jump.js @@ -81,7 +81,7 @@ JumpSchema.methods.toJSONFor = function(user) { updatedAt: this.updatedAt, author: this.author && typeof this.author.toProfileJSONFor === 'function' ? this.author.toProfileJSONFor(user) - : this.author ?? null + : (user && typeof user.toProfileJSONFor === 'function' ? user.toProfileJSONFor() : this.author ?? null) }; }; diff --git a/src/database/models/mongo/JumpFile.js b/src/database/models/mongo/JumpFile.js index 55ca010..eb41cca 100644 --- a/src/database/models/mongo/JumpFile.js +++ b/src/database/models/mongo/JumpFile.js @@ -35,7 +35,9 @@ JumpFileSchema.methods.toJSONFor = function(user){ type: this.type, createdAt: this.createdAt, updatedAt: this.updatedAt, - author: this.author.toProfileJSONFor(user) + author: this.author && typeof this.author.toProfileJSONFor === 'function' + ? this.author.toProfileJSONFor(user) + : (user && typeof user.toProfileJSONFor === 'function' ? user.toProfileJSONFor() : this.author ?? null) }; }; diff --git a/src/database/models/mongo/X2DataLog.js b/src/database/models/mongo/X2DataLog.js index 9fc641e..da97e94 100644 --- a/src/database/models/mongo/X2DataLog.js +++ b/src/database/models/mongo/X2DataLog.js @@ -100,7 +100,9 @@ X2DataLogSchema.methods.toJSONForUser = function(user) { createdOn: this.createdOn, createdAt: this.createdAt, updatedAt: this.updatedAt, - author: this.author.toProfileJSONFor(user) + author: this.author && typeof this.author.toProfileJSONFor === 'function' + ? this.author.toProfileJSONFor(user) + : (user && typeof user.toProfileJSONFor === 'function' ? user.toProfileJSONFor() : this.author ?? null) }; }; diff --git a/src/database/models/mongo/herowars/Clan.js b/src/database/models/mongo/herowars/Clan.js index afb82bc..b4bc47c 100644 --- a/src/database/models/mongo/herowars/Clan.js +++ b/src/database/models/mongo/herowars/Clan.js @@ -48,7 +48,9 @@ HWClanSchema.methods.toJSONFor = function() { topDungeon: this.topDungeon, createdAt: this.createdAt, updatedAt: this.updatedAt, - author: this.author.toJSONFor() + author: this.author && typeof this.author.toJSONFor === 'function' + ? this.author.toJSONFor() + : this.author ?? null }; }; diff --git a/src/database/models/mongo/herowars/Member.js b/src/database/models/mongo/herowars/Member.js index e0e8a46..1d0d25d 100644 --- a/src/database/models/mongo/herowars/Member.js +++ b/src/database/models/mongo/herowars/Member.js @@ -128,7 +128,9 @@ HWMemberSchema.methods.toJSONFor = function() { serverId: this.serverId, createdAt: this.createdAt, updatedAt: this.updatedAt, - author: this.author.toJSONFor() + author: this.author && typeof this.author.toJSONFor === 'function' + ? this.author.toJSONFor() + : this.author ?? null }; }; diff --git a/src/database/mysql.js b/src/database/mysql.js index 8982401..a51bb22 100644 --- a/src/database/mysql.js +++ b/src/database/mysql.js @@ -16,6 +16,7 @@ const TagModel = require('./models/mysql/Tag'); const TagListModel = require('./models/mysql/TagList'); const UserModel = require('./models/mysql/User'); const UserRoleXrefModel = require('./models/mysql/UserRoleXref'); +const SkydiverProfileModel = require('./models/mysql/SkydiverProfile'); const DB = { sequelize, @@ -35,6 +36,7 @@ const DB = { Tag: TagModel(sequelize, DataTypes), TagList: TagListModel(sequelize, DataTypes), UserRoleXref: UserRoleXrefModel(sequelize, DataTypes), + SkydiverProfile: SkydiverProfileModel(), }; module.exports = DB; diff --git a/src/routes/api/skydive/applications.routes.js b/src/routes/api/skydive/applications.routes.js index 19909db..29aa211 100644 --- a/src/routes/api/skydive/applications.routes.js +++ b/src/routes/api/skydive/applications.routes.js @@ -10,7 +10,6 @@ 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); @@ -52,7 +51,6 @@ router.get('/', auth.required, function (req, res, next) { .skip(Number(offset)) .limit(Number(limit)) .sort({ createdAt: 'desc' }) - .populate('author') .exec(), Application.countDocuments(query).exec(), req.payload ? UserService.getUserById(req.payload.id) : null @@ -117,8 +115,7 @@ router.post('/', auth.required, function (req, res, next) { */ router.get('/:application', auth.required, function (req, res, next) { Promise.all([ - req.payload ? UserService.getUserById(req.payload.id) : null, - req.application.populate('author') + req.payload ? UserService.getUserById(req.payload.id) : null ]).then(function (results) { let user = results[0]; return res.json({ application: req.application.toJSONFor(user) }); diff --git a/src/routes/api/skydive/jumps.routes.js b/src/routes/api/skydive/jumps.routes.js index 5468856..6e3aa3f 100644 --- a/src/routes/api/skydive/jumps.routes.js +++ b/src/routes/api/skydive/jumps.routes.js @@ -13,7 +13,6 @@ const { UserService } = require('../../../services'); // 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) { @@ -98,7 +97,6 @@ router.get('/', auth.required, function (req, res, next) { .skip(Number(offset)) .limit(Number(limit)) .sort({ numero: 'desc' }) - .populate('author') .populate('files') .populate('x2data') .exec(), @@ -107,9 +105,10 @@ router.get('/', auth.required, function (req, res, next) { ]).then(function (results) { let jumps = results[0]; let jumpsCount = results[1]; + let user = results[2]; return res.json({ jumps: jumps.map(function (jump) { - return jump.toJSONFor(null); + return jump.toJSONFor(user); }), jumpsCount: jumpsCount }); @@ -283,7 +282,6 @@ router.get('/last', auth.required, function (req, res, next) { Jump.find({author: user.id}) .limit(1) .sort({ numero: 'desc' }) - .populate('author') .populate('files') .populate('x2data') .exec() @@ -292,10 +290,10 @@ router.get('/last', auth.required, function (req, res, next) { return res.status(404).json({ errors: { "Not Found": "aucun saut trouvé" } }); } req.lastjump = jumps[0]; - if (req.lastjump.author.username !== req.payload.username) { + if (req.lastjump.author !== req.payload.id) { return res.status(403).json({ errors: { "Forbidden": "Vous ne disposez pas des autorisations suffisantes" } }); } - return res.json({ jump: req.lastjump.toJSONFor(null) }); + return res.json({ jump: req.lastjump.toJSONFor(user) }); }).catch(next); }).catch(next); }); @@ -317,7 +315,7 @@ router.get('/:jump', auth.required, function (req, res, next) { Jump.findOne({numero: (req.jump.numero + 1)}).exec(), ]).then(function (results) { return res.json({ - jump: req.jump.toJSONFor(null), + jump: req.jump.toJSONFor(user), prevJump: results[0], nextJump: results[1] }); @@ -336,7 +334,7 @@ router.post('/', auth.required, function (req, res, next) { let jump = new Jump(req.body.jump); jump.author = user.id; return jump.save().then(function () { - return res.json({ jump: jump.toJSONFor(null) }); + return res.json({ jump: jump.toJSONFor(user) }); }); }).catch(next); }); @@ -366,7 +364,7 @@ router.post('/data/:jump', auth.required, function (req, res, next) { 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(null) }); + return res.json({ jump: req.jump.toJSONFor(user) }); }).catch(next); }).catch(next); }).catch(next); @@ -492,7 +490,7 @@ router.put('/:jump', auth.required, function (req, res, next) { } return req.jump.save().then(function (jump) { - return res.json({ jump: jump.toJSONFor(null) }); + return res.json({ jump: jump.toJSONFor(user) }); }); }).catch(next); }); diff --git a/src/routes/api/skydive/pages.routes.js b/src/routes/api/skydive/pages.routes.js index ee0df26..edb577f 100644 --- a/src/routes/api/skydive/pages.routes.js +++ b/src/routes/api/skydive/pages.routes.js @@ -23,7 +23,6 @@ router.get('/aeronefs', auth.required, function (req, res, next) { .limit(1) .skip(0) .sort({ numero: 'desc' }) - .populate('author') .populate('files') .populate('x2data') .exec(), @@ -37,7 +36,7 @@ router.get('/aeronefs', auth.required, function (req, res, next) { .exec() ]).then(function (results) { if (results[0].length) { - let lastjump = results[0][0].toJSONFor(null); + 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 }; }); @@ -79,7 +78,6 @@ router.get('/canopies', auth.required, function (req, res, next) { .limit(1) .skip(0) .sort({ numero: 'desc' }) - .populate('author') .populate('files') .populate('x2data') .exec(), @@ -101,7 +99,7 @@ router.get('/canopies', auth.required, function (req, res, next) { .exec() ]).then(function (results) { if (results[0].length) { - let lastjump = results[0][0].toJSONFor(null); + let lastjump = results[0][0].toJSONFor(user); let canopiesBySize = results[1].map(function (data) { return { taille: data._id.taille, count: data.count }; }); @@ -146,7 +144,6 @@ router.get('/dropzones', auth.required, function (req, res, next) { .limit(1) .skip(0) .sort({ numero: 'desc' }) - .populate('author') .populate('files') .populate('x2data') .exec(), @@ -160,7 +157,7 @@ router.get('/dropzones', auth.required, function (req, res, next) { .exec() ]).then(function (results) { if (results[0].length) { - let lastjump = results[0][0].toJSONFor(null); + 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 }; }); @@ -197,7 +194,6 @@ router.get('/jumps', auth.required, function (req, res, next) { .limit(1) .skip(0) .sort({ numero: 'desc' }) - .populate('author') .populate('files') .populate('x2data') .exec(), @@ -223,7 +219,7 @@ router.get('/jumps', auth.required, function (req, res, next) { .exec() ]).then(function (results) { if (results[0].length) { - let lastjump = results[0][0].toJSONFor(null); + let lastjump = results[0][0].toJSONFor(user); let jumpsByYears = results[1].map(function (data) { return { year: data._id.year, count: data.count }; }); diff --git a/src/routes/queries.js b/src/routes/queries.js index 1ae0654..76a8ec1 100644 --- a/src/routes/queries.js +++ b/src/routes/queries.js @@ -2,11 +2,7 @@ const queries = { getJumpsByCategoryQuery: (userId) => { let query = [ { - '$match': { - '$expr': { - '$eq': [ '$author' , { '$toObjectId': userId } ] - } - } + '$match': { 'author': userId } }, { '$group': { '_id': { @@ -28,11 +24,7 @@ const queries = { getJumpsByDateQuery: (userId) => { let query = [ { - '$match': { - '$expr': { - '$eq': [ '$author' , { '$toObjectId': userId } ] - } - } + '$match': { 'author': userId } }, { '$project': { '_id': 0, @@ -65,11 +57,7 @@ const queries = { getJumpsByModuleByDateQuery: (userId) => { let query = [ { - '$match': { - '$expr': { - '$eq': [ '$author' , { '$toObjectId': userId } ] - } - } + '$match': { 'author': userId } }, { '$project': { '_id': 0, @@ -105,11 +93,7 @@ const queries = { getJumpsByModuleQuery: (userId) => { let query = [ { - '$match': { - '$expr': { - '$eq': [ '$author' , { '$toObjectId': userId } ] - } - } + '$match': { 'author': userId } }, { '$project': { '_id': 0, @@ -137,11 +121,7 @@ const queries = { getJumpByYearsQuery: (userId) => { let query = [ { - '$match': { - '$expr': { - '$eq': [ '$author' , { '$toObjectId': userId } ] - } - } + '$match': { 'author': userId } }, { '$project': { '_id': 0, @@ -170,17 +150,13 @@ const queries = { getAeronefsByImatQuery: (userId) => { let query = [ { - '$match': { - '$expr': { - '$eq': [ '$author' , { '$toObjectId': userId } ] - } - } + '$match': { 'author': userId } }, { '$group': { '_id': { 'aeronef': '$aeronef', 'imat': '$imat' - }, + }, 'count': { '$sum': 1 } @@ -198,11 +174,7 @@ const queries = { getAeronefsByImatByYearQuery: (userId) => { let query = [ { - '$match': { - '$expr': { - '$eq': [ '$author' , { '$toObjectId': userId } ] - } - } + '$match': { 'author': userId } }, { '$project': { '_id': 0, @@ -235,16 +207,12 @@ const queries = { getCanopiesBySizeQuery: (userId) => { let query = [ { - '$match': { - '$expr': { - '$eq': [ '$author' , { '$toObjectId': userId } ] - } - } + '$match': { 'author': userId } }, { '$group': { '_id': { 'taille': '$taille' - }, + }, 'count': { '$sum': 1 } @@ -262,11 +230,7 @@ const queries = { getCanopiesBySizeByYearQuery: (userId) => { let query = [ { - '$match': { - '$expr': { - '$eq': [ '$author' , { '$toObjectId': userId } ] - } - } + '$match': { 'author': userId } }, { '$project': { '_id': 0, @@ -297,17 +261,13 @@ const queries = { getCanopiesBySizeByModelQuery: (userId) => { let query = [ { - '$match': { - '$expr': { - '$eq': [ '$author' , { '$toObjectId': userId } ] - } - } + '$match': { 'author': userId } }, { '$group': { '_id': { 'taille': '$taille', 'voile': '$voile' - }, + }, 'count': { '$sum': 1 } @@ -325,11 +285,7 @@ const queries = { getCanopiesBySizeByModelByYearQuery: (userId) => { let query = [ { - '$match': { - '$expr': { - '$eq': [ '$author' , { '$toObjectId': userId } ] - } - } + '$match': { 'author': userId } }, { '$project': { '_id': 0, @@ -362,17 +318,13 @@ const queries = { getDropZonesByOaciQuery: (userId) => { let query = [ { - '$match': { - '$expr': { - '$eq': [ '$author' , { '$toObjectId': userId } ] - } - } + '$match': { 'author': userId } }, { '$group': { '_id': { 'lieu': '$lieu', 'oaci': '$oaci' - }, + }, 'count': { '$sum': 1 } @@ -390,11 +342,7 @@ const queries = { getDropZonesByOaciByYearQuery: (userId) => { let query = [ { - '$match': { - '$expr': { - '$eq': [ '$author' , { '$toObjectId': userId } ] - } - } + '$match': { 'author': userId } }, { '$project': { '_id': 0, @@ -435,18 +383,10 @@ const queries = { let query = [ { $match: { - $expr: { - $and: [ - { - $eq: [ '$author' , { '$toObjectId': userId } ] - }, - { - $gte: ['$date', new Date(dateRangeStart)] - }, - { - $lte: ['$date', new Date(dateRangeEnd)] - } - ] + author: userId, + date: { + $gte: new Date(dateRangeStart), + $lte: new Date(dateRangeEnd) } } }, { diff --git a/src/services/mongo/clan.service.js b/src/services/mongo/clan.service.js index a84af21..0b37888 100644 --- a/src/services/mongo/clan.service.js +++ b/src/services/mongo/clan.service.js @@ -16,7 +16,6 @@ class ClanService { .skip(Number(offset)) .limit(Number(limit)) .sort({ title: 'asc' }) - .populate('author') .exec(), HWClan.count(query).exec() ]).then(function (results) { @@ -31,7 +30,6 @@ class ClanService { static async getClanById(id) { return HWClan.findOne({ id: id }) - .populate('author') .then(function (clan) { if (!clan) { return null; @@ -42,7 +40,6 @@ class ClanService { static async getClanByTitle(title) { return HWClan.findOne({ title: title }) - .populate('author') .then(function (clan) { if (!clan) { return null; diff --git a/src/services/mongo/member.service.js b/src/services/mongo/member.service.js index 6a798c2..81002fc 100644 --- a/src/services/mongo/member.service.js +++ b/src/services/mongo/member.service.js @@ -16,7 +16,6 @@ class MemberService { .skip(Number(offset)) .limit(Number(limit)) .sort({ name: 'asc' }) - .populate('author') .exec(), HWMember.count(query).exec() ]).then(function (results) { @@ -31,7 +30,6 @@ class MemberService { static async getMemberById(id) { return HWMember.findOne({ id: id }) - .populate('author') .then(function (member) { if (!member) { return null; @@ -42,7 +40,6 @@ class MemberService { static async getMemberByName(name) { return HWMember.findOne({ name: name }) - .populate('author') .then(function (member) { if (!member) { return null;