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.
- package/dist/chunk-CE7E5MAB.js +2716 -0
- package/dist/chunk-CE7E5MAB.js.map +1 -0
- package/dist/chunk-GGD7I4JO.js +253 -0
- package/dist/chunk-GGD7I4JO.js.map +1 -0
- package/dist/chunk-KCQJHXZP.js +67 -0
- package/dist/chunk-KCQJHXZP.js.map +1 -0
- package/dist/cli/index.js +242 -0
- package/dist/components/index.d.ts +154 -0
- package/dist/components/index.js +47 -0
- package/dist/components/index.js.map +1 -0
- package/dist/handlers/index.d.ts +253 -0
- package/dist/handlers/index.js +587 -0
- package/dist/handlers/index.js.map +1 -0
- package/dist/index-CIX0MBHm.d.ts +180 -0
- package/dist/index.d.ts +130 -0
- package/dist/index.js +495 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/index.d.ts +4 -0
- package/dist/providers/index.js +28 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/styles/gavaengine.css +733 -0
- package/dist/types-BZgSeTU8.d.ts +101 -0
- package/package.json +103 -0
- package/src/prisma/schema.partial.prisma +76 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/config.ts
|
|
4
|
+
var DEFAULT_CONFIG = {
|
|
5
|
+
branding: {
|
|
6
|
+
name: "GavaEngine",
|
|
7
|
+
splashWords: ["Powerful", "Flexible", "Modular", "Secure", "Customizable"]
|
|
8
|
+
},
|
|
9
|
+
roles: {
|
|
10
|
+
list: ["admin", "redattore", "scrittore", "lettore"],
|
|
11
|
+
labels: {
|
|
12
|
+
admin: "Amministratore",
|
|
13
|
+
redattore: "Redattore",
|
|
14
|
+
scrittore: "Scrittore",
|
|
15
|
+
lettore: "Lettore"
|
|
16
|
+
},
|
|
17
|
+
canEdit: (role) => role !== "lettore",
|
|
18
|
+
canPublish: (role) => role === "admin" || role === "redattore",
|
|
19
|
+
adminRole: "admin"
|
|
20
|
+
},
|
|
21
|
+
categories: [
|
|
22
|
+
"Cultura ed Intercultura",
|
|
23
|
+
"Fatti ed Eventi",
|
|
24
|
+
"Poeti e Prosatori",
|
|
25
|
+
"Famiglia, Istituzioni e Territorio",
|
|
26
|
+
"Amici del Meucci"
|
|
27
|
+
],
|
|
28
|
+
upload: {
|
|
29
|
+
endpoint: "/api/upload",
|
|
30
|
+
imageTypes: ["image/jpeg", "image/png", "image/webp", "image/gif"],
|
|
31
|
+
videoTypes: ["video/mp4", "video/webm", "video/quicktime"],
|
|
32
|
+
maxImageSize: 5 * 1024 * 1024,
|
|
33
|
+
maxVideoSize: 50 * 1024 * 1024
|
|
34
|
+
},
|
|
35
|
+
editor: {
|
|
36
|
+
placeholder: "Inizia a scrivere il tuo articolo...",
|
|
37
|
+
autoSaveDelay: 2e3,
|
|
38
|
+
revisionThrottleMinutes: 5,
|
|
39
|
+
headingLevels: [2, 3]
|
|
40
|
+
},
|
|
41
|
+
routes: {
|
|
42
|
+
articles: "/dashboard/articoli",
|
|
43
|
+
articleEdit: (id) => `/dashboard/articoli/${id}`,
|
|
44
|
+
articleNew: "/dashboard/articoli/nuovo",
|
|
45
|
+
articleView: (slug) => `/articoli/${slug}`,
|
|
46
|
+
articlePreview: (id) => `/anteprima/${id}`,
|
|
47
|
+
users: "/dashboard/utenti",
|
|
48
|
+
userEdit: (id) => `/dashboard/utenti/${id}`,
|
|
49
|
+
userNew: "/dashboard/utenti/nuovo",
|
|
50
|
+
media: "/dashboard/media",
|
|
51
|
+
dashboard: "/dashboard",
|
|
52
|
+
login: "/login",
|
|
53
|
+
home: "/"
|
|
54
|
+
},
|
|
55
|
+
strings: {
|
|
56
|
+
articles: "Articoli",
|
|
57
|
+
newArticle: "Nuovo articolo",
|
|
58
|
+
media: "Media",
|
|
59
|
+
users: "Utenti",
|
|
60
|
+
statistics: "Statistiche",
|
|
61
|
+
settings: "Impostazioni",
|
|
62
|
+
revisions: "Cronologia",
|
|
63
|
+
publish: "Pubblica",
|
|
64
|
+
unpublish: "Ritira",
|
|
65
|
+
saveDraft: "Salva bozza",
|
|
66
|
+
delete: "Elimina",
|
|
67
|
+
edit: "Modifica",
|
|
68
|
+
search: "Cerca",
|
|
69
|
+
titlePlaceholder: "Titolo dell'articolo",
|
|
70
|
+
excerptPlaceholder: "Breve descrizione dell'articolo...",
|
|
71
|
+
slugPlaceholder: "slug-articolo",
|
|
72
|
+
authorPlaceholder: "Nome dell'autore",
|
|
73
|
+
category: "Categoria",
|
|
74
|
+
selectCategory: "Seleziona categoria",
|
|
75
|
+
excerpt: "Estratto",
|
|
76
|
+
author: "Autore",
|
|
77
|
+
slugUrl: "Slug URL",
|
|
78
|
+
draft: "Bozza",
|
|
79
|
+
published: "Pubblicato",
|
|
80
|
+
all: "Tutti",
|
|
81
|
+
drafts: "Bozze",
|
|
82
|
+
saving: "Salvando...",
|
|
83
|
+
saved: "Salvato",
|
|
84
|
+
saveError: "Errore nel salvataggio",
|
|
85
|
+
noArticles: "Nessun articolo.",
|
|
86
|
+
noArticlesSearch: "Nessun articolo trovato.",
|
|
87
|
+
noMedia: "Nessun file caricato.",
|
|
88
|
+
noMediaSearch: "Nessun file trovato.",
|
|
89
|
+
noRevisions: "Nessuna revisione salvata.",
|
|
90
|
+
loading: "Caricamento...",
|
|
91
|
+
confirm: "Conferma",
|
|
92
|
+
cancel: "Annulla",
|
|
93
|
+
apply: "Applica",
|
|
94
|
+
change: "Cambia",
|
|
95
|
+
logout: "Esci",
|
|
96
|
+
viewArticle: "Vedi articolo pubblicato",
|
|
97
|
+
preview: "Anteprima",
|
|
98
|
+
restore: "Ripristina",
|
|
99
|
+
restoreConfirm: "Ripristinare questa revisione? Lo stato attuale verr\xE0 salvato prima del ripristino.",
|
|
100
|
+
beforeRestore: "Prima del ripristino",
|
|
101
|
+
publishedNote: "Pubblicato",
|
|
102
|
+
deleteConfirm: (title) => `Eliminare "${title || "Senza titolo"}"?`,
|
|
103
|
+
unpublishConfirm: (title) => `Ritirare "${title || "Senza titolo"}"? L'articolo torner\xE0 in bozza.`,
|
|
104
|
+
deleteUserConfirm: (name) => `Sei sicuro di voler eliminare l'utente "${name}"?`,
|
|
105
|
+
totalArticles: (count) => `${count} articol${count === 1 ? "o" : "i"} totali`,
|
|
106
|
+
totalFiles: (count) => `${count} file caricati`,
|
|
107
|
+
coverImageLabel: "Immagine di copertina",
|
|
108
|
+
coverImageHint: "Scegli dalla libreria o carica un nuovo file",
|
|
109
|
+
chooseFile: "Scegli file",
|
|
110
|
+
dragHint: "Trascina un'immagine qui oppure",
|
|
111
|
+
uploadHint: "JPEG, PNG, WebP, GIF (max 5MB)",
|
|
112
|
+
mediaLibrary: "Libreria media",
|
|
113
|
+
uploadNew: "Carica nuovo",
|
|
114
|
+
searchFiles: "Cerca file...",
|
|
115
|
+
searchArticles: "Cerca articoli...",
|
|
116
|
+
selectImage: "Seleziona immagine",
|
|
117
|
+
noImages: "Nessuna immagine nella libreria.",
|
|
118
|
+
noImagesSearch: "Nessuna immagine trovata.",
|
|
119
|
+
copyUrl: "Copia URL",
|
|
120
|
+
editImage: "Modifica immagine",
|
|
121
|
+
restoreOriginal: "Ripristina originale",
|
|
122
|
+
freeAspect: "Libero",
|
|
123
|
+
unauthorized: "Non autorizzato",
|
|
124
|
+
notAuthenticated: "Non autenticato",
|
|
125
|
+
allFieldsRequired: "Tutti i campi sono obbligatori.",
|
|
126
|
+
passwordMinLength: "La password deve avere almeno 6 caratteri.",
|
|
127
|
+
emailExists: "Esiste gi\xE0 un utente con questa email.",
|
|
128
|
+
invalidRole: "Ruolo non valido.",
|
|
129
|
+
cannotDeleteSelf: "Non puoi eliminare il tuo stesso account.",
|
|
130
|
+
name: "Nome",
|
|
131
|
+
email: "Email",
|
|
132
|
+
password: "Password",
|
|
133
|
+
passwordEditHint: " (lascia vuoto per non modificarla)",
|
|
134
|
+
role: "Ruolo",
|
|
135
|
+
createdAt: "Creato il",
|
|
136
|
+
actions: "Azioni",
|
|
137
|
+
createUser: "Crea utente",
|
|
138
|
+
saveChanges: "Salva modifiche",
|
|
139
|
+
updated: "Aggiornato",
|
|
140
|
+
status: "Stato",
|
|
141
|
+
title: "Titolo",
|
|
142
|
+
untitled: "Senza titolo"
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
function deepMerge(target, source) {
|
|
146
|
+
const result = { ...target };
|
|
147
|
+
for (const key of Object.keys(source)) {
|
|
148
|
+
const sourceVal = source[key];
|
|
149
|
+
const targetVal = target[key];
|
|
150
|
+
if (sourceVal && typeof sourceVal === "object" && !Array.isArray(sourceVal) && typeof targetVal === "object" && !Array.isArray(targetVal) && typeof sourceVal !== "function") {
|
|
151
|
+
result[key] = deepMerge(
|
|
152
|
+
targetVal,
|
|
153
|
+
sourceVal
|
|
154
|
+
);
|
|
155
|
+
} else if (sourceVal !== void 0) {
|
|
156
|
+
result[key] = sourceVal;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
function defineConfig(overrides = {}) {
|
|
162
|
+
return deepMerge(DEFAULT_CONFIG, overrides);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/providers/ConfigContext.tsx
|
|
166
|
+
import { createContext, useContext } from "react";
|
|
167
|
+
import { jsx } from "react/jsx-runtime";
|
|
168
|
+
var ConfigContext = createContext(DEFAULT_CONFIG);
|
|
169
|
+
function useGavaConfig() {
|
|
170
|
+
return useContext(ConfigContext);
|
|
171
|
+
}
|
|
172
|
+
function ConfigProvider({
|
|
173
|
+
config,
|
|
174
|
+
children
|
|
175
|
+
}) {
|
|
176
|
+
return /* @__PURE__ */ jsx(ConfigContext.Provider, { value: config, children });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/providers/ActionsContext.tsx
|
|
180
|
+
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
181
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
182
|
+
var ActionsContext = createContext2(null);
|
|
183
|
+
function useGavaActions() {
|
|
184
|
+
const ctx = useContext2(ActionsContext);
|
|
185
|
+
if (!ctx) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
"useGavaActions must be used within a <GavaEngineProvider> with actions provided."
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
return ctx;
|
|
191
|
+
}
|
|
192
|
+
function ActionsProvider({
|
|
193
|
+
actions,
|
|
194
|
+
children
|
|
195
|
+
}) {
|
|
196
|
+
return /* @__PURE__ */ jsx2(ActionsContext.Provider, { value: actions, children });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/providers/SplashContext.tsx
|
|
200
|
+
import {
|
|
201
|
+
createContext as createContext3,
|
|
202
|
+
useContext as useContext3,
|
|
203
|
+
useState,
|
|
204
|
+
useCallback
|
|
205
|
+
} from "react";
|
|
206
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
207
|
+
var SplashContext = createContext3({
|
|
208
|
+
phase: "idle",
|
|
209
|
+
navigateWithSplash: () => {
|
|
210
|
+
},
|
|
211
|
+
openSplash: () => {
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
function useSplash() {
|
|
215
|
+
return useContext3(SplashContext);
|
|
216
|
+
}
|
|
217
|
+
function SplashProvider({
|
|
218
|
+
children,
|
|
219
|
+
navigate
|
|
220
|
+
}) {
|
|
221
|
+
const [phase, setPhase] = useState("idle");
|
|
222
|
+
const navigateWithSplash = useCallback(
|
|
223
|
+
(url) => {
|
|
224
|
+
if (phase !== "idle") return;
|
|
225
|
+
setPhase("closing");
|
|
226
|
+
setTimeout(() => {
|
|
227
|
+
setPhase("closed");
|
|
228
|
+
navigate(url);
|
|
229
|
+
}, 700);
|
|
230
|
+
},
|
|
231
|
+
[phase, navigate]
|
|
232
|
+
);
|
|
233
|
+
const openSplash = useCallback(() => {
|
|
234
|
+
if (phase !== "closed") return;
|
|
235
|
+
requestAnimationFrame(() => {
|
|
236
|
+
setPhase("opening");
|
|
237
|
+
setTimeout(() => setPhase("idle"), 900);
|
|
238
|
+
});
|
|
239
|
+
}, [phase]);
|
|
240
|
+
return /* @__PURE__ */ jsx3(SplashContext.Provider, { value: { phase, navigateWithSplash, openSplash }, children });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export {
|
|
244
|
+
DEFAULT_CONFIG,
|
|
245
|
+
defineConfig,
|
|
246
|
+
useGavaConfig,
|
|
247
|
+
ConfigProvider,
|
|
248
|
+
useGavaActions,
|
|
249
|
+
ActionsProvider,
|
|
250
|
+
useSplash,
|
|
251
|
+
SplashProvider
|
|
252
|
+
};
|
|
253
|
+
//# sourceMappingURL=chunk-GGD7I4JO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/providers/ConfigContext.tsx","../src/providers/ActionsContext.tsx","../src/providers/SplashContext.tsx"],"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","\"use client\";\n\nimport { createContext, useContext, type ReactNode } from \"react\";\nimport { type GavaEngineConfig, DEFAULT_CONFIG } from \"../config.js\";\n\nconst ConfigContext = createContext<GavaEngineConfig>(DEFAULT_CONFIG);\n\nexport function useGavaConfig() {\n return useContext(ConfigContext);\n}\n\nexport function ConfigProvider({\n config,\n children,\n}: {\n config: GavaEngineConfig;\n children: ReactNode;\n}) {\n return (\n <ConfigContext.Provider value={config}>{children}</ConfigContext.Provider>\n );\n}\n","\"use client\";\n\nimport { createContext, useContext, type ReactNode } from \"react\";\nimport type { GEActions } from \"../types.js\";\n\nconst ActionsContext = createContext<GEActions | null>(null);\n\nexport function useGavaActions(): GEActions {\n const ctx = useContext(ActionsContext);\n if (!ctx) {\n throw new Error(\n \"useGavaActions must be used within a <GavaEngineProvider> with actions provided.\"\n );\n }\n return ctx;\n}\n\nexport function ActionsProvider({\n actions,\n children,\n}: {\n actions: GEActions;\n children: ReactNode;\n}) {\n return (\n <ActionsContext.Provider value={actions}>{children}</ActionsContext.Provider>\n );\n}\n","\"use client\";\n\nimport {\n createContext,\n useContext,\n useState,\n useCallback,\n type ReactNode,\n} from \"react\";\n\ntype Phase = \"idle\" | \"closing\" | \"closed\" | \"opening\";\n\ninterface SplashContextValue {\n phase: Phase;\n navigateWithSplash: (url: string) => void;\n openSplash: () => void;\n}\n\nconst SplashContext = createContext<SplashContextValue>({\n phase: \"idle\",\n navigateWithSplash: () => {},\n openSplash: () => {},\n});\n\nexport function useSplash() {\n return useContext(SplashContext);\n}\n\nexport function SplashProvider({\n children,\n navigate,\n}: {\n children: ReactNode;\n navigate: (url: string) => void;\n}) {\n const [phase, setPhase] = useState<Phase>(\"idle\");\n\n const navigateWithSplash = useCallback(\n (url: string) => {\n if (phase !== \"idle\") return;\n setPhase(\"closing\");\n\n setTimeout(() => {\n setPhase(\"closed\");\n navigate(url);\n }, 700);\n },\n [phase, navigate]\n );\n\n const openSplash = useCallback(() => {\n if (phase !== \"closed\") return;\n requestAnimationFrame(() => {\n setPhase(\"opening\");\n setTimeout(() => setPhase(\"idle\"), 900);\n });\n }, [phase]);\n\n return (\n <SplashContext.Provider value={{ phase, navigateWithSplash, openSplash }}>\n {children}\n </SplashContext.Provider>\n );\n}\n"],"mappings":"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","names":["createContext","useContext","jsx","createContext","useContext","jsx"]}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
ActionsProvider,
|
|
4
|
+
ConfigProvider,
|
|
5
|
+
SplashProvider
|
|
6
|
+
} from "./chunk-GGD7I4JO.js";
|
|
7
|
+
|
|
8
|
+
// src/providers/GavaEngineProvider.tsx
|
|
9
|
+
import { useRouter } from "next/navigation";
|
|
10
|
+
|
|
11
|
+
// src/providers/SessionProvider.tsx
|
|
12
|
+
import { SessionProvider as NextAuthSessionProvider } from "next-auth/react";
|
|
13
|
+
import { jsx } from "react/jsx-runtime";
|
|
14
|
+
function SessionProvider({ children }) {
|
|
15
|
+
return /* @__PURE__ */ jsx(NextAuthSessionProvider, { children });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/providers/ThemeProvider.tsx
|
|
19
|
+
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
|
20
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
21
|
+
function ThemeProvider({ children }) {
|
|
22
|
+
return /* @__PURE__ */ jsx2(NextThemesProvider, { attribute: "class", defaultTheme: "system", enableSystem: true, children });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/providers/GavaEngineProvider.tsx
|
|
26
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
27
|
+
function GavaEngineProvider({
|
|
28
|
+
config,
|
|
29
|
+
actions,
|
|
30
|
+
children
|
|
31
|
+
}) {
|
|
32
|
+
const router = useRouter();
|
|
33
|
+
return /* @__PURE__ */ jsx3(SessionProvider, { children: /* @__PURE__ */ jsx3(ThemeProvider, { children: /* @__PURE__ */ jsx3(ConfigProvider, { config, children: /* @__PURE__ */ jsx3(ActionsProvider, { actions, children: /* @__PURE__ */ jsx3(SplashProvider, { navigate: (url) => router.push(url), children }) }) }) }) });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/providers/ThemeToggle.tsx
|
|
37
|
+
import { useTheme } from "next-themes";
|
|
38
|
+
import { useEffect, useState } from "react";
|
|
39
|
+
import { Sun, Moon } from "lucide-react";
|
|
40
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
41
|
+
function ThemeToggle() {
|
|
42
|
+
const [mounted, setMounted] = useState(false);
|
|
43
|
+
const { theme, setTheme } = useTheme();
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
setMounted(true);
|
|
46
|
+
}, []);
|
|
47
|
+
if (!mounted) {
|
|
48
|
+
return /* @__PURE__ */ jsx4("button", { className: "p-2 rounded-lg bg-card border border-card-border", children: /* @__PURE__ */ jsx4("div", { className: "w-5 h-5" }) });
|
|
49
|
+
}
|
|
50
|
+
return /* @__PURE__ */ jsx4(
|
|
51
|
+
"button",
|
|
52
|
+
{
|
|
53
|
+
onClick: () => setTheme(theme === "dark" ? "light" : "dark"),
|
|
54
|
+
className: "p-2 rounded-lg bg-card border border-card-border hover:bg-card-border transition-colors",
|
|
55
|
+
"aria-label": "Toggle theme",
|
|
56
|
+
children: theme === "dark" ? /* @__PURE__ */ jsx4(Sun, { className: "w-5 h-5 text-foreground" }) : /* @__PURE__ */ jsx4(Moon, { className: "w-5 h-5 text-foreground" })
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export {
|
|
62
|
+
SessionProvider,
|
|
63
|
+
ThemeProvider,
|
|
64
|
+
GavaEngineProvider,
|
|
65
|
+
ThemeToggle
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=chunk-KCQJHXZP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/providers/GavaEngineProvider.tsx","../src/providers/SessionProvider.tsx","../src/providers/ThemeProvider.tsx","../src/providers/ThemeToggle.tsx"],"sourcesContent":["\"use client\";\n\nimport { type ReactNode } from \"react\";\nimport { useRouter } from \"next/navigation\";\nimport { type GavaEngineConfig } from \"../config.js\";\nimport type { GEActions } from \"../types.js\";\nimport { ConfigProvider } from \"./ConfigContext.js\";\nimport { ActionsProvider } from \"./ActionsContext.js\";\nimport { SplashProvider } from \"./SplashContext.js\";\nimport { SessionProvider } from \"./SessionProvider.js\";\nimport { ThemeProvider } from \"./ThemeProvider.js\";\n\ninterface GavaEngineProviderProps {\n config: GavaEngineConfig;\n actions: GEActions;\n children: ReactNode;\n}\n\nexport function GavaEngineProvider({\n config,\n actions,\n children,\n}: GavaEngineProviderProps) {\n const router = useRouter();\n\n return (\n <SessionProvider>\n <ThemeProvider>\n <ConfigProvider config={config}>\n <ActionsProvider actions={actions}>\n <SplashProvider navigate={(url) => router.push(url)}>\n {children}\n </SplashProvider>\n </ActionsProvider>\n </ConfigProvider>\n </ThemeProvider>\n </SessionProvider>\n );\n}\n","\"use client\";\n\nimport { SessionProvider as NextAuthSessionProvider } from \"next-auth/react\";\nimport { type ReactNode } from \"react\";\n\nexport function SessionProvider({ children }: { children: ReactNode }) {\n return <NextAuthSessionProvider>{children}</NextAuthSessionProvider>;\n}\n","\"use client\";\n\nimport { ThemeProvider as NextThemesProvider } from \"next-themes\";\nimport { type ReactNode } from \"react\";\n\nexport function ThemeProvider({ children }: { children: ReactNode }) {\n return (\n <NextThemesProvider attribute=\"class\" defaultTheme=\"system\" enableSystem>\n {children}\n </NextThemesProvider>\n );\n}\n","\"use client\";\n\nimport { useTheme } from \"next-themes\";\nimport { useEffect, useState } from \"react\";\nimport { Sun, Moon } from \"lucide-react\";\n\nexport function ThemeToggle() {\n const [mounted, setMounted] = useState(false);\n const { theme, setTheme } = useTheme();\n\n useEffect(() => {\n setMounted(true);\n }, []);\n\n if (!mounted) {\n return (\n <button className=\"p-2 rounded-lg bg-card border border-card-border\">\n <div className=\"w-5 h-5\" />\n </button>\n );\n }\n\n return (\n <button\n onClick={() => setTheme(theme === \"dark\" ? \"light\" : \"dark\")}\n className=\"p-2 rounded-lg bg-card border border-card-border hover:bg-card-border transition-colors\"\n aria-label=\"Toggle theme\"\n >\n {theme === \"dark\" ? (\n <Sun className=\"w-5 h-5 text-foreground\" />\n ) : (\n <Moon className=\"w-5 h-5 text-foreground\" />\n )}\n </button>\n );\n}\n"],"mappings":";;;;;;;;AAGA,SAAS,iBAAiB;;;ACD1B,SAAS,mBAAmB,+BAA+B;AAIlD;AADF,SAAS,gBAAgB,EAAE,SAAS,GAA4B;AACrE,SAAO,oBAAC,2BAAyB,UAAS;AAC5C;;;ACLA,SAAS,iBAAiB,0BAA0B;AAKhD,gBAAAA,YAAA;AAFG,SAAS,cAAc,EAAE,SAAS,GAA4B;AACnE,SACE,gBAAAA,KAAC,sBAAmB,WAAU,SAAQ,cAAa,UAAS,cAAY,MACrE,UACH;AAEJ;;;AFmBY,gBAAAC,YAAA;AAZL,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AACF,GAA4B;AAC1B,QAAM,SAAS,UAAU;AAEzB,SACE,gBAAAA,KAAC,mBACC,0BAAAA,KAAC,iBACC,0BAAAA,KAAC,kBAAe,QACd,0BAAAA,KAAC,mBAAgB,SACf,0BAAAA,KAAC,kBAAe,UAAU,CAAC,QAAQ,OAAO,KAAK,GAAG,GAC/C,UACH,GACF,GACF,GACF,GACF;AAEJ;;;AGpCA,SAAS,gBAAgB;AACzB,SAAS,WAAW,gBAAgB;AACpC,SAAS,KAAK,YAAY;AAalB,gBAAAC,YAAA;AAXD,SAAS,cAAc;AAC5B,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,EAAE,OAAO,SAAS,IAAI,SAAS;AAErC,YAAU,MAAM;AACd,eAAW,IAAI;AAAA,EACjB,GAAG,CAAC,CAAC;AAEL,MAAI,CAAC,SAAS;AACZ,WACE,gBAAAA,KAAC,YAAO,WAAU,oDAChB,0BAAAA,KAAC,SAAI,WAAU,WAAU,GAC3B;AAAA,EAEJ;AAEA,SACE,gBAAAA;AAAA,IAAC;AAAA;AAAA,MACC,SAAS,MAAM,SAAS,UAAU,SAAS,UAAU,MAAM;AAAA,MAC3D,WAAU;AAAA,MACV,cAAW;AAAA,MAEV,oBAAU,SACT,gBAAAA,KAAC,OAAI,WAAU,2BAA0B,IAEzC,gBAAAA,KAAC,QAAK,WAAU,2BAA0B;AAAA;AAAA,EAE9C;AAEJ;","names":["jsx","jsx","jsx"]}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
#!/usr/bin/env node
|
|
3
|
+
|
|
4
|
+
// src/cli/index.ts
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
var TEMPLATES = {
|
|
8
|
+
"gavaengine.config.ts": `import { defineConfig } from "gavaengine";
|
|
9
|
+
|
|
10
|
+
export const geConfig = defineConfig({
|
|
11
|
+
branding: {
|
|
12
|
+
name: "My CMS",
|
|
13
|
+
// logo: "/images/logo.png",
|
|
14
|
+
splashWords: ["Powerful", "Flexible", "Modular", "Secure", "Customizable"],
|
|
15
|
+
},
|
|
16
|
+
// categories: ["News", "Blog", "Events"],
|
|
17
|
+
// routes: { ... },
|
|
18
|
+
// strings: { ... },
|
|
19
|
+
});
|
|
20
|
+
`,
|
|
21
|
+
"src/lib/gava-actions.ts": `"use server";
|
|
22
|
+
|
|
23
|
+
import { auth } from "@/lib/auth";
|
|
24
|
+
import { prisma } from "@/lib/prisma";
|
|
25
|
+
import { geConfig } from "@/gavaengine.config";
|
|
26
|
+
import { revalidatePath } from "next/cache";
|
|
27
|
+
import {
|
|
28
|
+
createArticleHandlers,
|
|
29
|
+
createMediaHandlers,
|
|
30
|
+
createRevisionHandlers,
|
|
31
|
+
createUserHandlers,
|
|
32
|
+
} from "gavaengine/handlers";
|
|
33
|
+
|
|
34
|
+
const articles = createArticleHandlers(prisma, geConfig);
|
|
35
|
+
const media = createMediaHandlers(prisma, geConfig);
|
|
36
|
+
const revisions = createRevisionHandlers(prisma, geConfig);
|
|
37
|
+
const users = createUserHandlers(prisma, geConfig);
|
|
38
|
+
|
|
39
|
+
async function requireAuth() {
|
|
40
|
+
const session = await auth();
|
|
41
|
+
if (!session) throw new Error("Not authenticated");
|
|
42
|
+
return session;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function requireRole(check: (role: string) => boolean) {
|
|
46
|
+
const session = await requireAuth();
|
|
47
|
+
if (!check(session.user.role)) throw new Error("Unauthorized");
|
|
48
|
+
return session;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// \u2500\u2500 Articles \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
52
|
+
export async function getArticles() {
|
|
53
|
+
await requireAuth();
|
|
54
|
+
return articles.getArticles();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function getArticleById(id: string) {
|
|
58
|
+
await requireAuth();
|
|
59
|
+
return articles.getArticleById(id);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function createArticle() {
|
|
63
|
+
const session = await requireRole(geConfig.roles.canEdit);
|
|
64
|
+
return articles.createArticle(session.user.name ?? "");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function updateArticle(
|
|
68
|
+
id: string,
|
|
69
|
+
data: {
|
|
70
|
+
title?: string;
|
|
71
|
+
slug?: string;
|
|
72
|
+
excerpt?: string;
|
|
73
|
+
content?: string;
|
|
74
|
+
coverImage?: string;
|
|
75
|
+
category?: string;
|
|
76
|
+
authorName?: string;
|
|
77
|
+
}
|
|
78
|
+
) {
|
|
79
|
+
const session = await requireRole(geConfig.roles.canEdit);
|
|
80
|
+
const result = await articles.updateArticle(session.user.id, id, data);
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function deleteArticle(id: string) {
|
|
85
|
+
await requireRole(geConfig.roles.canEdit);
|
|
86
|
+
const result = await articles.deleteArticle(id);
|
|
87
|
+
revalidatePath(geConfig.routes.articles);
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function publishArticle(id: string) {
|
|
92
|
+
const session = await requireRole(geConfig.roles.canPublish);
|
|
93
|
+
const result = await articles.publishArticle(session.user.id, id);
|
|
94
|
+
revalidatePath(geConfig.routes.articles);
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function unpublishArticle(id: string) {
|
|
99
|
+
await requireRole(geConfig.roles.canPublish);
|
|
100
|
+
const result = await articles.unpublishArticle(id);
|
|
101
|
+
revalidatePath(geConfig.routes.articles);
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// \u2500\u2500 Media \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
106
|
+
export async function getMedia(search?: string) {
|
|
107
|
+
await requireAuth();
|
|
108
|
+
return media.getMedia(search);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function deleteMedia(id: string) {
|
|
112
|
+
const session = await requireRole(geConfig.roles.canEdit);
|
|
113
|
+
const result = await media.deleteMedia(session.user.id, session.user.role, id);
|
|
114
|
+
revalidatePath(geConfig.routes.media);
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// \u2500\u2500 Revisions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
119
|
+
export async function getRevisions(articleId: string) {
|
|
120
|
+
await requireAuth();
|
|
121
|
+
return revisions.getRevisions(articleId);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function restoreRevision(articleId: string, revisionId: string) {
|
|
125
|
+
const session = await requireRole(geConfig.roles.canEdit);
|
|
126
|
+
return revisions.restoreRevision(articleId, revisionId, session.user.id);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// \u2500\u2500 Users \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
130
|
+
export async function getUsers() {
|
|
131
|
+
const session = await requireAuth();
|
|
132
|
+
if (session.user.role !== geConfig.roles.adminRole) throw new Error("Unauthorized");
|
|
133
|
+
return users.getUsers();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function getUserById(id: string) {
|
|
137
|
+
const session = await requireAuth();
|
|
138
|
+
if (session.user.role !== geConfig.roles.adminRole) throw new Error("Unauthorized");
|
|
139
|
+
return users.getUserById(id);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function createUser(formData: FormData) {
|
|
143
|
+
const session = await requireAuth();
|
|
144
|
+
if (session.user.role !== geConfig.roles.adminRole) throw new Error("Unauthorized");
|
|
145
|
+
return users.createUser(formData);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export async function updateUser(id: string, formData: FormData) {
|
|
149
|
+
const session = await requireAuth();
|
|
150
|
+
if (session.user.role !== geConfig.roles.adminRole) throw new Error("Unauthorized");
|
|
151
|
+
return users.updateUser(id, formData);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function deleteUser(id: string) {
|
|
155
|
+
const session = await requireAuth();
|
|
156
|
+
if (session.user.role !== geConfig.roles.adminRole) throw new Error("Unauthorized");
|
|
157
|
+
return users.deleteUser(session.user.id, id);
|
|
158
|
+
}
|
|
159
|
+
`,
|
|
160
|
+
"src/app/api/upload/route.ts": `import { NextRequest, NextResponse } from "next/server";
|
|
161
|
+
import { auth } from "@/lib/auth";
|
|
162
|
+
import { prisma } from "@/lib/prisma";
|
|
163
|
+
import { geConfig } from "@/gavaengine.config";
|
|
164
|
+
import { createUploadHandler } from "gavaengine/handlers";
|
|
165
|
+
import path from "path";
|
|
166
|
+
import fs from "fs/promises";
|
|
167
|
+
import crypto from "crypto";
|
|
168
|
+
|
|
169
|
+
const uploadHandler = createUploadHandler(prisma, geConfig, {
|
|
170
|
+
async save(filename: string, buffer: Buffer, mimeType: string) {
|
|
171
|
+
const ext = path.extname(filename) || "." + mimeType.split("/")[1];
|
|
172
|
+
const uniqueName = crypto.randomUUID() + ext;
|
|
173
|
+
const uploadDir = path.join(process.cwd(), "public", "images", "articles");
|
|
174
|
+
await fs.mkdir(uploadDir, { recursive: true });
|
|
175
|
+
await fs.writeFile(path.join(uploadDir, uniqueName), buffer);
|
|
176
|
+
return "/images/articles/" + uniqueName;
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
export async function POST(request: NextRequest) {
|
|
181
|
+
const session = await auth();
|
|
182
|
+
if (!session || !geConfig.roles.canEdit(session.user.role)) {
|
|
183
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const formData = await request.formData();
|
|
187
|
+
const file = formData.get("file") as File | null;
|
|
188
|
+
if (!file) {
|
|
189
|
+
return NextResponse.json({ error: "No file uploaded" }, { status: 400 });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const result = await uploadHandler.handleUpload(file, session.user.id);
|
|
193
|
+
if (result.error) {
|
|
194
|
+
return NextResponse.json({ error: result.error }, { status: 400 });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return NextResponse.json({ url: result.url });
|
|
198
|
+
}
|
|
199
|
+
`
|
|
200
|
+
};
|
|
201
|
+
function main() {
|
|
202
|
+
const args = process.argv.slice(2);
|
|
203
|
+
const command = args[0];
|
|
204
|
+
if (command !== "init") {
|
|
205
|
+
console.log("Usage: npx gavaengine init");
|
|
206
|
+
console.log("");
|
|
207
|
+
console.log("Commands:");
|
|
208
|
+
console.log(" init Scaffold GavaEngine files in your project");
|
|
209
|
+
process.exit(0);
|
|
210
|
+
}
|
|
211
|
+
const cwd = process.cwd();
|
|
212
|
+
console.log("");
|
|
213
|
+
console.log(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
|
|
214
|
+
console.log(" \u2551 GAVA ENGINE init \u2551");
|
|
215
|
+
console.log(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
|
|
216
|
+
console.log("");
|
|
217
|
+
let created = 0;
|
|
218
|
+
let skipped = 0;
|
|
219
|
+
for (const [relativePath, content] of Object.entries(TEMPLATES)) {
|
|
220
|
+
const fullPath = path.join(cwd, relativePath);
|
|
221
|
+
const dir = path.dirname(fullPath);
|
|
222
|
+
if (fs.existsSync(fullPath)) {
|
|
223
|
+
console.log(` skip ${relativePath} (already exists)`);
|
|
224
|
+
skipped++;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
228
|
+
fs.writeFileSync(fullPath, content, "utf-8");
|
|
229
|
+
console.log(` create ${relativePath}`);
|
|
230
|
+
created++;
|
|
231
|
+
}
|
|
232
|
+
console.log("");
|
|
233
|
+
console.log(` Done! ${created} files created, ${skipped} skipped.`);
|
|
234
|
+
console.log("");
|
|
235
|
+
console.log(" Next steps:");
|
|
236
|
+
console.log(" 1. Copy models from node_modules/gavaengine/src/prisma/schema.partial.prisma into your schema");
|
|
237
|
+
console.log(" 2. Run prisma migrate dev to create tables");
|
|
238
|
+
console.log(" 3. Import 'gavaengine/styles' in your layout");
|
|
239
|
+
console.log(" 4. Wrap your dashboard with <GavaEngineProvider>");
|
|
240
|
+
console.log("");
|
|
241
|
+
}
|
|
242
|
+
main();
|