@lastbrain/app 0.1.8 → 0.1.9
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/scripts/init-app.js +0 -2
- package/package.json +3 -2
- package/src/app-shell/(admin)/layout.tsx +13 -0
- package/src/app-shell/(auth)/layout.tsx +13 -0
- package/src/app-shell/(public)/page.tsx +11 -0
- package/src/app-shell/layout.tsx +5 -0
- package/src/app-shell/not-found.tsx +28 -0
- package/src/auth/authHelpers.ts +24 -0
- package/src/auth/useAuthSession.ts +54 -0
- package/src/cli.ts +96 -0
- package/src/index.ts +21 -0
- package/src/layouts/AdminLayout.tsx +7 -0
- package/src/layouts/AppProviders.tsx +61 -0
- package/src/layouts/AuthLayout.tsx +7 -0
- package/src/layouts/PublicLayout.tsx +7 -0
- package/src/layouts/RootLayout.tsx +27 -0
- package/src/modules/module-loader.ts +14 -0
- package/src/scripts/README.md +262 -0
- package/src/scripts/db-init.ts +338 -0
- package/src/scripts/db-migrations-sync.ts +86 -0
- package/src/scripts/dev-sync.ts +218 -0
- package/src/scripts/init-app.ts +1077 -0
- package/src/scripts/module-add.ts +242 -0
- package/src/scripts/module-build.ts +502 -0
- package/src/scripts/module-create.ts +809 -0
- package/src/scripts/module-list.ts +37 -0
- package/src/scripts/module-remove.ts +367 -0
- package/src/scripts/readme-build.ts +60 -0
- package/src/styles.css +3 -0
- package/src/templates/AuthGuidePage.tsx +68 -0
- package/src/templates/DefaultDoc.tsx +462 -0
- package/src/templates/DocPage.tsx +381 -0
- package/src/templates/DocsPageWithModules.tsx +22 -0
- package/src/templates/MigrationsGuidePage.tsx +61 -0
- package/src/templates/ModuleGuidePage.tsx +71 -0
- package/src/templates/SimpleDocPage.tsx +587 -0
- package/src/templates/SimpleHomePage.tsx +385 -0
- package/src/templates/env.example/.env.example +6 -0
- package/src/templates/migrations/20201010100000_app_base.sql +228 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
// SIMPLE VERSION FOR DEBUGGING
|
|
2
|
+
import React from "react";
|
|
3
|
+
import NextLink from "next/link";
|
|
4
|
+
import {
|
|
5
|
+
Button,
|
|
6
|
+
Card,
|
|
7
|
+
CardBody,
|
|
8
|
+
CardHeader,
|
|
9
|
+
Chip,
|
|
10
|
+
Snippet,
|
|
11
|
+
} from "@lastbrain/ui";
|
|
12
|
+
import {
|
|
13
|
+
Rocket,
|
|
14
|
+
Package,
|
|
15
|
+
Database,
|
|
16
|
+
Code,
|
|
17
|
+
Zap,
|
|
18
|
+
Shield,
|
|
19
|
+
ArrowRight,
|
|
20
|
+
BookOpen,
|
|
21
|
+
} from "lucide-react";
|
|
22
|
+
|
|
23
|
+
interface HomePageProps {
|
|
24
|
+
showAuth?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function SimpleHomePage({ showAuth }: HomePageProps) {
|
|
28
|
+
return (
|
|
29
|
+
<div className="min-h-screen">
|
|
30
|
+
{/* Hero Section */}
|
|
31
|
+
<section className="relative overflow-hidden bg-linear-to-br from-blue-50 via-purple-50 to-pink-50 dark:from-slate-900 dark:via-purple-900/20 dark:to-blue-900/20">
|
|
32
|
+
<div className="absolute inset-0 bg-grid-slate-200/50 dark:bg-grid-slate-700/25 mask-[radial-gradient(ellipse_at_center,transparent_20%,black)]"></div>
|
|
33
|
+
|
|
34
|
+
<div className="relative container mx-auto px-4 py-24 md:py-32">
|
|
35
|
+
<div className="max-w-4xl mx-auto text-center">
|
|
36
|
+
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 mb-8">
|
|
37
|
+
<Zap size={16} />
|
|
38
|
+
<span className="text-sm font-medium">
|
|
39
|
+
Framework modulaire Next.js
|
|
40
|
+
</span>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<h1 className="text-5xl md:text-7xl font-bold mb-6 bg-linear-to-r from-blue-600 via-purple-600 to-pink-600 bg-clip-text text-transparent">
|
|
44
|
+
Bienvenue sur LastBrain
|
|
45
|
+
</h1>
|
|
46
|
+
|
|
47
|
+
<p className="text-xl md:text-2xl text-slate-600 dark:text-slate-300 mb-12 max-w-2xl mx-auto">
|
|
48
|
+
Créez des applications modernes avec une architecture modulaire,
|
|
49
|
+
puissante et évolutive.
|
|
50
|
+
</p>
|
|
51
|
+
|
|
52
|
+
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
|
53
|
+
<NextLink href="/docs">
|
|
54
|
+
<Button
|
|
55
|
+
size="lg"
|
|
56
|
+
color="primary"
|
|
57
|
+
endContent={<ArrowRight size={20} />}
|
|
58
|
+
className="text-base font-medium"
|
|
59
|
+
>
|
|
60
|
+
Voir la documentation
|
|
61
|
+
</Button>
|
|
62
|
+
</NextLink>
|
|
63
|
+
{showAuth && (
|
|
64
|
+
<NextLink href="/signup">
|
|
65
|
+
<Button
|
|
66
|
+
size="lg"
|
|
67
|
+
variant="bordered"
|
|
68
|
+
className="text-base font-medium"
|
|
69
|
+
>
|
|
70
|
+
Créer un compte
|
|
71
|
+
</Button>
|
|
72
|
+
</NextLink>
|
|
73
|
+
)}
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</section>
|
|
78
|
+
|
|
79
|
+
{/* Features Section */}
|
|
80
|
+
<section className="py-20 bg-white dark:bg-slate-950">
|
|
81
|
+
<div className="container mx-auto px-4">
|
|
82
|
+
<div className="max-w-6xl mx-auto">
|
|
83
|
+
<div className="text-center mb-16">
|
|
84
|
+
<h2 className="text-3xl md:text-4xl font-bold mb-4">
|
|
85
|
+
Tout ce dont vous avez besoin
|
|
86
|
+
</h2>
|
|
87
|
+
<p className="text-lg text-slate-600 dark:text-slate-400">
|
|
88
|
+
Une stack moderne pour démarrer rapidement
|
|
89
|
+
</p>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
|
93
|
+
<Card className="hover:shadow-xl transition-shadow">
|
|
94
|
+
<CardHeader className="pb-2">
|
|
95
|
+
<div className="flex items-center gap-3">
|
|
96
|
+
<div className="p-2 rounded-lg bg-blue-100 dark:bg-blue-900/30">
|
|
97
|
+
<Package
|
|
98
|
+
className="text-blue-600 dark:text-blue-400"
|
|
99
|
+
size={24}
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
<h3 className="text-xl font-semibold">Modulaire</h3>
|
|
103
|
+
</div>
|
|
104
|
+
</CardHeader>
|
|
105
|
+
<CardBody className="pt-2">
|
|
106
|
+
<p className="text-slate-600 dark:text-slate-400">
|
|
107
|
+
Architecture basée sur des modules réutilisables. Ajoutez ou
|
|
108
|
+
retirez des fonctionnalités en une commande.
|
|
109
|
+
</p>
|
|
110
|
+
</CardBody>
|
|
111
|
+
</Card>
|
|
112
|
+
|
|
113
|
+
<Card className="hover:shadow-xl transition-shadow">
|
|
114
|
+
<CardHeader className="pb-2">
|
|
115
|
+
<div className="flex items-center gap-3">
|
|
116
|
+
<div className="p-2 rounded-lg bg-purple-100 dark:bg-purple-900/30">
|
|
117
|
+
<Database
|
|
118
|
+
className="text-purple-600 dark:text-purple-400"
|
|
119
|
+
size={24}
|
|
120
|
+
/>
|
|
121
|
+
</div>
|
|
122
|
+
<h3 className="text-xl font-semibold">Supabase</h3>
|
|
123
|
+
</div>
|
|
124
|
+
</CardHeader>
|
|
125
|
+
<CardBody className="pt-2">
|
|
126
|
+
<p className="text-slate-600 dark:text-slate-400">
|
|
127
|
+
Base de données PostgreSQL, authentification et migrations
|
|
128
|
+
automatiques. Tout en local pour le développement.
|
|
129
|
+
</p>
|
|
130
|
+
</CardBody>
|
|
131
|
+
</Card>
|
|
132
|
+
|
|
133
|
+
<Card className="hover:shadow-xl transition-shadow">
|
|
134
|
+
<CardHeader className="pb-2">
|
|
135
|
+
<div className="flex items-center gap-3">
|
|
136
|
+
<div className="p-2 rounded-lg bg-green-100 dark:bg-green-900/30">
|
|
137
|
+
<Zap
|
|
138
|
+
className="text-green-600 dark:text-green-400"
|
|
139
|
+
size={24}
|
|
140
|
+
/>
|
|
141
|
+
</div>
|
|
142
|
+
<h3 className="text-xl font-semibold">Rapide</h3>
|
|
143
|
+
</div>
|
|
144
|
+
</CardHeader>
|
|
145
|
+
<CardBody className="pt-2">
|
|
146
|
+
<p className="text-slate-600 dark:text-slate-400">
|
|
147
|
+
Next.js 15, React 19, Tailwind CSS v4. Hot reload et
|
|
148
|
+
génération automatique des routes.
|
|
149
|
+
</p>
|
|
150
|
+
</CardBody>
|
|
151
|
+
</Card>
|
|
152
|
+
|
|
153
|
+
<Card className="hover:shadow-xl transition-shadow">
|
|
154
|
+
<CardHeader className="pb-2">
|
|
155
|
+
<div className="flex items-center gap-3">
|
|
156
|
+
<div className="p-2 rounded-lg bg-orange-100 dark:bg-orange-900/30">
|
|
157
|
+
<Code
|
|
158
|
+
className="text-orange-600 dark:text-orange-400"
|
|
159
|
+
size={24}
|
|
160
|
+
/>
|
|
161
|
+
</div>
|
|
162
|
+
<h3 className="text-xl font-semibold">TypeScript</h3>
|
|
163
|
+
</div>
|
|
164
|
+
</CardHeader>
|
|
165
|
+
<CardBody className="pt-2">
|
|
166
|
+
<p className="text-slate-600 dark:text-slate-400">
|
|
167
|
+
100% TypeScript avec types partagés entre frontend et
|
|
168
|
+
backend. Monorepo pnpm pour une gestion optimale.
|
|
169
|
+
</p>
|
|
170
|
+
</CardBody>
|
|
171
|
+
</Card>
|
|
172
|
+
|
|
173
|
+
<Card className="hover:shadow-xl transition-shadow">
|
|
174
|
+
<CardHeader className="pb-2">
|
|
175
|
+
<div className="flex items-center gap-3">
|
|
176
|
+
<div className="p-2 rounded-lg bg-pink-100 dark:bg-pink-900/30">
|
|
177
|
+
<Shield
|
|
178
|
+
className="text-pink-600 dark:text-pink-400"
|
|
179
|
+
size={24}
|
|
180
|
+
/>
|
|
181
|
+
</div>
|
|
182
|
+
<h3 className="text-xl font-semibold">Sécurisé</h3>
|
|
183
|
+
</div>
|
|
184
|
+
</CardHeader>
|
|
185
|
+
<CardBody className="pt-2">
|
|
186
|
+
<p className="text-slate-600 dark:text-slate-400">
|
|
187
|
+
Middleware d'authentification automatique. Routes protégées
|
|
188
|
+
pour /auth et /admin configurées par défaut.
|
|
189
|
+
</p>
|
|
190
|
+
</CardBody>
|
|
191
|
+
</Card>
|
|
192
|
+
|
|
193
|
+
<Card className="hover:shadow-xl transition-shadow">
|
|
194
|
+
<CardHeader className="pb-2">
|
|
195
|
+
<div className="flex items-center gap-3">
|
|
196
|
+
<div className="p-2 rounded-lg bg-cyan-100 dark:bg-cyan-900/30">
|
|
197
|
+
<BookOpen
|
|
198
|
+
className="text-cyan-600 dark:text-cyan-400"
|
|
199
|
+
size={24}
|
|
200
|
+
/>
|
|
201
|
+
</div>
|
|
202
|
+
<h3 className="text-xl font-semibold">Documenté</h3>
|
|
203
|
+
</div>
|
|
204
|
+
</CardHeader>
|
|
205
|
+
<CardBody className="pt-2">
|
|
206
|
+
<p className="text-slate-600 dark:text-slate-400">
|
|
207
|
+
Documentation complète et interactive. Chaque module
|
|
208
|
+
documente automatiquement ses pages et APIs.
|
|
209
|
+
</p>
|
|
210
|
+
</CardBody>
|
|
211
|
+
</Card>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
</section>
|
|
216
|
+
|
|
217
|
+
{/* Quick Start Section */}
|
|
218
|
+
<section className="py-20 bg-slate-50 dark:bg-slate-900/50">
|
|
219
|
+
<div className="container mx-auto px-4">
|
|
220
|
+
<div className="max-w-4xl mx-auto">
|
|
221
|
+
<div className="text-center mb-12">
|
|
222
|
+
<div className="inline-flex items-center gap-2 mb-4">
|
|
223
|
+
<Rocket size={24} className="text-blue-600" />
|
|
224
|
+
<h2 className="text-3xl md:text-4xl font-bold">
|
|
225
|
+
Démarrez en 4 étapes
|
|
226
|
+
</h2>
|
|
227
|
+
</div>
|
|
228
|
+
<p className="text-lg text-slate-600 dark:text-slate-400">
|
|
229
|
+
Votre application prête en quelques minutes
|
|
230
|
+
</p>
|
|
231
|
+
</div>
|
|
232
|
+
|
|
233
|
+
<div className="space-y-6">
|
|
234
|
+
<Card className="border-l-4 border-l-blue-500">
|
|
235
|
+
<CardBody>
|
|
236
|
+
<div className="flex items-start gap-4">
|
|
237
|
+
<div className="shrink-0 w-8 h-8 rounded-full bg-blue-100 dark:bg-blue-900/30 flex items-center justify-center font-bold text-blue-600 dark:text-blue-400">
|
|
238
|
+
1
|
|
239
|
+
</div>
|
|
240
|
+
<div className="flex-1">
|
|
241
|
+
<h3 className="text-lg font-semibold mb-2">
|
|
242
|
+
Créez votre application
|
|
243
|
+
</h3>
|
|
244
|
+
<Snippet className="w-full">
|
|
245
|
+
pnpx @lastbrain/create-app mon-app
|
|
246
|
+
</Snippet>
|
|
247
|
+
<p className="text-sm text-slate-600 dark:text-slate-400 mt-2">
|
|
248
|
+
L'assistant interactif vous guidera pour :
|
|
249
|
+
</p>
|
|
250
|
+
<ul className="text-sm text-slate-600 dark:text-slate-400 mt-2 ml-4 space-y-1">
|
|
251
|
+
<li>• Choisir les modules à installer (auth, ai...)</li>
|
|
252
|
+
<li>• Créer la structure complète du projet</li>
|
|
253
|
+
<li>• Installer automatiquement les dépendances</li>
|
|
254
|
+
<li>• Générer les routes des modules</li>
|
|
255
|
+
</ul>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
</CardBody>
|
|
259
|
+
</Card>
|
|
260
|
+
|
|
261
|
+
<Card className="border-l-4 border-l-purple-500">
|
|
262
|
+
<CardBody>
|
|
263
|
+
<div className="flex items-start gap-4">
|
|
264
|
+
<div className="shrink-0 w-8 h-8 rounded-full bg-purple-100 dark:bg-purple-900/30 flex items-center justify-center font-bold text-purple-600 dark:text-purple-400">
|
|
265
|
+
2
|
|
266
|
+
</div>
|
|
267
|
+
<div className="flex-1">
|
|
268
|
+
<h3 className="text-lg font-semibold mb-2">
|
|
269
|
+
Accédez au dossier
|
|
270
|
+
</h3>
|
|
271
|
+
<Snippet className="w-full">cd mon-app</Snippet>
|
|
272
|
+
<p className="text-sm text-slate-600 dark:text-slate-400 mt-2">
|
|
273
|
+
Entrez dans le dossier de votre nouvelle application
|
|
274
|
+
</p>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
</CardBody>
|
|
278
|
+
</Card>
|
|
279
|
+
|
|
280
|
+
<Card className="border-l-4 border-l-orange-500">
|
|
281
|
+
<CardBody>
|
|
282
|
+
<div className="flex items-start gap-4">
|
|
283
|
+
<div className="shrink-0 w-8 h-8 rounded-full bg-orange-100 dark:bg-orange-900/30 flex items-center justify-center font-bold text-orange-600 dark:text-orange-400">
|
|
284
|
+
3
|
|
285
|
+
</div>
|
|
286
|
+
<div className="flex-1">
|
|
287
|
+
<h3 className="text-lg font-semibold mb-2">
|
|
288
|
+
Initialisez Supabase
|
|
289
|
+
</h3>
|
|
290
|
+
<Snippet className="w-full">pnpm db:init</Snippet>
|
|
291
|
+
<p className="text-sm text-slate-600 dark:text-slate-400 mt-2">
|
|
292
|
+
Démarre Supabase en local et applique toutes les
|
|
293
|
+
migrations (base + modules)
|
|
294
|
+
</p>
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
</CardBody>
|
|
298
|
+
</Card>
|
|
299
|
+
|
|
300
|
+
<Card className="border-l-4 border-l-green-500">
|
|
301
|
+
<CardBody>
|
|
302
|
+
<div className="flex items-start gap-4">
|
|
303
|
+
<div className="shrink-0 w-8 h-8 rounded-full bg-green-100 dark:bg-green-900/30 flex items-center justify-center font-bold text-green-600 dark:text-green-400">
|
|
304
|
+
4
|
|
305
|
+
</div>
|
|
306
|
+
<div className="flex-1">
|
|
307
|
+
<h3 className="text-lg font-semibold mb-2">
|
|
308
|
+
Lancez le serveur
|
|
309
|
+
</h3>
|
|
310
|
+
<Snippet className="w-full">pnpm dev</Snippet>
|
|
311
|
+
<p className="text-sm text-slate-600 dark:text-slate-400 mt-2">
|
|
312
|
+
Ouvre l'application sur{" "}
|
|
313
|
+
<strong>http://localhost:3000</strong>
|
|
314
|
+
</p>
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
</CardBody>
|
|
318
|
+
</Card>
|
|
319
|
+
|
|
320
|
+
{/* Info supplémentaire */}
|
|
321
|
+
<Card className="bg-blue-50 dark:bg-blue-950/30 border-blue-200 dark:border-blue-800">
|
|
322
|
+
<CardBody>
|
|
323
|
+
<div className="flex items-start gap-3">
|
|
324
|
+
<div className="p-2 rounded-lg bg-blue-100 dark:bg-blue-900/30 shrink-0">
|
|
325
|
+
<Package
|
|
326
|
+
className="text-blue-600 dark:text-blue-400"
|
|
327
|
+
size={20}
|
|
328
|
+
/>
|
|
329
|
+
</div>
|
|
330
|
+
<div>
|
|
331
|
+
<h4 className="font-semibold mb-2 text-blue-900 dark:text-blue-100">
|
|
332
|
+
Ajouter des modules plus tard
|
|
333
|
+
</h4>
|
|
334
|
+
<div className="space-y-2">
|
|
335
|
+
<Snippet className="w-full" size="sm">
|
|
336
|
+
pnpm lastbrain add-module auth
|
|
337
|
+
</Snippet>
|
|
338
|
+
<Snippet className="w-full" size="sm">
|
|
339
|
+
pnpm build:modules
|
|
340
|
+
</Snippet>
|
|
341
|
+
</div>
|
|
342
|
+
<div className="text-sm text-blue-700 dark:text-blue-300 mt-2">
|
|
343
|
+
Modules disponibles :{" "}
|
|
344
|
+
<Chip size="sm" color="primary" variant="flat">
|
|
345
|
+
auth
|
|
346
|
+
</Chip>{" "}
|
|
347
|
+
<Chip size="sm" color="primary" variant="flat">
|
|
348
|
+
ai
|
|
349
|
+
</Chip>
|
|
350
|
+
</div>
|
|
351
|
+
</div>
|
|
352
|
+
</div>
|
|
353
|
+
</CardBody>
|
|
354
|
+
</Card>
|
|
355
|
+
</div>
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
</section>
|
|
359
|
+
|
|
360
|
+
{/* CTA Section */}
|
|
361
|
+
<section className="py-20 bg-linear-to-r from-blue-600 to-purple-600 text-white">
|
|
362
|
+
<div className="container mx-auto px-4">
|
|
363
|
+
<div className="max-w-3xl mx-auto text-center">
|
|
364
|
+
<h2 className="text-3xl md:text-4xl font-bold mb-4">
|
|
365
|
+
Prêt à commencer ?
|
|
366
|
+
</h2>
|
|
367
|
+
<p className="text-lg text-blue-100 mb-8">
|
|
368
|
+
Explorez la documentation complète pour découvrir toutes les
|
|
369
|
+
fonctionnalités
|
|
370
|
+
</p>
|
|
371
|
+
<NextLink href="/docs">
|
|
372
|
+
<Button
|
|
373
|
+
size="lg"
|
|
374
|
+
className="bg-white text-blue-600 hover:bg-blue-50 font-medium"
|
|
375
|
+
endContent={<BookOpen size={20} />}
|
|
376
|
+
>
|
|
377
|
+
Consulter la documentation
|
|
378
|
+
</Button>
|
|
379
|
+
</NextLink>
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
</section>
|
|
383
|
+
</div>
|
|
384
|
+
);
|
|
385
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
|
|
2
|
+
-- ===========================================================================
|
|
3
|
+
-- Helper: is_superadmin (if not already present)
|
|
4
|
+
-- ===========================================================================
|
|
5
|
+
CREATE OR REPLACE FUNCTION public.is_superadmin(user_id uuid)
|
|
6
|
+
RETURNS boolean
|
|
7
|
+
LANGUAGE sql
|
|
8
|
+
STABLE
|
|
9
|
+
SECURITY DEFINER
|
|
10
|
+
SET search_path = public, auth
|
|
11
|
+
AS $function$
|
|
12
|
+
SELECT COALESCE((
|
|
13
|
+
SELECT
|
|
14
|
+
COALESCE((raw_app_meta_data ->> 'is_super_admin')::boolean, FALSE)
|
|
15
|
+
OR COALESCE((raw_user_meta_data ->> 'is_super_admin')::boolean, FALSE)
|
|
16
|
+
OR COALESCE((raw_app_meta_data -> 'roles') ? 'admin', FALSE)
|
|
17
|
+
OR COALESCE((raw_user_meta_data -> 'roles') ? 'admin', FALSE)
|
|
18
|
+
FROM auth.users
|
|
19
|
+
WHERE id = user_id
|
|
20
|
+
), FALSE);
|
|
21
|
+
$function$;
|
|
22
|
+
|
|
23
|
+
REVOKE ALL ON FUNCTION public.is_superadmin(uuid) FROM PUBLIC;
|
|
24
|
+
GRANT EXECUTE ON FUNCTION public.is_superadmin(uuid) TO authenticated, service_role, anon;
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
-- ===========================================================================
|
|
29
|
+
-- Storage Buckets (for avatars and file storage)
|
|
30
|
+
-- ===========================================================================
|
|
31
|
+
INSERT INTO storage.buckets (id, name, public)
|
|
32
|
+
VALUES ('avatar', 'avatar', TRUE)
|
|
33
|
+
ON CONFLICT (id) DO NOTHING;
|
|
34
|
+
|
|
35
|
+
INSERT INTO storage.buckets (id, name, public)
|
|
36
|
+
VALUES ('app', 'app', FALSE)
|
|
37
|
+
ON CONFLICT (id) DO NOTHING;
|
|
38
|
+
|
|
39
|
+
-- ===========================================================================
|
|
40
|
+
-- Avatar bucket policies
|
|
41
|
+
-- ===========================================================================
|
|
42
|
+
DROP POLICY IF EXISTS avatar_public_read ON storage.objects;
|
|
43
|
+
CREATE POLICY avatar_public_read ON storage.objects
|
|
44
|
+
FOR SELECT USING (bucket_id = 'avatar');
|
|
45
|
+
|
|
46
|
+
DROP POLICY IF EXISTS avatar_insert_owner ON storage.objects;
|
|
47
|
+
CREATE POLICY avatar_insert_owner ON storage.objects
|
|
48
|
+
FOR INSERT WITH CHECK (
|
|
49
|
+
bucket_id = 'avatar' AND (
|
|
50
|
+
owner = auth.uid() OR public.is_superadmin(auth.uid())
|
|
51
|
+
)
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
DROP POLICY IF EXISTS avatar_manage_owner ON storage.objects;
|
|
55
|
+
CREATE POLICY avatar_manage_owner ON storage.objects
|
|
56
|
+
FOR UPDATE USING (
|
|
57
|
+
bucket_id = 'avatar' AND (
|
|
58
|
+
owner = auth.uid() OR public.is_superadmin(auth.uid())
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
WITH CHECK (
|
|
62
|
+
bucket_id = 'avatar' AND (
|
|
63
|
+
owner = auth.uid() OR public.is_superadmin(auth.uid())
|
|
64
|
+
)
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
DROP POLICY IF EXISTS avatar_delete_owner ON storage.objects;
|
|
68
|
+
CREATE POLICY avatar_delete_owner ON storage.objects
|
|
69
|
+
FOR DELETE USING (
|
|
70
|
+
bucket_id = 'avatar' AND (
|
|
71
|
+
owner = auth.uid() OR public.is_superadmin(auth.uid())
|
|
72
|
+
)
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
-- ===========================================================================
|
|
76
|
+
-- App bucket policies (per-user folders app/{uuid})
|
|
77
|
+
-- ===========================================================================
|
|
78
|
+
DROP POLICY IF EXISTS app_user_select ON storage.objects;
|
|
79
|
+
CREATE POLICY app_user_select ON storage.objects
|
|
80
|
+
FOR SELECT USING (
|
|
81
|
+
bucket_id = 'app' AND (
|
|
82
|
+
owner = auth.uid()
|
|
83
|
+
OR split_part(name, '/', 1) = auth.uid()::text
|
|
84
|
+
OR public.is_superadmin(auth.uid())
|
|
85
|
+
)
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
DROP POLICY IF EXISTS app_user_insert ON storage.objects;
|
|
89
|
+
CREATE POLICY app_user_insert ON storage.objects
|
|
90
|
+
FOR INSERT WITH CHECK (
|
|
91
|
+
bucket_id = 'app' AND (
|
|
92
|
+
split_part(name, '/', 1) = auth.uid()::text
|
|
93
|
+
OR owner = auth.uid()
|
|
94
|
+
OR public.is_superadmin(auth.uid())
|
|
95
|
+
)
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
DROP POLICY IF EXISTS app_user_manage ON storage.objects;
|
|
99
|
+
CREATE POLICY app_user_manage ON storage.objects
|
|
100
|
+
FOR UPDATE USING (
|
|
101
|
+
bucket_id = 'app' AND (
|
|
102
|
+
owner = auth.uid()
|
|
103
|
+
OR split_part(name, '/', 1) = auth.uid()::text
|
|
104
|
+
OR public.is_superadmin(auth.uid())
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
WITH CHECK (
|
|
108
|
+
bucket_id = 'app' AND (
|
|
109
|
+
owner = auth.uid()
|
|
110
|
+
OR split_part(name, '/', 1) = auth.uid()::text
|
|
111
|
+
OR public.is_superadmin(auth.uid())
|
|
112
|
+
)
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
DROP POLICY IF EXISTS app_user_delete ON storage.objects;
|
|
116
|
+
CREATE POLICY app_user_delete ON storage.objects
|
|
117
|
+
FOR DELETE USING (
|
|
118
|
+
bucket_id = 'app' AND (
|
|
119
|
+
owner = auth.uid()
|
|
120
|
+
OR split_part(name, '/', 1) = auth.uid()::text
|
|
121
|
+
OR public.is_superadmin(auth.uid())
|
|
122
|
+
)
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
-- ===========================================================================
|
|
126
|
+
-- RPC Function: Get table structure
|
|
127
|
+
-- ===========================================================================
|
|
128
|
+
CREATE OR REPLACE FUNCTION public.get_table_structure(p_table_name text)
|
|
129
|
+
RETURNS TABLE (
|
|
130
|
+
column_name text,
|
|
131
|
+
data_type text,
|
|
132
|
+
is_nullable text,
|
|
133
|
+
column_default text,
|
|
134
|
+
is_primary_key boolean,
|
|
135
|
+
is_foreign_key boolean,
|
|
136
|
+
foreign_table text,
|
|
137
|
+
foreign_column text,
|
|
138
|
+
description text
|
|
139
|
+
)
|
|
140
|
+
LANGUAGE plpgsql
|
|
141
|
+
SECURITY DEFINER
|
|
142
|
+
SET search_path = public, pg_catalog
|
|
143
|
+
AS $$
|
|
144
|
+
BEGIN
|
|
145
|
+
RETURN QUERY
|
|
146
|
+
SELECT
|
|
147
|
+
c.column_name::text,
|
|
148
|
+
CASE
|
|
149
|
+
WHEN c.data_type = 'USER-DEFINED' THEN c.udt_name::text
|
|
150
|
+
WHEN c.data_type = 'ARRAY' THEN c.udt_name::text || '[]'
|
|
151
|
+
ELSE c.data_type::text
|
|
152
|
+
END as data_type,
|
|
153
|
+
c.is_nullable::text,
|
|
154
|
+
c.column_default::text,
|
|
155
|
+
COALESCE(
|
|
156
|
+
EXISTS(
|
|
157
|
+
SELECT 1 FROM information_schema.table_constraints tc
|
|
158
|
+
JOIN information_schema.key_column_usage kcu
|
|
159
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
160
|
+
AND tc.table_schema = kcu.table_schema
|
|
161
|
+
WHERE tc.constraint_type = 'PRIMARY KEY'
|
|
162
|
+
AND tc.table_schema = 'public'
|
|
163
|
+
AND tc.table_name = p_table_name
|
|
164
|
+
AND kcu.column_name = c.column_name
|
|
165
|
+
), false
|
|
166
|
+
) as is_primary_key,
|
|
167
|
+
COALESCE(
|
|
168
|
+
EXISTS(
|
|
169
|
+
SELECT 1 FROM information_schema.table_constraints tc
|
|
170
|
+
JOIN information_schema.key_column_usage kcu
|
|
171
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
172
|
+
AND tc.table_schema = kcu.table_schema
|
|
173
|
+
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
174
|
+
AND tc.table_schema = 'public'
|
|
175
|
+
AND tc.table_name = p_table_name
|
|
176
|
+
AND kcu.column_name = c.column_name
|
|
177
|
+
), false
|
|
178
|
+
) as is_foreign_key,
|
|
179
|
+
(
|
|
180
|
+
SELECT ccu.table_name::text
|
|
181
|
+
FROM information_schema.table_constraints tc
|
|
182
|
+
JOIN information_schema.key_column_usage kcu
|
|
183
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
184
|
+
AND tc.table_schema = kcu.table_schema
|
|
185
|
+
JOIN information_schema.constraint_column_usage ccu
|
|
186
|
+
ON ccu.constraint_name = tc.constraint_name
|
|
187
|
+
AND ccu.table_schema = tc.table_schema
|
|
188
|
+
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
189
|
+
AND tc.table_schema = 'public'
|
|
190
|
+
AND tc.table_name = p_table_name
|
|
191
|
+
AND kcu.column_name = c.column_name
|
|
192
|
+
LIMIT 1
|
|
193
|
+
) as foreign_table,
|
|
194
|
+
(
|
|
195
|
+
SELECT ccu.column_name::text
|
|
196
|
+
FROM information_schema.table_constraints tc
|
|
197
|
+
JOIN information_schema.key_column_usage kcu
|
|
198
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
199
|
+
AND tc.table_schema = kcu.table_schema
|
|
200
|
+
JOIN information_schema.constraint_column_usage ccu
|
|
201
|
+
ON ccu.constraint_name = tc.constraint_name
|
|
202
|
+
AND ccu.table_schema = tc.table_schema
|
|
203
|
+
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
204
|
+
AND tc.table_schema = 'public'
|
|
205
|
+
AND tc.table_name = p_table_name
|
|
206
|
+
AND kcu.column_name = c.column_name
|
|
207
|
+
LIMIT 1
|
|
208
|
+
) as foreign_column,
|
|
209
|
+
pgd.description::text
|
|
210
|
+
FROM information_schema.columns c
|
|
211
|
+
LEFT JOIN pg_catalog.pg_statio_all_tables st
|
|
212
|
+
ON c.table_schema = st.schemaname
|
|
213
|
+
AND c.table_name = st.relname
|
|
214
|
+
LEFT JOIN pg_catalog.pg_description pgd
|
|
215
|
+
ON pgd.objoid = st.relid
|
|
216
|
+
AND pgd.objsubid = c.ordinal_position
|
|
217
|
+
WHERE c.table_schema = 'public'
|
|
218
|
+
AND c.table_name = p_table_name
|
|
219
|
+
ORDER BY c.ordinal_position;
|
|
220
|
+
END;
|
|
221
|
+
$$;
|
|
222
|
+
|
|
223
|
+
REVOKE ALL ON FUNCTION public.get_table_structure(text) FROM PUBLIC;
|
|
224
|
+
GRANT EXECUTE ON FUNCTION public.get_table_structure(text) TO authenticated, anon;
|
|
225
|
+
|
|
226
|
+
COMMENT ON FUNCTION public.get_table_structure(text) IS
|
|
227
|
+
'Returns the structure of a table including column names, types, constraints, and foreign key relationships';
|
|
228
|
+
|