@lastbrain/module-auth 2.0.19 → 2.0.31

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 (178) hide show
  1. package/README.md +55 -7
  2. package/dist/api/admin/signup-stats.d.ts.map +1 -1
  3. package/dist/api/admin/signup-stats.js +2 -1
  4. package/dist/api/admin/storage/usage.d.ts +18 -0
  5. package/dist/api/admin/storage/usage.d.ts.map +1 -0
  6. package/dist/api/admin/storage/usage.js +100 -0
  7. package/dist/api/admin/users/[id]/notifications.d.ts.map +1 -1
  8. package/dist/api/admin/users/[id]/notifications.js +3 -2
  9. package/dist/api/admin/users/[id].d.ts.map +1 -1
  10. package/dist/api/admin/users/[id].js +3 -2
  11. package/dist/api/admin/users/reactivate/[id].d.ts +16 -0
  12. package/dist/api/admin/users/reactivate/[id].d.ts.map +1 -0
  13. package/dist/api/admin/users/reactivate/[id].js +59 -0
  14. package/dist/api/admin/users/suspend/[id].d.ts +16 -0
  15. package/dist/api/admin/users/suspend/[id].d.ts.map +1 -0
  16. package/dist/api/admin/users/suspend/[id].js +59 -0
  17. package/dist/api/admin/users-by-source.d.ts.map +1 -1
  18. package/dist/api/admin/users-by-source.js +2 -1
  19. package/dist/api/admin/users.d.ts.map +1 -1
  20. package/dist/api/admin/users.js +53 -2
  21. package/dist/api/auth/account/email-change.d.ts +7 -0
  22. package/dist/api/auth/account/email-change.d.ts.map +1 -0
  23. package/dist/api/auth/account/email-change.js +39 -0
  24. package/dist/api/auth/account/reset-password.d.ts +7 -0
  25. package/dist/api/auth/account/reset-password.d.ts.map +1 -0
  26. package/dist/api/auth/account/reset-password.js +36 -0
  27. package/dist/api/auth/check-username.d.ts +9 -0
  28. package/dist/api/auth/check-username.d.ts.map +1 -0
  29. package/dist/api/auth/check-username.js +35 -0
  30. package/dist/api/auth/establish-session.d.ts +2 -0
  31. package/dist/api/auth/establish-session.d.ts.map +1 -0
  32. package/dist/api/auth/establish-session.js +23 -0
  33. package/dist/api/auth/me.d.ts +4 -4
  34. package/dist/api/auth/me.d.ts.map +1 -1
  35. package/dist/api/auth/me.js +28 -6
  36. package/dist/api/auth/profile.d.ts.map +1 -1
  37. package/dist/api/auth/profile.js +6 -3
  38. package/dist/api/auth/storage/recalculate.d.ts +15 -0
  39. package/dist/api/auth/storage/recalculate.d.ts.map +1 -0
  40. package/dist/api/auth/storage/recalculate.js +68 -0
  41. package/dist/api/auth/storage/usage.d.ts +10 -0
  42. package/dist/api/auth/storage/usage.d.ts.map +1 -0
  43. package/dist/api/auth/storage/usage.js +86 -0
  44. package/dist/api/public/add-welcome-bonus.d.ts +16 -0
  45. package/dist/api/public/add-welcome-bonus.d.ts.map +1 -0
  46. package/dist/api/public/add-welcome-bonus.js +177 -0
  47. package/dist/api/public/callback.d.ts +3 -0
  48. package/dist/api/public/callback.d.ts.map +1 -0
  49. package/dist/api/public/callback.js +197 -0
  50. package/dist/api/public/reset-password.d.ts +3 -0
  51. package/dist/api/public/reset-password.d.ts.map +1 -0
  52. package/dist/api/public/reset-password.js +43 -0
  53. package/dist/api/public/set-session.d.ts +7 -0
  54. package/dist/api/public/set-session.d.ts.map +1 -0
  55. package/dist/api/public/set-session.js +55 -0
  56. package/dist/api/public/signin.d.ts.map +1 -1
  57. package/dist/api/public/signin.js +31 -0
  58. package/dist/api/public/signup.d.ts.map +1 -1
  59. package/dist/api/public/signup.js +38 -27
  60. package/dist/api/public/webhook/storage-addon.d.ts +9 -0
  61. package/dist/api/public/webhook/storage-addon.d.ts.map +1 -0
  62. package/dist/api/public/webhook/storage-addon.js +155 -0
  63. package/dist/api/storage.js +2 -2
  64. package/dist/auth.build.config.d.ts.map +1 -1
  65. package/dist/auth.build.config.js +134 -14
  66. package/dist/components/AccountButton.d.ts.map +1 -1
  67. package/dist/components/AccountButton.js +54 -28
  68. package/dist/components/Doc.d.ts.map +1 -1
  69. package/dist/components/Doc.js +1 -1
  70. package/dist/components/HasProfil.d.ts +4 -0
  71. package/dist/components/HasProfil.d.ts.map +1 -0
  72. package/dist/components/HasProfil.js +39 -0
  73. package/dist/{web → components}/auth/dashboard.d.ts +1 -1
  74. package/dist/components/auth/dashboard.d.ts.map +1 -0
  75. package/dist/components/auth/dashboard.js +74 -0
  76. package/dist/index.d.ts +3 -1
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +3 -1
  79. package/dist/lib/app-branding-data.d.ts +22 -0
  80. package/dist/lib/app-branding-data.d.ts.map +1 -0
  81. package/dist/lib/app-branding-data.js +49 -0
  82. package/dist/lib/auth-email-service.d.ts +57 -0
  83. package/dist/lib/auth-email-service.d.ts.map +1 -0
  84. package/dist/lib/auth-email-service.js +382 -0
  85. package/dist/lib/auth-email-templates.d.ts +2 -0
  86. package/dist/lib/auth-email-templates.d.ts.map +1 -0
  87. package/dist/lib/auth-email-templates.js +1 -0
  88. package/dist/lib/site-url.d.ts +3 -0
  89. package/dist/lib/site-url.d.ts.map +1 -0
  90. package/dist/lib/site-url.js +11 -0
  91. package/dist/sitemap/manifest.d.ts +9 -0
  92. package/dist/sitemap/manifest.d.ts.map +1 -0
  93. package/dist/sitemap/manifest.js +14 -0
  94. package/dist/web/admin/signup-stats.js +3 -3
  95. package/dist/web/admin/user-detail.d.ts.map +1 -1
  96. package/dist/web/admin/user-detail.js +135 -14
  97. package/dist/web/admin/users-by-signup-source.js +2 -2
  98. package/dist/web/admin/users.d.ts.map +1 -1
  99. package/dist/web/admin/users.js +26 -7
  100. package/dist/web/auth/folder.d.ts.map +1 -1
  101. package/dist/web/auth/folder.js +4 -3
  102. package/dist/web/auth/profile.d.ts.map +1 -1
  103. package/dist/web/auth/profile.js +132 -13
  104. package/dist/web/auth/reglage.d.ts.map +1 -1
  105. package/dist/web/auth/reglage.js +15 -8
  106. package/dist/web/public/ResetPassword.d.ts.map +1 -1
  107. package/dist/web/public/ResetPassword.js +172 -2
  108. package/dist/web/public/SignInPage.d.ts.map +1 -1
  109. package/dist/web/public/SignInPage.js +39 -3
  110. package/dist/web/public/SignUpPage.d.ts.map +1 -1
  111. package/dist/web/public/SignUpPage.js +7 -2
  112. package/dist/web/public/auth-code-error.d.ts +2 -0
  113. package/dist/web/public/auth-code-error.d.ts.map +1 -0
  114. package/dist/web/public/auth-code-error.js +14 -0
  115. package/dist/web/public/confirm.d.ts +2 -0
  116. package/dist/web/public/confirm.d.ts.map +1 -0
  117. package/dist/web/public/confirm.js +157 -0
  118. package/package.json +10 -5
  119. package/src/api/admin/signup-stats.ts +2 -1
  120. package/src/api/admin/storage/usage.ts +141 -0
  121. package/src/api/admin/users/[id]/notifications.ts +3 -2
  122. package/src/api/admin/users/[id].ts +3 -2
  123. package/src/api/admin/users/reactivate/[id].ts +88 -0
  124. package/src/api/admin/users/suspend/[id].ts +85 -0
  125. package/src/api/admin/users-by-source.ts +2 -1
  126. package/src/api/admin/users.ts +59 -2
  127. package/src/api/auth/account/email-change.ts +54 -0
  128. package/src/api/auth/account/reset-password.ts +47 -0
  129. package/src/api/auth/check-username.ts +52 -0
  130. package/src/api/auth/establish-session.ts +32 -0
  131. package/src/api/auth/me.ts +29 -7
  132. package/src/api/auth/profile.ts +6 -2
  133. package/src/api/auth/storage/recalculate.ts +108 -0
  134. package/src/api/auth/storage/usage.ts +113 -0
  135. package/src/api/public/add-welcome-bonus.ts +229 -0
  136. package/src/api/public/callback.ts +307 -0
  137. package/src/api/public/reset-password.ts +52 -0
  138. package/src/api/public/set-session.ts +73 -0
  139. package/src/api/public/signin.ts +36 -0
  140. package/src/api/public/signup.ts +44 -37
  141. package/src/api/public/webhook/storage-addon.ts +267 -0
  142. package/src/api/storage.ts +2 -2
  143. package/src/auth.build.config.ts +134 -14
  144. package/src/components/AccountButton.tsx +114 -90
  145. package/src/components/Doc.tsx +47 -9
  146. package/src/components/HasProfil.tsx +63 -0
  147. package/src/{web → components}/auth/dashboard.tsx +64 -20
  148. package/src/i18n/en.json +78 -8
  149. package/src/i18n/es.json +330 -0
  150. package/src/i18n/fr.json +75 -8
  151. package/src/index.ts +3 -1
  152. package/src/lib/app-branding-data.ts +90 -0
  153. package/src/lib/auth-email-service.ts +508 -0
  154. package/src/lib/auth-email-templates.ts +5 -0
  155. package/src/lib/site-url.ts +17 -0
  156. package/src/sitemap/manifest.ts +26 -0
  157. package/src/web/admin/signup-stats.tsx +3 -3
  158. package/src/web/admin/user-detail.tsx +314 -15
  159. package/src/web/admin/users-by-signup-source.tsx +2 -2
  160. package/src/web/admin/users.tsx +50 -14
  161. package/src/web/auth/folder.tsx +23 -5
  162. package/src/web/auth/profile.tsx +227 -13
  163. package/src/web/auth/reglage.tsx +55 -24
  164. package/src/web/public/ResetPassword.tsx +301 -1
  165. package/src/web/public/SignInPage.tsx +43 -3
  166. package/src/web/public/SignUpPage.tsx +14 -5
  167. package/src/web/public/auth-code-error.tsx +49 -0
  168. package/src/web/public/confirm.tsx +195 -0
  169. package/supabase/migrations/20251112000001_auto_profile_and_admin_view.sql +3 -1
  170. package/supabase/migrations/20251127100000_rename_body_to_message.sql +8 -3
  171. package/supabase/migrations/20260120150001_add_user_storage.sql +105 -0
  172. package/supabase/migrations/20260122131200_add_global_addons_system.sql +305 -0
  173. package/supabase/migrations/20260123100000_enable_vector_extension.sql +9 -0
  174. package/supabase/migrations/20260123140000_module_auth_fix_get_user_limits_null.sql +93 -0
  175. package/supabase/migrations/20260123145000_module_auth_fix_circular_storage_base.sql +89 -0
  176. package/supabase/migrations/20260129400000_add_username_to_user_profil.sql +33 -0
  177. package/dist/web/auth/dashboard.d.ts.map +0 -1
  178. package/dist/web/auth/dashboard.js +0 -48
@@ -0,0 +1,36 @@
1
+ import { NextResponse } from "next/server";
2
+ import { getSupabaseServerClient } from "@lastbrain/core/server";
3
+ import { sendPasswordResetEmail } from "../../../lib/auth-email-service";
4
+ import { logger } from "@lastbrain/core";
5
+ import { resolveSiteUrl } from "../../../lib/site-url";
6
+ function extractLocale(request) {
7
+ const lang = request.headers
8
+ .get("accept-language")
9
+ ?.split(",")?.[0]
10
+ ?.split("-")?.[0];
11
+ return lang === "fr" || lang === "en" ? lang : undefined;
12
+ }
13
+ const DEFAULT_LOCALE = "fr";
14
+ export async function POST(request) {
15
+ try {
16
+ const locale = extractLocale(request);
17
+ const supabase = await getSupabaseServerClient();
18
+ const { data: { user }, error, } = await supabase.auth.getUser();
19
+ if (error || !user) {
20
+ return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
21
+ }
22
+ const resolvedLocale = locale || DEFAULT_LOCALE;
23
+ await sendPasswordResetEmail({
24
+ email: user.email || "",
25
+ displayName: user.user_metadata?.full_name,
26
+ ownerId: user.id,
27
+ redirectTo: `${resolveSiteUrl(request)}/${resolvedLocale}/reset-password`,
28
+ locale,
29
+ });
30
+ return NextResponse.json({ message: "Lien de réinitialisation envoyé" });
31
+ }
32
+ catch (err) {
33
+ logger.error("account reset-password error:", err);
34
+ return NextResponse.json({ error: "Impossible d'envoyer le lien de réinitialisation" }, { status: 500 });
35
+ }
36
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Check username availability
3
+ * GET /api/auth/check-username?username=xxx&owner_id=xxx
4
+ */
5
+ import { NextRequest, NextResponse } from "next/server";
6
+ export declare function GET(req: NextRequest): Promise<NextResponse<{
7
+ available: boolean;
8
+ }>>;
9
+ //# sourceMappingURL=check-username.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-username.d.ts","sourceRoot":"","sources":["../../../src/api/auth/check-username.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGxD,wBAAsB,GAAG,CAAC,GAAG,EAAE,WAAW;;IA2CzC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Check username availability
3
+ * GET /api/auth/check-username?username=xxx&owner_id=xxx
4
+ */
5
+ import { NextResponse } from "next/server";
6
+ import { getSupabaseServiceClient } from "@lastbrain/core/server";
7
+ export async function GET(req) {
8
+ try {
9
+ const { searchParams } = new URL(req.url);
10
+ const username = searchParams.get("username");
11
+ const owner_id = searchParams.get("owner_id");
12
+ if (!username) {
13
+ return NextResponse.json({ available: false, error: "Username required" }, { status: 400 });
14
+ }
15
+ const supabase = getSupabaseServiceClient();
16
+ // Check if username exists (case-insensitive), excluding current user
17
+ let query = supabase
18
+ .from("user_profil")
19
+ .select("username")
20
+ .ilike("username", username)
21
+ .limit(1);
22
+ if (owner_id) {
23
+ query = query.neq("owner_id", owner_id);
24
+ }
25
+ const { data, error } = await query;
26
+ if (error) {
27
+ return NextResponse.json({ available: false, error: error.message }, { status: 500 });
28
+ }
29
+ return NextResponse.json({ available: !data || data.length === 0 });
30
+ }
31
+ catch (error) {
32
+ console.error("[check-username] Error:", error);
33
+ return NextResponse.json({ available: false, error: "Internal server error" }, { status: 500 });
34
+ }
35
+ }
@@ -0,0 +1,2 @@
1
+ export declare function POST(request: Request): Promise<Response>;
2
+ //# sourceMappingURL=establish-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"establish-session.d.ts","sourceRoot":"","sources":["../../../src/api/auth/establish-session.ts"],"names":[],"mappings":"AAOA,wBAAsB,IAAI,CAAC,OAAO,EAAE,OAAO,qBAwB1C"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Establish server-side session with cookies
3
+ * Called after client-side setSession() to ensure HTTP-only cookies are set
4
+ */
5
+ import { logger } from "@lastbrain/core";
6
+ import { getSupabaseServerClient } from "@lastbrain/core/server";
7
+ export async function POST(request) {
8
+ try {
9
+ const supabase = await getSupabaseServerClient();
10
+ // Verify session exists on server
11
+ const { data: { user }, error: userError, } = await supabase.auth.getUser();
12
+ if (userError || !user) {
13
+ logger.error("❌ No user session on server:", userError);
14
+ return Response.json({ error: "Unauthorized" }, { status: 401 });
15
+ }
16
+ // Cookies are automatically set by Supabase middleware
17
+ return Response.json({ success: true, user: user.id });
18
+ }
19
+ catch (error) {
20
+ logger.debug("💥 Error establishing session:", error);
21
+ return Response.json({ error: "Failed to establish session" }, { status: 500 });
22
+ }
23
+ }
@@ -1,13 +1,13 @@
1
- import { NextResponse } from "next/server";
1
+ import { NextResponse, type NextRequest } from "next/server";
2
2
  /**
3
3
  * GET /api/auth/me
4
4
  * Returns the current authenticated user and their profile
5
5
  */
6
- export declare function GET(): Promise<NextResponse<{
6
+ export declare function GET(request: NextRequest): Promise<NextResponse<{
7
7
  data: {
8
- id: string;
8
+ id: string | undefined;
9
9
  email: string | undefined;
10
- created_at: string;
10
+ created_at: string | undefined;
11
11
  profile: any;
12
12
  };
13
13
  }> | NextResponse<{
@@ -1 +1 @@
1
- {"version":3,"file":"me.d.ts","sourceRoot":"","sources":["../../../src/api/auth/me.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C;;;GAGG;AACH,wBAAsB,GAAG;;;;;;;;;;IAiCxB"}
1
+ {"version":3,"file":"me.d.ts","sourceRoot":"","sources":["../../../src/api/auth/me.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAI7D;;;GAGG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;;;;;;;IAsD7C"}
@@ -1,10 +1,11 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { getSupabaseServerClient } from "@lastbrain/core/server";
3
+ import { logger } from "@lastbrain/core";
3
4
  /**
4
5
  * GET /api/auth/me
5
6
  * Returns the current authenticated user and their profile
6
7
  */
7
- export async function GET() {
8
+ export async function GET(request) {
8
9
  try {
9
10
  const supabase = await getSupabaseServerClient();
10
11
  // Get the authenticated user
@@ -14,19 +15,40 @@ export async function GET() {
14
15
  const { data: profile } = await supabase
15
16
  .from("user_profil")
16
17
  .select("*")
17
- .eq("owner_id", user.id)
18
+ .eq("owner_id", user?.id)
18
19
  .single();
19
20
  // Profile might not exist yet, that's OK
20
21
  const userData = {
21
- id: user.id,
22
- email: user.email,
23
- created_at: user.created_at,
22
+ id: user?.id,
23
+ email: user?.email,
24
+ created_at: user?.created_at,
24
25
  profile: profile || null,
25
26
  };
27
+ // Si le client a envoyé un cookie NEXT_LOCALE, synchroniser sa valeur
28
+ // avec la langue du profil (si présente). Ne rien créer si le cookie
29
+ // est absent. Si la langue diffère, renvoyer une réponse JSON indiquant
30
+ // le changement de langue et définir le cookie côté serveur. Le client
31
+ // effectuera ensuite la redirection côté client pour éviter que fetch
32
+ // suive automatiquement un redirect 302.
33
+ try {
34
+ // Return localeUpdated to let the client/realtime decide what to do.
35
+ const cookieLang = request.cookies.get("NEXT_LOCALE")?.value;
36
+ const profileLang = profile?.language;
37
+ if (cookieLang && profileLang && profileLang !== cookieLang) {
38
+ return NextResponse.json({
39
+ data: userData,
40
+ localeUpdated: true,
41
+ profileLang,
42
+ });
43
+ }
44
+ }
45
+ catch (e) {
46
+ logger.debug("me: locale sync error", e);
47
+ }
26
48
  return NextResponse.json({ data: userData });
27
49
  }
28
50
  catch (error) {
29
- console.error("Error fetching user:", error);
51
+ logger.error("Error fetching user:", error);
30
52
  return NextResponse.json({ error: "Internal Server Error", message: "Failed to fetch user data" }, { status: 500 });
31
53
  }
32
54
  }
@@ -1 +1 @@
1
- {"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../../src/api/auth/profile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGxD;;;GAGG;AACH,wBAAsB,GAAG;;;;;IA+BxB;AAED;;;GAGG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;;IAyF7C;AAED;;;GAGG;AACH,wBAAsB,KAAK,CAAC,OAAO,EAAE,WAAW;;;;;IAE/C"}
1
+ {"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../../src/api/auth/profile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIxD;;;GAGG;AACH,wBAAsB,GAAG;;;;;IA+BxB;AAED;;;GAGG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;;IA4F7C;AAED;;;GAGG;AACH,wBAAsB,KAAK,CAAC,OAAO,EAAE,WAAW;;;;;IAE/C"}
@@ -1,5 +1,6 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { getSupabaseServerClient } from "@lastbrain/core/server";
3
+ import { logger } from "@lastbrain/core";
3
4
  /**
4
5
  * GET /api/auth/profile
5
6
  * Returns the user's profile
@@ -21,7 +22,7 @@ export async function GET() {
21
22
  return NextResponse.json({ data: profile || null });
22
23
  }
23
24
  catch (error) {
24
- console.error("Error fetching profile:", error);
25
+ logger.error("Error fetching profile:", error);
25
26
  return NextResponse.json({ error: "Internal Server Error", message: "Failed to fetch profile" }, { status: 500 });
26
27
  }
27
28
  }
@@ -35,7 +36,7 @@ export async function PUT(request) {
35
36
  const { data: { user }, } = await supabase.auth.getUser();
36
37
  // L'utilisateur est déjà authentifié grâce au middleware
37
38
  const body = await request.json();
38
- const { first_name, last_name, avatar_url, bio, phone, company, website, location, language, timezone, preferences, } = body;
39
+ const { first_name, last_name, avatar_url, bio, phone, company, website, location, language, timezone, username, preferences, } = body;
39
40
  // Check if profile exists
40
41
  const { data: existingProfile } = await supabase
41
42
  .from("user_profil")
@@ -59,6 +60,7 @@ export async function PUT(request) {
59
60
  language,
60
61
  timezone,
61
62
  preferences,
63
+ username,
62
64
  })
63
65
  .eq("owner_id", user.id)
64
66
  .select()
@@ -88,10 +90,11 @@ export async function PUT(request) {
88
90
  if (result.error) {
89
91
  return NextResponse.json({ error: "Database Error", message: result.error.message }, { status: 500 });
90
92
  }
93
+ // Indiquer au client si la langue a été modifiée (sans toucher aux cookies)
91
94
  return NextResponse.json({ data: result.data });
92
95
  }
93
96
  catch (error) {
94
- console.error("Error updating profile:", error);
97
+ logger.error("Error updating profile:", error);
95
98
  return NextResponse.json({ error: "Internal Server Error", message: "Failed to update profile" }, { status: 500 });
96
99
  }
97
100
  }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * POST /api/auth/storage/recalculate
3
+ * Force recalculation of user storage quota from addons
4
+ */
5
+ import { NextRequest, NextResponse } from "next/server";
6
+ export declare const runtime = "nodejs";
7
+ export declare const dynamic = "force-dynamic";
8
+ export declare function POST(request: NextRequest): Promise<NextResponse<{
9
+ error: string;
10
+ }> | NextResponse<{
11
+ success: boolean;
12
+ storage_quota_gb: any;
13
+ breakdown: any;
14
+ }>>;
15
+ //# sourceMappingURL=recalculate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recalculate.d.ts","sourceRoot":"","sources":["../../../../src/api/auth/storage/recalculate.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIxD,eAAO,MAAM,OAAO,WAAW,CAAC;AAChC,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAEvC,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;;IA+F9C"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * POST /api/auth/storage/recalculate
3
+ * Force recalculation of user storage quota from addons
4
+ */
5
+ import { NextResponse } from "next/server";
6
+ import { getSupabaseServerClient } from "@lastbrain/core/server";
7
+ import { logger } from "@lastbrain/core";
8
+ export const runtime = "nodejs";
9
+ export const dynamic = "force-dynamic";
10
+ export async function POST(request) {
11
+ try {
12
+ const supabase = await getSupabaseServerClient();
13
+ // Get authenticated user
14
+ const { data: { user }, error: authError, } = await supabase.auth.getUser();
15
+ if (authError || !user) {
16
+ return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
17
+ }
18
+ logger.debug(`[Storage Recalculate] Recalculating for user ${user.id}`);
19
+ // Call get_user_limits RPC
20
+ const { data: limitsData, error: limitsError } = await supabase.rpc("get_user_limits", {
21
+ owner_id_param: user.id,
22
+ });
23
+ if (limitsError) {
24
+ logger.error("[Storage Recalculate] Error calling get_user_limits:", limitsError);
25
+ return NextResponse.json({ error: "Erreur lors du calcul des limites" }, { status: 500 });
26
+ }
27
+ logger.debug(`[Storage Recalculate] get_user_limits result:`, limitsData);
28
+ // Parse result (could be string or object)
29
+ let parsedLimits = limitsData;
30
+ if (typeof limitsData === "string") {
31
+ try {
32
+ parsedLimits = JSON.parse(limitsData);
33
+ }
34
+ catch (e) {
35
+ logger.error("[Storage Recalculate] Failed to parse limits data:", e);
36
+ }
37
+ }
38
+ const totalStorageGb = parsedLimits?.storage_quota_gb;
39
+ if (totalStorageGb !== null && totalStorageGb !== undefined) {
40
+ // Update user_profil
41
+ const { error: updateError } = await supabase.from("user_profil").upsert({
42
+ owner_id: user.id,
43
+ stockage: totalStorageGb,
44
+ updated_at: new Date().toISOString(),
45
+ }, {
46
+ onConflict: "owner_id",
47
+ });
48
+ if (updateError) {
49
+ logger.error("[Storage Recalculate] Error updating user_profil:", updateError);
50
+ return NextResponse.json({ error: "Erreur lors de la mise à jour" }, { status: 500 });
51
+ }
52
+ logger.info(`[Storage Recalculate] Updated user_profil.stockage to ${totalStorageGb} GB for user ${user.id}`);
53
+ return NextResponse.json({
54
+ success: true,
55
+ storage_quota_gb: totalStorageGb,
56
+ breakdown: parsedLimits?.storage_breakdown,
57
+ });
58
+ }
59
+ else {
60
+ logger.error("[Storage Recalculate] get_user_limits returned null for storage_quota_gb");
61
+ return NextResponse.json({ error: "Calcul invalide" }, { status: 500 });
62
+ }
63
+ }
64
+ catch (error) {
65
+ logger.error("[Storage Recalculate] Error:", error);
66
+ return NextResponse.json({ error: "Erreur lors du recalcul" }, { status: 500 });
67
+ }
68
+ }
@@ -0,0 +1,10 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { StorageUsageData } from "@lastbrain/core";
3
+ export declare function GET(request: NextRequest): Promise<NextResponse<{
4
+ success: boolean;
5
+ error: string;
6
+ }> | NextResponse<{
7
+ success: boolean;
8
+ data: StorageUsageData;
9
+ }>>;
10
+ //# sourceMappingURL=usage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../../../../src/api/auth/storage/usage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAExD,OAAO,EAAmB,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAqBpE,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;;;IAyF7C"}
@@ -0,0 +1,86 @@
1
+ import { NextResponse } from "next/server";
2
+ import { getSupabaseServerClient } from "@lastbrain/core/server";
3
+ import { getStorageUsage } from "@lastbrain/core";
4
+ const storageCache = new Map();
5
+ const CACHE_TTL = 5 * 60 * 1000; // 5 minutes en millisecondes
6
+ // Nettoyer le cache périodiquement
7
+ function cleanExpiredCache() {
8
+ const now = Date.now();
9
+ for (const [key, entry] of storageCache.entries()) {
10
+ if (now - entry.timestamp > CACHE_TTL) {
11
+ storageCache.delete(key);
12
+ }
13
+ }
14
+ }
15
+ export async function GET(request) {
16
+ try {
17
+ const supabase = await getSupabaseServerClient();
18
+ // Vérifier l'authentification
19
+ const { data: { user }, error: authError, } = await supabase.auth.getUser();
20
+ if (authError || !user) {
21
+ return NextResponse.json({ success: false, error: "Non authentifié" }, { status: 401 });
22
+ }
23
+ // Route auth: uniquement les données de l'utilisateur connecté
24
+ const ownerId = user.id;
25
+ const { searchParams } = new URL(request.url);
26
+ const forceRefresh = searchParams.get("forceRefresh") === "true";
27
+ // Nettoyer le cache avant utilisation
28
+ cleanExpiredCache();
29
+ // Vérifier si on a des données en cache (sauf si forceRefresh)
30
+ const cacheKey = `storage-${ownerId}`;
31
+ let shouldUseCache = false;
32
+ if (!forceRefresh) {
33
+ const cachedEntry = storageCache.get(cacheKey);
34
+ const now = Date.now();
35
+ if (cachedEntry && now - cachedEntry.timestamp < CACHE_TTL) {
36
+ // Vérifier d'abord si l'allocation de stockage a changé
37
+ // Récupérer juste l'allocation depuis la base (requête légère)
38
+ const { data: userProfile } = await supabase
39
+ .from("user_profil")
40
+ .select("stockage")
41
+ .eq("owner_id", ownerId)
42
+ .single();
43
+ const currentAllocatedGb = userProfile?.stockage || 2.0;
44
+ const cachedAllocatedGb = cachedEntry.data.allocatedGb;
45
+ // Si l'allocation n'a pas changé, utiliser le cache
46
+ if (currentAllocatedGb === cachedAllocatedGb) {
47
+ shouldUseCache = true;
48
+ }
49
+ else {
50
+ // L'allocation a changé, invalider le cache
51
+ storageCache.delete(cacheKey);
52
+ }
53
+ }
54
+ if (shouldUseCache && cachedEntry) {
55
+ // Retourner les données depuis le cache
56
+ return NextResponse.json({
57
+ success: true,
58
+ data: cachedEntry.data,
59
+ cached: true,
60
+ });
61
+ }
62
+ }
63
+ else {
64
+ // Forcer la suppression du cache pour cet utilisateur
65
+ storageCache.delete(cacheKey);
66
+ }
67
+ // Récupérer les données d'usage de stockage
68
+ const storageData = await getStorageUsage(supabase, ownerId);
69
+ // Mettre en cache
70
+ storageCache.set(cacheKey, {
71
+ data: storageData,
72
+ timestamp: Date.now(),
73
+ });
74
+ return NextResponse.json({
75
+ success: true,
76
+ data: storageData,
77
+ });
78
+ }
79
+ catch (error) {
80
+ console.error("Erreur API storage usage:", error);
81
+ return NextResponse.json({
82
+ success: false,
83
+ error: error instanceof Error ? error.message : "Erreur serveur",
84
+ }, { status: 500 });
85
+ }
86
+ }
@@ -0,0 +1,16 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ /**
3
+ * POST /api/public/add-welcome-bonus
4
+ * Adds welcome bonus tokens and notifications after email confirmation
5
+ * Called from the confirm page after successful session establishment
6
+ */
7
+ export declare function POST(request: NextRequest): Promise<NextResponse<{
8
+ error: string;
9
+ }> | NextResponse<{
10
+ message: string;
11
+ alreadyExists: boolean;
12
+ }> | NextResponse<{
13
+ success: boolean;
14
+ message: string;
15
+ }>>;
16
+ //# sourceMappingURL=add-welcome-bonus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-welcome-bonus.d.ts","sourceRoot":"","sources":["../../../src/api/public/add-welcome-bonus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAQxD;;;;GAIG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;;;;IAuN9C"}
@@ -0,0 +1,177 @@
1
+ import { NextResponse } from "next/server";
2
+ import { getSupabaseServerClient, getSupabaseServiceClient, } from "@lastbrain/core/server";
3
+ import { sendWelcomeOnboardingEmail } from "../../lib/auth-email-service";
4
+ import { logger } from "@lastbrain/core";
5
+ /**
6
+ * POST /api/public/add-welcome-bonus
7
+ * Adds welcome bonus tokens and notifications after email confirmation
8
+ * Called from the confirm page after successful session establishment
9
+ */
10
+ export async function POST(request) {
11
+ try {
12
+ // Get authenticated user from cookies
13
+ const supabase = await getSupabaseServerClient();
14
+ const { data: { user }, error: authError, } = await supabase.auth.getUser();
15
+ if (authError || !user?.id) {
16
+ return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
17
+ }
18
+ // Use service client to bypass RLS for system operations
19
+ const serviceClient = await getSupabaseServiceClient();
20
+ logger.debug(`[add-welcome-bonus] Starting for user ${user.id} (${user.email})`);
21
+ // Check if signup bonus is enabled
22
+ const ENABLE_SIGNUP_BONUS = process.env.NEXT_PUBLIC_ENABLE_SIGNUP_BONUS === "true";
23
+ const SIGNUP_BONUS_AMOUNT_USD = parseFloat(process.env.NEXT_PUBLIC_SIGNUP_BONUS_AMOUNT_USD || "1.0");
24
+ if (!ENABLE_SIGNUP_BONUS) {
25
+ logger.debug(`[add-welcome-bonus] Signup bonus disabled for user ${user.id}`);
26
+ return NextResponse.json({
27
+ message: "Signup bonus disabled",
28
+ alreadyExists: false,
29
+ });
30
+ }
31
+ // Check if we already added bonus for this user
32
+ const { data: existingBonus } = await serviceClient
33
+ .from("user_token_ledger")
34
+ .select("id")
35
+ .eq("owner_id", user.id)
36
+ .eq("type", "signup_bonus")
37
+ .single();
38
+ // If bonus already exists, skip
39
+ if (existingBonus) {
40
+ logger.debug(`⏭️ Bonus already exists for user ${user.id}, skipping...`);
41
+ return NextResponse.json({
42
+ message: "Bonus already added",
43
+ alreadyExists: true,
44
+ });
45
+ }
46
+ // Add welcome bonus using wallet system
47
+ const MARGIN_TARGET = 0.6;
48
+ const providerBudgetUsd = SIGNUP_BONUS_AMOUNT_USD * (1 - MARGIN_TARGET); // 40%
49
+ // Insert into ledger with USD values
50
+ const { error: ledgerError } = await serviceClient
51
+ .from("user_token_ledger")
52
+ .insert({
53
+ owner_id: user.id,
54
+ type: "signup_bonus",
55
+ amount: 0, // Tokens deprecated
56
+ sell_value_added_usd: SIGNUP_BONUS_AMOUNT_USD,
57
+ provider_budget_added_usd: providerBudgetUsd,
58
+ meta: {
59
+ reason: "signup_welcome_bonus",
60
+ provider: "system",
61
+ },
62
+ created_by: null,
63
+ });
64
+ if (ledgerError) {
65
+ logger.error("Error adding welcome bonus to ledger:", ledgerError);
66
+ return NextResponse.json({ error: "Failed to add bonus" }, { status: 500 });
67
+ }
68
+ // Update wallet table
69
+ const { data: currentWallet } = await serviceClient
70
+ .from("user_token_wallet")
71
+ .select("wallet_provider_budget_usd, wallet_sell_value_usd")
72
+ .eq("user_id", user.id)
73
+ .single();
74
+ const newProviderBudget = (currentWallet?.wallet_provider_budget_usd || 0) + providerBudgetUsd;
75
+ const newSellValue = (currentWallet?.wallet_sell_value_usd || 0) + SIGNUP_BONUS_AMOUNT_USD;
76
+ const { error: walletError } = await serviceClient
77
+ .from("user_token_wallet")
78
+ .upsert({
79
+ user_id: user.id,
80
+ wallet_provider_budget_usd: newProviderBudget,
81
+ wallet_sell_value_usd: newSellValue,
82
+ updated_at: new Date().toISOString(),
83
+ }, {
84
+ onConflict: "user_id",
85
+ });
86
+ if (walletError) {
87
+ logger.error("Error updating wallet for welcome bonus:", walletError);
88
+ return NextResponse.json({ error: "Failed to update wallet" }, { status: 500 });
89
+ }
90
+ logger.debug(`✅ Added $${SIGNUP_BONUS_AMOUNT_USD} welcome bonus to user ${user.id}`);
91
+ // Determine locale: prefer cookie set by the client (NEXT_LOCALE),
92
+ // then user metadata, then Accept-Language header, fallback to 'en'.
93
+ let locale = "en";
94
+ try {
95
+ const cookieLocale = request.cookies.get("NEXT_LOCALE")?.value;
96
+ if (cookieLocale) {
97
+ locale = cookieLocale;
98
+ }
99
+ else if (user.user_metadata?.locale) {
100
+ locale = user.user_metadata.locale;
101
+ }
102
+ else {
103
+ const accept = request.headers.get("accept-language");
104
+ if (accept) {
105
+ // take first language tag, e.g. 'fr-FR,fr;q=0.9' -> 'fr'
106
+ const first = accept.split(",")[0].split("-")[0];
107
+ if (first)
108
+ locale = first;
109
+ }
110
+ }
111
+ }
112
+ catch (e) {
113
+ // if anything goes wrong, keep default 'en'
114
+ logger.warn("Could not determine locale from request, using default 'en'", e);
115
+ }
116
+ // Add welcome notifications only if bonus enabled
117
+ if (ENABLE_SIGNUP_BONUS && SIGNUP_BONUS_AMOUNT_USD > 0) {
118
+ const notifications = [
119
+ {
120
+ title: locale === "fr" ? "Bienvenue ! 👋" : "Welcome! 👋",
121
+ body: locale === "fr"
122
+ ? "Merci de vous être inscrit ! Explorez toutes les fonctionnalités disponibles."
123
+ : "Thank you for signing up! Explore all available features.",
124
+ type: "primary",
125
+ },
126
+ {
127
+ title: locale === "fr"
128
+ ? `🎁 Cadeau de bienvenue : $${SIGNUP_BONUS_AMOUNT_USD}`
129
+ : `🎁 Welcome gift: $${SIGNUP_BONUS_AMOUNT_USD}`,
130
+ body: locale === "fr"
131
+ ? `Nous vous offrons $${SIGNUP_BONUS_AMOUNT_USD} de crédit IA pour démarrer. Utilisez-les pour accéder à nos services premium.`
132
+ : `We're giving you $${SIGNUP_BONUS_AMOUNT_USD} AI credits to get started. Use them to access our premium services.`,
133
+ type: "success",
134
+ },
135
+ ];
136
+ for (const notification of notifications) {
137
+ const { error: notifError } = await serviceClient
138
+ .from("user_notifications")
139
+ .insert({
140
+ owner_id: user.id,
141
+ title: notification.title,
142
+ body: notification.body,
143
+ type: notification.type,
144
+ });
145
+ if (notifError) {
146
+ logger.error("Error creating notification:", notifError);
147
+ }
148
+ }
149
+ logger.debug(`✅ Created welcome notifications for user ${user.id}`);
150
+ }
151
+ // Send welcome onboarding email with bonus amount if enabled
152
+ try {
153
+ await sendWelcomeOnboardingEmail({
154
+ email: user.email || "",
155
+ displayName: user.user_metadata?.full_name || "user",
156
+ locale,
157
+ ownerId: user.id,
158
+ bonusAmountUsd: ENABLE_SIGNUP_BONUS
159
+ ? SIGNUP_BONUS_AMOUNT_USD
160
+ : undefined,
161
+ });
162
+ logger.debug(`📧 Sent welcome onboarding email to ${user.email}`);
163
+ }
164
+ catch (emailError) {
165
+ logger.warn("Failed to send welcome email (non-blocking):", emailError);
166
+ // Don't fail the response if email fails
167
+ }
168
+ return NextResponse.json({
169
+ success: true,
170
+ message: "Welcome bonus added successfully",
171
+ });
172
+ }
173
+ catch (error) {
174
+ logger.error("Error in add-welcome-bonus:", error);
175
+ return NextResponse.json({ error: "Internal server error" }, { status: 500 });
176
+ }
177
+ }
@@ -0,0 +1,3 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ export declare function GET(request: NextRequest): Promise<NextResponse<unknown>>;
3
+ //# sourceMappingURL=callback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callback.d.ts","sourceRoot":"","sources":["../../../src/api/public/callback.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAWxD,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW,kCAuS7C"}