@lastbrain/module-auth 2.0.27 → 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.
- package/README.md +55 -7
- package/dist/api/admin/signup-stats.d.ts.map +1 -1
- package/dist/api/admin/signup-stats.js +2 -1
- package/dist/api/admin/storage/usage.d.ts +18 -0
- package/dist/api/admin/storage/usage.d.ts.map +1 -0
- package/dist/api/admin/storage/usage.js +100 -0
- package/dist/api/admin/users/[id]/notifications.d.ts.map +1 -1
- package/dist/api/admin/users/[id]/notifications.js +3 -2
- package/dist/api/admin/users/[id].d.ts.map +1 -1
- package/dist/api/admin/users/[id].js +3 -2
- package/dist/api/admin/users/reactivate/[id].d.ts +16 -0
- package/dist/api/admin/users/reactivate/[id].d.ts.map +1 -0
- package/dist/api/admin/users/reactivate/[id].js +59 -0
- package/dist/api/admin/users/suspend/[id].d.ts +16 -0
- package/dist/api/admin/users/suspend/[id].d.ts.map +1 -0
- package/dist/api/admin/users/suspend/[id].js +59 -0
- package/dist/api/admin/users-by-source.d.ts.map +1 -1
- package/dist/api/admin/users-by-source.js +2 -1
- package/dist/api/admin/users.d.ts.map +1 -1
- package/dist/api/admin/users.js +53 -2
- package/dist/api/auth/account/email-change.d.ts +7 -0
- package/dist/api/auth/account/email-change.d.ts.map +1 -0
- package/dist/api/auth/account/email-change.js +39 -0
- package/dist/api/auth/account/reset-password.d.ts +7 -0
- package/dist/api/auth/account/reset-password.d.ts.map +1 -0
- package/dist/api/auth/account/reset-password.js +36 -0
- package/dist/api/auth/check-username.d.ts +9 -0
- package/dist/api/auth/check-username.d.ts.map +1 -0
- package/dist/api/auth/check-username.js +35 -0
- package/dist/api/auth/establish-session.d.ts +2 -0
- package/dist/api/auth/establish-session.d.ts.map +1 -0
- package/dist/api/auth/establish-session.js +23 -0
- package/dist/api/auth/me.d.ts +4 -4
- package/dist/api/auth/me.d.ts.map +1 -1
- package/dist/api/auth/me.js +28 -6
- package/dist/api/auth/profile.d.ts.map +1 -1
- package/dist/api/auth/profile.js +6 -3
- package/dist/api/auth/storage/recalculate.d.ts +15 -0
- package/dist/api/auth/storage/recalculate.d.ts.map +1 -0
- package/dist/api/auth/storage/recalculate.js +68 -0
- package/dist/api/auth/storage/usage.d.ts +10 -0
- package/dist/api/auth/storage/usage.d.ts.map +1 -0
- package/dist/api/auth/storage/usage.js +86 -0
- package/dist/api/public/add-welcome-bonus.d.ts +16 -0
- package/dist/api/public/add-welcome-bonus.d.ts.map +1 -0
- package/dist/api/public/add-welcome-bonus.js +177 -0
- package/dist/api/public/callback.d.ts +3 -0
- package/dist/api/public/callback.d.ts.map +1 -0
- package/dist/api/public/callback.js +197 -0
- package/dist/api/public/reset-password.d.ts +3 -0
- package/dist/api/public/reset-password.d.ts.map +1 -0
- package/dist/api/public/reset-password.js +43 -0
- package/dist/api/public/set-session.d.ts +7 -0
- package/dist/api/public/set-session.d.ts.map +1 -0
- package/dist/api/public/set-session.js +55 -0
- package/dist/api/public/signin.d.ts.map +1 -1
- package/dist/api/public/signin.js +31 -0
- package/dist/api/public/signup.d.ts.map +1 -1
- package/dist/api/public/signup.js +38 -27
- package/dist/api/public/webhook/storage-addon.d.ts +9 -0
- package/dist/api/public/webhook/storage-addon.d.ts.map +1 -0
- package/dist/api/public/webhook/storage-addon.js +155 -0
- package/dist/api/storage.js +2 -2
- package/dist/auth.build.config.d.ts.map +1 -1
- package/dist/auth.build.config.js +126 -11
- package/dist/components/AccountButton.d.ts.map +1 -1
- package/dist/components/AccountButton.js +54 -28
- package/dist/components/Doc.d.ts.map +1 -1
- package/dist/components/Doc.js +1 -1
- package/dist/components/HasProfil.d.ts +4 -0
- package/dist/components/HasProfil.d.ts.map +1 -0
- package/dist/components/HasProfil.js +39 -0
- package/dist/components/auth/dashboard.d.ts +1 -1
- package/dist/components/auth/dashboard.d.ts.map +1 -1
- package/dist/components/auth/dashboard.js +34 -7
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/lib/app-branding-data.d.ts +22 -0
- package/dist/lib/app-branding-data.d.ts.map +1 -0
- package/dist/lib/app-branding-data.js +49 -0
- package/dist/lib/auth-email-service.d.ts +57 -0
- package/dist/lib/auth-email-service.d.ts.map +1 -0
- package/dist/lib/auth-email-service.js +382 -0
- package/dist/lib/auth-email-templates.d.ts +2 -0
- package/dist/lib/auth-email-templates.d.ts.map +1 -0
- package/dist/lib/auth-email-templates.js +1 -0
- package/dist/lib/site-url.d.ts +3 -0
- package/dist/lib/site-url.d.ts.map +1 -0
- package/dist/lib/site-url.js +11 -0
- package/dist/sitemap/manifest.d.ts +9 -0
- package/dist/sitemap/manifest.d.ts.map +1 -0
- package/dist/sitemap/manifest.js +14 -0
- package/dist/web/admin/signup-stats.js +3 -3
- package/dist/web/admin/user-detail.d.ts.map +1 -1
- package/dist/web/admin/user-detail.js +135 -14
- package/dist/web/admin/users-by-signup-source.js +2 -2
- package/dist/web/admin/users.d.ts.map +1 -1
- package/dist/web/admin/users.js +26 -7
- package/dist/web/auth/folder.d.ts.map +1 -1
- package/dist/web/auth/folder.js +4 -3
- package/dist/web/auth/profile.d.ts.map +1 -1
- package/dist/web/auth/profile.js +132 -13
- package/dist/web/auth/reglage.d.ts.map +1 -1
- package/dist/web/auth/reglage.js +15 -8
- package/dist/web/public/ResetPassword.d.ts.map +1 -1
- package/dist/web/public/ResetPassword.js +172 -2
- package/dist/web/public/SignInPage.d.ts.map +1 -1
- package/dist/web/public/SignInPage.js +39 -3
- package/dist/web/public/SignUpPage.d.ts.map +1 -1
- package/dist/web/public/SignUpPage.js +7 -2
- package/dist/web/public/auth-code-error.d.ts +2 -0
- package/dist/web/public/auth-code-error.d.ts.map +1 -0
- package/dist/web/public/auth-code-error.js +14 -0
- package/dist/web/public/confirm.d.ts +2 -0
- package/dist/web/public/confirm.d.ts.map +1 -0
- package/dist/web/public/confirm.js +157 -0
- package/package.json +10 -5
- package/src/api/admin/signup-stats.ts +2 -1
- package/src/api/admin/storage/usage.ts +141 -0
- package/src/api/admin/users/[id]/notifications.ts +3 -2
- package/src/api/admin/users/[id].ts +3 -2
- package/src/api/admin/users/reactivate/[id].ts +88 -0
- package/src/api/admin/users/suspend/[id].ts +85 -0
- package/src/api/admin/users-by-source.ts +2 -1
- package/src/api/admin/users.ts +59 -2
- package/src/api/auth/account/email-change.ts +54 -0
- package/src/api/auth/account/reset-password.ts +47 -0
- package/src/api/auth/check-username.ts +52 -0
- package/src/api/auth/establish-session.ts +32 -0
- package/src/api/auth/me.ts +29 -7
- package/src/api/auth/profile.ts +6 -2
- package/src/api/auth/storage/recalculate.ts +108 -0
- package/src/api/auth/storage/usage.ts +113 -0
- package/src/api/public/add-welcome-bonus.ts +229 -0
- package/src/api/public/callback.ts +307 -0
- package/src/api/public/reset-password.ts +52 -0
- package/src/api/public/set-session.ts +73 -0
- package/src/api/public/signin.ts +36 -0
- package/src/api/public/signup.ts +44 -37
- package/src/api/public/webhook/storage-addon.ts +267 -0
- package/src/api/storage.ts +2 -2
- package/src/auth.build.config.ts +126 -11
- package/src/components/AccountButton.tsx +114 -90
- package/src/components/Doc.tsx +47 -9
- package/src/components/HasProfil.tsx +63 -0
- package/src/components/auth/dashboard.tsx +54 -13
- package/src/i18n/en.json +76 -8
- package/src/i18n/es.json +330 -0
- package/src/i18n/fr.json +74 -8
- package/src/index.ts +2 -0
- package/src/lib/app-branding-data.ts +90 -0
- package/src/lib/auth-email-service.ts +508 -0
- package/src/lib/auth-email-templates.ts +5 -0
- package/src/lib/site-url.ts +17 -0
- package/src/sitemap/manifest.ts +26 -0
- package/src/web/admin/signup-stats.tsx +3 -3
- package/src/web/admin/user-detail.tsx +314 -15
- package/src/web/admin/users-by-signup-source.tsx +2 -2
- package/src/web/admin/users.tsx +50 -14
- package/src/web/auth/folder.tsx +23 -5
- package/src/web/auth/profile.tsx +227 -13
- package/src/web/auth/reglage.tsx +55 -24
- package/src/web/public/ResetPassword.tsx +301 -1
- package/src/web/public/SignInPage.tsx +43 -3
- package/src/web/public/SignUpPage.tsx +14 -5
- package/src/web/public/auth-code-error.tsx +49 -0
- package/src/web/public/confirm.tsx +195 -0
- package/supabase/migrations/20251112000001_auto_profile_and_admin_view.sql +3 -1
- package/supabase/migrations/20251127100000_rename_body_to_message.sql +8 -3
- package/supabase/migrations/20260120150001_add_user_storage.sql +105 -0
- package/supabase/migrations/20260122131200_add_global_addons_system.sql +305 -0
- package/supabase/migrations/20260123100000_enable_vector_extension.sql +9 -0
- package/supabase/migrations/20260123140000_module_auth_fix_get_user_limits_null.sql +93 -0
- package/supabase/migrations/20260123145000_module_auth_fix_circular_storage_base.sql +89 -0
- package/supabase/migrations/20260129400000_add_username_to_user_profil.sql +33 -0
- package/dist/web/auth/dashboard.d.ts +0 -2
- package/dist/web/auth/dashboard.d.ts.map +0 -1
- package/dist/web/auth/dashboard.js +0 -48
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { getSupabaseServerClient, getSupabaseServiceClient, } from "@lastbrain/core/server";
|
|
3
|
+
import { logger } from "@lastbrain/core";
|
|
4
|
+
import { sendWelcomeOnboardingEmail, sendNewUserNotificationToAdmin, } from "../../lib/auth-email-service";
|
|
5
|
+
export async function GET(request) {
|
|
6
|
+
const { searchParams } = new URL(request.url);
|
|
7
|
+
const code = searchParams.get("code");
|
|
8
|
+
const next = searchParams.get("next") ?? "/";
|
|
9
|
+
const error = searchParams.get("error");
|
|
10
|
+
const errorDescription = searchParams.get("error_description");
|
|
11
|
+
// If there's an error from Supabase, redirect to error page
|
|
12
|
+
if (error) {
|
|
13
|
+
logger.error("Auth callback error:", error, errorDescription);
|
|
14
|
+
return NextResponse.redirect(new URL(`/auth-code-error?error=${encodeURIComponent(error)}&description=${encodeURIComponent(errorDescription || "")}`, request.url));
|
|
15
|
+
}
|
|
16
|
+
// If we have a code, exchange it server-side for a session
|
|
17
|
+
if (code) {
|
|
18
|
+
try {
|
|
19
|
+
// Get Supabase server client with proper cookie handlers
|
|
20
|
+
const supabase = await getSupabaseServerClient();
|
|
21
|
+
// Exchange code for session - cookies are set automatically via @supabase/ssr
|
|
22
|
+
const { data, error: exchangeError } = await supabase.auth.exchangeCodeForSession(code);
|
|
23
|
+
if (exchangeError || !data?.session) {
|
|
24
|
+
logger.error("Code exchange failed:", exchangeError);
|
|
25
|
+
return NextResponse.redirect(new URL(`/auth-code-error?error=${encodeURIComponent(exchangeError?.message || "Failed to exchange code for session")}`, request.url));
|
|
26
|
+
}
|
|
27
|
+
logger.debug("✅ Session established successfully for user:", data.user?.email);
|
|
28
|
+
// Add welcome bonus when email is confirmed
|
|
29
|
+
if (data.user?.id) {
|
|
30
|
+
try {
|
|
31
|
+
// Check if welcome bonus is enabled via env var
|
|
32
|
+
const ENABLE_SIGNUP_BONUS = process.env.NEXT_PUBLIC_ENABLE_SIGNUP_BONUS === "true";
|
|
33
|
+
const SIGNUP_BONUS_AMOUNT_USD = parseFloat(process.env.NEXT_PUBLIC_SIGNUP_BONUS_AMOUNT_USD || "1.0");
|
|
34
|
+
logger.debug(`[callback] Signup bonus config - enabled: ${ENABLE_SIGNUP_BONUS}, amount: ${SIGNUP_BONUS_AMOUNT_USD}`);
|
|
35
|
+
if (!ENABLE_SIGNUP_BONUS) {
|
|
36
|
+
logger.debug("Signup bonus disabled via NEXT_PUBLIC_ENABLE_SIGNUP_BONUS");
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
const serviceClient = await getSupabaseServiceClient();
|
|
40
|
+
// Check if we already added bonus for this user
|
|
41
|
+
const { data: existingBonus } = await serviceClient
|
|
42
|
+
.from("user_token_ledger")
|
|
43
|
+
.select("id")
|
|
44
|
+
.eq("owner_id", data.user.id)
|
|
45
|
+
.eq("type", "signup_bonus")
|
|
46
|
+
.single();
|
|
47
|
+
logger.debug(`[callback] Existing bonus check for user ${data.user.id}:`, existingBonus ? "Already exists" : "Not found");
|
|
48
|
+
// Only add bonus if not already added
|
|
49
|
+
if (!existingBonus) {
|
|
50
|
+
// Add welcome bonus using wallet system
|
|
51
|
+
const MARGIN_TARGET = 0.6;
|
|
52
|
+
const providerBudgetUsd = SIGNUP_BONUS_AMOUNT_USD * (1 - MARGIN_TARGET); // 40%
|
|
53
|
+
logger.debug(`[callback] Inserting bonus for user ${data.user.id}: sell=$${SIGNUP_BONUS_AMOUNT_USD}, provider=$${providerBudgetUsd}`);
|
|
54
|
+
// Insert into ledger with USD values
|
|
55
|
+
const { error: ledgerError } = await serviceClient
|
|
56
|
+
.from("user_token_ledger")
|
|
57
|
+
.insert({
|
|
58
|
+
owner_id: data.user.id,
|
|
59
|
+
type: "signup_bonus",
|
|
60
|
+
amount: 0, // Tokens deprecated
|
|
61
|
+
sell_value_added_usd: SIGNUP_BONUS_AMOUNT_USD,
|
|
62
|
+
provider_budget_added_usd: providerBudgetUsd,
|
|
63
|
+
meta: {
|
|
64
|
+
reason: "signup_welcome_bonus",
|
|
65
|
+
provider: "system",
|
|
66
|
+
},
|
|
67
|
+
created_by: null,
|
|
68
|
+
});
|
|
69
|
+
if (ledgerError) {
|
|
70
|
+
logger.error("[callback] Error adding welcome bonus to ledger:", ledgerError);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
logger.debug(`[callback] ✅ Bonus inserted in ledger for user ${data.user.id}`);
|
|
74
|
+
// Update wallet table
|
|
75
|
+
const { data: currentWallet } = await serviceClient
|
|
76
|
+
.from("user_token_wallet")
|
|
77
|
+
.select("wallet_provider_budget_usd, wallet_sell_value_usd")
|
|
78
|
+
.eq("user_id", data.user.id)
|
|
79
|
+
.single();
|
|
80
|
+
const newProviderBudget = (currentWallet?.wallet_provider_budget_usd || 0) +
|
|
81
|
+
providerBudgetUsd;
|
|
82
|
+
const newSellValue = (currentWallet?.wallet_sell_value_usd || 0) +
|
|
83
|
+
SIGNUP_BONUS_AMOUNT_USD;
|
|
84
|
+
const { error: walletError } = await serviceClient
|
|
85
|
+
.from("user_token_wallet")
|
|
86
|
+
.upsert({
|
|
87
|
+
user_id: data.user.id,
|
|
88
|
+
wallet_provider_budget_usd: newProviderBudget,
|
|
89
|
+
wallet_sell_value_usd: newSellValue,
|
|
90
|
+
updated_at: new Date().toISOString(),
|
|
91
|
+
}, {
|
|
92
|
+
onConflict: "user_id",
|
|
93
|
+
});
|
|
94
|
+
if (walletError) {
|
|
95
|
+
logger.error("Error updating wallet for welcome bonus:", walletError);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
logger.debug(`✅ Added $${SIGNUP_BONUS_AMOUNT_USD} welcome bonus to user ${data.user.id}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Extract locale from user metadata or default to 'en'
|
|
103
|
+
const locale = data.user.user_metadata?.locale || "en";
|
|
104
|
+
logger.debug(`[callback] User locale detected: ${locale} (from metadata: ${data.user.user_metadata?.locale || "not set"})`);
|
|
105
|
+
// Add welcome notifications
|
|
106
|
+
const notifications = [
|
|
107
|
+
{
|
|
108
|
+
title: locale === "fr" ? "Bienvenue ! 👋" : "Welcome! 👋",
|
|
109
|
+
body: locale === "fr"
|
|
110
|
+
? "Merci de vous être inscrit ! Explorez toutes les fonctionnalités disponibles."
|
|
111
|
+
: "Thank you for signing up! Explore all available features.",
|
|
112
|
+
type: "primary",
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
// Add signup bonus notification only if enabled
|
|
116
|
+
if (ENABLE_SIGNUP_BONUS && SIGNUP_BONUS_AMOUNT_USD > 0) {
|
|
117
|
+
notifications.push({
|
|
118
|
+
title: locale === "fr"
|
|
119
|
+
? `🎁 Cadeau de bienvenue : $${SIGNUP_BONUS_AMOUNT_USD}`
|
|
120
|
+
: `🎁 Welcome gift: $${SIGNUP_BONUS_AMOUNT_USD}`,
|
|
121
|
+
body: locale === "fr"
|
|
122
|
+
? `Nous vous offrons $${SIGNUP_BONUS_AMOUNT_USD} de crédit IA pour démarrer. Utilisez-les pour accéder à nos services premium.`
|
|
123
|
+
: `We're giving you $${SIGNUP_BONUS_AMOUNT_USD} AI credits to get started. Use them to access our premium services.`,
|
|
124
|
+
type: "success",
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
for (const notification of notifications) {
|
|
128
|
+
const { error: notifError } = await serviceClient
|
|
129
|
+
.from("user_notifications")
|
|
130
|
+
.insert({
|
|
131
|
+
owner_id: data.user.id,
|
|
132
|
+
title: notification.title,
|
|
133
|
+
body: notification.body,
|
|
134
|
+
type: notification.type,
|
|
135
|
+
});
|
|
136
|
+
if (notifError) {
|
|
137
|
+
logger.error("[callback] Error creating welcome notification:", notifError);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
logger.debug(`[callback] ✅ Notification created: ${notification.title}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
logger.debug(`✅ Created welcome notifications for user ${data.user.id}`);
|
|
144
|
+
// Send welcome onboarding email with bonus amount if enabled
|
|
145
|
+
try {
|
|
146
|
+
await sendWelcomeOnboardingEmail({
|
|
147
|
+
email: data.user.email || "",
|
|
148
|
+
displayName: data.user.user_metadata?.full_name || "user",
|
|
149
|
+
locale,
|
|
150
|
+
ownerId: data.user.id,
|
|
151
|
+
bonusAmountUsd: ENABLE_SIGNUP_BONUS && SIGNUP_BONUS_AMOUNT_USD > 0
|
|
152
|
+
? SIGNUP_BONUS_AMOUNT_USD
|
|
153
|
+
: undefined,
|
|
154
|
+
});
|
|
155
|
+
logger.debug(`📧 Sent welcome onboarding email to ${data.user.email}`);
|
|
156
|
+
}
|
|
157
|
+
catch (emailError) {
|
|
158
|
+
logger.warn("Failed to send welcome email (non-blocking):", emailError);
|
|
159
|
+
// Don't fail the response if email fails
|
|
160
|
+
}
|
|
161
|
+
// Send notification to admin about new user signup
|
|
162
|
+
try {
|
|
163
|
+
await sendNewUserNotificationToAdmin({
|
|
164
|
+
userEmail: data.user.email || "",
|
|
165
|
+
displayName: data.user.user_metadata?.full_name || undefined,
|
|
166
|
+
userId: data.user.id,
|
|
167
|
+
locale,
|
|
168
|
+
bonusAmountUsd: ENABLE_SIGNUP_BONUS && SIGNUP_BONUS_AMOUNT_USD > 0
|
|
169
|
+
? SIGNUP_BONUS_AMOUNT_USD
|
|
170
|
+
: undefined,
|
|
171
|
+
});
|
|
172
|
+
logger.debug(`📧 Sent admin notification for new user: ${data.user.email}`);
|
|
173
|
+
}
|
|
174
|
+
catch (adminEmailError) {
|
|
175
|
+
logger.warn("Failed to send admin notification email (non-blocking):", adminEmailError);
|
|
176
|
+
// Don't fail the response if admin email fails
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch (bonusError) {
|
|
181
|
+
logger.error("Error adding welcome bonus (non-blocking):", bonusError);
|
|
182
|
+
// Don't fail auth if bonus creation fails
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Success! Cookies have been set via the @supabase/ssr cookie handlers
|
|
186
|
+
// Redirect to destination
|
|
187
|
+
return NextResponse.redirect(new URL(next, request.url));
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
logger.error("Unexpected callback error:", err);
|
|
191
|
+
return NextResponse.redirect(new URL(`/auth-code-error?error=${encodeURIComponent("Unexpected error during authentication")}`, request.url));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// No code or error, redirect to next or home
|
|
195
|
+
logger.warn("Auth callback called without code or error");
|
|
196
|
+
return NextResponse.redirect(new URL(next, request.url));
|
|
197
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reset-password.d.ts","sourceRoot":"","sources":["../../../src/api/public/reset-password.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAsBxD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW,8BA6B9C"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { sendPasswordResetEmail } from "../../lib/auth-email-service";
|
|
3
|
+
import { logger } from "@lastbrain/core";
|
|
4
|
+
import { resolveSiteUrl } from "../../lib/site-url";
|
|
5
|
+
function extractLocaleFromRequest(request) {
|
|
6
|
+
// 1. Priorité : cookie NEXT_LOCALE
|
|
7
|
+
const localeCookie = request.cookies.get("NEXT_LOCALE")?.value;
|
|
8
|
+
if (localeCookie && /^[a-z]{2}$/.test(localeCookie)) {
|
|
9
|
+
return localeCookie;
|
|
10
|
+
}
|
|
11
|
+
// 2. Fallback : header accept-language
|
|
12
|
+
const lang = request.headers
|
|
13
|
+
.get("accept-language")
|
|
14
|
+
?.split(",")?.[0]
|
|
15
|
+
?.split("-")?.[0];
|
|
16
|
+
return lang === "fr" || lang === "en" ? lang : undefined;
|
|
17
|
+
}
|
|
18
|
+
const DEFAULT_LOCALE = "fr";
|
|
19
|
+
export async function POST(request) {
|
|
20
|
+
try {
|
|
21
|
+
const locale = extractLocaleFromRequest(request);
|
|
22
|
+
const body = await request.json();
|
|
23
|
+
const { email, next } = body;
|
|
24
|
+
const resolvedLocale = locale || DEFAULT_LOCALE;
|
|
25
|
+
const baseRedirect = `${resolveSiteUrl(request)}/${resolvedLocale}/reset-password`;
|
|
26
|
+
const redirectTo = next
|
|
27
|
+
? `${baseRedirect}?next=${encodeURIComponent(next)}`
|
|
28
|
+
: baseRedirect;
|
|
29
|
+
if (!email) {
|
|
30
|
+
return NextResponse.json({ error: "Email requis" }, { status: 400 });
|
|
31
|
+
}
|
|
32
|
+
logger.debug("[reset-password] computed redirectTo", { redirectTo, next });
|
|
33
|
+
await sendPasswordResetEmail({ email, locale, redirectTo, next });
|
|
34
|
+
const respBody = { message: "Lien de réinitialisation envoyé" };
|
|
35
|
+
if (process.env.NODE_ENV !== "production")
|
|
36
|
+
respBody.redirectTo = redirectTo;
|
|
37
|
+
return NextResponse.json(respBody);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
logger.error("reset-password error:", error);
|
|
41
|
+
return NextResponse.json({ error: "Impossible d'envoyer le lien de réinitialisation" }, { status: 500 });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"set-session.d.ts","sourceRoot":"","sources":["../../../src/api/public/set-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIxD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;IAoE9C"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { getSupabaseServerClient } from "@lastbrain/core/server";
|
|
3
|
+
import { logger } from "@lastbrain/core";
|
|
4
|
+
export async function POST(request) {
|
|
5
|
+
try {
|
|
6
|
+
const payload = await request.json().catch((e) => {
|
|
7
|
+
logger.error("[set-session] failed to parse body", e);
|
|
8
|
+
return {};
|
|
9
|
+
});
|
|
10
|
+
const { access_token, refresh_token } = payload;
|
|
11
|
+
logger.debug("[set-session] incoming payload", {
|
|
12
|
+
hasAccessToken: !!access_token,
|
|
13
|
+
hasRefreshToken: !!refresh_token,
|
|
14
|
+
url: request.url,
|
|
15
|
+
headers: {
|
|
16
|
+
host: request.headers.get("host"),
|
|
17
|
+
origin: request.headers.get("origin"),
|
|
18
|
+
referer: request.headers.get("referer"),
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
if (!access_token || !refresh_token) {
|
|
22
|
+
logger.warn("[set-session] missing tokens", { payload });
|
|
23
|
+
return NextResponse.json({ error: "Missing tokens" }, { status: 400 });
|
|
24
|
+
}
|
|
25
|
+
const supabase = await getSupabaseServerClient();
|
|
26
|
+
logger.debug("[set-session] calling supabase.auth.setSession");
|
|
27
|
+
const { data, error } = await supabase.auth
|
|
28
|
+
.setSession({
|
|
29
|
+
access_token,
|
|
30
|
+
refresh_token,
|
|
31
|
+
})
|
|
32
|
+
.catch((e) => {
|
|
33
|
+
logger.error("[set-session] supabase.setSession threw", e);
|
|
34
|
+
return { data: null, error: { message: String(e) } };
|
|
35
|
+
});
|
|
36
|
+
logger.debug("[set-session] supabase response", {
|
|
37
|
+
hasSession: !!data?.session,
|
|
38
|
+
userId: data?.session?.user?.id,
|
|
39
|
+
error: error?.message,
|
|
40
|
+
});
|
|
41
|
+
if (error || !data?.session) {
|
|
42
|
+
logger.warn("[set-session] failed to set session", { error });
|
|
43
|
+
return NextResponse.json({ error: error?.message || "Failed to set session" }, { status: 401 });
|
|
44
|
+
}
|
|
45
|
+
// Cookies are set by the Supabase SSR adapter inside setSession
|
|
46
|
+
logger.debug("[set-session] session set successfully for user", {
|
|
47
|
+
userId: data.session.user.id,
|
|
48
|
+
});
|
|
49
|
+
return NextResponse.json({ success: true });
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
logger.error("[set-session] unexpected error", err);
|
|
53
|
+
return NextResponse.json({ error: err?.message || "Unexpected error" }, { status: 500 });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signin.d.ts","sourceRoot":"","sources":["../../../src/api/public/signin.ts"],"names":[],"mappings":"AAWA,wBAAsB,IAAI,CAAC,OAAO,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"signin.d.ts","sourceRoot":"","sources":["../../../src/api/public/signin.ts"],"names":[],"mappings":"AAWA,wBAAsB,IAAI,CAAC,OAAO,EAAE,OAAO,qBAuD1C"}
|
|
@@ -21,5 +21,36 @@ export async function POST(request) {
|
|
|
21
21
|
if (error) {
|
|
22
22
|
return jsonResponse({ error: error.message }, 400);
|
|
23
23
|
}
|
|
24
|
+
// Si le client a envoyé un cookie NEXT_LOCALE, synchroniser côté serveur
|
|
25
|
+
// la préférence stockée en base (user_profil.language). Ne rien faire
|
|
26
|
+
// si le cookie n'existe pas.
|
|
27
|
+
try {
|
|
28
|
+
const cookieHeader = request.headers.get("cookie") || "";
|
|
29
|
+
const match = cookieHeader.match(/(?:^|; )NEXT_LOCALE=([^;]+)/);
|
|
30
|
+
const currentCookie = match ? decodeURIComponent(match[1]) : null;
|
|
31
|
+
if (currentCookie && data?.user?.id) {
|
|
32
|
+
const { data: profil, error: _profilErr } = await supabase
|
|
33
|
+
.from("user_profil")
|
|
34
|
+
.select("language")
|
|
35
|
+
.eq("user_id", data.user.id)
|
|
36
|
+
.maybeSingle();
|
|
37
|
+
const profileLang = profil?.language;
|
|
38
|
+
if (profileLang && profileLang !== currentCookie) {
|
|
39
|
+
const maxAge = 365 * 24 * 60 * 60; // 1 an
|
|
40
|
+
const cookieValue = `NEXT_LOCALE=${encodeURIComponent(profileLang)}; Path=/; Max-Age=${maxAge}; SameSite=Lax`;
|
|
41
|
+
return new Response(JSON.stringify({ data, updated: true }), {
|
|
42
|
+
headers: {
|
|
43
|
+
"content-type": "application/json",
|
|
44
|
+
"set-cookie": cookieValue,
|
|
45
|
+
},
|
|
46
|
+
status: 200,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
// ne pas bloquer la connexion si la sync échoue
|
|
53
|
+
console.debug("signin: sync-locale failed", e);
|
|
54
|
+
}
|
|
24
55
|
return jsonResponse({ data });
|
|
25
56
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signup.d.ts","sourceRoot":"","sources":["../../../src/api/public/signup.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"signup.d.ts","sourceRoot":"","sources":["../../../src/api/public/signup.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAqBxD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;;;IA0F9C"}
|
|
@@ -1,56 +1,67 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getSupabaseServiceClient } from "@lastbrain/core/server";
|
|
2
2
|
import { NextResponse } from "next/server";
|
|
3
|
+
import { sendSignupConfirmationEmail } 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
|
+
}
|
|
3
13
|
export async function POST(request) {
|
|
4
14
|
try {
|
|
5
15
|
const body = await request.json();
|
|
16
|
+
const locale = extractLocale(request);
|
|
6
17
|
const defaultSource = process.env.APP_NAME || "undefined";
|
|
7
|
-
console.log("🚀 ~ POST ~ defaultSource:", defaultSource);
|
|
8
18
|
const { email, password, fullName, signupSource = defaultSource } = body;
|
|
9
19
|
// Validate required fields
|
|
10
20
|
if (!email || !password) {
|
|
11
21
|
return NextResponse.json({ error: "Email et mot de passe requis." }, { status: 400 });
|
|
12
22
|
}
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (signUpError) {
|
|
28
|
-
return NextResponse.json({ error: signUpError.message }, { status: 400 });
|
|
23
|
+
// Générer le lien d'inscription + envoyer l'email via module-contact-pro
|
|
24
|
+
let user;
|
|
25
|
+
try {
|
|
26
|
+
user = await sendSignupConfirmationEmail({
|
|
27
|
+
email,
|
|
28
|
+
password,
|
|
29
|
+
locale,
|
|
30
|
+
fullName,
|
|
31
|
+
signupSource,
|
|
32
|
+
// Use client-side confirm page so Supabase can return hash tokens
|
|
33
|
+
// and the client `ConfirmPage` will set the session and add welcome bonus.
|
|
34
|
+
redirectTo: `${resolveSiteUrl(request)}/confirm`,
|
|
35
|
+
next: body.next,
|
|
36
|
+
});
|
|
29
37
|
}
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
catch (emailError) {
|
|
39
|
+
// Handle Supabase auth errors
|
|
40
|
+
if (emailError?.status === 422 && emailError?.code === "email_exists") {
|
|
41
|
+
return NextResponse.json({ error: "Cet email est déjà utilisé." }, { status: 409 });
|
|
42
|
+
}
|
|
43
|
+
throw emailError;
|
|
32
44
|
}
|
|
33
|
-
// Create user profile with signup_source
|
|
34
45
|
const serviceClient = await getSupabaseServiceClient();
|
|
35
46
|
// Check if profile already exists
|
|
36
47
|
const { data: existingProfile } = await serviceClient
|
|
37
48
|
.from("user_profil")
|
|
38
49
|
.select("owner_id")
|
|
39
|
-
.eq("owner_id",
|
|
50
|
+
.eq("owner_id", user?.id)
|
|
40
51
|
.single();
|
|
41
52
|
// Only create profile if it doesn't exist
|
|
42
53
|
if (!existingProfile) {
|
|
43
54
|
const { error: profileError } = await serviceClient
|
|
44
55
|
.from("user_profil")
|
|
45
56
|
.insert({
|
|
46
|
-
owner_id:
|
|
57
|
+
owner_id: user?.id,
|
|
47
58
|
first_name: fullName?.split(" ")[0] || "",
|
|
48
59
|
last_name: fullName?.split(" ").slice(1).join(" ") || "",
|
|
49
60
|
signup_source: signupSource,
|
|
50
61
|
preferences: {},
|
|
51
62
|
});
|
|
52
63
|
if (profileError) {
|
|
53
|
-
|
|
64
|
+
logger.error("Error creating user profile:", profileError);
|
|
54
65
|
return NextResponse.json({
|
|
55
66
|
error: "Compte créé mais profil non configuré",
|
|
56
67
|
message: profileError.message,
|
|
@@ -59,13 +70,13 @@ export async function POST(request) {
|
|
|
59
70
|
}
|
|
60
71
|
return NextResponse.json({
|
|
61
72
|
data: {
|
|
62
|
-
user
|
|
63
|
-
message: "Compte créé avec succès",
|
|
73
|
+
user,
|
|
74
|
+
message: "Compte créé avec succès. Vérifiez vos emails pour confirmer votre compte.",
|
|
64
75
|
},
|
|
65
76
|
}, { status: 201 });
|
|
66
77
|
}
|
|
67
78
|
catch (error) {
|
|
68
|
-
|
|
79
|
+
logger.error("Signup error:", error);
|
|
69
80
|
return NextResponse.json({ error: "Erreur interne du serveur" }, { status: 500 });
|
|
70
81
|
}
|
|
71
82
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/auth/webhook/storage-addon
|
|
3
|
+
* Webhook handler for storage addon subscription events
|
|
4
|
+
* Called by module-billing-pro when a storage addon subscription item is updated
|
|
5
|
+
*/
|
|
6
|
+
export declare const runtime = "nodejs";
|
|
7
|
+
export declare const dynamic = "force-dynamic";
|
|
8
|
+
export declare function POST(request: Request): Promise<Response>;
|
|
9
|
+
//# sourceMappingURL=storage-addon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-addon.d.ts","sourceRoot":"","sources":["../../../../src/api/public/webhook/storage-addon.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,eAAO,MAAM,OAAO,WAAW,CAAC;AAChC,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAevC,wBAAsB,IAAI,CAAC,OAAO,EAAE,OAAO,qBAgP1C"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/auth/webhook/storage-addon
|
|
3
|
+
* Webhook handler for storage addon subscription events
|
|
4
|
+
* Called by module-billing-pro when a storage addon subscription item is updated
|
|
5
|
+
*/
|
|
6
|
+
import { getSupabaseServiceClient } from "@lastbrain/core/server";
|
|
7
|
+
import { logger } from "@lastbrain/core";
|
|
8
|
+
// Runtime config
|
|
9
|
+
export const runtime = "nodejs";
|
|
10
|
+
export const dynamic = "force-dynamic";
|
|
11
|
+
export async function POST(request) {
|
|
12
|
+
try {
|
|
13
|
+
const body = await request.json();
|
|
14
|
+
const { eventType, owner_id, addon_key, stripe_subscription_id, stripe_subscription_item_id, stripe_customer_id, quantity = 1, status, current_period_start, current_period_end, } = body;
|
|
15
|
+
logger.debug(`[Storage Addon Webhook] Processing ${eventType} for user ${owner_id}, addon ${addon_key}`);
|
|
16
|
+
if (!owner_id ||
|
|
17
|
+
!addon_key ||
|
|
18
|
+
!stripe_subscription_id ||
|
|
19
|
+
!stripe_subscription_item_id) {
|
|
20
|
+
logger.warn("[Storage Addon Webhook] Missing required fields in payload");
|
|
21
|
+
return Response.json({ received: true }, { status: 200 });
|
|
22
|
+
}
|
|
23
|
+
const supabase = await getSupabaseServiceClient();
|
|
24
|
+
// Find the addon by key
|
|
25
|
+
const { data: addon, error: addonError } = await supabase
|
|
26
|
+
.from("global_addons")
|
|
27
|
+
.select("id")
|
|
28
|
+
.eq("addon_key", addon_key)
|
|
29
|
+
.eq("addon_type", "storage")
|
|
30
|
+
.eq("is_active", true)
|
|
31
|
+
.single();
|
|
32
|
+
if (addonError || !addon) {
|
|
33
|
+
logger.warn(`[Storage Addon Webhook] Addon not found for key: ${addon_key}`);
|
|
34
|
+
return Response.json({ received: true }, { status: 200 });
|
|
35
|
+
}
|
|
36
|
+
// Handle different event types
|
|
37
|
+
switch (eventType) {
|
|
38
|
+
case "addon.created":
|
|
39
|
+
case "addon.updated": {
|
|
40
|
+
// Upsert user addon activation
|
|
41
|
+
// Use owner_id + addon_id as the unique key (one addon type per user)
|
|
42
|
+
// When a subscription is canceled and recreated, we update the same record with new stripe IDs
|
|
43
|
+
const { error: upsertError } = await supabase
|
|
44
|
+
.from("user_global_addons")
|
|
45
|
+
.upsert({
|
|
46
|
+
owner_id,
|
|
47
|
+
addon_id: addon.id,
|
|
48
|
+
quantity,
|
|
49
|
+
status,
|
|
50
|
+
current_period_start,
|
|
51
|
+
current_period_end,
|
|
52
|
+
stripe_customer_id,
|
|
53
|
+
stripe_subscription_id,
|
|
54
|
+
stripe_subscription_item_id,
|
|
55
|
+
updated_at: new Date().toISOString(),
|
|
56
|
+
}, {
|
|
57
|
+
// Use owner_id + addon_id as the unique key for idempotency
|
|
58
|
+
// This ensures one addon per user, even if subscription is recreated
|
|
59
|
+
onConflict: "owner_id,addon_id",
|
|
60
|
+
});
|
|
61
|
+
if (upsertError) {
|
|
62
|
+
logger.error("[Storage Addon Webhook] Error upserting addon:", upsertError);
|
|
63
|
+
return Response.json({ error: "Failed to update storage addon" }, { status: 500 });
|
|
64
|
+
}
|
|
65
|
+
logger.debug(`[Storage Addon Webhook] Storage addon ${addon_key} ${eventType} for user ${owner_id}: status=${status}, quantity=${quantity}`);
|
|
66
|
+
// Update user_profil.stockage to reflect total (optional, since get_user_limits calculates dynamically)
|
|
67
|
+
// We'll keep this for backward compatibility with existing code
|
|
68
|
+
try {
|
|
69
|
+
const limitsResult = await supabase.rpc("get_user_limits", {
|
|
70
|
+
owner_id_param: owner_id,
|
|
71
|
+
});
|
|
72
|
+
logger.debug(`[Storage Addon Webhook] get_user_limits raw result:`, JSON.stringify(limitsResult, null, 2));
|
|
73
|
+
if (limitsResult.error) {
|
|
74
|
+
logger.error(`[Storage Addon Webhook] get_user_limits error:`, limitsResult.error);
|
|
75
|
+
}
|
|
76
|
+
if (limitsResult.data) {
|
|
77
|
+
// Supabase RPC returns JSON as string, need to parse if string
|
|
78
|
+
const limitsData = typeof limitsResult.data === "string"
|
|
79
|
+
? JSON.parse(limitsResult.data)
|
|
80
|
+
: limitsResult.data;
|
|
81
|
+
logger.debug(`[Storage Addon Webhook] Parsed limits data:`, limitsData);
|
|
82
|
+
const totalStorageGb = limitsData.storage_quota_gb;
|
|
83
|
+
logger.debug(`[Storage Addon Webhook] Extracted storage_quota_gb: ${totalStorageGb}`);
|
|
84
|
+
// Update user_profil with new total
|
|
85
|
+
await supabase.from("user_profil").upsert({
|
|
86
|
+
owner_id,
|
|
87
|
+
stockage: totalStorageGb,
|
|
88
|
+
updated_at: new Date().toISOString(),
|
|
89
|
+
}, {
|
|
90
|
+
onConflict: "owner_id",
|
|
91
|
+
});
|
|
92
|
+
logger.debug(`[Storage Addon Webhook] Updated user_profil.stockage to ${totalStorageGb} GB for user ${owner_id}`);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
logger.warn(`[Storage Addon Webhook] get_user_limits returned no data for user ${owner_id}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
logger.error("[Storage Addon Webhook] Error updating user_profil.stockage:", error);
|
|
100
|
+
// Don't fail - the addon is still recorded
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case "addon.deleted":
|
|
105
|
+
case "addon.canceled": {
|
|
106
|
+
// Mark addon as canceled but keep for period_end grace
|
|
107
|
+
const { error: cancelError } = await supabase
|
|
108
|
+
.from("user_global_addons")
|
|
109
|
+
.update({
|
|
110
|
+
status: "canceled",
|
|
111
|
+
updated_at: new Date().toISOString(),
|
|
112
|
+
})
|
|
113
|
+
.eq("stripe_subscription_item_id", stripe_subscription_item_id);
|
|
114
|
+
if (cancelError) {
|
|
115
|
+
logger.error("[Storage Addon Webhook] Error canceling addon:", cancelError);
|
|
116
|
+
return Response.json({ error: "Failed to cancel storage addon" }, { status: 500 });
|
|
117
|
+
}
|
|
118
|
+
logger.debug(`[Storage Addon Webhook] Storage addon ${addon_key} canceled for user ${owner_id}`);
|
|
119
|
+
// Recalculate total storage
|
|
120
|
+
try {
|
|
121
|
+
const limitsResult = await supabase.rpc("get_user_limits", {
|
|
122
|
+
owner_id_param: owner_id,
|
|
123
|
+
});
|
|
124
|
+
if (limitsResult.data) {
|
|
125
|
+
const limitsData = typeof limitsResult.data === "string"
|
|
126
|
+
? JSON.parse(limitsResult.data)
|
|
127
|
+
: limitsResult.data;
|
|
128
|
+
const totalStorageGb = limitsData.storage_quota_gb;
|
|
129
|
+
await supabase.from("user_profil").upsert({
|
|
130
|
+
owner_id,
|
|
131
|
+
stockage: totalStorageGb,
|
|
132
|
+
updated_at: new Date().toISOString(),
|
|
133
|
+
}, {
|
|
134
|
+
onConflict: "owner_id",
|
|
135
|
+
});
|
|
136
|
+
logger.debug(`[Storage Addon Webhook] Recalculated user_profil.stockage to ${totalStorageGb} GB after addon cancellation`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
logger.warn("[Storage Addon Webhook] Could not recalculate storage:", error);
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
default:
|
|
145
|
+
logger.warn(`[Storage Addon Webhook] Unknown event type: ${eventType}`);
|
|
146
|
+
}
|
|
147
|
+
return Response.json({ received: true }, { status: 200 });
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
logger.error("[Storage Addon Webhook] Error:", error);
|
|
151
|
+
return Response.json({
|
|
152
|
+
error: error instanceof Error ? error.message : "Webhook error",
|
|
153
|
+
}, { status: 400 });
|
|
154
|
+
}
|
|
155
|
+
}
|