@nitronjs/framework 0.1.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.
Files changed (87) hide show
  1. package/README.md +429 -0
  2. package/cli/create.js +260 -0
  3. package/cli/njs.js +164 -0
  4. package/lib/Auth/Manager.js +111 -0
  5. package/lib/Build/Manager.js +1232 -0
  6. package/lib/Console/Commands/BuildCommand.js +25 -0
  7. package/lib/Console/Commands/DevCommand.js +385 -0
  8. package/lib/Console/Commands/MakeCommand.js +110 -0
  9. package/lib/Console/Commands/MigrateCommand.js +98 -0
  10. package/lib/Console/Commands/MigrateFreshCommand.js +97 -0
  11. package/lib/Console/Commands/SeedCommand.js +92 -0
  12. package/lib/Console/Commands/StorageLinkCommand.js +31 -0
  13. package/lib/Console/Stubs/controller.js +19 -0
  14. package/lib/Console/Stubs/middleware.js +9 -0
  15. package/lib/Console/Stubs/migration.js +23 -0
  16. package/lib/Console/Stubs/model.js +7 -0
  17. package/lib/Console/Stubs/page-hydration.tsx +54 -0
  18. package/lib/Console/Stubs/seeder.js +9 -0
  19. package/lib/Console/Stubs/vendor.tsx +11 -0
  20. package/lib/Core/Config.js +86 -0
  21. package/lib/Core/Environment.js +21 -0
  22. package/lib/Core/Paths.js +188 -0
  23. package/lib/Database/Connection.js +61 -0
  24. package/lib/Database/DB.js +84 -0
  25. package/lib/Database/Drivers/MySQLDriver.js +234 -0
  26. package/lib/Database/Manager.js +162 -0
  27. package/lib/Database/Model.js +161 -0
  28. package/lib/Database/QueryBuilder.js +714 -0
  29. package/lib/Database/QueryValidation.js +62 -0
  30. package/lib/Database/Schema/Blueprint.js +126 -0
  31. package/lib/Database/Schema/Manager.js +116 -0
  32. package/lib/Date/DateTime.js +108 -0
  33. package/lib/Date/Locale.js +68 -0
  34. package/lib/Encryption/Manager.js +47 -0
  35. package/lib/Filesystem/Manager.js +49 -0
  36. package/lib/Hashing/Manager.js +25 -0
  37. package/lib/Http/Server.js +317 -0
  38. package/lib/Logging/Manager.js +153 -0
  39. package/lib/Mail/Manager.js +120 -0
  40. package/lib/Route/Loader.js +81 -0
  41. package/lib/Route/Manager.js +265 -0
  42. package/lib/Runtime/Entry.js +11 -0
  43. package/lib/Session/File.js +299 -0
  44. package/lib/Session/Manager.js +259 -0
  45. package/lib/Session/Memory.js +67 -0
  46. package/lib/Session/Session.js +196 -0
  47. package/lib/Support/Str.js +100 -0
  48. package/lib/Translation/Manager.js +49 -0
  49. package/lib/Validation/MimeTypes.js +39 -0
  50. package/lib/Validation/Validator.js +691 -0
  51. package/lib/View/Manager.js +544 -0
  52. package/lib/View/Templates/default/Home.tsx +262 -0
  53. package/lib/View/Templates/default/MainLayout.tsx +44 -0
  54. package/lib/View/Templates/errors/404.tsx +13 -0
  55. package/lib/View/Templates/errors/500.tsx +13 -0
  56. package/lib/View/Templates/errors/ErrorLayout.tsx +112 -0
  57. package/lib/View/Templates/messages/Maintenance.tsx +17 -0
  58. package/lib/View/Templates/messages/MessageLayout.tsx +136 -0
  59. package/lib/index.js +57 -0
  60. package/package.json +47 -0
  61. package/skeleton/.env.example +26 -0
  62. package/skeleton/app/Controllers/HomeController.js +9 -0
  63. package/skeleton/app/Kernel.js +11 -0
  64. package/skeleton/app/Middlewares/Authentication.js +9 -0
  65. package/skeleton/app/Middlewares/Guest.js +9 -0
  66. package/skeleton/app/Middlewares/VerifyCsrf.js +24 -0
  67. package/skeleton/app/Models/User.js +7 -0
  68. package/skeleton/config/app.js +4 -0
  69. package/skeleton/config/auth.js +16 -0
  70. package/skeleton/config/database.js +27 -0
  71. package/skeleton/config/hash.js +3 -0
  72. package/skeleton/config/server.js +28 -0
  73. package/skeleton/config/session.js +21 -0
  74. package/skeleton/database/migrations/2025_01_01_00_00_users.js +20 -0
  75. package/skeleton/database/seeders/UserSeeder.js +15 -0
  76. package/skeleton/globals.d.ts +1 -0
  77. package/skeleton/package.json +24 -0
  78. package/skeleton/public/.gitkeep +0 -0
  79. package/skeleton/resources/css/.gitkeep +0 -0
  80. package/skeleton/resources/langs/.gitkeep +0 -0
  81. package/skeleton/resources/views/Site/Home.tsx +66 -0
  82. package/skeleton/routes/web.js +4 -0
  83. package/skeleton/storage/app/private/.gitkeep +0 -0
  84. package/skeleton/storage/app/public/.gitkeep +0 -0
  85. package/skeleton/storage/framework/sessions/.gitkeep +0 -0
  86. package/skeleton/storage/logs/.gitkeep +0 -0
  87. package/skeleton/tsconfig.json +33 -0
