@lastbrain/app 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/README.md +256 -0
- package/dist/app-shell/(admin)/dashboard/page.d.ts +2 -0
- package/dist/app-shell/(admin)/dashboard/page.d.ts.map +1 -0
- package/dist/app-shell/(admin)/dashboard/page.js +5 -0
- package/dist/app-shell/(admin)/layout.d.ts +3 -0
- package/dist/app-shell/(admin)/layout.d.ts.map +1 -0
- package/dist/app-shell/(admin)/layout.js +5 -0
- package/dist/app-shell/(admin)/page.d.ts +2 -0
- package/dist/app-shell/(admin)/page.d.ts.map +1 -0
- package/dist/app-shell/(admin)/page.js +5 -0
- package/dist/app-shell/(auth)/dashboard/page.d.ts +2 -0
- package/dist/app-shell/(auth)/dashboard/page.d.ts.map +1 -0
- package/dist/app-shell/(auth)/dashboard/page.js +5 -0
- package/dist/app-shell/(auth)/layout.d.ts +3 -0
- package/dist/app-shell/(auth)/layout.d.ts.map +1 -0
- package/dist/app-shell/(auth)/layout.js +5 -0
- package/dist/app-shell/(auth)/page.d.ts +2 -0
- package/dist/app-shell/(auth)/page.d.ts.map +1 -0
- package/dist/app-shell/(auth)/page.js +5 -0
- package/dist/app-shell/(public)/page.d.ts +2 -0
- package/dist/app-shell/(public)/page.d.ts.map +1 -0
- package/dist/app-shell/(public)/page.js +5 -0
- package/dist/app-shell/layout.d.ts +3 -0
- package/dist/app-shell/layout.d.ts.map +1 -0
- package/dist/app-shell/layout.js +3 -0
- package/dist/app-shell/not-found.d.ts +2 -0
- package/dist/app-shell/not-found.d.ts.map +1 -0
- package/dist/app-shell/not-found.js +10 -0
- package/dist/auth/authHelpers.d.ts +7 -0
- package/dist/auth/authHelpers.d.ts.map +1 -0
- package/dist/auth/authHelpers.js +19 -0
- package/dist/auth/useAuthSession.d.ts +7 -0
- package/dist/auth/useAuthSession.d.ts.map +1 -0
- package/dist/auth/useAuthSession.js +43 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +88 -0
- package/dist/components/TableStructure.d.ts +8 -0
- package/dist/components/TableStructure.d.ts.map +1 -0
- package/dist/components/TableStructure.js +37 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/layouts/AdminLayout.d.ts +3 -0
- package/dist/layouts/AdminLayout.d.ts.map +1 -0
- package/dist/layouts/AdminLayout.js +5 -0
- package/dist/layouts/AppProviders.d.ts +13 -0
- package/dist/layouts/AppProviders.d.ts.map +1 -0
- package/dist/layouts/AppProviders.js +35 -0
- package/dist/layouts/AuthLayout.d.ts +3 -0
- package/dist/layouts/AuthLayout.d.ts.map +1 -0
- package/dist/layouts/AuthLayout.js +5 -0
- package/dist/layouts/PublicLayout.d.ts +3 -0
- package/dist/layouts/PublicLayout.d.ts.map +1 -0
- package/dist/layouts/PublicLayout.js +5 -0
- package/dist/layouts/RootLayout.d.ts +3 -0
- package/dist/layouts/RootLayout.d.ts.map +1 -0
- package/dist/layouts/RootLayout.js +8 -0
- package/dist/module-build.d.ts +2 -0
- package/dist/module-build.d.ts.map +1 -0
- package/dist/module-build.js +50 -0
- package/dist/modules/index.d.ts +3 -0
- package/dist/modules/index.d.ts.map +1 -0
- package/dist/modules/index.js +2 -0
- package/dist/modules/module-loader.d.ts +5 -0
- package/dist/modules/module-loader.d.ts.map +1 -0
- package/dist/modules/module-loader.js +10 -0
- package/dist/scripts/db-init.d.ts +2 -0
- package/dist/scripts/db-init.d.ts.map +1 -0
- package/dist/scripts/db-init.js +281 -0
- package/dist/scripts/db-migrations-sync.d.ts +2 -0
- package/dist/scripts/db-migrations-sync.d.ts.map +1 -0
- package/dist/scripts/db-migrations-sync.js +55 -0
- package/dist/scripts/dev-sync.d.ts +2 -0
- package/dist/scripts/dev-sync.d.ts.map +1 -0
- package/dist/scripts/dev-sync.js +182 -0
- package/dist/scripts/init-app.d.ts +11 -0
- package/dist/scripts/init-app.d.ts.map +1 -0
- package/dist/scripts/init-app.js +846 -0
- package/dist/scripts/module-add.d.ts +13 -0
- package/dist/scripts/module-add.d.ts.map +1 -0
- package/dist/scripts/module-add.js +178 -0
- package/dist/scripts/module-build.d.ts +2 -0
- package/dist/scripts/module-build.d.ts.map +1 -0
- package/dist/scripts/module-build.js +413 -0
- package/dist/scripts/module-create.d.ts +5 -0
- package/dist/scripts/module-create.d.ts.map +1 -0
- package/dist/scripts/module-create.js +694 -0
- package/dist/scripts/module-list.d.ts +2 -0
- package/dist/scripts/module-list.d.ts.map +1 -0
- package/dist/scripts/module-list.js +31 -0
- package/dist/scripts/module-remove.d.ts +2 -0
- package/dist/scripts/module-remove.d.ts.map +1 -0
- package/dist/scripts/module-remove.js +282 -0
- package/dist/scripts/readme-build.d.ts +2 -0
- package/dist/scripts/readme-build.d.ts.map +1 -0
- package/dist/scripts/readme-build.js +39 -0
- package/dist/templates/AuthGuidePage.d.ts +2 -0
- package/dist/templates/AuthGuidePage.d.ts.map +1 -0
- package/dist/templates/AuthGuidePage.js +9 -0
- package/dist/templates/DefaultDoc.d.ts +2 -0
- package/dist/templates/DefaultDoc.d.ts.map +1 -0
- package/dist/templates/DefaultDoc.js +29 -0
- package/dist/templates/DocPage.d.ts +17 -0
- package/dist/templates/DocPage.d.ts.map +1 -0
- package/dist/templates/DocPage.js +165 -0
- package/dist/templates/DocsPageWithModules.d.ts +2 -0
- package/dist/templates/DocsPageWithModules.d.ts.map +1 -0
- package/dist/templates/DocsPageWithModules.js +8 -0
- package/dist/templates/HomePage.d.ts +6 -0
- package/dist/templates/HomePage.d.ts.map +1 -0
- package/dist/templates/HomePage.js +6 -0
- package/dist/templates/MigrationsGuidePage.d.ts +2 -0
- package/dist/templates/MigrationsGuidePage.d.ts.map +1 -0
- package/dist/templates/MigrationsGuidePage.js +11 -0
- package/dist/templates/ModuleGuidePage.d.ts +2 -0
- package/dist/templates/ModuleGuidePage.d.ts.map +1 -0
- package/dist/templates/ModuleGuidePage.js +14 -0
- package/dist/templates/SimpleDocPage.d.ts +2 -0
- package/dist/templates/SimpleDocPage.d.ts.map +1 -0
- package/dist/templates/SimpleDocPage.js +28 -0
- package/dist/templates/SimpleHomePage.d.ts +6 -0
- package/dist/templates/SimpleHomePage.d.ts.map +1 -0
- package/dist/templates/SimpleHomePage.js +7 -0
- package/dist/templates/env.example/.env.example +6 -0
- package/dist/templates/migrations/20201010100000_app_base.sql +228 -0
- package/dist/templates/migrations/20201010100000_init.sql +123 -0
- package/package.json +75 -0
- 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/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/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 +1011 -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/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,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
|
+
|
|
@@ -0,0 +1,123 @@
|
|
|
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
|
+
);
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lastbrain/app",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Framework modulaire Next.js avec CLI et système de modules",
|
|
5
|
+
"private": false,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"nextjs",
|
|
11
|
+
"framework",
|
|
12
|
+
"modular",
|
|
13
|
+
"typescript",
|
|
14
|
+
"supabase",
|
|
15
|
+
"monorepo",
|
|
16
|
+
"cli"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/lastpublication/starter.git",
|
|
21
|
+
"directory": "packages/app"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"bin": {
|
|
27
|
+
"lastbrain": "./dist/cli.js"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"src/app-shell",
|
|
32
|
+
"src/layouts",
|
|
33
|
+
"src/scripts",
|
|
34
|
+
"src/templates"
|
|
35
|
+
],
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@lastbrain/core": "0.1.0",
|
|
38
|
+
"@lastbrain/module-auth": "0.1.0",
|
|
39
|
+
"@lastbrain/ui": "0.1.0",
|
|
40
|
+
"@supabase/supabase-js": "^2.36.0",
|
|
41
|
+
"chalk": "^5.3.0",
|
|
42
|
+
"commander": "^12.1.0",
|
|
43
|
+
"fs-extra": "^11.2.0",
|
|
44
|
+
"lucide-react": "^0.554.0",
|
|
45
|
+
"next-themes": "^0.4.6",
|
|
46
|
+
"react": "^19.0.0",
|
|
47
|
+
"react-dom": "^19.0.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"next": ">=15.0.0"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/fs-extra": "^11.0.4",
|
|
54
|
+
"@types/inquirer": "^9.0.9",
|
|
55
|
+
"@types/node": "^20.0.0",
|
|
56
|
+
"inquirer": "^13.0.1",
|
|
57
|
+
"typescript": "^5.4.0"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"dev": "tsc -p tsconfig.json --watch",
|
|
61
|
+
"build": "tsc -p tsconfig.json && npm run copy:templates && chmod +x dist/cli.js",
|
|
62
|
+
"copy:templates": "mkdir -p dist/templates/migrations dist/templates/env.example dist/templates/gitignore && cp src/templates/migrations/* dist/templates/migrations/ 2>/dev/null || true && cp src/templates/env.example/.env.example dist/templates/env.example/ 2>/dev/null || true && cp src/templates/gitignore/.gitignore dist/templates/gitignore/ 2>/dev/null || true",
|
|
63
|
+
"module:build": "node dist/scripts/module-build.js",
|
|
64
|
+
"module:create": "node dist/scripts/module-create.js",
|
|
65
|
+
"module:add": "node dist/scripts/module-add.js",
|
|
66
|
+
"module:remove": "node dist/scripts/module-remove.js",
|
|
67
|
+
"module:list": "node dist/scripts/module-list.js",
|
|
68
|
+
"build:modules": "node dist/scripts/module-build.js",
|
|
69
|
+
"db:migrations:sync": "node dist/scripts/db-migrations-sync.js",
|
|
70
|
+
"db:init": "node dist/scripts/db-init.js",
|
|
71
|
+
"readme:create": "node dist/scripts/readme-build.js",
|
|
72
|
+
"dev:shell": "next dev ./src/app-shell -p 3001",
|
|
73
|
+
"dev:sync": "node dist/scripts/dev-sync.js"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { PropsWithChildren } from "react";
|
|
4
|
+
|
|
5
|
+
export default function AdminShellLayout({ children }: PropsWithChildren<{}>) {
|
|
6
|
+
return (
|
|
7
|
+
<div className="mx-auto max-w-5xl px-4 py-16">
|
|
8
|
+
<h1 className="text-3xl font-bold">Section Admin</h1>
|
|
9
|
+
<p className="text-slate-600">Gestion avancée des modules.</p>
|
|
10
|
+
{children}
|
|
11
|
+
</div>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { PropsWithChildren } from "react";
|
|
4
|
+
|
|
5
|
+
export default function AuthLayout({ children }: PropsWithChildren<{}>) {
|
|
6
|
+
return (
|
|
7
|
+
<div className="mx-auto max-w-5xl px-4 py-16">
|
|
8
|
+
<h1 className="text-3xl font-bold">Section Auth</h1>
|
|
9
|
+
<p className="text-slate-600">Les pages authentifiées regroupées ici.</p>
|
|
10
|
+
{children}
|
|
11
|
+
</div>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
export default function PublicPage() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="mx-auto max-w-3xl space-y-4 px-4 py-16 text-center">
|
|
6
|
+
<h1 className="text-3xl font-bold">Bienvenue sur LastBrain</h1>
|
|
7
|
+
<p className="text-slate-600">Cette coquille publique présente la structure par défaut.</p>
|
|
8
|
+
<button className="rounded-full bg-slate-900 px-5 py-2 text-white">Connexion</button>
|
|
9
|
+
</div>
|
|
10
|
+
);
|
|
11
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Button } from "@lastbrain/ui";
|
|
3
|
+
import { useRouter } from "next/navigation";
|
|
4
|
+
|
|
5
|
+
export default function NotFound() {
|
|
6
|
+
const router = useRouter();
|
|
7
|
+
return (
|
|
8
|
+
<div className="flex min-h-screen items-center justify-center bg-background">
|
|
9
|
+
<div className="mx-auto max-w-md text-center">
|
|
10
|
+
<h1 className="mb-4 text-6xl font-bold text-foreground">404</h1>
|
|
11
|
+
<h2 className="mb-4 text-2xl font-semibold text-foreground">
|
|
12
|
+
Page non trouvée
|
|
13
|
+
</h2>
|
|
14
|
+
<p className="mb-8 text-muted-foreground">
|
|
15
|
+
La page que vous recherchez n'existe pas ou a été déplacée.
|
|
16
|
+
</p>
|
|
17
|
+
<Button
|
|
18
|
+
onPress={() => {
|
|
19
|
+
router.back();
|
|
20
|
+
}}
|
|
21
|
+
className="inline-flex items-center justify-center rounded-md bg-primary px-6 py-3 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90"
|
|
22
|
+
>
|
|
23
|
+
Retour à l'accueil
|
|
24
|
+
</Button>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { PropsWithChildren } from "react";
|
|
4
|
+
import { createContext, useContext, useMemo } from "react";
|
|
5
|
+
import { getModuleConfigs } from "../modules/module-loader.js";
|
|
6
|
+
import { Header, ToastProvider } from "@lastbrain/ui";
|
|
7
|
+
import { useAuthSession } from "../auth/useAuthSession.js";
|
|
8
|
+
import type { User } from "@supabase/supabase-js";
|
|
9
|
+
import { useRouter } from "next/navigation.js";
|
|
10
|
+
|
|
11
|
+
const ModuleContext = createContext(getModuleConfigs());
|
|
12
|
+
const NotificationContext = createContext({ messages: [] as string[] });
|
|
13
|
+
const AuthContext = createContext<{
|
|
14
|
+
user: User | null;
|
|
15
|
+
loading: boolean;
|
|
16
|
+
isSuperAdmin: boolean;
|
|
17
|
+
}>({
|
|
18
|
+
user: null,
|
|
19
|
+
loading: true,
|
|
20
|
+
isSuperAdmin: false,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export function useModules() {
|
|
24
|
+
return useContext(ModuleContext);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function useNotifications() {
|
|
28
|
+
return useContext(NotificationContext);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function useAuth() {
|
|
32
|
+
return useContext(AuthContext);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function AppProviders({ children }: PropsWithChildren<{}>) {
|
|
36
|
+
const modules = useMemo(() => getModuleConfigs(), []);
|
|
37
|
+
const notifications = useMemo(() => ({ messages: [] as string[] }), []);
|
|
38
|
+
const { user, loading, isSuperAdmin } = useAuthSession();
|
|
39
|
+
// const router = useRouter();
|
|
40
|
+
const authValue = useMemo(
|
|
41
|
+
() => ({ user, loading, isSuperAdmin }),
|
|
42
|
+
[user, loading, isSuperAdmin]
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// const handleLogout = async () => {
|
|
46
|
+
// const { signOut } = await import("../auth/authHelpers.js");
|
|
47
|
+
// await signOut();
|
|
48
|
+
// router.push("/auth/signin");
|
|
49
|
+
// };
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<AuthContext.Provider value={authValue}>
|
|
53
|
+
<ModuleContext.Provider value={modules}>
|
|
54
|
+
<NotificationContext.Provider value={notifications}>
|
|
55
|
+
{children}
|
|
56
|
+
<ToastProvider placement="top-right" toastOffset={75} />
|
|
57
|
+
</NotificationContext.Provider>
|
|
58
|
+
</ModuleContext.Provider>
|
|
59
|
+
</AuthContext.Provider>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { PropsWithChildren } from "react";
|
|
4
|
+
import { ThemeProvider } from "next-themes";
|
|
5
|
+
import { AppProviders } from "./AppProviders.js";
|
|
6
|
+
|
|
7
|
+
// Note: L'app Next.js doit importer son propre globals.css dans son layout
|
|
8
|
+
export function RootLayout({ children }: PropsWithChildren<{}>) {
|
|
9
|
+
return (
|
|
10
|
+
<html lang="fr" suppressHydrationWarning>
|
|
11
|
+
<body className="min-h-screen">
|
|
12
|
+
<ThemeProvider
|
|
13
|
+
attribute="class"
|
|
14
|
+
defaultTheme="light"
|
|
15
|
+
enableSystem={false}
|
|
16
|
+
storageKey="lastbrain-theme"
|
|
17
|
+
>
|
|
18
|
+
<AppProviders>
|
|
19
|
+
<div className=" min-h-screen bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-white">
|
|
20
|
+
{children}
|
|
21
|
+
</div>
|
|
22
|
+
</AppProviders>
|
|
23
|
+
</ThemeProvider>
|
|
24
|
+
</body>
|
|
25
|
+
</html>
|
|
26
|
+
);
|
|
27
|
+
}
|