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
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
|
|
2
|
+
// Nouvelle fonction pour ajouter un fichier privé
|
|
3
|
+
import {maxPrivateFileSize, megabytes} from "../constants.js";
|
|
4
|
+
import {isLocalUser} from "../data.js";
|
|
5
|
+
import i18n from "../../../data-primals-engine/src/i18n.js";
|
|
6
|
+
import {getUserStorageLimit} from "../user.js";
|
|
7
|
+
import {getCollection} from "./mongodb.js";
|
|
8
|
+
import {getFileExtension, isGUID, uuidv4} from "../core.js";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import process from "node:process";
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import { checkServerCapacity} from "./data.js";
|
|
13
|
+
import crypto from "node:crypto";
|
|
14
|
+
import * as tar from "tar";
|
|
15
|
+
import {promisify} from "node:util";
|
|
16
|
+
import {calculateTotalUserStorageUsage, hasPermission} from "./user.js";
|
|
17
|
+
|
|
18
|
+
const pbkdf2Async = promisify(crypto.pbkdf2);
|
|
19
|
+
|
|
20
|
+
const fsPromises = fs.promises;
|
|
21
|
+
|
|
22
|
+
// Encryption settings
|
|
23
|
+
const algorithm = 'aes-256-cbc'; // Algorithm to use
|
|
24
|
+
const iterations = 100000; // Number of iterations for PBKDF2
|
|
25
|
+
const keyLength = 32; // Key length for AES-256
|
|
26
|
+
const ivLength = 16; // IV length for AES
|
|
27
|
+
|
|
28
|
+
// Function to derive a key and IV from a passphrase and salt
|
|
29
|
+
async function deriveKeyAndIV(password, salt) {
|
|
30
|
+
const key = await pbkdf2Async(password, salt, iterations, keyLength, 'sha256');
|
|
31
|
+
// L'IV n'est plus dérivé ici.
|
|
32
|
+
return { key };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
export const unzip = async (file) => {
|
|
37
|
+
await tar.extract({ file: file, gzip: true, sync: true });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const zip = async (filename) => {
|
|
41
|
+
await tar.create({ gzip: true, sync: true, file: filename+'.gz' }, [filename])
|
|
42
|
+
}
|
|
43
|
+
export const addFile = async (file, user) => {
|
|
44
|
+
if (!file) throw new Error("Le fichier est requis");
|
|
45
|
+
|
|
46
|
+
if (file.size > maxPrivateFileSize) {
|
|
47
|
+
throw new Error(`La taille du fichier dépasse la limite autorisée (${maxPrivateFileSize / megabytes} Mo).`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (user.username !== 'demo' && isLocalUser(user) && !await hasPermission(["API_ADMIN", "API_UPLOAD_FILE"], user)) {
|
|
51
|
+
throw new Error(i18n.t("api.permission.uploadFile"));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const incomingDataSize = file.size;
|
|
55
|
+
|
|
56
|
+
const userStorageLimit = getUserStorageLimit(user);
|
|
57
|
+
const currentStorageUsage = await calculateTotalUserStorageUsage(user);
|
|
58
|
+
|
|
59
|
+
if (currentStorageUsage + incomingDataSize > userStorageLimit) {
|
|
60
|
+
throw new Error(i18n.t("api.data.storageLimitExceeded", { limit: Math.round(userStorageLimit / megabytes) }));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const serverCapacity = await checkServerCapacity(incomingDataSize);
|
|
64
|
+
if (!serverCapacity.isSufficient) {
|
|
65
|
+
throw new Error(i18n.t("api.data.serverStorageFull", "Le serveur a atteint sa capacité de stockage maximale. Veuillez réessayer plus tard."));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const collection = getCollection("files");
|
|
69
|
+
|
|
70
|
+
// Générer un GUID pour le fichier
|
|
71
|
+
const guid = uuidv4();
|
|
72
|
+
const filename = guid + path.extname(file.originalname || file.name); // Préserver l'extension
|
|
73
|
+
const filepath = path.join(process.cwd(), 'uploads', 'private', filename);
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
// Enregistrer le fichier sur le serveur
|
|
77
|
+
await fsPromises.mkdir(path.join(process.cwd(), 'uploads', 'private'), {recursive: true});
|
|
78
|
+
await fsPromises.writeFile(filepath, file.buffer || fs.readFileSync(file.path)); // Utiliser buffer si disponible
|
|
79
|
+
if( file.path && fs.existsSync(file.path) ){
|
|
80
|
+
fs.unlinkSync(file.path);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Enregistrer les métadonnées du fichier
|
|
84
|
+
const fileMetadata = {
|
|
85
|
+
guid,
|
|
86
|
+
filename: file.originalname || file.name, // Conserver le nom original
|
|
87
|
+
mimeType: file.type,
|
|
88
|
+
size: file.size,
|
|
89
|
+
mainUser: user._user,
|
|
90
|
+
user: user.username,
|
|
91
|
+
timestamp: new Date()
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Insérer le fichier dans une collection dédiée (par exemple, "privateFiles")
|
|
95
|
+
const result = await collection.insertOne({...fileMetadata, _model: "privateFile"});
|
|
96
|
+
if (!result.insertedId) throw new Error("Échec de l'indexation du fichier.");
|
|
97
|
+
|
|
98
|
+
return guid;
|
|
99
|
+
|
|
100
|
+
} catch (error) {
|
|
101
|
+
// Nettoyage en cas d'erreur
|
|
102
|
+
if (fs.existsSync(filepath)) {
|
|
103
|
+
fs.unlinkSync(filepath);
|
|
104
|
+
}
|
|
105
|
+
throw new Error(`Erreur lors de l'ajout du fichier: ${error.message}`);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const removeFile = async (guid, user) => {
|
|
110
|
+
if (!guid) return false;
|
|
111
|
+
if (!isGUID(guid)) throw new Error("Le GUID du fichier n'est pas valide.");
|
|
112
|
+
|
|
113
|
+
const collection = getCollection("files");
|
|
114
|
+
|
|
115
|
+
// Trouver le fichier et vérifier l'autorisation (propriétaire ou admin)
|
|
116
|
+
const file = await collection.findOne({ guid });
|
|
117
|
+
if (!file) throw new Error("Fichier non trouvé (" + guid + ")");
|
|
118
|
+
|
|
119
|
+
if (user.username !== 'demo' && isLocalUser(user) && !await hasPermission(["API_ADMIN", "API_EDIT_DATA", "API_EDIT_DATA_privateFile", `API_EDIT_DATA_privateFile_${guid}`], user) ) {
|
|
120
|
+
if (file._user !== (user._user || user.username)) {
|
|
121
|
+
throw new Error("Vous n'êtes pas autorisé à supprimer ce fichier.");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
// Supprimer le fichier de la base de données
|
|
127
|
+
const deleteResult = await collection.deleteOne({ _model: "privateFile", guid });
|
|
128
|
+
if (deleteResult.deletedCount !== 1) throw new Error("Échec de la suppression de l'indexation du fichier.");
|
|
129
|
+
|
|
130
|
+
// Supprimer le fichier du serveur
|
|
131
|
+
const filepath = path.join(process.cwd(), 'uploads', 'private', guid)+'.'+getFileExtension(file.filename);
|
|
132
|
+
if (fs.existsSync(filepath)) {
|
|
133
|
+
fs.unlinkSync(filepath);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return { success: true, message: "Fichier supprimé avec succès." };
|
|
137
|
+
} catch (error) {
|
|
138
|
+
throw new Error(`Erreur lors de la suppression du fichier: ${error.message}`);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
// Function to encrypt the file content
|
|
146
|
+
export async function encryptFile(filePath, password) {
|
|
147
|
+
try {
|
|
148
|
+
const salt = crypto.randomBytes(16);
|
|
149
|
+
const iv = crypto.randomBytes(ivLength);
|
|
150
|
+
|
|
151
|
+
// On ne dérive que la clé
|
|
152
|
+
const { key } = await deriveKeyAndIV(password, salt);
|
|
153
|
+
|
|
154
|
+
const fileData = await fs.promises.readFile(filePath);
|
|
155
|
+
const cipher = crypto.createCipheriv(algorithm, key, iv);
|
|
156
|
+
const encryptedData = Buffer.concat([cipher.update(fileData), cipher.final()]);
|
|
157
|
+
|
|
158
|
+
// On it : [salt][iv][données chiffrées]
|
|
159
|
+
await fs.promises.writeFile(filePath, Buffer.concat([salt, iv, encryptedData]));
|
|
160
|
+
|
|
161
|
+
console.log('File encrypted successfully.', filePath);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.error('Error during encryption:', error.message);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Function to decrypt the file content
|
|
167
|
+
export async function decryptFile(filePath, password) {
|
|
168
|
+
try {
|
|
169
|
+
const fileData = await fs.promises.readFile(filePath);
|
|
170
|
+
|
|
171
|
+
// Extraire le sel, l'IV et les données
|
|
172
|
+
const salt = fileData.slice(0, 16);
|
|
173
|
+
// NOUVEAU: Extraire l'IV qui suit le sel
|
|
174
|
+
const iv = fileData.slice(16, 16 + ivLength);
|
|
175
|
+
const encryptedData = fileData.slice(16 + ivLength);
|
|
176
|
+
|
|
177
|
+
// On dérive la même clé en utilisant le sel extrait
|
|
178
|
+
const { key } = await deriveKeyAndIV(password, salt);
|
|
179
|
+
|
|
180
|
+
// On utilise l'IV extrait pour le déchiffrement
|
|
181
|
+
const decipher = crypto.createDecipheriv(algorithm, key, iv);
|
|
182
|
+
const decryptedData = Buffer.concat([decipher.update(encryptedData), decipher.final()]);
|
|
183
|
+
|
|
184
|
+
await fs.promises.writeFile(filePath, decryptedData);
|
|
185
|
+
|
|
186
|
+
console.log('File decrypted successfully.');
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error('Error during decryption:', error.message);
|
|
189
|
+
// Relancer l'erreur peut être utile pour que l'appelant sache que ça a échoué
|
|
190
|
+
throw new Error(`Decryption failed: ${error.message}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
|
|
2
|
+
import process from "process";
|
|
3
|
+
import {MongoClient as InternalMongoClient} from "mongodb";
|
|
4
|
+
import {Logger} from "../gameObject.js";
|
|
5
|
+
import {MongoDatabase} from "../engine.js";
|
|
6
|
+
import * as tls from "node:tls";
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
|
|
9
|
+
export let modelsCollection, datasCollection, filesCollection, packsCollection;
|
|
10
|
+
|
|
11
|
+
let engine, logger;
|
|
12
|
+
export async function onInit(defaultEngine) {
|
|
13
|
+
engine = defaultEngine;
|
|
14
|
+
logger = engine.getComponent(Logger);
|
|
15
|
+
|
|
16
|
+
const isProduction = process.env.NODE_ENV === 'production'
|
|
17
|
+
|
|
18
|
+
let ca, cert, key;
|
|
19
|
+
try {
|
|
20
|
+
ca = fs.readFileSync('certs/mongodb-cert.crt');
|
|
21
|
+
cert = fs.readFileSync('certs/mongodb.pem');
|
|
22
|
+
key = fs.readFileSync(`certs/mongodb-cert.key`);
|
|
23
|
+
} catch (e) {
|
|
24
|
+
|
|
25
|
+
}
|
|
26
|
+
// Create a SecureContext object
|
|
27
|
+
// Connection URL
|
|
28
|
+
const dbUrl = process.env.MONGO_DB_URL || 'mongodb://localhost:27017';
|
|
29
|
+
const MongoClient = new InternalMongoClient(dbUrl, {
|
|
30
|
+
tls: false, maxPoolSize: 20
|
|
31
|
+
});
|
|
32
|
+
await MongoClient.connect();
|
|
33
|
+
|
|
34
|
+
modelsCollection = MongoDatabase.collection("models");
|
|
35
|
+
datasCollection = MongoDatabase.collection("datas");
|
|
36
|
+
filesCollection = MongoDatabase.collection("files");
|
|
37
|
+
packsCollection = MongoDatabase.collection("packs");
|
|
38
|
+
|
|
39
|
+
logger.info("MongoDB collections loaded.");
|
|
40
|
+
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
export const isObjectId = (id) => {
|
|
45
|
+
return (typeof(id) === 'string' && id.match(/^[0-9a-fA-F]{24}$/));
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
export const getCollection = (str) => {
|
|
50
|
+
return MongoDatabase.collection(str);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
// New function to determine the collection name for a user
|
|
56
|
+
export const getUserCollectionName = (user) => {
|
|
57
|
+
return user?.userPlan === 'premium' ? `datas_${user.username}` : 'datas';
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
// Modify existing functions to use the correct collection
|
|
62
|
+
export const getCollectionForUser = (user) => {
|
|
63
|
+
const collectionName = getUserCollectionName(user);
|
|
64
|
+
return getCollection(collectionName);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
import swaggerUi from 'swagger-ui-express';
|
|
3
|
+
|
|
4
|
+
import YAML from 'yaml';
|
|
5
|
+
|
|
6
|
+
import {Logger} from "../gameObject.js";
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
let engine, logger;
|
|
9
|
+
|
|
10
|
+
export async function onInit(defaultEngine) {
|
|
11
|
+
engine = defaultEngine;
|
|
12
|
+
logger = engine.getComponent(Logger);
|
|
13
|
+
|
|
14
|
+
const swaggerDocument = YAML.parse(fs.readFileSync(process.cwd() + '/swagger-en.yml', 'utf8'));
|
|
15
|
+
|
|
16
|
+
engine.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));
|
|
17
|
+
|
|
18
|
+
};
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import i18n from "../../../data-primals-engine/src/i18n.js";
|
|
2
|
+
import {MongoClient, MongoDatabase} from "../engine.js";
|
|
3
|
+
import {getCollection, getCollectionForUser, getUserCollectionName} from "./mongodb.js";
|
|
4
|
+
import {dbName, plans} from "../constants.js";
|
|
5
|
+
import {isLocalUser} from "../data.js";
|
|
6
|
+
import {ObjectId} from "mongodb";
|
|
7
|
+
import {getAPILang} from "./data.js";
|
|
8
|
+
import {Logger} from "../gameObject.js";
|
|
9
|
+
import rateLimit from "express-rate-limit";
|
|
10
|
+
|
|
11
|
+
export const userInitiator = async (req, res, next) => {
|
|
12
|
+
|
|
13
|
+
const lang = getAPILang(req.query.lang || req.headers['Accept-Language']);
|
|
14
|
+
|
|
15
|
+
req.lang = lang;
|
|
16
|
+
if(req.me)
|
|
17
|
+
req.me.lang = lang;
|
|
18
|
+
res.setHeader('Content-Language', lang);
|
|
19
|
+
|
|
20
|
+
// set current lang for user
|
|
21
|
+
i18n.changeLanguage(lang);
|
|
22
|
+
|
|
23
|
+
if (req.me.userPlan === 'premium') {
|
|
24
|
+
const collections = await MongoDatabase.listCollections().toArray();
|
|
25
|
+
const collectionNames = collections.map(c => c.name);
|
|
26
|
+
const coll = getUserCollectionName(req.me);
|
|
27
|
+
if (collectionNames.includes(coll)) {
|
|
28
|
+
const collection = await MongoDatabase.createCollection(coll);
|
|
29
|
+
const indexes = await collection.indexes();
|
|
30
|
+
if (!indexes.find(i => i.name === 'genericPartialIndex')) {
|
|
31
|
+
await collection.createIndex({"$**": 1}, {
|
|
32
|
+
name: 'genericPartialIndex',
|
|
33
|
+
partialFilterExpression: {
|
|
34
|
+
_model: 1,
|
|
35
|
+
_user: 1
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (!await collection.indexExists("_hash")) {
|
|
40
|
+
await collection.createIndex({_hash: 1});
|
|
41
|
+
}
|
|
42
|
+
if (!indexes.find(i => i.name === 'modelUserIndex')) {
|
|
43
|
+
await collection.createIndex({_model: 1, _user: 1}, {name: 'modelUserIndex'});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
next();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
export const middlewareAuthenticator = async (req, res, next) => {
|
|
52
|
+
const engine = req.app.get('engine');
|
|
53
|
+
if (!engine || !engine.userProvider) {
|
|
54
|
+
// Sécurité pour s'assurer que le moteur est bien configuré
|
|
55
|
+
return res.status(500).json({ error: "UserProvider not configured in engine." });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
// 1. On demande au provider (votre PrimalsUserProvider) d'identifier l'utilisateur
|
|
60
|
+
await engine.userProvider.initiateUser(req);
|
|
61
|
+
|
|
62
|
+
// 2. On vérifie simplement si le provider a attaché un utilisateur
|
|
63
|
+
if (req.me) {
|
|
64
|
+
// L'utilisateur est authentifié, on continue
|
|
65
|
+
return next();
|
|
66
|
+
} else {
|
|
67
|
+
// Le provider n'a trouvé aucun utilisateur valide
|
|
68
|
+
return res.status(401).json({ error: "Authentication required" });
|
|
69
|
+
}
|
|
70
|
+
} catch (e) {
|
|
71
|
+
// Le provider peut lever une erreur (ex: token invalide, compte non vérifié)
|
|
72
|
+
return res.status(401).json({ error: e.message || "Authentication failed" });
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const generateLimiter = rateLimit({
|
|
77
|
+
windowMs: 7000,
|
|
78
|
+
limit: 1,
|
|
79
|
+
standardHeaders: true,
|
|
80
|
+
legacyHeaders: false,
|
|
81
|
+
skip: (req) => {
|
|
82
|
+
return !!req.body?.confirmedAction;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const freeSearchLimiter = rateLimit({
|
|
87
|
+
windowMs: 1000 * 3600,
|
|
88
|
+
limit: plans.free.requestLimitPerHour,
|
|
89
|
+
standardHeaders: true,
|
|
90
|
+
legacyHeaders: false,
|
|
91
|
+
});
|
|
92
|
+
const premiumSearchLimiter = rateLimit({
|
|
93
|
+
windowMs: 1000 * 3600,
|
|
94
|
+
limit: plans.premium.requestLimitPerHour,
|
|
95
|
+
standardHeaders: true,
|
|
96
|
+
legacyHeaders: false,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
export const myFreePremiumAnonymousLimiter = async function (req, res, next) {
|
|
100
|
+
const user = req.me;
|
|
101
|
+
if (user?.userPlan === "premium") {
|
|
102
|
+
return premiumSearchLimiter(req, res, next);
|
|
103
|
+
}
|
|
104
|
+
return freeSearchLimiter(req, res, next);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
let logger,engine;
|
|
108
|
+
export async function onInit(defaultEngine) {
|
|
109
|
+
engine = defaultEngine;
|
|
110
|
+
logger = engine.getComponent(Logger);
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function hasPermission(permissionNames, user) {
|
|
116
|
+
if( !isLocalUser(user)){
|
|
117
|
+
return user.roles?.some(f => permissionNames.includes(f));
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
// Si on a une string on le transforme en tableau.
|
|
121
|
+
const permissionNamesArray = Array.isArray(permissionNames) ? permissionNames : [permissionNames];
|
|
122
|
+
const collection = getCollectionForUser(user);
|
|
123
|
+
|
|
124
|
+
const job = [
|
|
125
|
+
{
|
|
126
|
+
$lookup: {
|
|
127
|
+
from: 'datas',
|
|
128
|
+
let: { rolesIds: (user.roles ||[]).map(m => new ObjectId(m)) },
|
|
129
|
+
pipeline: [
|
|
130
|
+
{
|
|
131
|
+
$match: {
|
|
132
|
+
$expr: {
|
|
133
|
+
$and: [
|
|
134
|
+
{ $in: ['$_id', '$$rolesIds'] },
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
$lookup: {
|
|
141
|
+
from: 'datas',
|
|
142
|
+
let: { rolePermissions: {
|
|
143
|
+
"$map": {
|
|
144
|
+
"input": "$permissions",
|
|
145
|
+
"in": { "$toObjectId": "$$this" }
|
|
146
|
+
}
|
|
147
|
+
} },
|
|
148
|
+
pipeline: [
|
|
149
|
+
{
|
|
150
|
+
$match: {
|
|
151
|
+
$expr: {
|
|
152
|
+
$and: [
|
|
153
|
+
{ $in: ['$_id', '$$rolePermissions'] },
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
{ $limit: 1 },
|
|
159
|
+
],
|
|
160
|
+
as: 'permissions',
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
{ $unwind: { path: '$permissions', preserveNullAndEmptyArrays: true } },
|
|
164
|
+
],
|
|
165
|
+
as: 'roles',
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
{ $unwind: { path: '$roles', preserveNullAndEmptyArrays: true } },
|
|
169
|
+
{ $match: { 'roles.permissions.name': { $in: permissionNamesArray } } }, // Match if permissions.name in array
|
|
170
|
+
{ $limit: 1 },
|
|
171
|
+
{ $project: { _id: 0, hasPermission: { $cond: [{ $in: ['$roles.permissions.name', permissionNamesArray] }, true, false] } } } //check the value
|
|
172
|
+
];
|
|
173
|
+
const result = await collection.aggregate(job).toArray();
|
|
174
|
+
return result.length === 1 && result[0].hasPermission;
|
|
175
|
+
} catch (e) {
|
|
176
|
+
logger.error(e);
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Calcule l'utilisation totale de l'espace de stockage pour un utilisateur en octets.
|
|
183
|
+
* Cela inclut la taille des documents dans sa collection de données et la taille de ses fichiers uploadés.
|
|
184
|
+
* @param {object} user - L'objet utilisateur.
|
|
185
|
+
* @returns {Promise<number>} - L'utilisation totale en octets.
|
|
186
|
+
*/
|
|
187
|
+
export async function calculateTotalUserStorageUsage(user) {
|
|
188
|
+
const userId = user._user || user.username;
|
|
189
|
+
const datasCollection = getCollectionForUser(user);
|
|
190
|
+
const filesCollection = getCollection("files");
|
|
191
|
+
|
|
192
|
+
// Pipeline pour calculer la taille des documents de données
|
|
193
|
+
const dataSizePipeline = [
|
|
194
|
+
{ $match: { _user: userId } },
|
|
195
|
+
{
|
|
196
|
+
$group: {
|
|
197
|
+
_id: null, // Grouper tous les documents ensemble
|
|
198
|
+
totalSize: { $sum: { $bsonSize: "$$ROOT" } } // Sommer la taille BSON de chaque document
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
// Pipeline pour calculer la taille des fichiers
|
|
204
|
+
const fileSizePipeline = [
|
|
205
|
+
// Le champ est 'user' dans la collection 'files' selon votre fonction addFile
|
|
206
|
+
{ $match: { user: userId, _model: "privateFile" } },
|
|
207
|
+
{
|
|
208
|
+
$group: {
|
|
209
|
+
_id: null,
|
|
210
|
+
totalSize: { $sum: "$size" }
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
// Exécuter les deux calculs en parallèle pour plus d'efficacité
|
|
216
|
+
const [dataResult, fileResult] = await Promise.all([
|
|
217
|
+
datasCollection.aggregate(dataSizePipeline).toArray(),
|
|
218
|
+
filesCollection.aggregate(fileSizePipeline).toArray()
|
|
219
|
+
]);
|
|
220
|
+
|
|
221
|
+
const dataSize = dataResult.length > 0 ? dataResult[0].totalSize : 0;
|
|
222
|
+
const filesSize = fileResult.length > 0 ? fileResult[0].totalSize : 0;
|
|
223
|
+
|
|
224
|
+
logger.debug(`[Storage] User ${userId}: Data size = ${dataSize} bytes, Files size = ${filesSize} bytes. Total = ${dataSize + filesSize} bytes.`);
|
|
225
|
+
return dataSize + filesSize;
|
|
226
|
+
}
|