@lastbrain/module-auth 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/api/public/signin.d.ts +2 -0
  2. package/dist/api/public/signin.d.ts.map +1 -0
  3. package/dist/api/public/signin.js +25 -0
  4. package/dist/auth.build.config.d.ts +4 -0
  5. package/dist/auth.build.config.d.ts.map +1 -0
  6. package/dist/auth.build.config.js +113 -0
  7. package/dist/components/Doc.d.ts +2 -0
  8. package/dist/components/Doc.d.ts.map +1 -0
  9. package/dist/components/Doc.js +5 -0
  10. package/dist/index.d.ts +10 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +12 -0
  13. package/dist/server.d.ts +2 -0
  14. package/dist/server.d.ts.map +1 -0
  15. package/dist/server.js +2 -0
  16. package/dist/web/admin/users.d.ts +2 -0
  17. package/dist/web/admin/users.d.ts.map +1 -0
  18. package/dist/web/admin/users.js +4 -0
  19. package/dist/web/auth/dashboard.d.ts +2 -0
  20. package/dist/web/auth/dashboard.d.ts.map +1 -0
  21. package/dist/web/auth/dashboard.js +4 -0
  22. package/dist/web/auth/profile.d.ts +2 -0
  23. package/dist/web/auth/profile.d.ts.map +1 -0
  24. package/dist/web/auth/profile.js +4 -0
  25. package/dist/web/auth/reglage.d.ts +2 -0
  26. package/dist/web/auth/reglage.d.ts.map +1 -0
  27. package/dist/web/auth/reglage.js +4 -0
  28. package/dist/web/public/ResetPassword.d.ts +2 -0
  29. package/dist/web/public/ResetPassword.d.ts.map +1 -0
  30. package/dist/web/public/ResetPassword.js +4 -0
  31. package/dist/web/public/SignInPage.d.ts +2 -0
  32. package/dist/web/public/SignInPage.d.ts.map +1 -0
  33. package/dist/web/public/SignInPage.js +108 -0
  34. package/dist/web/public/SignUpPage.d.ts +2 -0
  35. package/dist/web/public/SignUpPage.d.ts.map +1 -0
  36. package/dist/web/public/SignUpPage.js +102 -0
  37. package/package.json +67 -0
  38. package/supabase/migrations/20251112000000_user_init.sql +175 -0
  39. package/supabase/migrations-down/20251112000000_user_init.down.sql +54 -0
