@programisto/edrm-exams 0.3.9 → 0.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
1
+ import { Types } from 'mongoose';
1
2
  import { EnduranceSchema } from '@programisto/endurance';
2
3
  import Company from './company.model.js';
3
4
  import TestQuestion from './test-question.model.js';
@@ -32,6 +33,8 @@ declare class Test extends EnduranceSchema {
32
33
  title: string;
33
34
  description: string;
34
35
  companyId: typeof Company;
36
+ /** Identifiant de l'entité (portail multi-entités). Optionnel pour rétrocompatibilité. */
37
+ entityId?: Types.ObjectId;
35
38
  userId: typeof User;
36
39
  questions: TestQuestions[];
37
40
  state: TestState;
@@ -7,6 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
+ import { Types } from 'mongoose';
10
11
  import { EnduranceSchema, EnduranceModelType } from '@programisto/endurance';
11
12
  import Company from './company.model.js';
12
13
  import TestJob from './test-job.model.js';
@@ -44,6 +45,8 @@ let Test = class Test extends EnduranceSchema {
44
45
  title;
45
46
  description;
46
47
  companyId;
48
+ /** Identifiant de l'entité (portail multi-entités). Optionnel pour rétrocompatibilité. */
49
+ entityId;
47
50
  userId;
48
51
  questions;
49
52
  state;
@@ -90,6 +93,10 @@ __decorate([
90
93
  EnduranceModelType.prop({ ref: () => Company, required: false }),
91
94
  __metadata("design:type", Object)
92
95
  ], Test.prototype, "companyId", void 0);
96
+ __decorate([
97
+ EnduranceModelType.prop({ required: false }),
98
+ __metadata("design:type", Types.ObjectId)
99
+ ], Test.prototype, "entityId", void 0);
93
100
  __decorate([
94
101
  EnduranceModelType.prop({ ref: () => User, required: false }),
95
102
  __metadata("design:type", Object)
@@ -25,6 +25,23 @@ async function getJobName(targetJob) {
25
25
  }
26
26
  return 'Job inconnu';
27
27
  }
28
+ /** Entité par défaut : les tests sans entityId lui sont rattachés (slug programisto/progamisto ou isDefault). */
29
+ function isDefaultEntity(req) {
30
+ if (!req?.entity)
31
+ return false;
32
+ const slug = req.entity.slug;
33
+ return req.entity.isDefault === true || slug === 'programisto' || slug === 'progamisto';
34
+ }
35
+ /** Vérifie qu'un test appartient à l'entité courante (pour GET/UPDATE/DELETE). Pour l'entité par défaut, un test sans entityId est accepté. */
36
+ function testBelongsToEntity(test, req) {
37
+ if (!req?.entity?._id)
38
+ return true;
39
+ const testEid = test?.entityId?.toString?.() ?? (test?.entityId != null ? String(test.entityId) : '');
40
+ const reqEid = req.entity._id?.toString?.() ?? String(req.entity._id);
41
+ if (isDefaultEntity(req))
42
+ return testEid === reqEid || testEid === '';
43
+ return testEid === reqEid;
44
+ }
28
45
  // Fonction pour migrer automatiquement un test si nécessaire
29
46
  async function migrateTestIfNeeded(test) {
30
47
  if (typeof test.targetJob === 'string') {
@@ -404,7 +421,8 @@ class ExamsRouter extends EnduranceRouter {
404
421
  targetJob: targetJobId,
405
422
  seniorityLevel,
406
423
  state,
407
- categories: processedCategories
424
+ categories: processedCategories,
425
+ ...(req.entity?._id && { entityId: req.entity._id })
408
426
  });
409
427
  await newTest.save();
410
428
  res.status(201).json({ message: 'test created with sucess', data: newTest });
@@ -451,6 +469,9 @@ class ExamsRouter extends EnduranceRouter {
451
469
  if (!test) {
452
470
  return res.status(404).json({ message: 'Test non trouvé' });
453
471
  }
472
+ if (!testBelongsToEntity(test, req)) {
473
+ return res.status(404).json({ message: 'Test non trouvé' });
474
+ }
454
475
  if (title)
455
476
  test.title = title;
456
477
  if (description)
@@ -539,6 +560,9 @@ class ExamsRouter extends EnduranceRouter {
539
560
  if (!test) {
540
561
  return res.status(404).json({ message: 'Test not found' });
541
562
  }
563
+ if (!testBelongsToEntity(test, req)) {
564
+ return res.status(404).json({ message: 'Test not found' });
565
+ }
542
566
  for (let i = 0; i < test.questions.length; i++) {
543
567
  await TestQuestion.findByIdAndDelete(test.questions[i].questionId);
544
568
  }
@@ -579,6 +603,10 @@ class ExamsRouter extends EnduranceRouter {
579
603
  if (!test) {
580
604
  return res.status(404).json({ message: 'no test founded with this id' });
581
605
  }
606
+ // Vérifier que le test appartient à l'entité courante (multi-entités)
607
+ if (!testBelongsToEntity(test, req)) {
608
+ return res.status(404).json({ message: 'no test founded with this id' });
609
+ }
582
610
  // Migration automatique si nécessaire
583
611
  await migrateTestIfNeeded(test);
584
612
  const questions = [];
@@ -666,6 +694,23 @@ class ExamsRouter extends EnduranceRouter {
666
694
  const sortOrder = req.query.sortOrder || 'desc';
667
695
  // Construction de la requête de recherche
668
696
  const query = {};
697
+ // Filtre par entité. Entité par défaut (programisto/progamisto ou isDefault) = tests sans entityId inclus.
698
+ if (req.entity?._id) {
699
+ if (isDefaultEntity(req)) {
700
+ query.$and = query.$and || [];
701
+ query.$and.push({
702
+ $or: [
703
+ { entityId: req.entity._id },
704
+ { entityId: req.entity._id.toString?.() ?? String(req.entity._id) },
705
+ { entityId: { $exists: false } },
706
+ { entityId: null }
707
+ ]
708
+ });
709
+ }
710
+ else {
711
+ query.entityId = req.entity._id;
712
+ }
713
+ }
669
714
  // Filtres
670
715
  if (targetJob !== 'all') {
671
716
  // Si on filtre par targetJob, on cherche d'abord le TestJob correspondant
@@ -784,9 +829,13 @@ class ExamsRouter extends EnduranceRouter {
784
829
  const category = await TestCategory.findOne({ name: categoryName });
785
830
  if (!category)
786
831
  return res.status(404).json({ message: 'Category not found' });
787
- const test = await Test.findByIdAndUpdate(testId, { $pull: { categories: { categoryId: category._id } } }, { new: true });
832
+ let test = await Test.findById(testId);
788
833
  if (!test)
789
834
  return res.status(404).json({ message: 'Test not found' });
835
+ if (!testBelongsToEntity(test, req)) {
836
+ return res.status(404).json({ message: 'Test not found' });
837
+ }
838
+ test = await Test.findByIdAndUpdate(testId, { $pull: { categories: { categoryId: category._id } } }, { new: true });
790
839
  res.status(200).json({ message: 'Category removed', test });
791
840
  }
792
841
  catch (err) {
@@ -839,6 +888,9 @@ class ExamsRouter extends EnduranceRouter {
839
888
  if (!test) {
840
889
  return res.status(404).json({ message: 'Test not found' });
841
890
  }
891
+ if (!testBelongsToEntity(test, req)) {
892
+ return res.status(404).json({ message: 'Test not found' });
893
+ }
842
894
  const categoryExists = test.categories.some(cat => cat.categoryId.equals(category._id));
843
895
  if (categoryExists) {
844
896
  return res.status(200).json({ message: 'Category already exists in the test' });
@@ -909,6 +961,9 @@ class ExamsRouter extends EnduranceRouter {
909
961
  if (!test) {
910
962
  return res.status(404).json({ message: 'Test not found' });
911
963
  }
964
+ if (!testBelongsToEntity(test, req)) {
965
+ return res.status(404).json({ message: 'Test not found' });
966
+ }
912
967
  const questions = [];
913
968
  for (const questionId of test.questions) {
914
969
  const question = await TestQuestion.findById(questionId);
@@ -959,6 +1014,9 @@ class ExamsRouter extends EnduranceRouter {
959
1014
  if (!test) {
960
1015
  return res.status(404).json({ message: 'no test founded with this id' });
961
1016
  }
1017
+ if (!testBelongsToEntity(test, req)) {
1018
+ return res.status(404).json({ message: 'no test founded with this id' });
1019
+ }
962
1020
  // Supprimer la question du tableau questions en filtrant par questionId
963
1021
  test.questions = test.questions.filter(q => q.questionId.toString() !== questionId);
964
1022
  // Recalculer les ordres pour que ça se suive
@@ -995,6 +1053,9 @@ class ExamsRouter extends EnduranceRouter {
995
1053
  if (!test) {
996
1054
  return res.status(404).json({ message: 'no test founded with this id' });
997
1055
  }
1056
+ if (!testBelongsToEntity(test, req)) {
1057
+ return res.status(404).json({ message: 'no test founded with this id' });
1058
+ }
998
1059
  for (const questionId of test.questions) {
999
1060
  await TestQuestion.findByIdAndDelete(questionId);
1000
1061
  }
@@ -1104,6 +1165,9 @@ class ExamsRouter extends EnduranceRouter {
1104
1165
  if (!test) {
1105
1166
  return res.status(404).json({ message: 'no test founded with this id' });
1106
1167
  }
1168
+ if (!testBelongsToEntity(test, req)) {
1169
+ return res.status(404).json({ message: 'no test founded with this id' });
1170
+ }
1107
1171
  const question = new TestQuestion({
1108
1172
  questionType,
1109
1173
  instruction,
@@ -1162,6 +1226,9 @@ class ExamsRouter extends EnduranceRouter {
1162
1226
  if (!test) {
1163
1227
  return res.status(404).json({ message: 'no test founded with this id' });
1164
1228
  }
1229
+ if (!testBelongsToEntity(test, req)) {
1230
+ return res.status(404).json({ message: 'no test founded with this id' });
1231
+ }
1165
1232
  const otherQuestionsIds = test.questions.map(question => question.questionId);
1166
1233
  const otherQuestions = await TestQuestion.find({ _id: { $in: otherQuestionsIds } });
1167
1234
  const jobName = await getJobName(test.targetJob);
@@ -1212,6 +1279,9 @@ class ExamsRouter extends EnduranceRouter {
1212
1279
  if (!test) {
1213
1280
  return res.status(404).json({ message: 'Test not found' });
1214
1281
  }
1282
+ if (!testBelongsToEntity(test, req)) {
1283
+ return res.status(404).json({ message: 'Test not found' });
1284
+ }
1215
1285
  for (let i = test.questions.length - 1; i > 0; i--) {
1216
1286
  const j = Math.floor(Math.random() * (i + 1));
1217
1287
  [test.questions[i], test.questions[j]] = [test.questions[j], test.questions[i]];
@@ -1262,6 +1332,9 @@ class ExamsRouter extends EnduranceRouter {
1262
1332
  if (!test) {
1263
1333
  return res.status(404).json({ message: 'no test founded with this id' });
1264
1334
  }
1335
+ if (!testBelongsToEntity(test, req)) {
1336
+ return res.status(404).json({ message: 'no test founded with this id' });
1337
+ }
1265
1338
  test.invitationText = invitationText;
1266
1339
  await test.save();
1267
1340
  res.status(200).json({
@@ -1856,6 +1929,9 @@ class ExamsRouter extends EnduranceRouter {
1856
1929
  if (!test) {
1857
1930
  return res.status(404).json({ message: 'Test non trouvé' });
1858
1931
  }
1932
+ if (!testBelongsToEntity(test, req)) {
1933
+ return res.status(404).json({ message: 'Test non trouvé' });
1934
+ }
1859
1935
  let categoriesToUse = [];
1860
1936
  if (category && category !== 'ALL') {
1861
1937
  const categoryInfo = test.categories.find(cat => cat.categoryId.toString() === category);
@@ -1988,6 +2064,9 @@ class ExamsRouter extends EnduranceRouter {
1988
2064
  if (!test) {
1989
2065
  return res.status(404).json({ message: 'Test non trouvé' });
1990
2066
  }
2067
+ if (!testBelongsToEntity(test, req)) {
2068
+ return res.status(404).json({ message: 'Test non trouvé' });
2069
+ }
1991
2070
  // Construction de la requête
1992
2071
  const query = { testId };
1993
2072
  if (state !== 'all') {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@programisto/edrm-exams",
4
- "version": "0.3.9",
4
+ "version": "0.3.11",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },