@lastbrain/module-auth 0.1.2 → 0.1.4

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 (48) hide show
  1. package/README.md +533 -0
  2. package/dist/api/admin/users.d.ts +9 -0
  3. package/dist/api/admin/users.d.ts.map +1 -0
  4. package/dist/api/admin/users.js +38 -0
  5. package/dist/api/auth/me.d.ts +17 -0
  6. package/dist/api/auth/me.d.ts.map +1 -0
  7. package/dist/api/auth/me.js +32 -0
  8. package/dist/api/auth/profile.d.ts +32 -0
  9. package/dist/api/auth/profile.d.ts.map +1 -0
  10. package/dist/api/auth/profile.js +104 -0
  11. package/dist/api/public/signin.js +3 -3
  12. package/dist/api/storage.d.ts +13 -0
  13. package/dist/api/storage.d.ts.map +1 -0
  14. package/dist/api/storage.js +47 -0
  15. package/dist/auth.build.config.d.ts.map +1 -1
  16. package/dist/auth.build.config.js +42 -2
  17. package/dist/web/admin/users.d.ts.map +1 -1
  18. package/dist/web/admin/users.js +94 -2
  19. package/dist/web/auth/dashboard.d.ts +1 -1
  20. package/dist/web/auth/dashboard.d.ts.map +1 -1
  21. package/dist/web/auth/dashboard.js +42 -2
  22. package/dist/web/auth/profile.d.ts.map +1 -1
  23. package/dist/web/auth/profile.js +191 -2
  24. package/dist/web/auth/reglage.d.ts.map +1 -1
  25. package/dist/web/auth/reglage.js +98 -2
  26. package/dist/web/public/SignInPage.d.ts.map +1 -1
  27. package/dist/web/public/SignInPage.js +1 -1
  28. package/dist/web/public/SignUpPage.js +1 -1
  29. package/package.json +8 -7
  30. package/src/api/admin/users.ts +51 -0
  31. package/src/api/auth/me.ts +39 -0
  32. package/src/api/auth/profile.ts +142 -0
  33. package/src/api/public/signin.ts +3 -3
  34. package/src/api/storage.ts +66 -0
  35. package/src/auth.build.config.ts +42 -2
  36. package/src/web/admin/users.tsx +290 -1
  37. package/src/web/auth/dashboard.tsx +207 -1
  38. package/src/web/auth/profile.tsx +420 -1
  39. package/src/web/auth/reglage.tsx +284 -1
  40. package/src/web/public/SignInPage.tsx +1 -2
  41. package/src/web/public/SignUpPage.tsx +2 -2
  42. package/supabase/.temp/cli-latest +1 -0
  43. package/supabase/migrations/20251112000000_user_init.sql +1 -1
  44. package/supabase/migrations/20251112000001_auto_profile_and_admin_view.sql +206 -0
  45. package/supabase/migrations/20251112000002_sync_avatars.sql +54 -0
  46. package/supabase/migrations-down/20251112000000_user_init.down.sql +2 -0
  47. package/supabase/migrations-down/20251112000001_auto_profile_and_admin_view.down.sql +23 -0
  48. package/supabase/migrations-down/20251112000002_sync_avatars.down.sql +9 -0