@@ -0,0 +1,259 @@
1
+ import crypto from "crypto";
2
+ import Config from "../Core/Config.js";
3
+ import Memory from "./Memory.js";
4
+ import File from "./File.js";
5
+ import Session from "./Session.js";
6
+
7
+ /**
8
+ * SessionManager
9
+ *
10
+ * Singleton that manages all sessions for the application.
11
+ * Handles loading, creation, persistence, and lifecycle.
12
+ *
13
+ * Features:
14
+ * - Cookie-based session IDs (signed)
15
+ * - Pluggable storage drivers (Memory, File)
16
+ * - Automatic session persistence
17
+ * - Lifetime management and expiration
18
+ * - Garbage collection for expired sessions
19
+ */
20
+ class SessionManager {
21
+ static instance = null;
22
+ static #isInternal = false;
23
+
24
+ // Instance properties
25
+ config = null;
26
+ store = null;
27
+ ready = null;
28
+ gcTimer = null;
29
+
30
+ constructor () {
31
+ if (!SessionManager.#isInternal) {
32
+ throw new Error("SessionManager must be initialized with await SessionManager.getInstance()");
33
+ }
34
+ }
35
+
36
+ static getSessionConfig() {
37
+ return Config.all("session");
38
+ }
39
+
40
+ static async createInstance() {
41
+ SessionManager.#isInternal = true;
42
+ const instance = new SessionManager();
43
+ SessionManager.#isInternal = false;
44
+
45
+ instance.config = this.getSessionConfig();
46
+ instance.config.cookie.signed = true; // Enforce signed cookies
47
+ // Load storage driver based on config
48
+ instance.store = instance.#createDriver(instance.config.driver);
49
+ // Store ready promise (for File driver initialization)
50
+ instance.ready = instance.store.ready || Promise.resolve();
51
+ return instance;
52
+ }
53
+
54
+ /**
55
+ * Create storage driver instance
56
+ * @param {string} driver - Driver name (memory, file)
57
+ * @returns {object} Driver instance
58
+ */
59
+ #createDriver(driver) {
60
+ switch (driver) {
61
+ case 'file':
62
+ return new File();
63
+ case 'memory':
64
+ default:
65
+ return new Memory();
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Get singleton instance
71
+ */
72
+ static async getInstance () {
73
+ if (!SessionManager.instance) {
74
+ SessionManager.instance = await SessionManager.createInstance();
75
+ }
76
+ return SessionManager.instance;
77
+ }
78
+
79
+ // ========================================
80
+ // Session Lifecycle
81
+ // ========================================
82
+
83
+ /**
84
+ * Load existing session or create a new one
85
+ *
86
+ * Validates cookie signature, checks session lifetime,
87
+ * and retrieves session data from the store.
88
+ *
89
+ * @param {object} request - Fastify request object
90
+ * @param {object} response - Fastify response object
91
+ * @returns {Session} Session instance
92
+ */
93
+ async load (request, response) {
94
+ // Wait for store initialization (File driver directory setup)
95
+ await this.ready;
96
+
97
+ const signedCookie = request.cookies[this.config.cookieName];
98
+
99
+ // No cookie → new session
100
+ if (!signedCookie) {
101
+ return this.create(response);
102
+ }
103
+
104
+ // Verify cookie signature
105
+ const unsignResult = request.unsignCookie(signedCookie);
106
+ if (!unsignResult.valid) {
107
+ response.clearCookie(this.config.cookieName);
108
+
109
+ return this.create(response);
110
+ }
111
+
112
+ const sessionId = unsignResult.value;
113
+ const sessionData = await this.store.get(sessionId);
114
+
115
+ // Session not found → new session
116
+ if (!sessionData) {
117
+ response.clearCookie(this.config.cookieName);
118
+
119
+ return this.create(response);
120
+ }
121
+
122
+ // Check if session expired (lifetime)
123
+ const lastActivity = sessionData.lastActivity || sessionData.createdAt;
124
+ if (Date.now() - lastActivity > this.config.lifetime) {
125
+ await this.store.delete(sessionId);
126
+ response.clearCookie(this.config.cookieName);
127
+
128
+ return this.create(response);
129
+ }
130
+
131
+ return new Session(sessionId, sessionData);
132
+ }
133
+
134
+ /**
135
+ * Create new session
136
+ * @param {object} response - Fastify response
137
+ * @returns {Session} New session instance
138
+ */
139
+ async create (response) {
140
+ const id = crypto.randomBytes(32).toString("hex");
141
+ const sessionData = {
142
+ data: {},
143
+ createdAt: Date.now(),
144
+ };
145
+
146
+ await this.store.set(id, sessionData);
147
+
148
+ // Set signed cookie
149
+ const cookieOptions = { ...this.config.cookie, signed: true };
150
+ response.setCookie(this.config.cookieName, id, cookieOptions);
151
+
152
+ return new Session(id, sessionData);
153
+ }
154
+
155
+ // ========================================
156
+ // Garbage Collection
157
+ // ========================================
158
+
159
+ /**
160
+ * Start automatic garbage collection (every 15 minutes)
161
+ */
162
+ startGarbageCollection () {
163
+ if (typeof this.store.gc !== 'function') {
164
+ return; // Store doesn't support GC
165
+ }
166
+
167
+ const gcInterval = 15 * 60 * 1000; // 15 minutes
168
+
169
+ this.gcTimer = setInterval(async () => {
170
+ try {
171
+ const deleted = await this.store.gc(this.config.lifetime);
172
+ if (deleted > 0) {
173
+ console.log(`[Session GC] Deleted ${deleted} expired sessions`);
174
+ }
175
+ }
176
+ catch (error) {
177
+ console.error('[Session GC] Error:', error.message);
178
+ }
179
+ }, gcInterval);
180
+
181
+ // Run immediately on startup
182
+ this.store.gc(this.config.lifetime).catch(err => {
183
+ console.error('[Session GC] Initial run failed:', err.message);
184
+ });
185
+ }
186
+
187
+ /**
188
+ * Stop garbage collection (graceful shutdown)
189
+ */
190
+ stopGarbageCollection () {
191
+ if (this.gcTimer) {
192
+ clearInterval(this.gcTimer);
193
+ this.gcTimer = null;
194
+ }
195
+ }
196
+
197
+ // ========================================
198
+ // Server Setup
199
+ // ========================================
200
+
201
+ /**
202
+ * Setup session middleware for Fastify server
203
+ * @param {object} server - Fastify instance
204
+ */
205
+ static async setup (server) {
206
+ const manager = await SessionManager.getInstance();
207
+
208
+ server.decorateRequest("session", null);
209
+
210
+ // Load session before handling request
211
+ server.addHook('preHandler', async (request, response) => {
212
+ request.session = await manager.load(request, response);
213
+ });
214
+
215
+ // Handle cookie changes before response is sent
216
+ server.addHook('onSend', async (request, response, payload) => {
217
+ if (!request.session) return payload;
218
+
219
+ // Handle regeneration - set new session cookie
220
+ if (request.session.shouldRegenerate()) {
221
+ response.setCookie(
222
+ manager.config.cookieName,
223
+ request.session.id,
224
+ { ...manager.config.cookie, signed: true }
225
+ );
226
+ }
227
+
228
+ return payload;
229
+ });
230
+
231
+ // Persist session after sending response
232
+ server.addHook('onResponse', async (request, response) => {
233
+ if (!request.session) return;
234
+
235
+ // Skip persistence for error responses (4xx/5xx)
236
+ if (response.statusCode >= 400) return;
237
+
238
+ // Handle regeneration - delete old session
239
+ if (request.session.shouldRegenerate()) {
240
+ const oldId = request.session.getOldId();
241
+ if (oldId) {
242
+ await manager.store.delete(oldId);
243
+ }
244
+ }
245
+
246
+ // Persist session data
247
+ await manager.store.set(request.session.id, {
248
+ data: request.session.all(),
249
+ createdAt: request.session.createdAt,
250
+ lastActivity: Date.now(),
251
+ });
252
+ });
253
+
254
+ // Start garbage collection
255
+ manager.startGarbageCollection();
256
+ }
257
+ }
258
+
259
+ export default SessionManager;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Memory Session Store
3
+ *
4
+ * In-memory session storage using JavaScript Map.
5
+ * Fast but not persistent - all data is lost on server restart.
6
+ *
7
+ * Use cases:
8
+ * - Development and testing
9
+ * - Single-server deployments with acceptable data loss
10
+ *
11
+ * Production: Use File store for persistence or Redis for clustering.
12
+ */
13
+ class Memory {
14
+ constructor () {
15
+ this.sessions = new Map();
16
+ }
17
+
18
+ /**
19
+ * Get session data by ID
20
+ * @param {string} id - Session ID
21
+ * @returns {object|null} Session data or null if not found
22
+ */
23
+ async get (id) {
24
+ return this.sessions.get(id) || null;
25
+ }
26
+
27
+ /**
28
+ * Save session data
29
+ * @param {string} id - Session ID
30
+ * @param {object} value - Session data
31
+ */
32
+ async set (id, value) {
33
+ this.sessions.set(id, value);
34
+ }
35
+
36
+ /**
37
+ * Delete session
38
+ * @param {string} id - Session ID
39
+ * @returns {boolean} True if deleted
40
+ */
41
+ async delete (id) {
42
+ return this.sessions.delete(id);
43
+ }
44
+
45
+ /**
46
+ * Garbage collection - remove expired sessions
47
+ * @param {number} lifetime - Session lifetime in milliseconds
48
+ * @returns {number} Number of deleted sessions
49
+ */
50
+ async gc (lifetime) {
51
+ const now = Date.now();
52
+ let deleted = 0;
53
+
54
+ for (const [id, sessionData] of this.sessions.entries()) {
55
+ const lastActivity = sessionData.lastActivity || sessionData.createdAt;
56
+
57
+ if (now - lastActivity > lifetime) {
58
+ this.sessions.delete(id);
59
+ deleted++;
60
+ }
61
+ }
62
+
63
+ return deleted;
64
+ }
65
+ }
66
+
67
+ export default Memory;
@@ -0,0 +1,196 @@
1
+ import crypto from "crypto";
2
+
3
+ /**
4
+ * Session
5
+ *
6
+ * Represents a single user session with secure data storage.
7
+ * All data is private and accessed through controlled methods.
8
+ *
9
+ * Features:
10
+ * - Private data storage (no external mutation)
11
+ * - CSRF token generation and verification
12
+ * - Flash messages (one-time messages)
13
+ * - Session regeneration (security)
14
+ * - Session destruction (logout)
15
+ */
16
+ class Session {
17
+ // Private fields
18
+ #id;
19
+ #data;
20
+ #createdAt;
21
+ #lastActivity;
22
+ #flags = {
23
+ shouldRegenerate: false,
24
+ oldId: null
25
+ };
26
+
27
+ constructor (id, sessionData) {
28
+ this.#id = id;
29
+ this.#data = sessionData.data || {};
30
+ this.#createdAt = sessionData.createdAt || Date.now();
31
+ this.#lastActivity = sessionData.lastActivity || null;
32
+ }
33
+
34
+ // ========================================
35
+ // Getters
36
+ // ========================================
37
+
38
+ get id () {
39
+ return this.#id;
40
+ }
41
+
42
+ get createdAt () {
43
+ return this.#createdAt;
44
+ }
45
+
46
+ get lastActivity () {
47
+ return this.#lastActivity;
48
+ }
49
+
50
+ // ========================================
51
+ // Data Management
52
+ // ========================================
53
+
54
+ /**
55
+ * Get a session value
56
+ */
57
+ get (key) {
58
+ return this.#data[key];
59
+ }
60
+
61
+ /**
62
+ * Set a session value
63
+ */
64
+ set (key, value) {
65
+ this.#data[key] = value;
66
+ }
67
+
68
+ /**
69
+ * Get all session data (copy to prevent mutations)
70
+ */
71
+ all () {
72
+ return { ...this.#data };
73
+ }
74
+
75
+ // ========================================
76
+ // CSRF Protection
77
+ // ========================================
78
+
79
+ /**
80
+ * Generate new CSRF token
81
+ * @returns {string} Generated token
82
+ */
83
+ generateCsrfToken () {
84
+ const token = crypto.randomBytes(32).toString('hex');
85
+ this.set('_csrf', token);
86
+
87
+ return token;
88
+ }
89
+
90
+ /**
91
+ * Get CSRF token (generate if doesn't exist)
92
+ * @returns {string} CSRF token
93
+ */
94
+ getCsrfToken () {
95
+ return this.get('_csrf') || this.generateCsrfToken();
96
+ }
97
+
98
+ /**
99
+ * Verify CSRF token (timing-safe comparison)
100
+ * @param {string} token - Token to verify
101
+ * @returns {boolean} True if valid
102
+ */
103
+ verifyCsrfToken (token) {
104
+ const sessionToken = this.get('_csrf');
105
+
106
+ if (!sessionToken || !token) return false;
107
+
108
+ try {
109
+ return crypto.timingSafeEqual(
110
+ Buffer.from(sessionToken, 'hex'),
111
+ Buffer.from(token, 'hex')
112
+ );
113
+ }
114
+ catch {
115
+ return false;
116
+ }
117
+ }
118
+
119
+ // ========================================
120
+ // Flash Messages
121
+ // ========================================
122
+
123
+ /**
124
+ * Store a flash message (available only for the next request)
125
+ * @param {string} key - Message key
126
+ * @param {any} value - Message value
127
+ */
128
+ flash (key, value) {
129
+ if (!this.#data._flash) {
130
+ this.#data._flash = {};
131
+ }
132
+
133
+ this.#data._flash[key] = value;
134
+ }
135
+
136
+ /**
137
+ * Get and remove a flash message
138
+ * @param {string} key - Message key
139
+ * @returns {any} Message value or undefined
140
+ */
141
+ getFlash (key) {
142
+ if (!this.#data._flash?.[key]) return undefined;
143
+
144
+ const value = this.#data._flash[key];
145
+
146
+ delete this.#data._flash[key];
147
+
148
+ return value;
149
+ }
150
+
151
+ // ========================================
152
+ // Lifecycle Management
153
+ // ========================================
154
+
155
+ /**
156
+ * Regenerate session ID and CSRF token
157
+ *
158
+ * Prevents session fixation attacks by creating a new session ID.
159
+ * Call this after login, logout, or any privilege escalation.
160
+ *
161
+ * @returns {object} {oldId, newId}
162
+ */
163
+ regenerate () {
164
+ // Guard: Prevent concurrent regeneration (idempotent)
165
+ if (this.#flags.shouldRegenerate) {
166
+ return {
167
+ oldId: this.#flags.oldId,
168
+ newId: this.#id
169
+ };
170
+ }
171
+
172
+ this.#flags.oldId = this.#id;
173
+ this.#id = crypto.randomBytes(32).toString('hex');
174
+ this.#flags.shouldRegenerate = true;
175
+ this.generateCsrfToken();
176
+
177
+ return {
178
+ oldId: this.#flags.oldId,
179
+ newId: this.#id
180
+ };
181
+ }
182
+
183
+ // ========================================
184
+ // Internal Flags (used by Manager)
185
+ // ========================================
186
+
187
+ shouldRegenerate () {
188
+ return this.#flags.shouldRegenerate;
189
+ }
190
+
191
+ getOldId () {
192
+ return this.#flags.oldId;
193
+ }
194
+ }
195
+
196
+ export default Session;
@@ -0,0 +1,100 @@
1
+ class Str {
2
+ static random(length) {
3
+ const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
4
+ let result = "";
5
+ const charactersLength = characters.length;
6
+
7
+ for (let i = 0; i < length; i++) {
8
+ result += characters.charAt(Math.floor(Math.random() * charactersLength));
9
+ }
10
+
11
+ return result;
12
+ }
13
+
14
+ static slug(text) {
15
+ const normalizedInput = text
16
+ .toLowerCase()
17
+ .normalize("NFD")
18
+ .replace(/[\u0300-\u036f]/g, "");
19
+
20
+ const slug = normalizedInput
21
+ .replace(/ /g, "-")
22
+ .replace(/ı/g, "i")
23
+ .replace(/[^\w-]+/g, "");
24
+
25
+ return slug;
26
+ }
27
+
28
+ static ucfirst(str) {
29
+ if (!str) return "";
30
+ return str.charAt(0).toUpperCase() + str.slice(1);
31
+ }
32
+
33
+ static lcfirst(str) {
34
+ if (!str) return "";
35
+ return str.charAt(0).toLowerCase() + str.slice(1);
36
+ }
37
+
38
+ static camel(str) {
39
+ return str
40
+ .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ""))
41
+ .replace(/^(.)/, (c) => c.toLowerCase());
42
+ }
43
+
44
+ static pascal(str) {
45
+ return str
46
+ .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ""))
47
+ .replace(/^(.)/, (c) => c.toUpperCase());
48
+ }
49
+
50
+ static snake(str) {
51
+ return str
52
+ .replace(/([a-z])([A-Z])/g, "$1_$2")
53
+ .replace(/[-\s]+/g, "_")
54
+ .toLowerCase();
55
+ }
56
+
57
+ static kebab(str) {
58
+ return str
59
+ .replace(/([a-z])([A-Z])/g, "$1-$2")
60
+ .replace(/[_\s]+/g, "-")
61
+ .toLowerCase();
62
+ }
63
+
64
+ static limit(str, length, suffix = "...") {
65
+ if (!str || str.length <= length) return str;
66
+ return str.substring(0, length) + suffix;
67
+ }
68
+
69
+ static words(str, count, suffix = "...") {
70
+ const wordsArray = str.split(/\s+/);
71
+ if (wordsArray.length <= count) return str;
72
+ return wordsArray.slice(0, count).join(" ") + suffix;
73
+ }
74
+
75
+ static contains(haystack, needle) {
76
+ return haystack.includes(needle);
77
+ }
78
+
79
+ static startsWith(haystack, needle) {
80
+ return haystack.startsWith(needle);
81
+ }
82
+
83
+ static endsWith(haystack, needle) {
84
+ return haystack.endsWith(needle);
85
+ }
86
+
87
+ static replaceFirst(str, search, replace) {
88
+ const pos = str.indexOf(search);
89
+ if (pos === -1) return str;
90
+ return str.substring(0, pos) + replace + str.substring(pos + search.length);
91
+ }
92
+
93
+ static replaceLast(str, search, replace) {
94
+ const pos = str.lastIndexOf(search);
95
+ if (pos === -1) return str;
96
+ return str.substring(0, pos) + replace + str.substring(pos + search.length);
97
+ }
98
+ }
99
+
100
+ export default Str;
@@ -0,0 +1,49 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import Paths from "../Core/Paths.js";
4
+
5
+ class TranslationManager {
6
+ static #cache = new Map();
7
+
8
+ static get(req, key, params = null) {
9
+ const currentLang = req.language;
10
+ const translation = this.#getTranslation(currentLang, key);
11
+
12
+ if (params) {
13
+ return this.#replaceParams(translation, params);
14
+ }
15
+
16
+ return translation;
17
+ }
18
+
19
+ static #getTranslation(lang, key) {
20
+ const langPath = path.join(Paths.langs, lang + ".json");
21
+
22
+ if (!fs.existsSync(langPath)) {
23
+ return key;
24
+ }
25
+
26
+ let translations = this.#cache.get(lang);
27
+
28
+ if (!translations) {
29
+ const langFile = fs.readFileSync(langPath, { encoding: "utf-8", flag: "r" });
30
+ translations = JSON.parse(langFile);
31
+ this.#cache.set(lang, translations);
32
+ }
33
+
34
+ return translations[key] || key;
35
+ }
36
+
37
+ static #replaceParams(text, params) {
38
+ Object.keys(params).forEach((key) => {
39
+ text = text.replaceAll(":" + key, params[key]);
40
+ });
41
+ return text;
42
+ }
43
+
44
+ static clearCache() {
45
+ this.#cache.clear();
46
+ }
47
+ }
48
+
49
+ export default TranslationManager;
@@ -0,0 +1,39 @@
1
+ export const MIME_TYPES = {
2
+ jpg: "image/jpeg",
3
+ jpeg: "image/jpeg",
4
+ png: "image/png",
5
+ gif: "image/gif",
6
+ webp: "image/webp",
7
+ svg: "image/svg+xml",
8
+ bmp: "image/bmp",
9
+ ico: "image/x-icon",
10
+ pdf: "application/pdf",
11
+ doc: "application/msword",
12
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
13
+ xls: "application/vnd.ms-excel",
14
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
15
+ ppt: "application/vnd.ms-powerpoint",
16
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
17
+ txt: "text/plain",
18
+ csv: "text/csv",
19
+ zip: "application/zip",
20
+ rar: "application/x-rar-compressed",
21
+ "7z": "application/x-7z-compressed",
22
+ tar: "application/x-tar",
23
+ gz: "application/gzip",
24
+ mp3: "audio/mpeg",
25
+ wav: "audio/wav",
26
+ ogg: "audio/ogg",
27
+ m4a: "audio/mp4",
28
+ mp4: "video/mp4",
29
+ avi: "video/x-msvideo",
30
+ mov: "video/quicktime",
31
+ wmv: "video/x-ms-wmv",
32
+ flv: "video/x-flv",
33
+ webm: "video/webm",
34
+ json: "application/json",
35
+ xml: "application/xml",
36
+ js: "application/javascript",
37
+ css: "text/css",
38
+ html: "text/html"
39
+ };