gavaengine 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.
@@ -0,0 +1,587 @@
1
+ // src/config.ts
2
+ var DEFAULT_CONFIG = {
3
+ branding: {
4
+ name: "GavaEngine",
5
+ splashWords: ["Powerful", "Flexible", "Modular", "Secure", "Customizable"]
6
+ },
7
+ roles: {
8
+ list: ["admin", "redattore", "scrittore", "lettore"],
9
+ labels: {
10
+ admin: "Amministratore",
11
+ redattore: "Redattore",
12
+ scrittore: "Scrittore",
13
+ lettore: "Lettore"
14
+ },
15
+ canEdit: (role) => role !== "lettore",
16
+ canPublish: (role) => role === "admin" || role === "redattore",
17
+ adminRole: "admin"
18
+ },
19
+ categories: [
20
+ "Cultura ed Intercultura",
21
+ "Fatti ed Eventi",
22
+ "Poeti e Prosatori",
23
+ "Famiglia, Istituzioni e Territorio",
24
+ "Amici del Meucci"
25
+ ],
26
+ upload: {
27
+ endpoint: "/api/upload",
28
+ imageTypes: ["image/jpeg", "image/png", "image/webp", "image/gif"],
29
+ videoTypes: ["video/mp4", "video/webm", "video/quicktime"],
30
+ maxImageSize: 5 * 1024 * 1024,
31
+ maxVideoSize: 50 * 1024 * 1024
32
+ },
33
+ editor: {
34
+ placeholder: "Inizia a scrivere il tuo articolo...",
35
+ autoSaveDelay: 2e3,
36
+ revisionThrottleMinutes: 5,
37
+ headingLevels: [2, 3]
38
+ },
39
+ routes: {
40
+ articles: "/dashboard/articoli",
41
+ articleEdit: (id) => `/dashboard/articoli/${id}`,
42
+ articleNew: "/dashboard/articoli/nuovo",
43
+ articleView: (slug) => `/articoli/${slug}`,
44
+ articlePreview: (id) => `/anteprima/${id}`,
45
+ users: "/dashboard/utenti",
46
+ userEdit: (id) => `/dashboard/utenti/${id}`,
47
+ userNew: "/dashboard/utenti/nuovo",
48
+ media: "/dashboard/media",
49
+ dashboard: "/dashboard",
50
+ login: "/login",
51
+ home: "/"
52
+ },
53
+ strings: {
54
+ articles: "Articoli",
55
+ newArticle: "Nuovo articolo",
56
+ media: "Media",
57
+ users: "Utenti",
58
+ statistics: "Statistiche",
59
+ settings: "Impostazioni",
60
+ revisions: "Cronologia",
61
+ publish: "Pubblica",
62
+ unpublish: "Ritira",
63
+ saveDraft: "Salva bozza",
64
+ delete: "Elimina",
65
+ edit: "Modifica",
66
+ search: "Cerca",
67
+ titlePlaceholder: "Titolo dell'articolo",
68
+ excerptPlaceholder: "Breve descrizione dell'articolo...",
69
+ slugPlaceholder: "slug-articolo",
70
+ authorPlaceholder: "Nome dell'autore",
71
+ category: "Categoria",
72
+ selectCategory: "Seleziona categoria",
73
+ excerpt: "Estratto",
74
+ author: "Autore",
75
+ slugUrl: "Slug URL",
76
+ draft: "Bozza",
77
+ published: "Pubblicato",
78
+ all: "Tutti",
79
+ drafts: "Bozze",
80
+ saving: "Salvando...",
81
+ saved: "Salvato",
82
+ saveError: "Errore nel salvataggio",
83
+ noArticles: "Nessun articolo.",
84
+ noArticlesSearch: "Nessun articolo trovato.",
85
+ noMedia: "Nessun file caricato.",
86
+ noMediaSearch: "Nessun file trovato.",
87
+ noRevisions: "Nessuna revisione salvata.",
88
+ loading: "Caricamento...",
89
+ confirm: "Conferma",
90
+ cancel: "Annulla",
91
+ apply: "Applica",
92
+ change: "Cambia",
93
+ logout: "Esci",
94
+ viewArticle: "Vedi articolo pubblicato",
95
+ preview: "Anteprima",
96
+ restore: "Ripristina",
97
+ restoreConfirm: "Ripristinare questa revisione? Lo stato attuale verr\xE0 salvato prima del ripristino.",
98
+ beforeRestore: "Prima del ripristino",
99
+ publishedNote: "Pubblicato",
100
+ deleteConfirm: (title) => `Eliminare "${title || "Senza titolo"}"?`,
101
+ unpublishConfirm: (title) => `Ritirare "${title || "Senza titolo"}"? L'articolo torner\xE0 in bozza.`,
102
+ deleteUserConfirm: (name) => `Sei sicuro di voler eliminare l'utente "${name}"?`,
103
+ totalArticles: (count) => `${count} articol${count === 1 ? "o" : "i"} totali`,
104
+ totalFiles: (count) => `${count} file caricati`,
105
+ coverImageLabel: "Immagine di copertina",
106
+ coverImageHint: "Scegli dalla libreria o carica un nuovo file",
107
+ chooseFile: "Scegli file",
108
+ dragHint: "Trascina un'immagine qui oppure",
109
+ uploadHint: "JPEG, PNG, WebP, GIF (max 5MB)",
110
+ mediaLibrary: "Libreria media",
111
+ uploadNew: "Carica nuovo",
112
+ searchFiles: "Cerca file...",
113
+ searchArticles: "Cerca articoli...",
114
+ selectImage: "Seleziona immagine",
115
+ noImages: "Nessuna immagine nella libreria.",
116
+ noImagesSearch: "Nessuna immagine trovata.",
117
+ copyUrl: "Copia URL",
118
+ editImage: "Modifica immagine",
119
+ restoreOriginal: "Ripristina originale",
120
+ freeAspect: "Libero",
121
+ unauthorized: "Non autorizzato",
122
+ notAuthenticated: "Non autenticato",
123
+ allFieldsRequired: "Tutti i campi sono obbligatori.",
124
+ passwordMinLength: "La password deve avere almeno 6 caratteri.",
125
+ emailExists: "Esiste gi\xE0 un utente con questa email.",
126
+ invalidRole: "Ruolo non valido.",
127
+ cannotDeleteSelf: "Non puoi eliminare il tuo stesso account.",
128
+ name: "Nome",
129
+ email: "Email",
130
+ password: "Password",
131
+ passwordEditHint: " (lascia vuoto per non modificarla)",
132
+ role: "Ruolo",
133
+ createdAt: "Creato il",
134
+ actions: "Azioni",
135
+ createUser: "Crea utente",
136
+ saveChanges: "Salva modifiche",
137
+ updated: "Aggiornato",
138
+ status: "Stato",
139
+ title: "Titolo",
140
+ untitled: "Senza titolo"
141
+ }
142
+ };
143
+ function deepMerge(target, source) {
144
+ const result = { ...target };
145
+ for (const key of Object.keys(source)) {
146
+ const sourceVal = source[key];
147
+ const targetVal = target[key];
148
+ if (sourceVal && typeof sourceVal === "object" && !Array.isArray(sourceVal) && typeof targetVal === "object" && !Array.isArray(targetVal) && typeof sourceVal !== "function") {
149
+ result[key] = deepMerge(
150
+ targetVal,
151
+ sourceVal
152
+ );
153
+ } else if (sourceVal !== void 0) {
154
+ result[key] = sourceVal;
155
+ }
156
+ }
157
+ return result;
158
+ }
159
+ function defineConfig(overrides = {}) {
160
+ return deepMerge(DEFAULT_CONFIG, overrides);
161
+ }
162
+
163
+ // src/handlers/revisions.ts
164
+ function createRevisionHandlers(prisma, config) {
165
+ const throttleMs = config.editor.revisionThrottleMinutes * 60 * 1e3;
166
+ return {
167
+ async getRevisions(articleId) {
168
+ return prisma.articleRevision.findMany({
169
+ where: { articleId },
170
+ include: { editor: { select: { name: true } } },
171
+ orderBy: { createdAt: "desc" }
172
+ });
173
+ },
174
+ async restoreRevision(articleId, revisionId, editorId) {
175
+ const article = await prisma.article.findUnique({
176
+ where: { id: articleId }
177
+ });
178
+ if (!article) throw new Error("Article not found");
179
+ const revision = await prisma.articleRevision.findUnique({
180
+ where: { id: revisionId }
181
+ });
182
+ if (!revision || revision.articleId !== articleId) {
183
+ throw new Error("Revision not found");
184
+ }
185
+ await prisma.articleRevision.create({
186
+ data: {
187
+ articleId,
188
+ title: article.title,
189
+ content: article.content,
190
+ excerpt: article.excerpt,
191
+ coverImage: article.coverImage,
192
+ category: article.category,
193
+ editorId,
194
+ note: config.strings.beforeRestore
195
+ }
196
+ });
197
+ await prisma.article.update({
198
+ where: { id: articleId },
199
+ data: {
200
+ title: revision.title,
201
+ content: revision.content,
202
+ excerpt: revision.excerpt,
203
+ coverImage: revision.coverImage,
204
+ category: revision.category,
205
+ updatedAt: /* @__PURE__ */ new Date()
206
+ }
207
+ });
208
+ return { success: true };
209
+ },
210
+ async createRevisionSnapshot(articleId, editorId, note = "") {
211
+ const article = await prisma.article.findUnique({
212
+ where: { id: articleId }
213
+ });
214
+ if (!article) return;
215
+ const threshold = new Date(Date.now() - throttleMs);
216
+ const recent = await prisma.articleRevision.findFirst({
217
+ where: { articleId, createdAt: { gte: threshold } },
218
+ orderBy: { createdAt: "desc" }
219
+ });
220
+ if (recent && !note) return;
221
+ await prisma.articleRevision.create({
222
+ data: {
223
+ articleId,
224
+ title: article.title,
225
+ content: article.content,
226
+ excerpt: article.excerpt,
227
+ coverImage: article.coverImage,
228
+ category: article.category,
229
+ editorId,
230
+ note
231
+ }
232
+ });
233
+ }
234
+ };
235
+ }
236
+
237
+ // src/handlers/articles.ts
238
+ function createArticleHandlers(prisma, config) {
239
+ const revisionHandlers = createRevisionHandlers(prisma, config);
240
+ return {
241
+ async getArticles() {
242
+ return prisma.article.findMany({
243
+ orderBy: { updatedAt: "desc" }
244
+ });
245
+ },
246
+ async getArticleById(id) {
247
+ return prisma.article.findUnique({
248
+ where: { id }
249
+ });
250
+ },
251
+ async createArticle(authorName) {
252
+ const article = await prisma.article.create({
253
+ data: {
254
+ authorName
255
+ }
256
+ });
257
+ return article.id;
258
+ },
259
+ async updateArticle(userId, id, data) {
260
+ const article = await prisma.article.findUnique({ where: { id } });
261
+ if (!article) throw new Error("Article not found");
262
+ await revisionHandlers.createRevisionSnapshot(id, userId);
263
+ await prisma.article.update({
264
+ where: { id },
265
+ data: { ...data, updatedAt: /* @__PURE__ */ new Date() }
266
+ });
267
+ return { success: true };
268
+ },
269
+ async deleteArticle(id) {
270
+ const article = await prisma.article.findUnique({ where: { id } });
271
+ if (!article) throw new Error("Article not found");
272
+ await prisma.article.delete({ where: { id } });
273
+ return { success: true };
274
+ },
275
+ async publishArticle(userId, id) {
276
+ const article = await prisma.article.findUnique({ where: { id } });
277
+ if (!article) throw new Error("Article not found");
278
+ if (!article.title || !article.slug) {
279
+ throw new Error(
280
+ "Title and slug are required for publishing"
281
+ );
282
+ }
283
+ await revisionHandlers.createRevisionSnapshot(
284
+ id,
285
+ userId,
286
+ config.strings.publishedNote
287
+ );
288
+ await prisma.article.update({
289
+ where: { id },
290
+ data: {
291
+ status: "pubblicato",
292
+ publishedAt: /* @__PURE__ */ new Date()
293
+ }
294
+ });
295
+ return { success: true };
296
+ },
297
+ async unpublishArticle(id) {
298
+ const article = await prisma.article.findUnique({ where: { id } });
299
+ if (!article) throw new Error("Article not found");
300
+ await prisma.article.update({
301
+ where: { id },
302
+ data: {
303
+ status: "bozza",
304
+ publishedAt: null
305
+ }
306
+ });
307
+ return { success: true };
308
+ }
309
+ };
310
+ }
311
+
312
+ // src/handlers/users.ts
313
+ import bcrypt from "bcryptjs";
314
+ function createUserHandlers(prisma, config) {
315
+ return {
316
+ async getUsers() {
317
+ return prisma.user.findMany({
318
+ select: {
319
+ id: true,
320
+ name: true,
321
+ email: true,
322
+ role: true,
323
+ createdAt: true
324
+ },
325
+ orderBy: { createdAt: "desc" }
326
+ });
327
+ },
328
+ async getUserById(id) {
329
+ return prisma.user.findUnique({
330
+ where: { id },
331
+ select: {
332
+ id: true,
333
+ name: true,
334
+ email: true,
335
+ role: true
336
+ }
337
+ });
338
+ },
339
+ async createUser(formData) {
340
+ const name = formData.get("name");
341
+ const email = formData.get("email");
342
+ const password = formData.get("password");
343
+ const role = formData.get("role");
344
+ if (!name || !email || !password || !role) {
345
+ return { error: config.strings.allFieldsRequired };
346
+ }
347
+ if (!config.roles.list.includes(role)) {
348
+ return { error: config.strings.invalidRole };
349
+ }
350
+ if (password.length < 6) {
351
+ return { error: config.strings.passwordMinLength };
352
+ }
353
+ const existing = await prisma.user.findUnique({ where: { email } });
354
+ if (existing) {
355
+ return { error: config.strings.emailExists };
356
+ }
357
+ const hashedPassword = await bcrypt.hash(password, 10);
358
+ await prisma.user.create({
359
+ data: {
360
+ name,
361
+ email,
362
+ password: hashedPassword,
363
+ role
364
+ }
365
+ });
366
+ return { success: true };
367
+ },
368
+ async updateUser(id, formData) {
369
+ const name = formData.get("name");
370
+ const email = formData.get("email");
371
+ const password = formData.get("password");
372
+ const role = formData.get("role");
373
+ if (!name || !email || !role) {
374
+ return { error: config.strings.allFieldsRequired };
375
+ }
376
+ if (!config.roles.list.includes(role)) {
377
+ return { error: config.strings.invalidRole };
378
+ }
379
+ const existing = await prisma.user.findUnique({ where: { email } });
380
+ if (existing && existing.id !== id) {
381
+ return { error: config.strings.emailExists };
382
+ }
383
+ const data = {
384
+ name,
385
+ email,
386
+ role
387
+ };
388
+ if (password && password.length > 0) {
389
+ if (password.length < 6) {
390
+ return { error: config.strings.passwordMinLength };
391
+ }
392
+ data.password = await bcrypt.hash(password, 10);
393
+ }
394
+ await prisma.user.update({
395
+ where: { id },
396
+ data
397
+ });
398
+ return { success: true };
399
+ },
400
+ async deleteUser(currentUserId, id) {
401
+ if (currentUserId === id) {
402
+ return { error: config.strings.cannotDeleteSelf };
403
+ }
404
+ await prisma.user.delete({ where: { id } });
405
+ return { success: true };
406
+ }
407
+ };
408
+ }
409
+
410
+ // src/handlers/media.ts
411
+ function createMediaHandlers(prisma, _config) {
412
+ return {
413
+ async getMedia(search) {
414
+ return prisma.media.findMany({
415
+ where: search ? { filename: { contains: search } } : void 0,
416
+ include: { uploader: { select: { name: true } } },
417
+ orderBy: { createdAt: "desc" }
418
+ });
419
+ },
420
+ async deleteMedia(userId, userRole, id, deleteFileFromDisk) {
421
+ const media = await prisma.media.findUnique({ where: { id } });
422
+ if (!media) throw new Error("File not found");
423
+ if (userRole === "scrittore" && media.uploaderId !== userId) {
424
+ throw new Error("Unauthorized");
425
+ }
426
+ const usedInArticles = await prisma.article.findMany({
427
+ where: {
428
+ OR: [
429
+ { coverImage: media.path },
430
+ { content: { contains: media.path } }
431
+ ]
432
+ },
433
+ select: { id: true, title: true }
434
+ });
435
+ if (usedInArticles.length > 0) {
436
+ const titles = usedInArticles.map((a) => a.title || "Untitled").join(", ");
437
+ throw new Error(
438
+ `Cannot delete: image is used in ${usedInArticles.length} article(s) (${titles})`
439
+ );
440
+ }
441
+ if (deleteFileFromDisk) {
442
+ try {
443
+ await deleteFileFromDisk(media.path);
444
+ } catch {
445
+ }
446
+ }
447
+ await prisma.media.delete({ where: { id } });
448
+ return { success: true };
449
+ }
450
+ };
451
+ }
452
+
453
+ // src/handlers/upload.ts
454
+ function createUploadHandler(prisma, config, storage) {
455
+ const allowedTypes = [
456
+ ...config.upload.imageTypes,
457
+ ...config.upload.videoTypes
458
+ ];
459
+ return {
460
+ async handleUpload(file, uploaderId) {
461
+ if (!allowedTypes.includes(file.type)) {
462
+ return { error: "Unsupported file type" };
463
+ }
464
+ const isVideo = config.upload.videoTypes.includes(file.type);
465
+ const maxSize = isVideo ? config.upload.maxVideoSize : config.upload.maxImageSize;
466
+ if (file.size > maxSize) {
467
+ return {
468
+ error: `File too large. Max ${isVideo ? config.upload.maxVideoSize / (1024 * 1024) : config.upload.maxImageSize / (1024 * 1024)}MB.`
469
+ };
470
+ }
471
+ const buffer = Buffer.from(await file.arrayBuffer());
472
+ const url = await storage.save(file.name, buffer, file.type);
473
+ await prisma.media.create({
474
+ data: {
475
+ filename: file.name,
476
+ path: url,
477
+ mimeType: file.type,
478
+ size: file.size,
479
+ uploaderId
480
+ }
481
+ });
482
+ return { url };
483
+ }
484
+ };
485
+ }
486
+
487
+ // src/handlers/auth.ts
488
+ import bcrypt2 from "bcryptjs";
489
+ function buildCredentialsProvider(prisma) {
490
+ return {
491
+ credentials: {
492
+ email: {},
493
+ password: {}
494
+ },
495
+ async authorize(credentials) {
496
+ const email = credentials.email;
497
+ const password = credentials.password;
498
+ if (!email || !password) return null;
499
+ const user = await prisma.user.findUnique({
500
+ where: { email }
501
+ });
502
+ if (!user) return null;
503
+ const isValid = await bcrypt2.compare(password, user.password);
504
+ if (!isValid) return null;
505
+ return {
506
+ id: user.id,
507
+ name: user.name,
508
+ email: user.email,
509
+ role: user.role
510
+ };
511
+ }
512
+ };
513
+ }
514
+ function buildAuthCallbacks(config) {
515
+ return {
516
+ async jwt({
517
+ token,
518
+ user
519
+ }) {
520
+ if (user) {
521
+ token.id = user.id;
522
+ token.role = user.role;
523
+ }
524
+ return token;
525
+ },
526
+ async session({
527
+ session,
528
+ token
529
+ }) {
530
+ if (session.user) {
531
+ session.user.id = token.id;
532
+ session.user.role = token.role;
533
+ }
534
+ return session;
535
+ },
536
+ authorized({
537
+ auth,
538
+ request: { nextUrl }
539
+ }) {
540
+ const isLoggedIn = !!auth;
541
+ const role = auth?.user?.role;
542
+ const pathname = nextUrl.pathname;
543
+ if (pathname === config.routes.login && isLoggedIn) {
544
+ return Response.redirect(new URL(config.routes.articles, nextUrl));
545
+ }
546
+ if (pathname.startsWith(config.routes.dashboard)) {
547
+ if (!isLoggedIn) {
548
+ return Response.redirect(new URL(config.routes.login, nextUrl));
549
+ }
550
+ if (pathname.startsWith(config.routes.users) && role !== config.roles.adminRole) {
551
+ return Response.redirect(
552
+ new URL(config.routes.articles, nextUrl)
553
+ );
554
+ }
555
+ }
556
+ return true;
557
+ }
558
+ };
559
+ }
560
+
561
+ // src/handlers/auth-utils.ts
562
+ function createAuthUtils(config) {
563
+ return {
564
+ canEdit(role) {
565
+ return config.roles.canEdit(role);
566
+ },
567
+ canPublish(role) {
568
+ return config.roles.canPublish(role);
569
+ },
570
+ isAdmin(role) {
571
+ return role === config.roles.adminRole;
572
+ }
573
+ };
574
+ }
575
+ export {
576
+ DEFAULT_CONFIG,
577
+ buildAuthCallbacks,
578
+ buildCredentialsProvider,
579
+ createArticleHandlers,
580
+ createAuthUtils,
581
+ createMediaHandlers,
582
+ createRevisionHandlers,
583
+ createUploadHandler,
584
+ createUserHandlers,
585
+ defineConfig
586
+ };
587
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/config.ts","../../src/handlers/revisions.ts","../../src/handlers/articles.ts","../../src/handlers/users.ts","../../src/handlers/media.ts","../../src/handlers/upload.ts","../../src/handlers/auth.ts","../../src/handlers/auth-utils.ts"],"sourcesContent":["export interface GavaEngineConfig {\n branding: {\n name: string;\n logo?: string;\n splashWords: string[];\n };\n roles: {\n list: string[];\n labels: Record<string, string>;\n canEdit: (role: string) => boolean;\n canPublish: (role: string) => boolean;\n adminRole: string;\n };\n categories: string[];\n upload: {\n endpoint: string;\n imageTypes: string[];\n videoTypes: string[];\n maxImageSize: number;\n maxVideoSize: number;\n };\n editor: {\n placeholder: string;\n autoSaveDelay: number;\n revisionThrottleMinutes: number;\n headingLevels: number[];\n };\n routes: {\n articles: string;\n articleEdit: (id: string) => string;\n articleNew: string;\n articleView: (slug: string) => string;\n articlePreview: (id: string) => string;\n users: string;\n userEdit: (id: string) => string;\n userNew: string;\n media: string;\n dashboard: string;\n login: string;\n home: string;\n };\n strings: {\n articles: string;\n newArticle: string;\n media: string;\n users: string;\n statistics: string;\n settings: string;\n revisions: string;\n publish: string;\n unpublish: string;\n saveDraft: string;\n delete: string;\n edit: string;\n search: string;\n titlePlaceholder: string;\n excerptPlaceholder: string;\n slugPlaceholder: string;\n authorPlaceholder: string;\n category: string;\n selectCategory: string;\n excerpt: string;\n author: string;\n slugUrl: string;\n draft: string;\n published: string;\n all: string;\n drafts: string;\n saving: string;\n saved: string;\n saveError: string;\n noArticles: string;\n noArticlesSearch: string;\n noMedia: string;\n noMediaSearch: string;\n noRevisions: string;\n loading: string;\n confirm: string;\n cancel: string;\n apply: string;\n change: string;\n logout: string;\n viewArticle: string;\n preview: string;\n restore: string;\n restoreConfirm: string;\n beforeRestore: string;\n publishedNote: string;\n deleteConfirm: (title: string) => string;\n unpublishConfirm: (title: string) => string;\n deleteUserConfirm: (name: string) => string;\n totalArticles: (count: number) => string;\n totalFiles: (count: number) => string;\n coverImageLabel: string;\n coverImageHint: string;\n chooseFile: string;\n dragHint: string;\n uploadHint: string;\n mediaLibrary: string;\n uploadNew: string;\n searchFiles: string;\n searchArticles: string;\n selectImage: string;\n noImages: string;\n noImagesSearch: string;\n copyUrl: string;\n editImage: string;\n restoreOriginal: string;\n freeAspect: string;\n unauthorized: string;\n notAuthenticated: string;\n allFieldsRequired: string;\n passwordMinLength: string;\n emailExists: string;\n invalidRole: string;\n cannotDeleteSelf: string;\n name: string;\n email: string;\n password: string;\n passwordEditHint: string;\n role: string;\n createdAt: string;\n actions: string;\n createUser: string;\n saveChanges: string;\n updated: string;\n status: string;\n title: string;\n untitled: string;\n };\n}\n\nconst DEFAULT_CONFIG: GavaEngineConfig = {\n branding: {\n name: \"GavaEngine\",\n splashWords: [\"Powerful\", \"Flexible\", \"Modular\", \"Secure\", \"Customizable\"],\n },\n roles: {\n list: [\"admin\", \"redattore\", \"scrittore\", \"lettore\"],\n labels: {\n admin: \"Amministratore\",\n redattore: \"Redattore\",\n scrittore: \"Scrittore\",\n lettore: \"Lettore\",\n },\n canEdit: (role: string) => role !== \"lettore\",\n canPublish: (role: string) => role === \"admin\" || role === \"redattore\",\n adminRole: \"admin\",\n },\n categories: [\n \"Cultura ed Intercultura\",\n \"Fatti ed Eventi\",\n \"Poeti e Prosatori\",\n \"Famiglia, Istituzioni e Territorio\",\n \"Amici del Meucci\",\n ],\n upload: {\n endpoint: \"/api/upload\",\n imageTypes: [\"image/jpeg\", \"image/png\", \"image/webp\", \"image/gif\"],\n videoTypes: [\"video/mp4\", \"video/webm\", \"video/quicktime\"],\n maxImageSize: 5 * 1024 * 1024,\n maxVideoSize: 50 * 1024 * 1024,\n },\n editor: {\n placeholder: \"Inizia a scrivere il tuo articolo...\",\n autoSaveDelay: 2000,\n revisionThrottleMinutes: 5,\n headingLevels: [2, 3],\n },\n routes: {\n articles: \"/dashboard/articoli\",\n articleEdit: (id) => `/dashboard/articoli/${id}`,\n articleNew: \"/dashboard/articoli/nuovo\",\n articleView: (slug) => `/articoli/${slug}`,\n articlePreview: (id) => `/anteprima/${id}`,\n users: \"/dashboard/utenti\",\n userEdit: (id) => `/dashboard/utenti/${id}`,\n userNew: \"/dashboard/utenti/nuovo\",\n media: \"/dashboard/media\",\n dashboard: \"/dashboard\",\n login: \"/login\",\n home: \"/\",\n },\n strings: {\n articles: \"Articoli\",\n newArticle: \"Nuovo articolo\",\n media: \"Media\",\n users: \"Utenti\",\n statistics: \"Statistiche\",\n settings: \"Impostazioni\",\n revisions: \"Cronologia\",\n publish: \"Pubblica\",\n unpublish: \"Ritira\",\n saveDraft: \"Salva bozza\",\n delete: \"Elimina\",\n edit: \"Modifica\",\n search: \"Cerca\",\n titlePlaceholder: \"Titolo dell'articolo\",\n excerptPlaceholder: \"Breve descrizione dell'articolo...\",\n slugPlaceholder: \"slug-articolo\",\n authorPlaceholder: \"Nome dell'autore\",\n category: \"Categoria\",\n selectCategory: \"Seleziona categoria\",\n excerpt: \"Estratto\",\n author: \"Autore\",\n slugUrl: \"Slug URL\",\n draft: \"Bozza\",\n published: \"Pubblicato\",\n all: \"Tutti\",\n drafts: \"Bozze\",\n saving: \"Salvando...\",\n saved: \"Salvato\",\n saveError: \"Errore nel salvataggio\",\n noArticles: \"Nessun articolo.\",\n noArticlesSearch: \"Nessun articolo trovato.\",\n noMedia: \"Nessun file caricato.\",\n noMediaSearch: \"Nessun file trovato.\",\n noRevisions: \"Nessuna revisione salvata.\",\n loading: \"Caricamento...\",\n confirm: \"Conferma\",\n cancel: \"Annulla\",\n apply: \"Applica\",\n change: \"Cambia\",\n logout: \"Esci\",\n viewArticle: \"Vedi articolo pubblicato\",\n preview: \"Anteprima\",\n restore: \"Ripristina\",\n restoreConfirm:\n \"Ripristinare questa revisione? Lo stato attuale verrà salvato prima del ripristino.\",\n beforeRestore: \"Prima del ripristino\",\n publishedNote: \"Pubblicato\",\n deleteConfirm: (title) => `Eliminare \"${title || \"Senza titolo\"}\"?`,\n unpublishConfirm: (title) =>\n `Ritirare \"${title || \"Senza titolo\"}\"? L'articolo tornerà in bozza.`,\n deleteUserConfirm: (name) =>\n `Sei sicuro di voler eliminare l'utente \"${name}\"?`,\n totalArticles: (count) =>\n `${count} articol${count === 1 ? \"o\" : \"i\"} totali`,\n totalFiles: (count) => `${count} file caricati`,\n coverImageLabel: \"Immagine di copertina\",\n coverImageHint: \"Scegli dalla libreria o carica un nuovo file\",\n chooseFile: \"Scegli file\",\n dragHint: \"Trascina un'immagine qui oppure\",\n uploadHint: \"JPEG, PNG, WebP, GIF (max 5MB)\",\n mediaLibrary: \"Libreria media\",\n uploadNew: \"Carica nuovo\",\n searchFiles: \"Cerca file...\",\n searchArticles: \"Cerca articoli...\",\n selectImage: \"Seleziona immagine\",\n noImages: \"Nessuna immagine nella libreria.\",\n noImagesSearch: \"Nessuna immagine trovata.\",\n copyUrl: \"Copia URL\",\n editImage: \"Modifica immagine\",\n restoreOriginal: \"Ripristina originale\",\n freeAspect: \"Libero\",\n unauthorized: \"Non autorizzato\",\n notAuthenticated: \"Non autenticato\",\n allFieldsRequired: \"Tutti i campi sono obbligatori.\",\n passwordMinLength: \"La password deve avere almeno 6 caratteri.\",\n emailExists: \"Esiste già un utente con questa email.\",\n invalidRole: \"Ruolo non valido.\",\n cannotDeleteSelf: \"Non puoi eliminare il tuo stesso account.\",\n name: \"Nome\",\n email: \"Email\",\n password: \"Password\",\n passwordEditHint: \" (lascia vuoto per non modificarla)\",\n role: \"Ruolo\",\n createdAt: \"Creato il\",\n actions: \"Azioni\",\n createUser: \"Crea utente\",\n saveChanges: \"Salva modifiche\",\n updated: \"Aggiornato\",\n status: \"Stato\",\n title: \"Titolo\",\n untitled: \"Senza titolo\",\n },\n};\n\nfunction deepMerge<T extends Record<string, any>>(\n target: T,\n source: Partial<T>\n): T {\n const result = { ...target };\n for (const key of Object.keys(source) as (keyof T)[]) {\n const sourceVal = source[key];\n const targetVal = target[key];\n if (\n sourceVal &&\n typeof sourceVal === \"object\" &&\n !Array.isArray(sourceVal) &&\n typeof targetVal === \"object\" &&\n !Array.isArray(targetVal) &&\n typeof sourceVal !== \"function\"\n ) {\n (result as any)[key] = deepMerge(\n targetVal as Record<string, any>,\n sourceVal as Record<string, any>\n );\n } else if (sourceVal !== undefined) {\n (result as any)[key] = sourceVal;\n }\n }\n return result;\n}\n\nexport function defineConfig(\n overrides: Partial<GavaEngineConfig> = {}\n): GavaEngineConfig {\n return deepMerge(DEFAULT_CONFIG, overrides);\n}\n\nexport { DEFAULT_CONFIG };\n","import type { GavaEngineConfig } from \"../config.js\";\n\nexport function createRevisionHandlers(prisma: any, config: GavaEngineConfig) {\n const throttleMs = config.editor.revisionThrottleMinutes * 60 * 1000;\n\n return {\n async getRevisions(articleId: string) {\n return prisma.articleRevision.findMany({\n where: { articleId },\n include: { editor: { select: { name: true } } },\n orderBy: { createdAt: \"desc\" },\n });\n },\n\n async restoreRevision(\n articleId: string,\n revisionId: string,\n editorId: string\n ) {\n const article = await prisma.article.findUnique({\n where: { id: articleId },\n });\n if (!article) throw new Error(\"Article not found\");\n\n const revision = await prisma.articleRevision.findUnique({\n where: { id: revisionId },\n });\n if (!revision || revision.articleId !== articleId) {\n throw new Error(\"Revision not found\");\n }\n\n // Save current state before restoring\n await prisma.articleRevision.create({\n data: {\n articleId,\n title: article.title,\n content: article.content,\n excerpt: article.excerpt,\n coverImage: article.coverImage,\n category: article.category,\n editorId,\n note: config.strings.beforeRestore,\n },\n });\n\n // Restore\n await prisma.article.update({\n where: { id: articleId },\n data: {\n title: revision.title,\n content: revision.content,\n excerpt: revision.excerpt,\n coverImage: revision.coverImage,\n category: revision.category,\n updatedAt: new Date(),\n },\n });\n\n return { success: true };\n },\n\n async createRevisionSnapshot(\n articleId: string,\n editorId: string,\n note: string = \"\"\n ) {\n const article = await prisma.article.findUnique({\n where: { id: articleId },\n });\n if (!article) return;\n\n // Throttle: only create if last revision was > threshold ago\n const threshold = new Date(Date.now() - throttleMs);\n const recent = await prisma.articleRevision.findFirst({\n where: { articleId, createdAt: { gte: threshold } },\n orderBy: { createdAt: \"desc\" },\n });\n\n if (recent && !note) return;\n\n await prisma.articleRevision.create({\n data: {\n articleId,\n title: article.title,\n content: article.content,\n excerpt: article.excerpt,\n coverImage: article.coverImage,\n category: article.category,\n editorId,\n note,\n },\n });\n },\n };\n}\n","import type { GavaEngineConfig } from \"../config.js\";\nimport { createRevisionHandlers } from \"./revisions.js\";\n\nexport function createArticleHandlers(prisma: any, config: GavaEngineConfig) {\n const revisionHandlers = createRevisionHandlers(prisma, config);\n\n return {\n async getArticles() {\n return prisma.article.findMany({\n orderBy: { updatedAt: \"desc\" },\n });\n },\n\n async getArticleById(id: string) {\n return prisma.article.findUnique({\n where: { id },\n });\n },\n\n async createArticle(authorName: string) {\n const article = await prisma.article.create({\n data: {\n authorName,\n },\n });\n return article.id;\n },\n\n async updateArticle(\n userId: string,\n id: string,\n data: {\n title?: string;\n slug?: string;\n excerpt?: string;\n content?: string;\n coverImage?: string;\n category?: string;\n authorName?: string;\n }\n ) {\n const article = await prisma.article.findUnique({ where: { id } });\n if (!article) throw new Error(\"Article not found\");\n\n await revisionHandlers.createRevisionSnapshot(id, userId);\n\n await prisma.article.update({\n where: { id },\n data: { ...data, updatedAt: new Date() },\n });\n\n return { success: true };\n },\n\n async deleteArticle(id: string) {\n const article = await prisma.article.findUnique({ where: { id } });\n if (!article) throw new Error(\"Article not found\");\n\n await prisma.article.delete({ where: { id } });\n return { success: true };\n },\n\n async publishArticle(userId: string, id: string) {\n const article = await prisma.article.findUnique({ where: { id } });\n if (!article) throw new Error(\"Article not found\");\n\n if (!article.title || !article.slug) {\n throw new Error(\n \"Title and slug are required for publishing\"\n );\n }\n\n await revisionHandlers.createRevisionSnapshot(\n id,\n userId,\n config.strings.publishedNote\n );\n\n await prisma.article.update({\n where: { id },\n data: {\n status: \"pubblicato\",\n publishedAt: new Date(),\n },\n });\n\n return { success: true };\n },\n\n async unpublishArticle(id: string) {\n const article = await prisma.article.findUnique({ where: { id } });\n if (!article) throw new Error(\"Article not found\");\n\n await prisma.article.update({\n where: { id },\n data: {\n status: \"bozza\",\n publishedAt: null,\n },\n });\n\n return { success: true };\n },\n };\n}\n","import bcrypt from \"bcryptjs\";\nimport type { GavaEngineConfig } from \"../config.js\";\n\nexport function createUserHandlers(prisma: any, config: GavaEngineConfig) {\n return {\n async getUsers() {\n return prisma.user.findMany({\n select: {\n id: true,\n name: true,\n email: true,\n role: true,\n createdAt: true,\n },\n orderBy: { createdAt: \"desc\" },\n });\n },\n\n async getUserById(id: string) {\n return prisma.user.findUnique({\n where: { id },\n select: {\n id: true,\n name: true,\n email: true,\n role: true,\n },\n });\n },\n\n async createUser(formData: FormData) {\n const name = formData.get(\"name\") as string;\n const email = formData.get(\"email\") as string;\n const password = formData.get(\"password\") as string;\n const role = formData.get(\"role\") as string;\n\n if (!name || !email || !password || !role) {\n return { error: config.strings.allFieldsRequired };\n }\n\n if (!config.roles.list.includes(role)) {\n return { error: config.strings.invalidRole };\n }\n\n if (password.length < 6) {\n return { error: config.strings.passwordMinLength };\n }\n\n const existing = await prisma.user.findUnique({ where: { email } });\n if (existing) {\n return { error: config.strings.emailExists };\n }\n\n const hashedPassword = await bcrypt.hash(password, 10);\n\n await prisma.user.create({\n data: {\n name,\n email,\n password: hashedPassword,\n role,\n },\n });\n\n return { success: true };\n },\n\n async updateUser(id: string, formData: FormData) {\n const name = formData.get(\"name\") as string;\n const email = formData.get(\"email\") as string;\n const password = formData.get(\"password\") as string;\n const role = formData.get(\"role\") as string;\n\n if (!name || !email || !role) {\n return { error: config.strings.allFieldsRequired };\n }\n\n if (!config.roles.list.includes(role)) {\n return { error: config.strings.invalidRole };\n }\n\n const existing = await prisma.user.findUnique({ where: { email } });\n if (existing && existing.id !== id) {\n return { error: config.strings.emailExists };\n }\n\n const data: {\n name: string;\n email: string;\n role: string;\n password?: string;\n } = {\n name,\n email,\n role,\n };\n\n if (password && password.length > 0) {\n if (password.length < 6) {\n return { error: config.strings.passwordMinLength };\n }\n data.password = await bcrypt.hash(password, 10);\n }\n\n await prisma.user.update({\n where: { id },\n data,\n });\n\n return { success: true };\n },\n\n async deleteUser(currentUserId: string, id: string) {\n if (currentUserId === id) {\n return { error: config.strings.cannotDeleteSelf };\n }\n\n await prisma.user.delete({ where: { id } });\n return { success: true };\n },\n };\n}\n","import type { GavaEngineConfig } from \"../config.js\";\n\nexport function createMediaHandlers(prisma: any, _config: GavaEngineConfig) {\n return {\n async getMedia(search?: string) {\n return prisma.media.findMany({\n where: search ? { filename: { contains: search } } : undefined,\n include: { uploader: { select: { name: true } } },\n orderBy: { createdAt: \"desc\" },\n });\n },\n\n async deleteMedia(\n userId: string,\n userRole: string,\n id: string,\n deleteFileFromDisk?: (path: string) => Promise<void>\n ) {\n const media = await prisma.media.findUnique({ where: { id } });\n if (!media) throw new Error(\"File not found\");\n\n // Writers can only delete their own media\n if (userRole === \"scrittore\" && media.uploaderId !== userId) {\n throw new Error(\"Unauthorized\");\n }\n\n // Check if image is used in articles\n const usedInArticles = await prisma.article.findMany({\n where: {\n OR: [\n { coverImage: media.path },\n { content: { contains: media.path } },\n ],\n },\n select: { id: true, title: true },\n });\n\n if (usedInArticles.length > 0) {\n const titles = usedInArticles\n .map((a: { title: string }) => a.title || \"Untitled\")\n .join(\", \");\n throw new Error(\n `Cannot delete: image is used in ${usedInArticles.length} article(s) (${titles})`\n );\n }\n\n // Delete file from disk if handler provided\n if (deleteFileFromDisk) {\n try {\n await deleteFileFromDisk(media.path);\n } catch {\n // File may already be missing\n }\n }\n\n await prisma.media.delete({ where: { id } });\n return { success: true };\n },\n };\n}\n","import type { GavaEngineConfig } from \"../config.js\";\n\nexport interface UploadResult {\n url?: string;\n error?: string;\n}\n\nexport interface FileStorage {\n save(filename: string, buffer: Buffer, mimeType: string): Promise<string>;\n}\n\nexport function createUploadHandler(\n prisma: any,\n config: GavaEngineConfig,\n storage: FileStorage\n) {\n const allowedTypes = [\n ...config.upload.imageTypes,\n ...config.upload.videoTypes,\n ];\n\n return {\n async handleUpload(\n file: {\n name: string;\n type: string;\n size: number;\n arrayBuffer: () => Promise<ArrayBuffer>;\n },\n uploaderId: string\n ): Promise<UploadResult> {\n if (!allowedTypes.includes(file.type)) {\n return { error: \"Unsupported file type\" };\n }\n\n const isVideo = config.upload.videoTypes.includes(file.type);\n const maxSize = isVideo\n ? config.upload.maxVideoSize\n : config.upload.maxImageSize;\n\n if (file.size > maxSize) {\n return {\n error: `File too large. Max ${isVideo ? config.upload.maxVideoSize / (1024 * 1024) : config.upload.maxImageSize / (1024 * 1024)}MB.`,\n };\n }\n\n const buffer = Buffer.from(await file.arrayBuffer());\n const url = await storage.save(file.name, buffer, file.type);\n\n await prisma.media.create({\n data: {\n filename: file.name,\n path: url,\n mimeType: file.type,\n size: file.size,\n uploaderId,\n },\n });\n\n return { url };\n },\n };\n}\n","import bcrypt from \"bcryptjs\";\nimport type { GavaEngineConfig } from \"../config.js\";\n\nexport function buildCredentialsProvider(prisma: any) {\n return {\n credentials: {\n email: {},\n password: {},\n },\n async authorize(credentials: Record<string, unknown>) {\n const email = credentials.email as string;\n const password = credentials.password as string;\n\n if (!email || !password) return null;\n\n const user = await prisma.user.findUnique({\n where: { email },\n });\n\n if (!user) return null;\n\n const isValid = await bcrypt.compare(password, user.password);\n if (!isValid) return null;\n\n return {\n id: user.id,\n name: user.name,\n email: user.email,\n role: user.role,\n };\n },\n };\n}\n\nexport function buildAuthCallbacks(config: GavaEngineConfig) {\n return {\n async jwt({\n token,\n user,\n }: {\n token: Record<string, unknown>;\n user?: Record<string, unknown>;\n }) {\n if (user) {\n token.id = user.id as string;\n token.role = user.role as string;\n }\n return token;\n },\n async session({\n session,\n token,\n }: {\n session: Record<string, any>;\n token: Record<string, unknown>;\n }) {\n if (session.user) {\n session.user.id = token.id as string;\n session.user.role = token.role as string;\n }\n return session;\n },\n authorized({\n auth,\n request: { nextUrl },\n }: {\n auth: Record<string, any> | null;\n request: { nextUrl: URL };\n }) {\n const isLoggedIn = !!auth;\n const role = auth?.user?.role;\n const pathname = nextUrl.pathname;\n\n if (pathname === config.routes.login && isLoggedIn) {\n return Response.redirect(new URL(config.routes.articles, nextUrl));\n }\n\n if (pathname.startsWith(config.routes.dashboard)) {\n if (!isLoggedIn) {\n return Response.redirect(new URL(config.routes.login, nextUrl));\n }\n\n if (\n pathname.startsWith(config.routes.users) &&\n role !== config.roles.adminRole\n ) {\n return Response.redirect(\n new URL(config.routes.articles, nextUrl)\n );\n }\n }\n\n return true;\n },\n };\n}\n","import type { GavaEngineConfig } from \"../config.js\";\n\nexport function createAuthUtils(config: GavaEngineConfig) {\n return {\n canEdit(role: string): boolean {\n return config.roles.canEdit(role);\n },\n canPublish(role: string): boolean {\n return config.roles.canPublish(role);\n },\n isAdmin(role: string): boolean {\n return role === config.roles.adminRole;\n },\n };\n}\n"],"mappings":"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","names":["bcrypt"]}