@@ -1,4 +1,100 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useState } from "react";
4
+ import { Card, CardBody, CardHeader, Switch, Button, Spinner, Divider, Select, SelectItem, addToast, } from "@lastbrain/ui";
5
+ import { Settings, Save } from "lucide-react";
2
6
  export function ReglagePage() {
3
- return _jsx("div", { className: "pt-12", children: "Welcome to your Reglage!" });
7
+ const [preferences, setPreferences] = useState({
8
+ email_notifications: true,
9
+ push_notifications: false,
10
+ marketing_emails: false,
11
+ theme: "system",
12
+ language: "en",
13
+ timezone: "UTC",
14
+ });
15
+ const [isLoading, setIsLoading] = useState(true);
16
+ const [isSaving, setIsSaving] = useState(false);
17
+ useEffect(() => {
18
+ fetchSettings();
19
+ }, []);
20
+ const fetchSettings = async () => {
21
+ try {
22
+ setIsLoading(true);
23
+ const response = await fetch("/api/auth/profile");
24
+ if (!response.ok) {
25
+ throw new Error("Failed to fetch settings");
26
+ }
27
+ const result = await response.json();
28
+ if (result.data) {
29
+ const profile = result.data;
30
+ setPreferences((prev) => ({
31
+ ...prev,
32
+ language: profile.language || prev.language,
33
+ timezone: profile.timezone || prev.timezone,
34
+ ...(profile.preferences || {}),
35
+ }));
36
+ }
37
+ }
38
+ catch (err) {
39
+ console.error("Error loading settings:", err);
40
+ addToast({
41
+ title: "Error",
42
+ description: "Failed to load settings",
43
+ color: "danger",
44
+ });
45
+ }
46
+ finally {
47
+ setIsLoading(false);
48
+ }
49
+ };
50
+ const handleSave = async () => {
51
+ setIsSaving(true);
52
+ try {
53
+ const response = await fetch("/api/auth/profile", {
54
+ method: "PUT",
55
+ headers: {
56
+ "Content-Type": "application/json",
57
+ },
58
+ body: JSON.stringify({
59
+ language: preferences.language,
60
+ timezone: preferences.timezone,
61
+ preferences: {
62
+ email_notifications: preferences.email_notifications,
63
+ push_notifications: preferences.push_notifications,
64
+ marketing_emails: preferences.marketing_emails,
65
+ theme: preferences.theme,
66
+ },
67
+ }),
68
+ });
69
+ if (!response.ok) {
70
+ throw new Error("Failed to update settings");
71
+ }
72
+ addToast({
73
+ title: "Success",
74
+ description: "Settings updated successfully",
75
+ color: "success",
76
+ });
77
+ }
78
+ catch (err) {
79
+ console.error("Error updating settings:", err);
80
+ addToast({
81
+ title: "Error",
82
+ description: "Failed to update settings",
83
+ color: "danger",
84
+ });
85
+ }
86
+ finally {
87
+ setIsSaving(false);
88
+ }
89
+ };
90
+ const handleToggle = (key, value) => {
91
+ setPreferences((prev) => ({ ...prev, [key]: value }));
92
+ };
93
+ const handleSelect = (key, value) => {
94
+ setPreferences((prev) => ({ ...prev, [key]: value }));
95
+ };
96
+ if (isLoading) {
97
+ return (_jsx("div", { className: "flex justify-center items-center min-h-[400px]", children: _jsx(Spinner, { size: "lg", label: "Loading settings..." }) }));
98
+ }
99
+ return (_jsxs("div", { className: "pt-12 pb-12 max-w-4xl mx-auto px-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-8", children: [_jsx(Settings, { className: "w-8 h-8" }), _jsx("h1", { className: "text-3xl font-bold", children: "Account Settings" })] }), _jsxs("div", { className: "space-y-6", children: [_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: "Notifications" }) }), _jsx(Divider, {}), _jsx(CardBody, { children: _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex justify-between items-center", children: [_jsxs("div", { children: [_jsx("p", { className: "font-medium", children: "Email Notifications" }), _jsx("p", { className: "text-small text-default-500", children: "Receive email notifications for important updates" })] }), _jsx(Switch, { isSelected: preferences.email_notifications, onValueChange: (value) => handleToggle("email_notifications", value) })] }), _jsx(Divider, {}), _jsxs("div", { className: "flex justify-between items-center", children: [_jsxs("div", { children: [_jsx("p", { className: "font-medium", children: "Push Notifications" }), _jsx("p", { className: "text-small text-default-500", children: "Receive push notifications in your browser" })] }), _jsx(Switch, { isSelected: preferences.push_notifications, onValueChange: (value) => handleToggle("push_notifications", value) })] }), _jsx(Divider, {}), _jsxs("div", { className: "flex justify-between items-center", children: [_jsxs("div", { children: [_jsx("p", { className: "font-medium", children: "Marketing Emails" }), _jsx("p", { className: "text-small text-default-500", children: "Receive emails about new features and updates" })] }), _jsx(Switch, { isSelected: preferences.marketing_emails, onValueChange: (value) => handleToggle("marketing_emails", value) })] })] }) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: "Appearance" }) }), _jsx(Divider, {}), _jsx(CardBody, { children: _jsxs(Select, { label: "Theme", placeholder: "Select a theme", selectedKeys: preferences.theme ? [preferences.theme] : [], onChange: (e) => handleSelect("theme", e.target.value), children: [_jsx(SelectItem, { children: "Light" }, "light"), _jsx(SelectItem, { children: "Dark" }, "dark"), _jsx(SelectItem, { children: "System" }, "system")] }) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: "Language & Region" }) }), _jsx(Divider, {}), _jsx(CardBody, { children: _jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [_jsxs(Select, { label: "Language", placeholder: "Select a language", selectedKeys: preferences.language ? [preferences.language] : [], onChange: (e) => handleSelect("language", e.target.value), children: [_jsx(SelectItem, { children: "English" }, "en"), _jsx(SelectItem, { children: "Fran\u00E7ais" }, "fr"), _jsx(SelectItem, { children: "Espa\u00F1ol" }, "es"), _jsx(SelectItem, { children: "Deutsch" }, "de")] }), _jsxs(Select, { label: "Timezone", placeholder: "Select a timezone", selectedKeys: preferences.timezone ? [preferences.timezone] : [], onChange: (e) => handleSelect("timezone", e.target.value), children: [_jsx(SelectItem, { children: "UTC" }, "UTC"), _jsx(SelectItem, { children: "Europe/Paris" }, "Europe/Paris"), _jsx(SelectItem, { children: "America/New_York" }, "America/New_York"), _jsx(SelectItem, { children: "America/Los_Angeles" }, "America/Los_Angeles"), _jsx(SelectItem, { children: "Asia/Tokyo" }, "Asia/Tokyo")] })] }) })] }), _jsxs("div", { className: "flex justify-end gap-3", children: [_jsx(Button, { type: "button", variant: "flat", onPress: () => fetchSettings(), isDisabled: isSaving, children: "Reset" }), _jsx(Button, { type: "button", color: "primary", isLoading: isSaving, onPress: handleSave, startContent: !isSaving && _jsx(Save, { className: "w-4 h-4" }), children: isSaving ? "Saving..." : "Save Settings" })] })] })] }));
4
100
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SignInPage.d.ts","sourceRoot":"","sources":["../../../src/web/public/SignInPage.tsx"],"names":[],"mappings":"AAwPA,wBAAgB,UAAU,4CAMzB"}
1
+ {"version":3,"file":"SignInPage.d.ts","sourceRoot":"","sources":["../../../src/web/public/SignInPage.tsx"],"names":[],"mappings":"AAuPA,wBAAgB,UAAU,4CAMzB"}
@@ -10,7 +10,7 @@ function SignInForm() {
10
10
  const [email, setEmail] = useState("");
11
11
  const [password, setPassword] = useState("");
12
12
  const [isLoading, setIsLoading] = useState(false);
13
- const [error, setError] = useState(null);
13
+ const [error, _setError] = useState(null);
14
14
  const redirectUrl = searchParams.get("redirect");
15
15
  const router = useRouter();
16
16
  const handleSubmit = async (event) => {
@@ -70,7 +70,7 @@ function SignUpForm() {
70
70
  }
71
71
  }, 2000);
72
72
  }
73
- catch (err) {
73
+ catch {
74
74
  addToast({
75
75
  title: "Erreur",
76
76
  description: "Une erreur inattendue est survenue.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lastbrain/module-auth",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Module d'authentification complet pour LastBrain avec Supabase",
5
5
  "private": false,
6
6
  "type": "module",
@@ -28,10 +28,6 @@
28
28
  "src",
29
29
  "supabase"
30
30
  ],
31
- "scripts": {
32
- "build": "tsc -p tsconfig.json",
33
- "dev": "tsc -p tsconfig.json --watch"
34
- },
35
31
  "dependencies": {
36
32
  "@lastbrain/core": "^0.1.0",
37
33
  "@lastbrain/ui": "^0.1.4",
@@ -64,5 +60,10 @@
64
60
  "default": "./dist/api/*.js"
65
61
  }
66
62
  },
67
- "sideEffects": false
68
- }
63
+ "sideEffects": false,
64
+ "scripts": {
65
+ "build": "tsc -p tsconfig.json",
66
+ "dev": "tsc -p tsconfig.json --watch",
67
+ "lint": "eslint ."
68
+ }
69
+ }
@@ -0,0 +1,51 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { getSupabaseServerClient } from "@lastbrain/core/server";
3
+
4
+ /**
5
+ * GET /api/admin/users
6
+ * Returns all users (superadmin only)
7
+ * Supports pagination via query params: page, per_page
8
+ * Supports search via query param: search (email)
9
+ */
10
+ export async function GET(request: NextRequest) {
11
+ try {
12
+ const supabase = await getSupabaseServerClient();
13
+
14
+ // L'authentification et les droits superadmin sont déjà vérifiés par le middleware
15
+ // Get query parameters
16
+ const searchParams = request.nextUrl.searchParams;
17
+ const page = parseInt(searchParams.get("page") || "1");
18
+ const perPage = parseInt(searchParams.get("per_page") || "20");
19
+ const search = searchParams.get("search") || "";
20
+
21
+ // Use RPC function to get users with emails
22
+ const { data: result, error: usersError } = await supabase.rpc(
23
+ "get_admin_users",
24
+ {
25
+ page_number: page,
26
+ page_size: perPage,
27
+ search_term: search,
28
+ }
29
+ );
30
+
31
+ if (usersError) {
32
+ console.error("Error fetching users:", usersError);
33
+ return NextResponse.json(
34
+ { error: "Database Error", message: usersError.message },
35
+ { status: 500 },
36
+ );
37
+ }
38
+
39
+ // The RPC function returns the complete response with data and pagination
40
+ return NextResponse.json(result || { data: [], pagination: { page, per_page: perPage, total: 0, total_pages: 0 } });
41
+ } catch (error) {
42
+ console.error("Error in admin users endpoint:", error);
43
+ return NextResponse.json(
44
+ {
45
+ error: "Internal Server Error",
46
+ message: "Failed to fetch users",
47
+ },
48
+ { status: 500 },
49
+ );
50
+ }
51
+ }
@@ -0,0 +1,39 @@
1
+ import { NextResponse } from "next/server";
2
+ import { getSupabaseServerClient } from "@lastbrain/core/server";
3
+
4
+ /**
5
+ * GET /api/auth/me
6
+ * Returns the current authenticated user and their profile
7
+ */
8
+ export async function GET() {
9
+ try {
10
+ const supabase = await getSupabaseServerClient();
11
+
12
+ // Get the authenticated user
13
+ const { data: { user } } = await supabase.auth.getUser();
14
+
15
+ // L'utilisateur est déjà authentifié grâce au middleware
16
+ // Get user profile
17
+ const { data: profile } = await supabase
18
+ .from("user_profil")
19
+ .select("*")
20
+ .eq("owner_id", user!.id)
21
+ .single();
22
+
23
+ // Profile might not exist yet, that's OK
24
+ const userData = {
25
+ id: user!.id,
26
+ email: user!.email,
27
+ created_at: user!.created_at,
28
+ profile: profile || null,
29
+ };
30
+
31
+ return NextResponse.json({ data: userData });
32
+ } catch (error) {
33
+ console.error("Error fetching user:", error);
34
+ return NextResponse.json(
35
+ { error: "Internal Server Error", message: "Failed to fetch user data" },
36
+ { status: 500 },
37
+ );
38
+ }
39
+ }
@@ -0,0 +1,142 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { getSupabaseServerClient } from "@lastbrain/core/server";
3
+
4
+ /**
5
+ * GET /api/auth/profile
6
+ * Returns the user's profile
7
+ */
8
+ export async function GET() {
9
+ try {
10
+ const supabase = await getSupabaseServerClient();
11
+
12
+ const {
13
+ data: { user },
14
+ } = await supabase.auth.getUser();
15
+
16
+ // L'utilisateur est déjà authentifié grâce au middleware
17
+ const { data: profile, error: profileError } = await supabase
18
+ .from("user_profil")
19
+ .select("*")
20
+ .eq("owner_id", user!.id)
21
+ .single();
22
+
23
+ if (profileError && profileError.code !== "PGRST116") {
24
+ // PGRST116 = no rows returned, which is OK
25
+ return NextResponse.json(
26
+ { error: "Database Error", message: profileError.message },
27
+ { status: 500 },
28
+ );
29
+ }
30
+
31
+ return NextResponse.json({ data: profile || null });
32
+ } catch (error) {
33
+ console.error("Error fetching profile:", error);
34
+ return NextResponse.json(
35
+ { error: "Internal Server Error", message: "Failed to fetch profile" },
36
+ { status: 500 },
37
+ );
38
+ }
39
+ }
40
+
41
+ /**
42
+ * PUT /api/auth/profile
43
+ * Updates the user's profile
44
+ */
45
+ export async function PUT(request: NextRequest) {
46
+ try {
47
+ const supabase = await getSupabaseServerClient();
48
+
49
+ const {
50
+ data: { user },
51
+ } = await supabase.auth.getUser();
52
+
53
+ // L'utilisateur est déjà authentifié grâce au middleware
54
+ const body = await request.json();
55
+ const {
56
+ first_name,
57
+ last_name,
58
+ avatar_url,
59
+ bio,
60
+ phone,
61
+ company,
62
+ website,
63
+ location,
64
+ language,
65
+ timezone,
66
+ preferences,
67
+ } = body;
68
+
69
+ // Check if profile exists
70
+ const { data: existingProfile } = await supabase
71
+ .from("user_profil")
72
+ .select("id")
73
+ .eq("owner_id", user!.id)
74
+ .single();
75
+
76
+ let result;
77
+ if (existingProfile) {
78
+ // Update existing profile
79
+ result = await supabase
80
+ .from("user_profil")
81
+ .update({
82
+ first_name,
83
+ last_name,
84
+ avatar_url,
85
+ bio,
86
+ phone,
87
+ company,
88
+ website,
89
+ location,
90
+ language,
91
+ timezone,
92
+ preferences,
93
+ })
94
+ .eq("owner_id", user!.id)
95
+ .select()
96
+ .single();
97
+ } else {
98
+ // Create new profile
99
+ result = await supabase
100
+ .from("user_profil")
101
+ .insert({
102
+ owner_id: user!.id,
103
+ first_name,
104
+ last_name,
105
+ avatar_url,
106
+ bio,
107
+ phone,
108
+ company,
109
+ website,
110
+ location,
111
+ language,
112
+ timezone,
113
+ preferences,
114
+ })
115
+ .select()
116
+ .single();
117
+ }
118
+
119
+ if (result.error) {
120
+ return NextResponse.json(
121
+ { error: "Database Error", message: result.error.message },
122
+ { status: 500 },
123
+ );
124
+ }
125
+
126
+ return NextResponse.json({ data: result.data });
127
+ } catch (error) {
128
+ console.error("Error updating profile:", error);
129
+ return NextResponse.json(
130
+ { error: "Internal Server Error", message: "Failed to update profile" },
131
+ { status: 500 },
132
+ );
133
+ }
134
+ }
135
+
136
+ /**
137
+ * PATCH /api/auth/profile
138
+ * Partially updates the user's profile
139
+ */
140
+ export async function PATCH(request: NextRequest) {
141
+ return PUT(request);
142
+ }
@@ -3,9 +3,9 @@ import { getSupabaseServerClient } from "@lastbrain/core/server";
3
3
  const jsonResponse = (payload: unknown, status = 200) => {
4
4
  return new Response(JSON.stringify(payload), {
5
5
  headers: {
6
- "content-type": "application/json"
6
+ "content-type": "application/json",
7
7
  },
8
- status
8
+ status,
9
9
  });
10
10
  };
11
11
 
@@ -20,7 +20,7 @@ export async function POST(request: Request) {
20
20
 
21
21
  const { error, data } = await supabase.auth.signInWithPassword({
22
22
  email,
23
- password
23
+ password,
24
24
  });
25
25
 
26
26
  if (error) {
@@ -0,0 +1,66 @@
1
+ import { supabaseBrowserClient } from "@lastbrain/core";
2
+
3
+ /**
4
+ * Upload a file to Supabase Storage and return proxy URL
5
+ */
6
+ export async function uploadFile(
7
+ bucket: string,
8
+ path: string,
9
+ file: Blob,
10
+ contentType: string,
11
+ ): Promise<string> {
12
+ const { data, error } = await supabaseBrowserClient.storage
13
+ .from(bucket)
14
+ .upload(path, file, {
15
+ contentType,
16
+ upsert: true,
17
+ });
18
+
19
+ if (error) {
20
+ throw new Error(`Upload failed: ${error.message}`);
21
+ }
22
+
23
+ // Return proxy URL instead of Supabase public URL
24
+ return `/api/storage/${bucket}/${data.path}`;
25
+ }
26
+
27
+ /**
28
+ * Delete files from Supabase Storage
29
+ */
30
+ export async function deleteFiles(
31
+ bucket: string,
32
+ paths: string[],
33
+ ): Promise<void> {
34
+ const { error } = await supabaseBrowserClient.storage
35
+ .from(bucket)
36
+ .remove(paths);
37
+
38
+ if (error) {
39
+ throw new Error(`Delete failed: ${error.message}`);
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Delete files starting with a specific prefix (like user ID)
45
+ */
46
+ export async function deleteFilesWithPrefix(
47
+ bucket: string,
48
+ prefix: string,
49
+ ): Promise<void> {
50
+ // List files with the prefix
51
+ const { data: files, error: listError } = await supabaseBrowserClient.storage
52
+ .from(bucket)
53
+ .list("", {
54
+ search: prefix,
55
+ });
56
+
57
+ if (listError) {
58
+ console.warn("Failed to list files for deletion:", listError);
59
+ return;
60
+ }
61
+
62
+ if (files && files.length > 0) {
63
+ const filePaths = files.map((file) => file.name);
64
+ await deleteFiles(bucket, filePaths);
65
+ }
66
+ }
@@ -47,12 +47,52 @@ const authBuildConfig: ModuleBuildConfig = {
47
47
  entryPoint: "api/public/signin",
48
48
  authRequired: false,
49
49
  },
50
+ {
51
+ method: "GET",
52
+ path: "/api/auth/profile",
53
+ handlerExport: "GET",
54
+ entryPoint: "api/auth/profile",
55
+ authRequired: true,
56
+ },
57
+ {
58
+ method: "PUT",
59
+ path: "/api/auth/profile",
60
+ handlerExport: "PUT",
61
+ entryPoint: "api/auth/profile",
62
+ authRequired: true,
63
+ },
64
+ {
65
+ method: "PATCH",
66
+ path: "/api/auth/profile",
67
+ handlerExport: "PATCH",
68
+ entryPoint: "api/auth/profile",
69
+ authRequired: true,
70
+ },
71
+ {
72
+ method: "GET",
73
+ path: "/api/auth/me",
74
+ handlerExport: "GET",
75
+ entryPoint: "api/auth/me",
76
+ authRequired: true,
77
+ },
78
+ {
79
+ method: "GET",
80
+ path: "/api/admin/users",
81
+ handlerExport: "GET",
82
+ entryPoint: "api/admin/users",
83
+ authRequired: true,
84
+ },
50
85
  ],
51
86
  migrations: {
52
87
  enabled: true,
53
88
  priority: 20,
54
89
  path: "supabase/migrations",
55
- files: ["001_auth_base.sql"],
90
+ files: [
91
+ "20251112000000_user_init.sql",
92
+ "20251112000001_auto_profile_and_admin_view.sql",
93
+ "20251112000002_sync_avatars.sql"
94
+ ],
95
+ migrationsDownPath: "supabase/migrations-down",
56
96
  },
57
97
  menu: {
58
98
  public: [
@@ -76,7 +116,7 @@ const authBuildConfig: ModuleBuildConfig = {
76
116
  title: "Gestion des utilisateurs",
77
117
  description: "Gérez les utilisateurs de la plateforme",
78
118
  icon: "Users2",
79
- path: "/admin/users",
119
+ path: "/admin/auth/users",
80
120
  order: 1,
81
121
  },
82
122
  ],