data-primals-engine 1.5.1 → 1.6.0
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.
- package/README.md +924 -913
- package/client/README.md +136 -136
- package/client/index.js +0 -1
- package/client/public/doc/API.postman_collection.json +213 -213
- package/client/public/react.svg +9 -9
- package/client/src/AddWidgetTypeModal.jsx +47 -47
- package/client/src/App.css +42 -42
- package/client/src/App.jsx +92 -150
- package/client/src/App.scss +7 -1
- package/client/src/AssistantChat.jsx +362 -363
- package/client/src/Button.jsx +12 -12
- package/client/src/Dashboard.jsx +480 -480
- package/client/src/DashboardHtmlViewItem.jsx +146 -146
- package/client/src/DashboardView.jsx +654 -654
- package/client/src/DataEditor.jsx +383 -383
- package/client/src/DataLayout.jsx +779 -806
- package/client/src/DataTable.jsx +782 -822
- package/client/src/DataTable.scss +186 -186
- package/client/src/GeolocationField.jsx +93 -93
- package/client/src/HistoryDialog.jsx +307 -285
- package/client/src/HistoryDialog.scss +319 -319
- package/client/src/HtmlViewBuilderModal.jsx +90 -90
- package/client/src/HtmlViewBuilderModal.scss +17 -17
- package/client/src/HtmlViewCard.jsx +43 -43
- package/client/src/ModelCreatorField.jsx +950 -950
- package/client/src/ModelList.jsx +7 -2
- package/client/src/Notification.jsx +136 -136
- package/client/src/PackGallery.jsx +391 -391
- package/client/src/PackGallery.scss +231 -231
- package/client/src/Pagination.jsx +143 -143
- package/client/src/RelationField.jsx +354 -354
- package/client/src/RelationSelectorWidget.jsx +172 -172
- package/client/src/RelationValue.jsx +2 -1
- package/client/src/contexts/CommandContext.jsx +260 -0
- package/client/src/contexts/ModelContext.jsx +4 -1
- package/client/src/contexts/UIContext.jsx +72 -72
- package/client/src/filter.js +263 -262
- package/client/src/index.css +90 -90
- package/package.json +11 -10
- package/perf/artillery-hooks.js +37 -37
- package/perf/setup.yml +25 -25
- package/src/constants.js +4 -0
- package/src/core.js +8 -1
- package/src/engine.js +335 -293
- package/src/events.js +140 -21
- package/src/filter.js +274 -274
- package/src/index.js +3 -0
- package/src/modules/assistant/assistant.js +323 -192
- package/src/modules/assistant/constants.js +2 -1
- package/src/modules/auth-google/index.js +50 -50
- package/src/modules/auth-microsoft/index.js +81 -81
- package/src/modules/data/data.core.js +118 -118
- package/src/modules/data/data.history.js +555 -531
- package/src/modules/data/data.operations.js +145 -26
- package/src/modules/data/data.relations.js +686 -686
- package/src/modules/data/data.routes.js +1879 -1879
- package/src/modules/data/data.validation.js +2 -2
- package/src/modules/file.js +247 -247
- package/src/modules/user.js +1 -0
- package/src/modules/workflow.js +2 -2
- package/src/openai.jobs.js +3 -2
- package/src/packs.js +2 -2
- package/src/providers.js +2 -2
- package/src/services/stripe.js +196 -196
- package/src/sso.js +193 -193
- package/src/workers/import-export-worker.js +1 -1
- package/test/data.history.integration.test.js +72 -0
- package/test/data.integration.test.js +92 -1
- package/test/events.test.js +108 -10
- package/test/model.integration.test.js +377 -377
package/src/sso.js
CHANGED
|
@@ -1,194 +1,194 @@
|
|
|
1
|
-
import passport from 'passport';
|
|
2
|
-
import session from 'express-session';
|
|
3
|
-
import {
|
|
4
|
-
import { cookiesSecret } from "./constants.js";
|
|
5
|
-
import {UserProvider} from "./providers.js";
|
|
6
|
-
import {getCollection} from "./modules/mongodb.js";
|
|
7
|
-
import process from "process";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @class SSOUserProvider
|
|
11
|
-
* Gère les utilisateurs provenant de fournisseurs d'identité externes (SSO).
|
|
12
|
-
* Sa responsabilité principale est de trouver un utilisateur existant correspondant
|
|
13
|
-
* au profil SSO ou d'en créer un nouveau si nécessaire (provisioning à la volée).
|
|
14
|
-
*/
|
|
15
|
-
export class SSOUserProvider extends UserProvider {
|
|
16
|
-
|
|
17
|
-
usersCollection;
|
|
18
|
-
|
|
19
|
-
constructor(engine) {
|
|
20
|
-
super(engine);
|
|
21
|
-
// Idéalement, la collection d'utilisateurs devrait être centralisée.
|
|
22
|
-
// Pour cet exemple, nous la récupérons directement.
|
|
23
|
-
this.usersCollection = getCollection("users"); // Assurez-vous que cette collection existe
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Trouve un utilisateur par son ID. Nécessaire pour la désérialisation de Passport.
|
|
28
|
-
* @param {string} id - L'ID de l'utilisateur.
|
|
29
|
-
* @returns {Promise<object|null>}
|
|
30
|
-
*/
|
|
31
|
-
async findUserById(id) {
|
|
32
|
-
// Note: Assurez-vous que vos utilisateurs ont un champ `_id` stable.
|
|
33
|
-
// Si vous utilisez le `username` comme identifiant principal, adaptez cette méthode.
|
|
34
|
-
try {
|
|
35
|
-
const { ObjectId } = await import('mongodb');
|
|
36
|
-
return await this.usersCollection.findOne({ _id: new ObjectId(id) });
|
|
37
|
-
} catch (e) {
|
|
38
|
-
return null; // Gère le cas où l'ID n'est pas un ObjectId valide
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* La méthode clé pour l'intégration SSO.
|
|
44
|
-
* Cherche un utilisateur basé sur le profil fourni par le fournisseur d'identité.
|
|
45
|
-
* S'il n'existe pas, il le crée.
|
|
46
|
-
* @param {object} profile - Le profil utilisateur de Passport (ex: de Google, SAML).
|
|
47
|
-
* @returns {Promise<object>} L'utilisateur de votre système.
|
|
48
|
-
*/
|
|
49
|
-
async findOrCreate(profile) {
|
|
50
|
-
if (!profile.emails || !profile.emails[0] || !profile.emails[0].value) {
|
|
51
|
-
throw new Error("Le profil du fournisseur d'identité ne contient pas d'email.");
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const email = profile.emails[0].value;
|
|
55
|
-
|
|
56
|
-
// 1. Chercher un utilisateur existant avec cet email.
|
|
57
|
-
const existingUser = await this.usersCollection.findOne({ email: email });
|
|
58
|
-
|
|
59
|
-
if (existingUser) {
|
|
60
|
-
// TODO: Mettre à jour les informations de l'utilisateur si nécessaire (ex: nom, photo de profil)
|
|
61
|
-
// await this.usersCollection.updateOne({ _id: existingUser._id }, { $set: { lastLogin: new Date() } });
|
|
62
|
-
return existingUser;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// 2. Si aucun utilisateur n'est trouvé, en créer un nouveau.
|
|
66
|
-
const newUser = {
|
|
67
|
-
username: email, // Utiliser l'email comme nom d'utilisateur par défaut
|
|
68
|
-
email: email,
|
|
69
|
-
name: profile.displayName || email,
|
|
70
|
-
provider: profile.provider, // ex: 'google', 'saml'
|
|
71
|
-
providerId: profile.id,
|
|
72
|
-
userPlan: 'free', // Assigner un plan par défaut
|
|
73
|
-
createdAt: new Date()
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const result = await this.usersCollection.insertOne(newUser);
|
|
77
|
-
return await this.findUserById(result.insertedId);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Les autres méthodes comme validatePassword ne sont pas pertinentes pour le SSO.
|
|
81
|
-
// On peut les laisser vides ou les faire lever une erreur.
|
|
82
|
-
async validatePassword(user, password) { return false; }
|
|
83
|
-
async findUserByUsername(username) { return await this.usersCollection.findOne({ username }); }
|
|
84
|
-
async initiateUser(req) { /* Géré par Passport */ }
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* @class Sso
|
|
89
|
-
* @extends Behaviour
|
|
90
|
-
* Un composant réutilisable qui encapsule la logique de Passport.js pour l'authentification.
|
|
91
|
-
* Il gère la session, la sérialisation, et permet à d'autres modules d'enregistrer
|
|
92
|
-
* dynamiquement des stratégies d'authentification (Google, SAML, etc.).
|
|
93
|
-
*/
|
|
94
|
-
export class Sso extends Behaviour {
|
|
95
|
-
#ssoUserProvider = null;
|
|
96
|
-
#logger = null;
|
|
97
|
-
|
|
98
|
-
constructor(gameObject) {
|
|
99
|
-
super(gameObject);
|
|
100
|
-
this.strategies = new Map();
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Initialise le composant avec les middlewares et la configuration de base de Passport.
|
|
105
|
-
* @param {object} options
|
|
106
|
-
* @param {UserProvider} options.ssoUserProvider - Le provider pour trouver ou créer des utilisateurs SSO.
|
|
107
|
-
*/
|
|
108
|
-
initialize({ ssoUserProvider }) {
|
|
109
|
-
if (!ssoUserProvider) {
|
|
110
|
-
throw new Error("PassportAuth component requires an ssoUserProvider to be initialized.");
|
|
111
|
-
}
|
|
112
|
-
this.#ssoUserProvider = ssoUserProvider;
|
|
113
|
-
this.#logger = this.gameObject.getComponent(
|
|
114
|
-
|
|
115
|
-
const app = this.gameObject; // this.gameObject est l'instance de l'engine (Express app)
|
|
116
|
-
|
|
117
|
-
// 1. Configurer la session Express (prérequis pour Passport)
|
|
118
|
-
app.use(session({
|
|
119
|
-
secret: process.env.COOKIES_SECRET || cookiesSecret,
|
|
120
|
-
resave: false,
|
|
121
|
-
saveUninitialized: false,
|
|
122
|
-
cookie: { secure: process.env.NODE_ENV === 'production', httpOnly: true, sameSite: 'lax' }
|
|
123
|
-
}));
|
|
124
|
-
|
|
125
|
-
// 2. Initialiser Passport
|
|
126
|
-
app.use(passport.initialize());
|
|
127
|
-
app.use(passport.session());
|
|
128
|
-
|
|
129
|
-
// 3. Configurer la sérialisation/désérialisation
|
|
130
|
-
passport.serializeUser((user, done) => {
|
|
131
|
-
done(null, user._id);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
passport.deserializeUser(async (id, done) => {
|
|
135
|
-
try {
|
|
136
|
-
const user = await this.#ssoUserProvider.findUserById(id);
|
|
137
|
-
done(null, user);
|
|
138
|
-
} catch (err) {
|
|
139
|
-
done(err);
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
this.#logger.info("PassportAuth component initialized successfully.");
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Permet à d'autres modules d'enregistrer une nouvelle stratégie d'authentification.
|
|
148
|
-
* @param {string} name - Le nom de la stratégie (ex: 'google', 'saml').
|
|
149
|
-
* @param {object} strategy - L'instance de la stratégie Passport.
|
|
150
|
-
* @param {object} routes - Les chemins pour l'authentification.
|
|
151
|
-
* @param {string} routes.authPath - Le chemin pour initier l'authentification (ex: '/api/auth/google').
|
|
152
|
-
* @param {string} routes.callbackPath - Le chemin de callback (ex: '/api/auth/google/callback').
|
|
153
|
-
* @param {object} options - Options d'authentification pour Passport (ex: { scope: ['profile', 'email'] }).
|
|
154
|
-
*/
|
|
155
|
-
addStrategy(name, strategy, routes, options = {}) {
|
|
156
|
-
if (this.strategies.has(name)) {
|
|
157
|
-
this.#logger.warn(`Passport strategy '${name}' is already registered. Overwriting.`);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Enregistrer la stratégie auprès de Passport
|
|
161
|
-
passport.use(strategy);
|
|
162
|
-
|
|
163
|
-
const app = this.gameObject;
|
|
164
|
-
|
|
165
|
-
// Créer la route d'initiation
|
|
166
|
-
app.get(routes.authPath, passport.authenticate(name, options));
|
|
167
|
-
|
|
168
|
-
// Créer la route de callback
|
|
169
|
-
app.get(
|
|
170
|
-
routes.callbackPath,
|
|
171
|
-
passport.authenticate(name, { failureRedirect: '/login', failureMessage: true }),
|
|
172
|
-
(req, res) => {
|
|
173
|
-
// Redirection après une authentification réussie
|
|
174
|
-
res.redirect(req.session.returnTo || '/dashboard');
|
|
175
|
-
delete req.session.returnTo;
|
|
176
|
-
}
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
this.strategies.set(name, { strategy, routes, options });
|
|
180
|
-
this.#logger.info(`Passport strategy '${name}' registered with routes: ${routes.authPath}, ${routes.callbackPath}`);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
addLogoutRoute(path = '/api/auth/logout') {
|
|
184
|
-
this.gameObject.get(path, (req, res, next) => {
|
|
185
|
-
req.logout((err) => {
|
|
186
|
-
if (err) { return next(err); }
|
|
187
|
-
req.session.destroy(() => {
|
|
188
|
-
res.redirect('/');
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
this.#logger.info(`Logout route registered at '${path}'.`);
|
|
193
|
-
}
|
|
1
|
+
import passport from 'passport';
|
|
2
|
+
import session from 'express-session';
|
|
3
|
+
import {Behaviour, Logger} from './gameObject.js';
|
|
4
|
+
import { cookiesSecret } from "./constants.js";
|
|
5
|
+
import {UserProvider} from "./providers.js";
|
|
6
|
+
import {getCollection} from "./modules/mongodb.js";
|
|
7
|
+
import process from "process";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @class SSOUserProvider
|
|
11
|
+
* Gère les utilisateurs provenant de fournisseurs d'identité externes (SSO).
|
|
12
|
+
* Sa responsabilité principale est de trouver un utilisateur existant correspondant
|
|
13
|
+
* au profil SSO ou d'en créer un nouveau si nécessaire (provisioning à la volée).
|
|
14
|
+
*/
|
|
15
|
+
export class SSOUserProvider extends UserProvider {
|
|
16
|
+
|
|
17
|
+
usersCollection;
|
|
18
|
+
|
|
19
|
+
constructor(engine) {
|
|
20
|
+
super(engine);
|
|
21
|
+
// Idéalement, la collection d'utilisateurs devrait être centralisée.
|
|
22
|
+
// Pour cet exemple, nous la récupérons directement.
|
|
23
|
+
this.usersCollection = getCollection("users"); // Assurez-vous que cette collection existe
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Trouve un utilisateur par son ID. Nécessaire pour la désérialisation de Passport.
|
|
28
|
+
* @param {string} id - L'ID de l'utilisateur.
|
|
29
|
+
* @returns {Promise<object|null>}
|
|
30
|
+
*/
|
|
31
|
+
async findUserById(id) {
|
|
32
|
+
// Note: Assurez-vous que vos utilisateurs ont un champ `_id` stable.
|
|
33
|
+
// Si vous utilisez le `username` comme identifiant principal, adaptez cette méthode.
|
|
34
|
+
try {
|
|
35
|
+
const { ObjectId } = await import('mongodb');
|
|
36
|
+
return await this.usersCollection.findOne({ _id: new ObjectId(id) });
|
|
37
|
+
} catch (e) {
|
|
38
|
+
return null; // Gère le cas où l'ID n'est pas un ObjectId valide
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* La méthode clé pour l'intégration SSO.
|
|
44
|
+
* Cherche un utilisateur basé sur le profil fourni par le fournisseur d'identité.
|
|
45
|
+
* S'il n'existe pas, il le crée.
|
|
46
|
+
* @param {object} profile - Le profil utilisateur de Passport (ex: de Google, SAML).
|
|
47
|
+
* @returns {Promise<object>} L'utilisateur de votre système.
|
|
48
|
+
*/
|
|
49
|
+
async findOrCreate(profile) {
|
|
50
|
+
if (!profile.emails || !profile.emails[0] || !profile.emails[0].value) {
|
|
51
|
+
throw new Error("Le profil du fournisseur d'identité ne contient pas d'email.");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const email = profile.emails[0].value;
|
|
55
|
+
|
|
56
|
+
// 1. Chercher un utilisateur existant avec cet email.
|
|
57
|
+
const existingUser = await this.usersCollection.findOne({ email: email });
|
|
58
|
+
|
|
59
|
+
if (existingUser) {
|
|
60
|
+
// TODO: Mettre à jour les informations de l'utilisateur si nécessaire (ex: nom, photo de profil)
|
|
61
|
+
// await this.usersCollection.updateOne({ _id: existingUser._id }, { $set: { lastLogin: new Date() } });
|
|
62
|
+
return existingUser;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 2. Si aucun utilisateur n'est trouvé, en créer un nouveau.
|
|
66
|
+
const newUser = {
|
|
67
|
+
username: email, // Utiliser l'email comme nom d'utilisateur par défaut
|
|
68
|
+
email: email,
|
|
69
|
+
name: profile.displayName || email,
|
|
70
|
+
provider: profile.provider, // ex: 'google', 'saml'
|
|
71
|
+
providerId: profile.id,
|
|
72
|
+
userPlan: 'free', // Assigner un plan par défaut
|
|
73
|
+
createdAt: new Date()
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const result = await this.usersCollection.insertOne(newUser);
|
|
77
|
+
return await this.findUserById(result.insertedId);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Les autres méthodes comme validatePassword ne sont pas pertinentes pour le SSO.
|
|
81
|
+
// On peut les laisser vides ou les faire lever une erreur.
|
|
82
|
+
async validatePassword(user, password) { return false; }
|
|
83
|
+
async findUserByUsername(username) { return await this.usersCollection.findOne({ username }); }
|
|
84
|
+
async initiateUser(req) { /* Géré par Passport */ }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @class Sso
|
|
89
|
+
* @extends Behaviour
|
|
90
|
+
* Un composant réutilisable qui encapsule la logique de Passport.js pour l'authentification.
|
|
91
|
+
* Il gère la session, la sérialisation, et permet à d'autres modules d'enregistrer
|
|
92
|
+
* dynamiquement des stratégies d'authentification (Google, SAML, etc.).
|
|
93
|
+
*/
|
|
94
|
+
export class Sso extends Behaviour {
|
|
95
|
+
#ssoUserProvider = null;
|
|
96
|
+
#logger = null;
|
|
97
|
+
|
|
98
|
+
constructor(gameObject) {
|
|
99
|
+
super(gameObject);
|
|
100
|
+
this.strategies = new Map();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Initialise le composant avec les middlewares et la configuration de base de Passport.
|
|
105
|
+
* @param {object} options
|
|
106
|
+
* @param {UserProvider} options.ssoUserProvider - Le provider pour trouver ou créer des utilisateurs SSO.
|
|
107
|
+
*/
|
|
108
|
+
initialize({ ssoUserProvider }) {
|
|
109
|
+
if (!ssoUserProvider) {
|
|
110
|
+
throw new Error("PassportAuth component requires an ssoUserProvider to be initialized.");
|
|
111
|
+
}
|
|
112
|
+
this.#ssoUserProvider = ssoUserProvider;
|
|
113
|
+
this.#logger = this.gameObject.getComponent(Logger); // Assumant que Logger est un composant
|
|
114
|
+
|
|
115
|
+
const app = this.gameObject; // this.gameObject est l'instance de l'engine (Express app)
|
|
116
|
+
|
|
117
|
+
// 1. Configurer la session Express (prérequis pour Passport)
|
|
118
|
+
app.use(session({
|
|
119
|
+
secret: process.env.COOKIES_SECRET || cookiesSecret,
|
|
120
|
+
resave: false,
|
|
121
|
+
saveUninitialized: false,
|
|
122
|
+
cookie: { secure: process.env.NODE_ENV === 'production', httpOnly: true, sameSite: 'lax' }
|
|
123
|
+
}));
|
|
124
|
+
|
|
125
|
+
// 2. Initialiser Passport
|
|
126
|
+
app.use(passport.initialize());
|
|
127
|
+
app.use(passport.session());
|
|
128
|
+
|
|
129
|
+
// 3. Configurer la sérialisation/désérialisation
|
|
130
|
+
passport.serializeUser((user, done) => {
|
|
131
|
+
done(null, user._id);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
passport.deserializeUser(async (id, done) => {
|
|
135
|
+
try {
|
|
136
|
+
const user = await this.#ssoUserProvider.findUserById(id);
|
|
137
|
+
done(null, user);
|
|
138
|
+
} catch (err) {
|
|
139
|
+
done(err);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
this.#logger.info("PassportAuth component initialized successfully.");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Permet à d'autres modules d'enregistrer une nouvelle stratégie d'authentification.
|
|
148
|
+
* @param {string} name - Le nom de la stratégie (ex: 'google', 'saml').
|
|
149
|
+
* @param {object} strategy - L'instance de la stratégie Passport.
|
|
150
|
+
* @param {object} routes - Les chemins pour l'authentification.
|
|
151
|
+
* @param {string} routes.authPath - Le chemin pour initier l'authentification (ex: '/api/auth/google').
|
|
152
|
+
* @param {string} routes.callbackPath - Le chemin de callback (ex: '/api/auth/google/callback').
|
|
153
|
+
* @param {object} options - Options d'authentification pour Passport (ex: { scope: ['profile', 'email'] }).
|
|
154
|
+
*/
|
|
155
|
+
addStrategy(name, strategy, routes, options = {}) {
|
|
156
|
+
if (this.strategies.has(name)) {
|
|
157
|
+
this.#logger.warn(`Passport strategy '${name}' is already registered. Overwriting.`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Enregistrer la stratégie auprès de Passport
|
|
161
|
+
passport.use(strategy);
|
|
162
|
+
|
|
163
|
+
const app = this.gameObject;
|
|
164
|
+
|
|
165
|
+
// Créer la route d'initiation
|
|
166
|
+
app.get(routes.authPath, passport.authenticate(name, options));
|
|
167
|
+
|
|
168
|
+
// Créer la route de callback
|
|
169
|
+
app.get(
|
|
170
|
+
routes.callbackPath,
|
|
171
|
+
passport.authenticate(name, { failureRedirect: '/login', failureMessage: true }),
|
|
172
|
+
(req, res) => {
|
|
173
|
+
// Redirection après une authentification réussie
|
|
174
|
+
res.redirect(req.session.returnTo || '/dashboard');
|
|
175
|
+
delete req.session.returnTo;
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
this.strategies.set(name, { strategy, routes, options });
|
|
180
|
+
this.#logger.info(`Passport strategy '${name}' registered with routes: ${routes.authPath}, ${routes.callbackPath}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
addLogoutRoute(path = '/api/auth/logout') {
|
|
184
|
+
this.gameObject.get(path, (req, res, next) => {
|
|
185
|
+
req.logout((err) => {
|
|
186
|
+
if (err) { return next(err); }
|
|
187
|
+
req.session.destroy(() => {
|
|
188
|
+
res.redirect('/');
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
this.#logger.info(`Logout route registered at '${path}'.`);
|
|
193
|
+
}
|
|
194
194
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// __tests__/data.history.integration.test.js
|
|
2
2
|
import { ObjectId } from 'mongodb';
|
|
3
3
|
import { expect, describe, it, beforeAll, afterAll, beforeEach } from 'vitest';
|
|
4
|
+
import { handleGetHistoryRequest } from '../src/modules/data/data.history.js';
|
|
4
5
|
import { Config } from '../src/config.js';
|
|
5
6
|
import { sleep } from '../src/core.js';
|
|
6
7
|
import { insertData, editData, deleteModels } from '../src/index.js';
|
|
@@ -190,4 +191,75 @@ describe('Data History Module Integration Tests', () => {
|
|
|
190
191
|
const historyCount = await historyCollection.countDocuments({ documentId: docId });
|
|
191
192
|
expect(historyCount).toBe(1);
|
|
192
193
|
});
|
|
194
|
+
|
|
195
|
+
it('should filter history records by date range', async () => {
|
|
196
|
+
// 1. Setup model
|
|
197
|
+
const modelName = generateUniqueName('productHistoryDateFilter');
|
|
198
|
+
const productModelDef = {
|
|
199
|
+
name: modelName,
|
|
200
|
+
description: "",
|
|
201
|
+
_user: testUser.username,
|
|
202
|
+
history: { enabled: true },
|
|
203
|
+
fields: [{ name: 'name', type: 'string' }]
|
|
204
|
+
};
|
|
205
|
+
await modelsCollection.insertOne(productModelDef);
|
|
206
|
+
|
|
207
|
+
// 2. Insert initial document
|
|
208
|
+
const insertResult = await insertData(modelName, { name: 'Time-traveling Widget' }, {}, testUser);
|
|
209
|
+
const docId = new ObjectId(insertResult.insertedIds[0]);
|
|
210
|
+
|
|
211
|
+
// 3. Create history entries at different times
|
|
212
|
+
// To simulate different timestamps, we'll manually insert history records
|
|
213
|
+
// as editData() would create them too close together in time.
|
|
214
|
+
await historyCollection.updateOne({ documentId: docId, version: 1 }, { $set: { timestamp: new Date('2023-01-10T10:00:00Z') } });
|
|
215
|
+
|
|
216
|
+
await historyCollection.insertOne({
|
|
217
|
+
documentId: docId, model: modelName, version: 2, operation: 'update',
|
|
218
|
+
timestamp: new Date('2023-02-15T12:00:00Z'), user: { username: testUser.username }, changes: { name: { from: 'v1', to: 'v2' } }
|
|
219
|
+
});
|
|
220
|
+
await historyCollection.insertOne({
|
|
221
|
+
documentId: docId, model: modelName, version: 3, operation: 'update',
|
|
222
|
+
timestamp: new Date('2023-02-20T14:00:00Z'), user: { username: testUser.username }, changes: { name: { from: 'v2', to: 'v3' } }
|
|
223
|
+
});
|
|
224
|
+
await historyCollection.insertOne({
|
|
225
|
+
documentId: docId, model: modelName, version: 4, operation: 'update',
|
|
226
|
+
timestamp: new Date('2023-03-05T16:00:00Z'), user: { username: testUser.username }, changes: { name: { from: 'v3', to: 'v4' } }
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Mock Express req/res objects
|
|
230
|
+
const mockReq = (query) => ({
|
|
231
|
+
params: { modelName, recordId: docId.toString() },
|
|
232
|
+
query,
|
|
233
|
+
me: testUser
|
|
234
|
+
});
|
|
235
|
+
const mockRes = () => {
|
|
236
|
+
const res = {};
|
|
237
|
+
res.status = (code) => { res.statusCode = code; return res; };
|
|
238
|
+
res.json = (data) => { res.body = data; return res; };
|
|
239
|
+
return res;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// 4. Test cases
|
|
243
|
+
// Case A: Filter for February
|
|
244
|
+
let req = mockReq({ startDate: '2023-02-01', endDate: '2023-02-28' });
|
|
245
|
+
let res = mockRes();
|
|
246
|
+
await handleGetHistoryRequest(req, res);
|
|
247
|
+
expect(res.body.success).toBe(true);
|
|
248
|
+
expect(res.body.count).toBe(2);
|
|
249
|
+
expect(res.body.data.map(d => d._v).sort()).toEqual([2, 3]);
|
|
250
|
+
|
|
251
|
+
// Case B: Filter starting from Feb 15th
|
|
252
|
+
req = mockReq({ startDate: '2023-02-15' });
|
|
253
|
+
res = mockRes();
|
|
254
|
+
await handleGetHistoryRequest(req, res);
|
|
255
|
+
expect(res.body.success).toBe(true);
|
|
256
|
+
expect(res.body.count).toBe(3); // v2, v3, v4
|
|
257
|
+
|
|
258
|
+
// Case C: Filter up to Feb 15th (inclusive)
|
|
259
|
+
req = mockReq({ endDate: '2023-02-15' });
|
|
260
|
+
res = mockRes();
|
|
261
|
+
await handleGetHistoryRequest(req, res);
|
|
262
|
+
expect(res.body.success).toBe(true);
|
|
263
|
+
expect(res.body.count).toBe(2); // v1, v2
|
|
264
|
+
});
|
|
193
265
|
});
|
|
@@ -130,7 +130,7 @@ async function setupTestContext() {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
let engine;
|
|
133
|
-
describe('
|
|
133
|
+
describe('Data integration tests (CRUD, validation...)', () => {
|
|
134
134
|
|
|
135
135
|
beforeAll(async () =>{
|
|
136
136
|
Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant"]);
|
|
@@ -543,6 +543,97 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
543
543
|
// TODO: Ajouter plus de tests de validation pour editData, similaires à ceux de insertData
|
|
544
544
|
});
|
|
545
545
|
|
|
546
|
+
describe('$push operation using patchData', () =>{
|
|
547
|
+
describe('$push sur tableau existant', () => {
|
|
548
|
+
let docToPatchId;
|
|
549
|
+
let setupContext;
|
|
550
|
+
|
|
551
|
+
beforeEach(async () => {
|
|
552
|
+
setupContext = await setupTestContext();
|
|
553
|
+
const { currentTestUser, comprehensiveTestModelDefinition } = setupContext;
|
|
554
|
+
|
|
555
|
+
const initialData = {
|
|
556
|
+
stringRequired: 'InitialForPush',
|
|
557
|
+
stringUnique: 'UniqueForPush',
|
|
558
|
+
enumField: 'alpha',
|
|
559
|
+
passwordField: 'pushPass',
|
|
560
|
+
arrayString: ['tag1'],
|
|
561
|
+
arrayNumber: [100]
|
|
562
|
+
};
|
|
563
|
+
const insertResult = await insertData(comprehensiveTestModelDefinition.name, initialData, {}, currentTestUser, false);
|
|
564
|
+
docToPatchId = insertResult.insertedIds[0];
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
afterEach(async () => {
|
|
568
|
+
await setupContext.purge();
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it('devrait ajouter une seule valeur à un tableau existant', async () => {
|
|
572
|
+
const { currentTestUser, comprehensiveTestModelDefinition } = setupContext;
|
|
573
|
+
|
|
574
|
+
const result = await patchData(comprehensiveTestModelDefinition.name, { _id: docToPatchId }, { arrayString: { $push: 'tag2' } }, {}, currentTestUser);
|
|
575
|
+
|
|
576
|
+
expect(result.success, `patchData avec $push a échoué: ${result.error}`).toBe(true);
|
|
577
|
+
expect(result.modifiedCount).toBe(1);
|
|
578
|
+
|
|
579
|
+
const patchedDoc = await testDatasColInstance.findOne({ _id: new ObjectId(docToPatchId) });
|
|
580
|
+
expect(patchedDoc.arrayString).toEqual(['tag1', 'tag2']);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it('devrait ajouter plusieurs valeurs à un tableau existant', async () => {
|
|
584
|
+
const { currentTestUser, comprehensiveTestModelDefinition } = setupContext;
|
|
585
|
+
|
|
586
|
+
const result = await patchData(comprehensiveTestModelDefinition.name, { _id: docToPatchId }, { arrayNumber: { $push: [200, 300] } }, {}, currentTestUser);
|
|
587
|
+
|
|
588
|
+
expect(result.success, `patchData avec $push multiple a échoué: ${result.error}`).toBe(true);
|
|
589
|
+
expect(result.modifiedCount).toBe(1);
|
|
590
|
+
|
|
591
|
+
const patchedDoc = await testDatasColInstance.findOne({ _id: new ObjectId(docToPatchId) });
|
|
592
|
+
expect(patchedDoc.arrayNumber).toEqual([100, 200, 300]);
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
it('devrait rejeter un push si la valeur est invalide', async () => {
|
|
596
|
+
const { currentTestUser, comprehensiveTestModelDefinition } = setupContext;
|
|
597
|
+
|
|
598
|
+
// Tenter de pusher un nombre dans un tableau de strings
|
|
599
|
+
const result = await patchData(comprehensiveTestModelDefinition.name, { _id: docToPatchId }, { arrayString: { $push: 12345 } }, {}, currentTestUser);
|
|
600
|
+
|
|
601
|
+
expect(result.success).toBe(false);
|
|
602
|
+
expect(result.error).toContain("Valeur invalide fournie pour le tableau 'arrayString'");
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
it('devrait rejeter un push si une des valeurs du tableau est invalide', async () => {
|
|
606
|
+
const { currentTestUser, comprehensiveTestModelDefinition } = setupContext;
|
|
607
|
+
|
|
608
|
+
// Tenter de pusher un tableau contenant une valeur invalide
|
|
609
|
+
const result = await patchData(comprehensiveTestModelDefinition.name, { _id: docToPatchId }, { arrayNumber: { $push: [400, 'not-a-number', 500] } }, {}, currentTestUser);
|
|
610
|
+
|
|
611
|
+
expect(result.success).toBe(false);
|
|
612
|
+
expect(result.error).toContain("Valeur invalide fournie pour le tableau 'arrayNumber'");
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
});
|
|
616
|
+
describe('$push sur un tableau inexistant', () => {
|
|
617
|
+
it('devrait créer le tableau et pusher les valeurs s\'il n\'existe pas', async () => {
|
|
618
|
+
// On utilise un setup de contexte complètement isolé pour ce test
|
|
619
|
+
const { currentTestUser, comprehensiveTestModelDefinition, purge } = await setupTestContext();
|
|
620
|
+
|
|
621
|
+
const initialData = { stringRequired: 'NoArrayField', stringUnique: 'UniqueForNoArrayPush', enumField: 'beta', passwordField: 'pass' };
|
|
622
|
+
const insertResult = await insertData(comprehensiveTestModelDefinition.name, initialData, {}, currentTestUser, false);
|
|
623
|
+
const newDocId = insertResult.insertedIds[0];
|
|
624
|
+
|
|
625
|
+
const result = await patchData(comprehensiveTestModelDefinition.name, { _id: newDocId }, { arrayString: { $push: ['first', 'second'] } }, {}, currentTestUser);
|
|
626
|
+
|
|
627
|
+
expect(result.success, `patchData sur un tableau inexistant a échoué: ${result.error}`).toBe(true);
|
|
628
|
+
expect(result.modifiedCount).toBe(1);
|
|
629
|
+
|
|
630
|
+
const patchedDoc = await testDatasColInstance.findOne({ _id: new ObjectId(newDocId) });
|
|
631
|
+
expect(patchedDoc.arrayString).toEqual(['first', 'second']);
|
|
632
|
+
await purge();
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
});
|
|
636
|
+
|
|
546
637
|
// --- Tests deleteData (repris de votre exemple, peuvent être gardés tels quels ou adaptés) ---
|
|
547
638
|
describe('deleteData (tests existants)', () => {
|
|
548
639
|
it('supprimer des données par ID (deleteData)', async () => {
|