@@ -0,0 +1,2 @@
1
+ export declare function POST(request: Request): Promise<Response>;
2
+ //# sourceMappingURL=signin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signin.d.ts","sourceRoot":"","sources":["../../../src/api/public/signin.ts"],"names":[],"mappings":"AAWA,wBAAsB,IAAI,CAAC,OAAO,EAAE,OAAO,qBAmB1C"}
@@ -0,0 +1,25 @@
1
+ import { getSupabaseServerClient } from "@lastbrain/core/server";
2
+ const jsonResponse = (payload, status = 200) => {
3
+ return new Response(JSON.stringify(payload), {
4
+ headers: {
5
+ "content-type": "application/json"
6
+ },
7
+ status
8
+ });
9
+ };
10
+ export async function POST(request) {
11
+ const supabase = await getSupabaseServerClient();
12
+ const body = await request.json();
13
+ const { email, password } = body;
14
+ if (!email || !password) {
15
+ return jsonResponse({ error: "Email et mot de passe requis." }, 400);
16
+ }
17
+ const { error, data } = await supabase.auth.signInWithPassword({
18
+ email,
19
+ password
20
+ });
21
+ if (error) {
22
+ return jsonResponse({ error: error.message }, 400);
23
+ }
24
+ return jsonResponse({ data });
25
+ }
@@ -0,0 +1,4 @@
1
+ import type { ModuleBuildConfig } from "@lastbrain/core";
2
+ declare const authBuildConfig: ModuleBuildConfig;
3
+ export default authBuildConfig;
4
+ //# sourceMappingURL=auth.build.config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.build.config.d.ts","sourceRoot":"","sources":["../src/auth.build.config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,QAAA,MAAM,eAAe,EAAE,iBA+GtB,CAAC;AAEF,eAAe,eAAe,CAAC"}
@@ -0,0 +1,113 @@
1
+ const authBuildConfig = {
2
+ moduleName: "@lastbrain/module-auth",
3
+ pages: [
4
+ {
5
+ section: "public",
6
+ path: "/signin",
7
+ componentExport: "SignInPage",
8
+ },
9
+ {
10
+ section: "public",
11
+ path: "/signup",
12
+ componentExport: "SignUpPage",
13
+ },
14
+ {
15
+ section: "public",
16
+ path: "/reset-password",
17
+ componentExport: "ResetPassword",
18
+ },
19
+ {
20
+ section: "auth",
21
+ path: "/dashboard",
22
+ componentExport: "DashboardPage",
23
+ },
24
+ {
25
+ section: "auth",
26
+ path: "/reglage",
27
+ componentExport: "ReglagePage",
28
+ },
29
+ {
30
+ section: "auth",
31
+ path: "/profile",
32
+ componentExport: "ProfilePage",
33
+ },
34
+ {
35
+ section: "admin",
36
+ path: "/users",
37
+ componentExport: "AdminUsersPage",
38
+ },
39
+ ],
40
+ apis: [
41
+ {
42
+ method: "POST",
43
+ path: "/api/auth/signin",
44
+ handlerExport: "POST",
45
+ entryPoint: "api/public/signin",
46
+ authRequired: false,
47
+ },
48
+ ],
49
+ migrations: {
50
+ enabled: true,
51
+ priority: 20,
52
+ path: "supabase/migrations",
53
+ files: ["001_auth_base.sql"],
54
+ },
55
+ menu: {
56
+ public: [
57
+ {
58
+ title: "Connexion",
59
+ description: "Connectez-vous à votre compte",
60
+ icon: "LogIn",
61
+ path: "/signin",
62
+ order: 1,
63
+ },
64
+ {
65
+ title: "Inscription",
66
+ description: "Créez votre compte",
67
+ icon: "UserPlus2",
68
+ path: "/signup",
69
+ order: 2,
70
+ },
71
+ ],
72
+ admin: [
73
+ {
74
+ title: "Gestion des utilisateurs",
75
+ description: "Gérez les utilisateurs de la plateforme",
76
+ icon: "Users2",
77
+ path: "/admin/users",
78
+ order: 1,
79
+ },
80
+ ],
81
+ account: [
82
+ {
83
+ title: "Tableau de bord",
84
+ description: "Accédez à votre tableau de bord",
85
+ icon: "LayoutDashboard",
86
+ path: "/auth/dashboard",
87
+ order: 1,
88
+ },
89
+ {
90
+ title: "Mon profil",
91
+ description: "Gérez vos informations personnelles",
92
+ icon: "User2",
93
+ path: "/auth/profile",
94
+ order: 1,
95
+ },
96
+ {
97
+ title: "Paramètres",
98
+ description: "Configurez votre compte",
99
+ icon: "Settings",
100
+ path: "/auth/reglage",
101
+ order: 2,
102
+ },
103
+ {
104
+ title: "Déconnexion",
105
+ description: "Se déconnecter",
106
+ icon: "LogOut",
107
+ path: "/signout",
108
+ order: 99,
109
+ },
110
+ ],
111
+ },
112
+ };
113
+ export default authBuildConfig;
@@ -0,0 +1,2 @@
1
+ export declare function AuthModuleDoc(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=Doc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Doc.d.ts","sourceRoot":"","sources":["../../src/components/Doc.tsx"],"names":[],"mappings":"AAUA,wBAAgB,aAAa,4CA2S5B"}
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Card, CardBody, CardHeader, Snippet, Chip, TableStructure, } from "@lastbrain/ui";
3
+ export function AuthModuleDoc() {
4
+ return (_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "flex items-start justify-between", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-3xl font-bold mb-2", children: "Module Auth" }), _jsx("p", { className: "text-slate-600 dark:text-slate-400", children: "Authentification compl\u00E8te avec gestion des profils utilisateur" })] }), _jsx(Chip, { color: "primary", variant: "flat", children: "v0.1.0" })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-xl font-semibold", children: "\uD83D\uDCDD Informations" }) }), _jsxs(CardBody, { className: "space-y-2", children: [_jsxs("div", { children: [_jsx("span", { className: "font-semibold", children: "Auteur:" }), " LastBrain Team"] }), _jsxs("div", { children: [_jsx("span", { className: "font-semibold", children: "Package:" }), " ", "@lastbrain/module-auth"] }), _jsxs("div", { children: [_jsx("span", { className: "font-semibold", children: "Derni\u00E8re mise \u00E0 jour:" }), " 21 novembre 2025"] })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-xl font-semibold", children: "\uD83D\uDCC4 Pages Disponibles" }) }), _jsxs(CardBody, { className: "space-y-3", children: [_jsxs("div", { children: [_jsx("h4", { className: "font-semibold mb-2", children: "Pages Publiques" }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Chip, { size: "sm", color: "success", variant: "flat", children: "GET" }), _jsx("code", { className: "text-sm", children: "/signin" }), _jsx("span", { className: "text-slate-600 dark:text-slate-400", children: "- Connexion" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Chip, { size: "sm", color: "success", variant: "flat", children: "GET" }), _jsx("code", { className: "text-sm", children: "/signup" }), _jsx("span", { className: "text-slate-600 dark:text-slate-400", children: "- Inscription" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Chip, { size: "sm", color: "success", variant: "flat", children: "GET" }), _jsx("code", { className: "text-sm", children: "/reset-password" }), _jsx("span", { className: "text-slate-600 dark:text-slate-400", children: "- R\u00E9initialisation mot de passe" })] })] })] }), _jsxs("div", { children: [_jsx("h4", { className: "font-semibold mb-2", children: "Pages Prot\u00E9g\u00E9es (Auth)" }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Chip, { size: "sm", color: "primary", variant: "flat", children: "GET" }), _jsx("code", { className: "text-sm", children: "/auth/dashboard" }), _jsx("span", { className: "text-slate-600 dark:text-slate-400", children: "- Tableau de bord" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Chip, { size: "sm", color: "primary", variant: "flat", children: "GET" }), _jsx("code", { className: "text-sm", children: "/auth/profile" }), _jsx("span", { className: "text-slate-600 dark:text-slate-400", children: "- Profil utilisateur" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Chip, { size: "sm", color: "primary", variant: "flat", children: "GET" }), _jsx("code", { className: "text-sm", children: "/auth/reglage" }), _jsx("span", { className: "text-slate-600 dark:text-slate-400", children: "- Param\u00E8tres" })] })] })] }), _jsxs("div", { children: [_jsx("h4", { className: "font-semibold mb-2", children: "Pages Admin" }), _jsx("div", { className: "space-y-2", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Chip, { size: "sm", color: "danger", variant: "flat", children: "GET" }), _jsx("code", { className: "text-sm", children: "/admin/users" }), _jsx("span", { className: "text-slate-600 dark:text-slate-400", children: "- Gestion des utilisateurs" })] }) })] })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-xl font-semibold", children: "\uD83D\uDD0C Routes API" }) }), _jsxs(CardBody, { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Chip, { size: "sm", color: "warning", variant: "flat", children: "POST" }), _jsx("code", { className: "text-sm", children: "/api/auth/signin" }), _jsx("span", { className: "text-slate-600 dark:text-slate-400", children: "- Authentification" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Chip, { size: "sm", color: "warning", variant: "flat", children: "POST" }), _jsx("code", { className: "text-sm", children: "/api/auth/signup" }), _jsx("span", { className: "text-slate-600 dark:text-slate-400", children: "- Cr\u00E9ation de compte" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Chip, { size: "sm", color: "warning", variant: "flat", children: "POST" }), _jsx("code", { className: "text-sm", children: "/api/auth/signout" }), _jsx("span", { className: "text-slate-600 dark:text-slate-400", children: "- D\u00E9connexion" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Chip, { size: "sm", color: "warning", variant: "flat", children: "POST" }), _jsx("code", { className: "text-sm", children: "/api/auth/reset-password" }), _jsx("span", { className: "text-slate-600 dark:text-slate-400", children: "- R\u00E9initialisation" })] })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-xl font-semibold", children: "\uD83D\uDCC1 Structure" }) }), _jsx(CardBody, { children: _jsx(Snippet, { symbol: "", color: "default", size: "sm", hideSymbol: true, hideCopyButton: true, children: _jsxs("div", { className: "flex flex-col gap-1 text-xs font-mono", children: [_jsx("span", { children: "module-auth/" }), _jsx("span", { children: "\u251C\u2500\u2500 src/" }), _jsx("span", { children: "\u2502 \u251C\u2500\u2500 web/" }), _jsx("span", { children: "\u2502 \u2502 \u251C\u2500\u2500 signin/" }), _jsx("span", { children: "\u2502 \u2502 \u251C\u2500\u2500 signup/" }), _jsx("span", { children: "\u2502 \u2502 \u251C\u2500\u2500 dashboard/" }), _jsx("span", { children: "\u2502 \u2502 \u251C\u2500\u2500 profile/" }), _jsx("span", { children: "\u2502 \u2502 \u2514\u2500\u2500 reglage/" }), _jsx("span", { children: "\u2502 \u251C\u2500\u2500 api/" }), _jsx("span", { children: "\u2502 \u2502 \u251C\u2500\u2500 signin.ts" }), _jsx("span", { children: "\u2502 \u2502 \u251C\u2500\u2500 signup.ts" }), _jsx("span", { children: "\u2502 \u2502 \u251C\u2500\u2500 signout.ts" }), _jsx("span", { children: "\u2502 \u2502 \u2514\u2500\u2500 reset-password.ts" }), _jsx("span", { children: "\u2502 \u251C\u2500\u2500 components/" }), _jsx("span", { children: "\u2502 \u2502 \u2514\u2500\u2500 Doc.tsx" }), _jsx("span", { children: "\u2502 \u2514\u2500\u2500 auth.build.config.ts" }), _jsx("span", { children: "\u2514\u2500\u2500 supabase/" }), _jsx("span", { children: " \u2514\u2500\u2500 migrations/" }), _jsx("span", { children: " \u2514\u2500\u2500 20251112000000_user_init.sql" })] }) }) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-xl font-semibold", children: "\uD83D\uDDC4\uFE0F Tables de Donn\u00E9es" }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsx(TableStructure, { tableName: "user_profil", title: "user_profil", description: "Profil \u00E9tendu des utilisateurs (pr\u00E9nom, nom, t\u00E9l\u00E9phone, avatar, bio, etc.)" }), _jsx(TableStructure, { tableName: "user_address", title: "user_address", description: "Adresses des utilisateurs (livraison, facturation)" }), _jsx(TableStructure, { tableName: "user_notifications", title: "user_notifications", description: "Pr\u00E9f\u00E9rences de notifications (email, SMS, push)" })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-xl font-semibold", children: "\uD83D\uDCE6 Installation" }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Snippet, { symbol: "", color: "default", size: "sm", children: "pnpm lastbrain add-module auth" }), _jsx(Snippet, { symbol: "", color: "default", size: "sm", children: "pnpm build:modules" }), _jsx(Snippet, { symbol: "", color: "default", size: "sm", children: "supabase migration up" })] }), _jsxs("div", { className: "border-2 border-danger rounded-lg p-4 mt-6", children: [_jsx("h4", { className: "text-lg font-semibold text-danger mb-3", children: "\u26A0\uFE0F Danger Zone" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-3", children: "La suppression du module supprimera toutes les pages, routes API et migrations associ\u00E9es. Cette action est irr\u00E9versible." }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Snippet, { symbol: "", color: "danger", size: "sm", children: "pnpm lastbrain remove-module auth" }), _jsx(Snippet, { symbol: "", color: "danger", size: "sm", children: "pnpm build:modules" })] })] })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-xl font-semibold", children: "\uD83D\uDCA1 Utilisation" }) }), _jsxs(CardBody, { className: "space-y-3", children: [_jsxs("div", { children: [_jsx("h4", { className: "font-semibold mb-2", children: "Hooks disponibles" }), _jsxs(Snippet, { symbol: "", color: "default", size: "sm", children: ["import ", "{ useAuthSession }", " from \"@lastbrain/module-auth\";"] })] }), _jsxs("div", { children: [_jsx("h4", { className: "font-semibold mb-2 mt-4", children: "Exemple d'utilisation" }), _jsx(Snippet, { symbol: "", color: "default", size: "sm", hideSymbol: true, children: _jsxs("div", { className: "flex flex-col gap-1", children: [_jsxs("span", { children: ["const ", "{ session, user }", " = useAuthSession();"] }), _jsx("span", { children: "if (!session) return <p>Non connect\u00E9</p>;" }), _jsxs("span", { children: ["return <div>Bonjour ", "{user.email}", "</div>;"] })] }) })] })] })] })] }));
5
+ }
@@ -0,0 +1,10 @@
1
+ export { SignInPage } from "./web/public/SignInPage.js";
2
+ export { SignUpPage } from "./web/public/SignUpPage.js";
3
+ export { ResetPassword } from "./web/public/ResetPassword.js";
4
+ export { DashboardPage } from "./web/auth/dashboard.js";
5
+ export { ProfilePage } from "./web/auth/profile.js";
6
+ export { ReglagePage } from "./web/auth/reglage.js";
7
+ export { AdminUsersPage } from "./web/admin/users.js";
8
+ export { AuthModuleDoc } from "./components/Doc.js";
9
+ export { default as authBuildConfig } from "./auth.build.config.js";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ // Client Components uniquement
2
+ export { SignInPage } from "./web/public/SignInPage.js";
3
+ export { SignUpPage } from "./web/public/SignUpPage.js";
4
+ export { ResetPassword } from "./web/public/ResetPassword.js";
5
+ export { DashboardPage } from "./web/auth/dashboard.js";
6
+ export { ProfilePage } from "./web/auth/profile.js";
7
+ export { ReglagePage } from "./web/auth/reglage.js";
8
+ export { AdminUsersPage } from "./web/admin/users.js";
9
+ // Documentation
10
+ export { AuthModuleDoc } from "./components/Doc.js";
11
+ // Configuration de build (utilisée par les scripts)
12
+ export { default as authBuildConfig } from "./auth.build.config.js";
@@ -0,0 +1,2 @@
1
+ export { POST as signInApi } from "./api/public/signin.js";
2
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,IAAI,SAAS,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/server.js ADDED
@@ -0,0 +1,2 @@
1
+ // Server-only exports (Route Handlers, Server Actions, etc.)
2
+ export { POST as signInApi } from "./api/public/signin.js";
@@ -0,0 +1,2 @@
1
+ export declare function AdminUsersPage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=users.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../../src/web/admin/users.tsx"],"names":[],"mappings":"AAAA,wBAAgB,cAAc,4CAE7B"}
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export function AdminUsersPage() {
3
+ return _jsx("div", { children: "Admin Users Page" });
4
+ }
@@ -0,0 +1,2 @@
1
+ export declare function DashboardPage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=dashboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../../src/web/auth/dashboard.tsx"],"names":[],"mappings":"AAAA,wBAAgB,aAAa,4CAE5B"}
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export function DashboardPage() {
3
+ return _jsx("div", { className: "pt-12", children: "Welcome to your dashboard!" });
4
+ }
@@ -0,0 +1,2 @@
1
+ export declare function ProfilePage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=profile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../../src/web/auth/profile.tsx"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,4CAE1B"}
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export function ProfilePage() {
3
+ return _jsx("div", { className: "pt-12", children: "Welcome to your Profile!" });
4
+ }
@@ -0,0 +1,2 @@
1
+ export declare function ReglagePage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=reglage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reglage.d.ts","sourceRoot":"","sources":["../../../src/web/auth/reglage.tsx"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,4CAE1B"}
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export function ReglagePage() {
3
+ return _jsx("div", { className: "pt-12", children: "Welcome to your Reglage!" });
4
+ }
@@ -0,0 +1,2 @@
1
+ export declare function ResetPassword(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=ResetPassword.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResetPassword.d.ts","sourceRoot":"","sources":["../../../src/web/public/ResetPassword.tsx"],"names":[],"mappings":"AAAA,wBAAgB,aAAa,4CAE5B"}
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export function ResetPassword() {
3
+ return _jsx("div", { children: "Reset Password Page" });
4
+ }
@@ -0,0 +1,2 @@
1
+ export declare function SignInPage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=SignInPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SignInPage.d.ts","sourceRoot":"","sources":["../../../src/web/public/SignInPage.tsx"],"names":[],"mappings":"AAwPA,wBAAgB,UAAU,4CAMzB"}
@@ -0,0 +1,108 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Suspense, useState } from "react";
4
+ import { supabaseBrowserClient } from "@lastbrain/core";
5
+ import { addToast, Button, Card, CardBody, Chip, Input, Link, } from "@lastbrain/ui";
6
+ import { ArrowRight, Lock, Mail, Sparkles } from "lucide-react";
7
+ import { useRouter, useSearchParams } from "next/navigation";
8
+ function SignInForm() {
9
+ const searchParams = useSearchParams();
10
+ const [email, setEmail] = useState("");
11
+ const [password, setPassword] = useState("");
12
+ const [isLoading, setIsLoading] = useState(false);
13
+ const [error, setError] = useState(null);
14
+ const redirectUrl = searchParams.get("redirect");
15
+ const router = useRouter();
16
+ const handleSubmit = async (event) => {
17
+ event.preventDefault();
18
+ setIsLoading(true);
19
+ try {
20
+ const { data, error } = await supabaseBrowserClient.auth.signInWithPassword({
21
+ email,
22
+ password,
23
+ });
24
+ if (error) {
25
+ addToast({
26
+ color: "danger",
27
+ title: "Erreur de connexion",
28
+ description: error.message,
29
+ });
30
+ setIsLoading(false);
31
+ return;
32
+ }
33
+ setIsLoading(false);
34
+ console.log("Signed in user:", data.user);
35
+ addToast({
36
+ color: "success",
37
+ title: "Connecté avec succès !",
38
+ description: `Bienvenue ${data.user?.email}`,
39
+ });
40
+ router.push(redirectUrl || "/auth/dashboard");
41
+ }
42
+ catch (err) {
43
+ setIsLoading(false);
44
+ addToast({
45
+ color: "danger",
46
+ title: "Erreur de connexion",
47
+ description: `${err instanceof Error ? err.message : String(err)}`,
48
+ });
49
+ }
50
+ };
51
+ // return (
52
+ // <div className=" min-h-screen h-full flex flex-col justify-center items-center p-4">
53
+ // <Card className="w-full max-w-md">
54
+ // <CardHeader className="flex flex-col">
55
+ // <h1 className="text-2xl font-semibold text-slate-900 dark:text-white">
56
+ // Connexion
57
+ // </h1>
58
+ // <p className="mt-1 text-sm text-slate-500">
59
+ // Utilisez votre adresse email pour vous connecter.
60
+ // </p>
61
+ // </CardHeader>
62
+ // <CardBody>
63
+ // <form onSubmit={handleSubmit}>
64
+ // <div className="space-y-6">
65
+ // <Input
66
+ // label="Email"
67
+ // type="email"
68
+ // className="mb-6"
69
+ // required
70
+ // value={email}
71
+ // onChange={(event) => setEmail(event.target.value)}
72
+ // />
73
+ // <Input
74
+ // label="Mot de passe"
75
+ // type="password"
76
+ // required
77
+ // placeholder="Password"
78
+ // className="mb-6"
79
+ // value={password}
80
+ // onChange={(event) => setPassword(event.target.value)}
81
+ // />
82
+ // <Button
83
+ // size="lg"
84
+ // type="submit"
85
+ // className="w-full"
86
+ // isLoading={isLoading}
87
+ // >
88
+ // Se connecter
89
+ // </Button>
90
+ // </div>
91
+ // </form>
92
+ // </CardBody>
93
+ // </Card>
94
+ // </div>
95
+ // );
96
+ return (_jsxs("div", { className: "pt-12 relative min-h-screen w-full overflow-hidden bg-linear-to-br from-primary-50/30 to-secondary-50/30 dark:from-primary-950/50 dark:to-secondary-950/50", children: [_jsx("div", { className: "absolute inset-0 bg-grid-slate-100 dark:bg-grid-slate-800/20 mask-[linear-gradient(0deg,white,rgba(255,255,255,0.6))] dark:mask-[linear-gradient(0deg,rgba(255,255,255,0.1),rgba(255,255,255,0.05))]" }), _jsx("div", { className: "relative flex min-h-[60vh] items-center justify-center px-4 py-12", children: _jsxs("div", { className: "w-full max-w-md", children: [_jsxs("div", { className: "mb-8 text-center", children: [_jsx(Chip, { color: "primary", variant: "flat", startContent: _jsx(Sparkles, { className: "w-4 h-4" }), className: "mb-4 p-2", children: "Espace membre" }), _jsx("h1", { className: "mb-3 text-4xl font-bold", children: _jsx("span", { className: "bg-linear-to-r from-primary-600 to-secondary-600 bg-clip-text text-transparent", children: "Bon retour" }) }), _jsx("p", { className: "text-default-600 dark:text-default-400", children: "Connectez-vous pour acc\u00E9der \u00E0 votre espace" })] }), _jsx(Card, { className: "border border-default-200/60 bg-background/60 backdrop-blur-md backdrop-saturate-150 shadow-xl", children: _jsxs(CardBody, { className: "gap-6 p-8", children: [_jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-6", children: [_jsx(Input, { label: "Email", type: "email", placeholder: "votre@email.com", value: email, onChange: (e) => setEmail(e.target.value), required: true, startContent: _jsx(Mail, { className: "h-4 w-4 text-default-400" }), classNames: {
97
+ input: "text-base",
98
+ inputWrapper: "border border-default-200/60 hover:border-primary-400 focus-within:border-primary-500",
99
+ } }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Input, { label: "Mot de passe", type: "password", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", value: password, onChange: (e) => setPassword(e.target.value), required: true, minLength: 6, startContent: _jsx(Lock, { className: "h-4 w-4 text-default-400" }), classNames: {
100
+ input: "text-base",
101
+ inputWrapper: "border border-default-200/60 hover:border-primary-400 focus-within:border-primary-500",
102
+ } }), _jsx(Link, { href: "/forgot-password", className: "text-xs text-default-500 hover:text-primary-500 self-end", children: "Mot de passe oubli\u00E9 ?" })] }), error && (_jsx("div", { className: "rounded-lg border border-danger-200 bg-danger-50/50 px-4 py-3 dark:border-danger-800 dark:bg-danger-950/50", children: _jsx("p", { className: "text-sm text-danger-600 dark:text-danger-400", children: error }) })), _jsx(Button, { type: "submit", color: "primary", size: "lg", className: "w-full font-semibold", endContent: isLoading ? null : _jsx(ArrowRight, { className: "h-5 w-5" }), isLoading: isLoading, children: "Se connecter" })] }), _jsxs("div", { className: "relative flex items-center gap-4 py-2", children: [_jsx("div", { className: "h-px flex-1 bg-default-200" }), _jsx("span", { className: "text-xs text-default-400", children: "OU" }), _jsx("div", { className: "h-px flex-1 bg-default-200" })] }), _jsx("div", { className: "text-center", children: _jsxs("p", { className: "text-sm text-default-600", children: ["Pas encore de compte ?", " ", _jsx(Link, { href: redirectUrl
103
+ ? `/signup?redirect=${encodeURIComponent(redirectUrl)}`
104
+ : "/signup", className: "font-semibold text-primary-600 hover:text-primary-700", children: "Cr\u00E9er un compte" })] }) })] }) }), _jsxs("p", { className: "mt-8 text-center text-xs text-default-400", children: ["En vous connectant, vous acceptez nos", " ", _jsx(Link, { href: "/legal/terms", className: "underline hover:text-primary-500", children: "conditions d'utilisation" })] })] }) })] }));
105
+ }
106
+ export function SignInPage() {
107
+ return (_jsx(Suspense, { fallback: _jsx("div", { children: "Chargement..." }), children: _jsx(SignInForm, {}) }));
108
+ }
@@ -0,0 +1,2 @@
1
+ export declare function SignUpPage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=SignUpPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SignUpPage.d.ts","sourceRoot":"","sources":["../../../src/web/public/SignUpPage.tsx"],"names":[],"mappings":"AA8RA,wBAAgB,UAAU,4CAMzB"}
@@ -0,0 +1,102 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Button, Card, CardBody, Input, Link, Chip, addToast, } from "@lastbrain/ui";
4
+ import { useRouter, useSearchParams } from "next/navigation";
5
+ import { Suspense, useState } from "react";
6
+ import { Mail, Lock, User, ArrowRight, Sparkles, CheckCircle2, } from "lucide-react";
7
+ import { supabaseBrowserClient } from "@lastbrain/core";
8
+ function SignUpForm() {
9
+ const router = useRouter();
10
+ const searchParams = useSearchParams();
11
+ const [fullName, setFullName] = useState("");
12
+ const [email, setEmail] = useState("");
13
+ const [password, setPassword] = useState("");
14
+ const [confirmPassword, setConfirmPassword] = useState("");
15
+ const [loading, setLoading] = useState(false);
16
+ const [error, setError] = useState(null);
17
+ const [success, setSuccess] = useState(null);
18
+ // Récupérer le paramètre redirect
19
+ const redirectUrl = searchParams.get("redirect");
20
+ const handleSubmit = async (event) => {
21
+ event.preventDefault();
22
+ setError(null);
23
+ setSuccess(null);
24
+ if (password !== confirmPassword) {
25
+ setError("Les mots de passe ne correspondent pas.");
26
+ return;
27
+ }
28
+ if (password.length < 6) {
29
+ setError("Le mot de passe doit contenir au moins 6 caractères.");
30
+ return;
31
+ }
32
+ setLoading(true);
33
+ try {
34
+ const { data, error: signUpError } = await supabaseBrowserClient.auth.signUp({
35
+ email,
36
+ password,
37
+ options: {
38
+ emailRedirectTo: `${window.location.origin}/api/auth/callback${redirectUrl ? `?next=${encodeURIComponent(redirectUrl)}` : ""}`,
39
+ data: {
40
+ full_name: fullName,
41
+ },
42
+ },
43
+ });
44
+ if (signUpError) {
45
+ setError(signUpError.message);
46
+ return;
47
+ }
48
+ // Si la confirmation par email est requise
49
+ if (data.user && !data.session) {
50
+ setSuccess("Compte créé avec succès ! Veuillez vérifier votre email pour confirmer votre compte.");
51
+ return;
52
+ }
53
+ // Si l'utilisateur est directement connecté (confirmation email désactivée)
54
+ if (data.session) {
55
+ if (redirectUrl) {
56
+ window.location.href = redirectUrl;
57
+ }
58
+ else {
59
+ window.location.href = "/auth/dashboard";
60
+ }
61
+ return;
62
+ }
63
+ setSuccess("Compte créé. Vous pouvez désormais vous connecter.");
64
+ setTimeout(() => {
65
+ if (redirectUrl) {
66
+ router.push(`/signin?redirect=${encodeURIComponent(redirectUrl)}`);
67
+ }
68
+ else {
69
+ router.push("/signin");
70
+ }
71
+ }, 2000);
72
+ }
73
+ catch (err) {
74
+ addToast({
75
+ title: "Erreur",
76
+ description: "Une erreur inattendue est survenue.",
77
+ color: "danger",
78
+ });
79
+ }
80
+ finally {
81
+ setLoading(false);
82
+ }
83
+ };
84
+ return (_jsxs("div", { className: "pt-12 relative min-h-screen w-full overflow-hidden bg-linear-to-br from-secondary-50/30 to-primary-50/30 dark:from-secondary-950/50 dark:to-primary-950/50", children: [_jsx("div", { className: "absolute inset-0 bg-grid-slate-100 dark:bg-grid-slate-800/20 mask-[linear-gradient(0deg,white,rgba(255,255,255,0.6))] dark:mask-[linear-gradient(0deg,rgba(255,255,255,0.1),rgba(255,255,255,0.05))]" }), _jsx("div", { className: "relative flex min-h-screen items-center justify-center px-4 py-12", children: _jsxs("div", { className: "w-full max-w-md", children: [_jsxs("div", { className: "mb-8 text-center", children: [_jsx(Chip, { color: "secondary", variant: "flat", startContent: _jsx(Sparkles, { className: "w-4 h-4" }), className: "mb-4 p-2", children: "Rejoignez-nous" }), _jsx("h1", { className: "mb-3 text-4xl font-bold", children: _jsx("span", { className: "bg-linear-to-r from-secondary-600 to-primary-600 bg-clip-text text-transparent", children: "Commencer gratuitement" }) }), _jsx("p", { className: "text-default-600 dark:text-default-400", children: "Cr\u00E9ez votre compte en quelques secondes" })] }), _jsx(Card, { className: "border border-default-200/60 bg-background/60 backdrop-blur-md backdrop-saturate-150 shadow-xl", children: _jsxs(CardBody, { className: "gap-6 p-8", children: [_jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-6", children: [_jsx(Input, { label: "Nom complet", type: "text", placeholder: "Jean Dupont", value: fullName, onChange: (e) => setFullName(e.target.value), startContent: _jsx(User, { className: "h-4 w-4 text-default-400" }), classNames: {
85
+ input: "text-base",
86
+ inputWrapper: "border border-default-200/60 hover:border-secondary-400 focus-within:border-secondary-500",
87
+ } }), _jsx(Input, { label: "Email", type: "email", placeholder: "votre@email.com", value: email, onChange: (e) => setEmail(e.target.value), required: true, startContent: _jsx(Mail, { className: "h-4 w-4 text-default-400" }), classNames: {
88
+ input: "text-base",
89
+ inputWrapper: "border border-default-200/60 hover:border-secondary-400 focus-within:border-secondary-500",
90
+ } }), _jsx(Input, { label: "Mot de passe", type: "password", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", value: password, onChange: (e) => setPassword(e.target.value), required: true, minLength: 6, startContent: _jsx(Lock, { className: "h-4 w-4 text-default-400" }), classNames: {
91
+ input: "text-base",
92
+ inputWrapper: "border border-default-200/60 hover:border-secondary-400 focus-within:border-secondary-500",
93
+ }, description: "Minimum 6 caract\u00E8res" }), _jsx(Input, { label: "Confirmer le mot de passe", type: "password", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value), required: true, minLength: 6, startContent: _jsx(Lock, { className: "h-4 w-4 text-default-400" }), classNames: {
94
+ input: "text-base",
95
+ inputWrapper: "border border-default-200/60 hover:border-secondary-400 focus-within:border-secondary-500",
96
+ } }), error && (_jsx("div", { className: "rounded-lg border border-danger-200 bg-danger-50/50 px-4 py-3 dark:border-danger-800 dark:bg-danger-950/50", children: _jsx("p", { className: "text-sm text-danger-600 dark:text-danger-400", children: error }) })), success && (_jsx("div", { className: "rounded-lg border border-success-200 bg-success-50/50 px-4 py-3 dark:border-success-800 dark:bg-success-950/50", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-success-600 dark:text-success-400" }), _jsx("p", { className: "text-sm text-success-600 dark:text-success-400", children: success })] }) })), _jsx(Button, { type: "submit", color: "secondary", size: "lg", className: "w-full font-semibold", endContent: loading ? null : _jsx(ArrowRight, { className: "h-5 w-5" }), isLoading: loading, children: "Cr\u00E9er mon compte" })] }), _jsxs("div", { className: "relative flex items-center gap-4 py-2", children: [_jsx("div", { className: "h-px flex-1 bg-default-200" }), _jsx("span", { className: "text-xs text-default-400", children: "OU" }), _jsx("div", { className: "h-px flex-1 bg-default-200" })] }), _jsx("div", { className: "text-center", children: _jsxs("p", { className: "text-sm text-default-600", children: ["D\u00E9j\u00E0 inscrit ?", " ", _jsx(Link, { href: redirectUrl
97
+ ? `/signin?redirect=${encodeURIComponent(redirectUrl)}`
98
+ : "/signin", className: "font-semibold text-secondary-600 hover:text-secondary-700", children: "Se connecter" })] }) })] }) }), _jsxs("p", { className: "mt-8 text-center text-xs text-default-400", children: ["En cr\u00E9ant un compte, vous acceptez nos", " ", _jsx(Link, { href: "/legal/terms", className: "underline hover:text-secondary-500", children: "conditions d'utilisation" }), " ", "et notre", " ", _jsx(Link, { href: "/legal/privacy", className: "underline hover:text-secondary-500", children: "politique de confidentialit\u00E9" })] })] }) })] }));
99
+ }
100
+ export function SignUpPage() {
101
+ return (_jsx(Suspense, { fallback: _jsx("div", { children: "Chargement..." }), children: _jsx(SignUpForm, {}) }));
102
+ }
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@lastbrain/module-auth",
3
+ "version": "0.1.0",
4
+ "description": "Module d'authentification complet pour LastBrain avec Supabase",
5
+ "private": false,
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "keywords": [
10
+ "lastbrain",
11
+ "module",
12
+ "authentication",
13
+ "auth",
14
+ "supabase",
15
+ "login",
16
+ "signup"
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/lastpublication/starter.git",
21
+ "directory": "packages/module-auth"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "supabase"
29
+ ],
30
+ "dependencies": {
31
+ "@lastbrain/core": "0.1.0",
32
+ "@lastbrain/ui": "0.1.0",
33
+ "lucide-react": "^0.554.0",
34
+ "react": "^19.0.0",
35
+ "react-dom": "^19.0.0"
36
+ },
37
+ "peerDependencies": {
38
+ "next": ">=15.0.0"
39
+ },
40
+ "devDependencies": {
41
+ "next": "^15.5.6",
42
+ "typescript": "^5.4.0"
43
+ },
44
+ "exports": {
45
+ ".": {
46
+ "types": "./dist/index.d.ts",
47
+ "default": "./dist/index.js"
48
+ },
49
+ "./server": {
50
+ "types": "./dist/server.d.ts",
51
+ "default": "./dist/server.js"
52
+ },
53
+ "./auth.build.config": {
54
+ "types": "./dist/auth.build.config.d.ts",
55
+ "default": "./dist/auth.build.config.js"
56
+ },
57
+ "./api/*": {
58
+ "types": "./dist/api/*.d.ts",
59
+ "default": "./dist/api/*.js"
60
+ }
61
+ },
62
+ "sideEffects": false,
63
+ "scripts": {
64
+ "build": "tsc -p tsconfig.json",
65
+ "dev": "tsc -p tsconfig.json --watch"
66
+ }
67
+ }
@@ -0,0 +1,175 @@
1
+ -- User authentication and profile tables
2
+ -- Module: @lastbrain/module-auth
3
+
4
+ -- =====================================================
5
+ -- Table: user_profil
6
+ -- =====================================================
7
+ CREATE TABLE IF NOT EXISTS public.user_profil (
8
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
9
+ owner_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
10
+ first_name TEXT,
11
+ last_name TEXT,
12
+ avatar_url TEXT,
13
+ bio TEXT,
14
+ phone TEXT,
15
+ company TEXT,
16
+ website TEXT,
17
+ location TEXT,
18
+ language TEXT DEFAULT 'en',
19
+ timezone TEXT,
20
+ preferences JSONB DEFAULT '{}'::jsonb,
21
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
22
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
23
+ );
24
+
25
+ CREATE INDEX IF NOT EXISTS idx_user_profil_owner_id ON public.user_profil(owner_id);
26
+
27
+ -- RLS
28
+ ALTER TABLE public.user_profil ENABLE ROW LEVEL SECURITY;
29
+
30
+ DROP POLICY IF EXISTS user_profil_owner_select ON public.user_profil;
31
+ CREATE POLICY user_profil_owner_select ON public.user_profil
32
+ FOR SELECT USING (owner_id = auth.uid());
33
+
34
+ DROP POLICY IF EXISTS user_profil_owner_insert ON public.user_profil;
35
+ CREATE POLICY user_profil_owner_insert ON public.user_profil
36
+ FOR INSERT WITH CHECK (owner_id = auth.uid());
37
+
38
+ DROP POLICY IF EXISTS user_profil_owner_update ON public.user_profil;
39
+ CREATE POLICY user_profil_owner_update ON public.user_profil
40
+ FOR UPDATE USING (owner_id = auth.uid());
41
+
42
+ DROP POLICY IF EXISTS user_profil_owner_delete ON public.user_profil;
43
+ CREATE POLICY user_profil_owner_delete ON public.user_profil
44
+ FOR DELETE USING (owner_id = auth.uid());
45
+
46
+ DROP POLICY IF EXISTS user_profil_superadmin_all ON public.user_profil;
47
+ CREATE POLICY user_profil_superadmin_all ON public.user_profil
48
+ FOR ALL USING (is_superadmin(auth.uid()));
49
+
50
+ -- Trigger updated_at
51
+ CREATE OR REPLACE FUNCTION public.set_user_profil_updated_at()
52
+ RETURNS TRIGGER AS $$
53
+ BEGIN
54
+ NEW.updated_at = now();
55
+ RETURN NEW;
56
+ END;
57
+ $$ LANGUAGE plpgsql;
58
+
59
+ DROP TRIGGER IF EXISTS set_user_profil_updated_at ON public.user_profil;
60
+ CREATE TRIGGER set_user_profil_updated_at
61
+ BEFORE UPDATE ON public.user_profil
62
+ FOR EACH ROW
63
+ EXECUTE FUNCTION public.set_user_profil_updated_at();
64
+
65
+ -- =====================================================
66
+ -- Table: user_address
67
+ -- =====================================================
68
+ CREATE TABLE IF NOT EXISTS public.user_address (
69
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
70
+ owner_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
71
+ street TEXT NOT NULL,
72
+ street2 TEXT,
73
+ city TEXT NOT NULL,
74
+ state TEXT,
75
+ postal_code TEXT,
76
+ country TEXT NOT NULL,
77
+ is_default BOOLEAN DEFAULT false,
78
+ address_type TEXT DEFAULT 'billing',
79
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
80
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
81
+ );
82
+
83
+ CREATE INDEX IF NOT EXISTS idx_user_address_owner_id ON public.user_address(owner_id);
84
+
85
+ -- RLS
86
+ ALTER TABLE public.user_address ENABLE ROW LEVEL SECURITY;
87
+
88
+ DROP POLICY IF EXISTS user_address_owner_select ON public.user_address;
89
+ CREATE POLICY user_address_owner_select ON public.user_address
90
+ FOR SELECT USING (owner_id = auth.uid());
91
+
92
+ DROP POLICY IF EXISTS user_address_owner_insert ON public.user_address;
93
+ CREATE POLICY user_address_owner_insert ON public.user_address
94
+ FOR INSERT WITH CHECK (owner_id = auth.uid());
95
+
96
+ DROP POLICY IF EXISTS user_address_owner_update ON public.user_address;
97
+ CREATE POLICY user_address_owner_update ON public.user_address
98
+ FOR UPDATE USING (owner_id = auth.uid());
99
+
100
+ DROP POLICY IF EXISTS user_address_owner_delete ON public.user_address;
101
+ CREATE POLICY user_address_owner_delete ON public.user_address
102
+ FOR DELETE USING (owner_id = auth.uid());
103
+
104
+ DROP POLICY IF EXISTS user_address_superadmin_all ON public.user_address;
105
+ CREATE POLICY user_address_superadmin_all ON public.user_address
106
+ FOR ALL USING (is_superadmin(auth.uid()));
107
+
108
+ -- Trigger updated_at
109
+ CREATE OR REPLACE FUNCTION public.set_user_address_updated_at()
110
+ RETURNS TRIGGER AS $$
111
+ BEGIN
112
+ NEW.updated_at = now();
113
+ RETURN NEW;
114
+ END;
115
+ $$ LANGUAGE plpgsql;
116
+
117
+ DROP TRIGGER IF EXISTS set_user_address_updated_at ON public.user_address;
118
+ CREATE TRIGGER set_user_address_updated_at
119
+ BEFORE UPDATE ON public.user_address
120
+ FOR EACH ROW
121
+ EXECUTE FUNCTION public.set_user_address_updated_at();
122
+
123
+ -- =====================================================
124
+ -- Table: user_notifications
125
+ -- =====================================================
126
+ CREATE TABLE IF NOT EXISTS public.user_notifications (
127
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
128
+ owner_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
129
+ title TEXT NOT NULL,
130
+ body TEXT,
131
+ type TEXT NOT NULL DEFAULT 'primary',
132
+ read BOOLEAN NOT NULL DEFAULT false,
133
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
134
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
135
+ );
136
+
137
+ CREATE INDEX IF NOT EXISTS idx_user_notifications_owner_id ON public.user_notifications(owner_id);
138
+
139
+ -- RLS
140
+ ALTER TABLE public.user_notifications ENABLE ROW LEVEL SECURITY;
141
+
142
+ DROP POLICY IF EXISTS user_notifications_owner_or_superadmin_select ON public.user_notifications;
143
+ CREATE POLICY user_notifications_owner_or_superadmin_select ON public.user_notifications
144
+ FOR SELECT USING (owner_id = auth.uid() OR is_superadmin(auth.uid()));
145
+
146
+ DROP POLICY IF EXISTS user_notifications_owner_or_superadmin_insert ON public.user_notifications;
147
+ CREATE POLICY user_notifications_owner_or_superadmin_insert ON public.user_notifications
148
+ FOR INSERT WITH CHECK (owner_id = auth.uid() OR is_superadmin(auth.uid()));
149
+
150
+ DROP POLICY IF EXISTS user_notifications_owner_or_superadmin_update ON public.user_notifications;
151
+ CREATE POLICY user_notifications_owner_or_superadmin_update ON public.user_notifications
152
+ FOR UPDATE USING (owner_id = auth.uid() OR is_superadmin(auth.uid()));
153
+
154
+ DROP POLICY IF EXISTS user_notifications_owner_or_superadmin_delete ON public.user_notifications;
155
+ CREATE POLICY user_notifications_owner_or_superadmin_delete ON public.user_notifications
156
+ FOR DELETE USING (owner_id = auth.uid() OR is_superadmin(auth.uid()));
157
+
158
+ DROP POLICY IF EXISTS user_notifications_superadmin_all ON public.user_notifications;
159
+ CREATE POLICY user_notifications_superadmin_all ON public.user_notifications
160
+ FOR ALL USING (is_superadmin(auth.uid()));
161
+
162
+ -- Trigger updated_at
163
+ CREATE OR REPLACE FUNCTION public.set_user_notifications_updated_at()
164
+ RETURNS TRIGGER AS $$
165
+ BEGIN
166
+ NEW.updated_at = now();
167
+ RETURN NEW;
168
+ END;
169
+ $$ LANGUAGE plpgsql;
170
+
171
+ DROP TRIGGER IF EXISTS set_user_notifications_updated_at ON public.user_notifications;
172
+ CREATE TRIGGER set_user_notifications_updated_at
173
+ BEFORE UPDATE ON public.user_notifications
174
+ FOR EACH ROW
175
+ EXECUTE FUNCTION public.set_user_notifications_updated_at();
@@ -0,0 +1,54 @@
1
+ -- Down migration for user_init module
2
+ -- Module: @lastbrain/module-auth
3
+
4
+ -- =====================================================
5
+ -- user_notifications
6
+ -- =====================================================
7
+ DROP TRIGGER IF EXISTS set_user_notifications_updated_at ON public.user_notifications;
8
+ DROP FUNCTION IF EXISTS public.set_user_notifications_updated_at();
9
+
10
+ DROP POLICY IF EXISTS user_notifications_superadmin_all ON public.user_notifications;
11
+ DROP POLICY IF EXISTS user_notifications_owner_or_superadmin_delete ON public.user_notifications;
12
+ DROP POLICY IF EXISTS user_notifications_owner_or_superadmin_update ON public.user_notifications;
13
+ DROP POLICY IF EXISTS user_notifications_owner_or_superadmin_insert ON public.user_notifications;
14
+ DROP POLICY IF EXISTS user_notifications_owner_or_superadmin_select ON public.user_notifications;
15
+ ALTER TABLE IF EXISTS public.user_notifications DISABLE ROW LEVEL SECURITY;
16
+
17
+ DROP INDEX IF EXISTS idx_user_notifications_owner_id;
18
+
19
+ DROP TABLE IF EXISTS public.user_notifications;
20
+
21
+ -- =====================================================
22
+ -- user_address
23
+ -- =====================================================
24
+ DROP TRIGGER IF EXISTS set_user_address_updated_at ON public.user_address;
25
+ DROP FUNCTION IF EXISTS public.set_user_address_updated_at();
26
+
27
+ DROP POLICY IF EXISTS user_address_superadmin_all ON public.user_address;
28
+ DROP POLICY IF EXISTS user_address_owner_delete ON public.user_address;
29
+ DROP POLICY IF EXISTS user_address_owner_update ON public.user_address;
30
+ DROP POLICY IF EXISTS user_address_owner_insert ON public.user_address;
31
+ DROP POLICY IF EXISTS user_address_owner_select ON public.user_address;
32
+ ALTER TABLE IF EXISTS public.user_address DISABLE ROW LEVEL SECURITY;
33
+
34
+ DROP INDEX IF EXISTS idx_user_address_owner_id;
35
+
36
+ DROP TABLE IF EXISTS public.user_address;
37
+
38
+ -- =====================================================
39
+ -- user_profil
40
+ -- =====================================================
41
+ DROP TRIGGER IF EXISTS set_user_profil_updated_at ON public.user_profil;
42
+ DROP FUNCTION IF EXISTS public.set_user_profil_updated_at();
43
+
44
+ DROP POLICY IF EXISTS user_profil_superadmin_all ON public.user_profil;
45
+ DROP POLICY IF EXISTS user_profil_owner_delete ON public.user_profil;
46
+ DROP POLICY IF EXISTS user_profil_owner_update ON public.user_profil;
47
+ DROP POLICY IF EXISTS user_profil_owner_insert ON public.user_profil;
48
+ DROP POLICY IF EXISTS user_profil_owner_select ON public.user_profil;
49
+ ALTER TABLE IF EXISTS public.user_profil DISABLE ROW LEVEL SECURITY;
50
+
51
+ DROP INDEX IF EXISTS idx_user_profil_owner_id;
52
+
53
+ DROP TABLE IF EXISTS public.user_profil;
54
+