data-primals-engine 1.0.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/LICENSE.md +7 -0
- package/README.md +169 -0
- package/package.json +72 -0
- package/server.js +85 -0
- package/src/config.js +16 -0
- package/src/constants.js +210 -0
- package/src/core.js +340 -0
- package/src/data.js +495 -0
- package/src/defaultModels.js +1287 -0
- package/src/email.js +349 -0
- package/src/engine.js +168 -0
- package/src/events.js +81 -0
- package/src/gameObject.js +189 -0
- package/src/i18n.js +16855 -0
- package/src/index.js +15 -0
- package/src/middlewares/middleware-mongodb.js +131 -0
- package/src/middlewares/throttle.js +33 -0
- package/src/middlewares/timeout.js +47 -0
- package/src/migrate.js +216 -0
- package/src/migrations/20240522143000-content.js +20 -0
- package/src/modules/assistant.js +296 -0
- package/src/modules/bucket.js +233 -0
- package/src/modules/data.js +5692 -0
- package/src/modules/file.js +192 -0
- package/src/modules/mongodb.js +67 -0
- package/src/modules/swagger.js +18 -0
- package/src/modules/user.js +226 -0
- package/src/modules/workflow.js +1296 -0
- package/src/openai.jobs.js +145 -0
- package/src/packs.js +3799 -0
- package/src/providers.js +61 -0
- package/src/tutorials.js +112 -0
- package/src/user.js +17 -0
- package/src/workers/crypto-worker.js +77 -0
- package/src/workers/import-export-worker.js +36 -0
package/src/providers.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import {compare} from "bcrypt";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @class UserProvider
|
|
5
|
+
* Définit le contrat pour toutes les implémentations de fournisseurs d'utilisateurs.
|
|
6
|
+
* C'est une classe de base abstraite, elle n'est pas destinée à être utilisée directement.
|
|
7
|
+
*/
|
|
8
|
+
export class UserProvider {
|
|
9
|
+
constructor(engine) {
|
|
10
|
+
if (this.constructor === UserProvider) {
|
|
11
|
+
throw new Error("Abstract classes can't be instantiated.");
|
|
12
|
+
}
|
|
13
|
+
this.engine = engine;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Trouve un utilisateur par son nom d'utilisateur.
|
|
18
|
+
* @param {string} username - Le nom d'utilisateur à rechercher.
|
|
19
|
+
* @returns {Promise<object|null>} L'objet utilisateur ou null.
|
|
20
|
+
*/
|
|
21
|
+
async findUserByUsername(username) {
|
|
22
|
+
throw new Error("Method 'findUserByUsername()' must be implemented.");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Valide le mot de passe d'un utilisateur.
|
|
27
|
+
* @param {object} user - L'objet utilisateur.
|
|
28
|
+
* @param {string} password - Le mot de passe à vérifier.
|
|
29
|
+
* @returns {Promise<boolean>} True si le mot de passe est valide.
|
|
30
|
+
*/
|
|
31
|
+
async validatePassword(user, password) {
|
|
32
|
+
throw new Error("Method 'validatePassword()' must be implemented.");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Initialise un utilisateur sur la requête (pour les middlewares).
|
|
37
|
+
* @param {object} req - L'objet requête Express.
|
|
38
|
+
* @returns {Promise<void>}
|
|
39
|
+
*/
|
|
40
|
+
async initiateUser(req) {
|
|
41
|
+
throw new Error("Method 'initiateUser()' must be implemented.");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Ajoutez ici d'autres méthodes nécessaires : findUserById, createUser, etc.
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
export class DefaultUserProvider extends UserProvider {
|
|
49
|
+
|
|
50
|
+
async findUserByUsername(username) {
|
|
51
|
+
return { username: "test", password: "test" };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async validatePassword(user, password) {
|
|
55
|
+
if (!user || !user.password || !password) return false;
|
|
56
|
+
return await compare(password, user.password);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async initiateUser(req) {
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/tutorials.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Fichier : src/tutorials.js
|
|
2
|
+
|
|
3
|
+
export const tutorialsConfig = [
|
|
4
|
+
{
|
|
5
|
+
id: 'contact-management-basics',
|
|
6
|
+
name: 'Gestion des contacts : Les bases',
|
|
7
|
+
description: 'Apprenez les opérations fondamentales : créer et supprimer un contact.',
|
|
8
|
+
icon: 'FaUserPlus',
|
|
9
|
+
stages: [
|
|
10
|
+
{
|
|
11
|
+
stage: 1,
|
|
12
|
+
tourName: 'tour-create-contact', // Nom du tour guidé à lancer
|
|
13
|
+
name: 'Créez votre premier contact',
|
|
14
|
+
description: 'Ajoutez un nouveau contact avec le prénom "Richard".',
|
|
15
|
+
completionCondition: { model: 'contact', limit: 1, filter: { "firstName": "Richard" } }
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
stage: 2,
|
|
19
|
+
tourName: 'tour-delete-contact',
|
|
20
|
+
name: 'Effacez vos traces',
|
|
21
|
+
description: 'Maintenant, supprimez le contact "Richard" que vous venez de créer.',
|
|
22
|
+
// La condition pour valider une suppression est de vérifier que le nombre d'éléments correspondants est 0.
|
|
23
|
+
completionCondition: { model: 'contact', limit: 0, filter: { "firstName": "Richard" } }
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
rewards: {
|
|
27
|
+
xpBonus: 150,
|
|
28
|
+
achievement: 'CONTACT_MANAGER_NOVICE',
|
|
29
|
+
notification: { title: 'Gestionnaire de contacts', message: 'Vous maîtrisez les bases de la gestion de contacts !' }
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'advanced-permissions',
|
|
34
|
+
name: 'Permissions & rôles',
|
|
35
|
+
description: 'Maîtrisez la gestion des droits du bout des doigts !',
|
|
36
|
+
icon: 'FaUserShield',
|
|
37
|
+
stages: [
|
|
38
|
+
{
|
|
39
|
+
stage: 1,
|
|
40
|
+
tourName: 'tour-set-permissions',
|
|
41
|
+
name: 'Gestion des droits utilisateurs',
|
|
42
|
+
description: 'Trouvez l\'utilisateur "userTuto" et donnez-lui le droit "visitor".',
|
|
43
|
+
completionCondition: {model: 'user', limit: 1, filter: {"username": "userTuto", "roles": { "$find": {"$eq":["$$this.name","visitor"]}}}}
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
stage: 2,
|
|
47
|
+
tourName: 'tour-create-role',
|
|
48
|
+
name: 'Créer un nouveau rôle',
|
|
49
|
+
description: 'Créez un rôle personnalisé nommé "Modérateur".',
|
|
50
|
+
// Condition : un document dans le modèle 'role' avec le nom 'Modérateur' doit exister.
|
|
51
|
+
// (On suppose ici l'existence d'un modèle 'role' pour gérer les rôles).
|
|
52
|
+
completionCondition: { model: 'role', limit: 1, filter: { "name": "moderator" } }
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
stage: 3,
|
|
56
|
+
tourName: 'tour-assign-permission-to-role',
|
|
57
|
+
name: 'Ajouter une permission au rôle',
|
|
58
|
+
description: 'Modifiez le rôle "moderator" pour lui ajouter la permission "API_EDIT_DATA_content".',
|
|
59
|
+
// Condition : le rôle 'Modérateur' doit avoir une permission nommée 'delete_contact'.
|
|
60
|
+
// (On suppose que le modèle 'role' a un champ 'permissions' qui est un tableau de relations).
|
|
61
|
+
completionCondition: { model: 'role', limit: 1, filter: { "name": "moderator", "permissions": { "$find": { "$eq": ["$$this.name", "API_EDIT_DATA_content"] } } } }
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
stage: 4,
|
|
65
|
+
tourName: 'tour-assign-role-to-user',
|
|
66
|
+
name: 'Promouvoir un utilisateur',
|
|
67
|
+
description: 'Maintenant, assignez votre rôle "moderator" à l\'utilisateur "userTuto".',
|
|
68
|
+
// Condition : l'utilisateur 'userTuto' doit maintenant aussi avoir le rôle 'Modérateur'.
|
|
69
|
+
completionCondition: { model: 'user', limit: 1, filter: { "username": "userTuto", "roles": { "$find": { "$eq": ["$$this.name", "moderator"] } } } }
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
rewards: {
|
|
73
|
+
xpBonus: 500,
|
|
74
|
+
achievement: 'PERMISSION_ARCHITECT',
|
|
75
|
+
notification: { title: 'Architecte des Permissions', message: 'Vous savez maintenant comment finement gérer les accès !' }
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'requests',
|
|
80
|
+
name: 'Suivi d\'Activité',
|
|
81
|
+
description: 'Découvrez les succès liés à l\'activité.',
|
|
82
|
+
icon: 'FaUserShield',
|
|
83
|
+
stages: [
|
|
84
|
+
{
|
|
85
|
+
stage: 1,
|
|
86
|
+
tourName: null, // Pas de tour guidé, l'action peut se faire n'importe où
|
|
87
|
+
name: 'Première requête',
|
|
88
|
+
description: 'Effectuez des actions pour générer 50 requêtes système pour débloquer votre premier succès d\'activité.',
|
|
89
|
+
completionCondition: {model: 'request', limit: 50, filter: {}}
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
stage: 2,
|
|
93
|
+
tourName: null,
|
|
94
|
+
name: 'Activité soutenue',
|
|
95
|
+
description: 'Continuez votre activité et atteignez 250 requêtes pour le prochain palier.',
|
|
96
|
+
completionCondition: {model: 'request', limit: 250, filter: {}}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
stage: 3,
|
|
100
|
+
tourName: null,
|
|
101
|
+
name: 'Expert en requêtes',
|
|
102
|
+
description: 'Impressionnant ! Atteignez 1000 requêtes pour prouver votre maîtrise.',
|
|
103
|
+
completionCondition: {model: 'request', limit: 1000, filter: {}}
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
rewards: {
|
|
107
|
+
xpBonus: 1000,
|
|
108
|
+
achievement: 'REQUEST_MASTER',
|
|
109
|
+
notification: { title: 'Maître des Requêtes', message: 'Votre activité sur le réseau est remarquable !' }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
];
|
package/src/user.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {
|
|
2
|
+
maxTotalPrivateFilesSizeFree,
|
|
3
|
+
maxTotalPrivateFilesSizePremium,
|
|
4
|
+
maxTotalPrivateFilesSizeStandard
|
|
5
|
+
} from "./constants.js";
|
|
6
|
+
|
|
7
|
+
export const getUserStorageLimit = (user) => {
|
|
8
|
+
switch (user.userPlan) {
|
|
9
|
+
case 'premium':
|
|
10
|
+
return maxTotalPrivateFilesSizePremium;
|
|
11
|
+
case 'standard':
|
|
12
|
+
return maxTotalPrivateFilesSizeStandard;
|
|
13
|
+
case 'free':
|
|
14
|
+
default:
|
|
15
|
+
return maxTotalPrivateFilesSizeFree;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// C:/Dev/hackersonline-engine/server/src/workers/crypto-worker.js
|
|
2
|
+
import { parentPort } from 'worker_threads';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import fs from 'node:fs/promises';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import bcrypt from 'bcrypt'; // 1. Importer bcrypt
|
|
7
|
+
|
|
8
|
+
// --- Fonctions de cryptographie (inchangées) ---
|
|
9
|
+
const pbkdf2Async = promisify(crypto.pbkdf2);
|
|
10
|
+
const algorithm = 'aes-256-cbc';
|
|
11
|
+
const iterations = 100000;
|
|
12
|
+
const keyLength = 32;
|
|
13
|
+
const ivLength = 16;
|
|
14
|
+
|
|
15
|
+
async function deriveKeyAndIV(password, salt) {
|
|
16
|
+
const key = await pbkdf2Async(password, salt, iterations, keyLength, 'sha256');
|
|
17
|
+
return { key };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function encryptFile(filePath, password) {
|
|
21
|
+
// ... (fonction inchangée)
|
|
22
|
+
const salt = crypto.randomBytes(16);
|
|
23
|
+
const iv = crypto.randomBytes(ivLength);
|
|
24
|
+
const { key } = await deriveKeyAndIV(password, salt);
|
|
25
|
+
const fileData = await fs.readFile(filePath);
|
|
26
|
+
const cipher = crypto.createCipheriv(algorithm, key, iv);
|
|
27
|
+
const encryptedData = Buffer.concat([cipher.update(fileData), cipher.final()]);
|
|
28
|
+
await fs.writeFile(filePath, Buffer.concat([salt, iv, encryptedData]));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function decryptFile(filePath, password) {
|
|
32
|
+
// ... (fonction inchangée)
|
|
33
|
+
const fileData = await fs.readFile(filePath);
|
|
34
|
+
const salt = fileData.slice(0, 16);
|
|
35
|
+
const iv = fileData.slice(16, 16 + ivLength);
|
|
36
|
+
const encryptedData = fileData.slice(16 + ivLength);
|
|
37
|
+
const { key } = await deriveKeyAndIV(password, salt);
|
|
38
|
+
const decipher = crypto.createDecipheriv(algorithm, key, iv);
|
|
39
|
+
const decryptedData = Buffer.concat([decipher.update(encryptedData), decipher.final()]);
|
|
40
|
+
await fs.writeFile(filePath, decryptedData);
|
|
41
|
+
}
|
|
42
|
+
// --- Fin des fonctions de cryptographie ---
|
|
43
|
+
|
|
44
|
+
// 2. Mettre à jour le gestionnaire de messages pour être plus générique et ajouter le cas 'hash'
|
|
45
|
+
parentPort.on('message', async ({ action, payload }) => {
|
|
46
|
+
try {
|
|
47
|
+
let result;
|
|
48
|
+
switch (action) {
|
|
49
|
+
case 'encrypt':
|
|
50
|
+
console.log(`[Worker] Received action: ${action} for file: ${payload.filePath}`);
|
|
51
|
+
await encryptFile(payload.filePath, payload.password);
|
|
52
|
+
parentPort.postMessage({ success: true }); // Pas de données à retourner
|
|
53
|
+
break;
|
|
54
|
+
|
|
55
|
+
case 'decrypt':
|
|
56
|
+
console.log(`[Worker] Received action: ${action} for file: ${payload.filePath}`);
|
|
57
|
+
await decryptFile(payload.filePath, payload.password);
|
|
58
|
+
parentPort.postMessage({ success: true }); // Pas de données à retourner
|
|
59
|
+
break;
|
|
60
|
+
|
|
61
|
+
case 'hash':
|
|
62
|
+
console.log(`[Worker] Received action: hash`);
|
|
63
|
+
if (!payload.data) throw new Error('Data to hash is missing in payload.');
|
|
64
|
+
// Le worker gère à la fois la génération du "salt" et le hachage
|
|
65
|
+
const salt = await bcrypt.genSalt(10);
|
|
66
|
+
result = await bcrypt.hash(payload.data, salt);
|
|
67
|
+
parentPort.postMessage({ success: true, data: result }); // On retourne le hash
|
|
68
|
+
break;
|
|
69
|
+
|
|
70
|
+
default:
|
|
71
|
+
throw new Error(`Unknown crypto action: ${action}`);
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
// Envoie l'erreur au thread principal
|
|
75
|
+
parentPort.postMessage({ success: false, error: error.message });
|
|
76
|
+
}
|
|
77
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { parentPort } from 'worker_threads';
|
|
2
|
+
import { parse } from 'csv-parse/sync'; // On utilise la version synchrone, c'est ok dans un worker
|
|
3
|
+
|
|
4
|
+
parentPort.on('message', ({ action, payload }) => {
|
|
5
|
+
try {
|
|
6
|
+
let result;
|
|
7
|
+
switch (action) {
|
|
8
|
+
case 'parse-json':
|
|
9
|
+
// Tâche: Parser une chaîne/buffer JSON en objet JS
|
|
10
|
+
result = JSON.parse(payload.fileContent.toString('utf-8'));
|
|
11
|
+
break;
|
|
12
|
+
|
|
13
|
+
case 'parse-csv':
|
|
14
|
+
// Tâche: Parser une chaîne/buffer CSV en tableau d'objets JS
|
|
15
|
+
result = parse(payload.fileContent, {
|
|
16
|
+
columns: payload.options.columns,
|
|
17
|
+
skip_empty_lines: true,
|
|
18
|
+
trim: true,
|
|
19
|
+
});
|
|
20
|
+
break;
|
|
21
|
+
|
|
22
|
+
case 'stringify-json':
|
|
23
|
+
// Tâche: Sérialiser un objet/tableau JS en chaîne JSON
|
|
24
|
+
result = JSON.stringify(payload.data);
|
|
25
|
+
break;
|
|
26
|
+
|
|
27
|
+
default:
|
|
28
|
+
throw new Error(`Unknown action: ${action}`);
|
|
29
|
+
}
|
|
30
|
+
// Renvoyer le résultat au thread principal
|
|
31
|
+
parentPort.postMessage({ success: true, data: result });
|
|
32
|
+
} catch (error) {
|
|
33
|
+
// Renvoyer l'erreur au thread principal
|
|
34
|
+
parentPort.postMessage({ success: false, error: error.message });
|
|
35
|
+
}
|
|
36
|
+
});
|