@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
|
@@ -103,7 +103,9 @@ BEGIN
|
|
|
103
103
|
au.raw_user_meta_data->>'avatar',
|
|
104
104
|
fp.avatar_url
|
|
105
105
|
),
|
|
106
|
-
'avatar_sizes', au.raw_user_meta_data->'avatar_sizes',
|
|
106
|
+
'avatar_sizes', COALESCE(au.raw_user_meta_data->'avatar_sizes', '{}'::jsonb),
|
|
107
|
+
'raw_app_meta_data', COALESCE(au.raw_app_meta_data, '{}'::jsonb),
|
|
108
|
+
'raw_user_meta_data', COALESCE(au.raw_user_meta_data, '{}'::jsonb),
|
|
107
109
|
'metadata', au.raw_user_meta_data,
|
|
108
110
|
'profile', json_build_object(
|
|
109
111
|
'first_name', fp.first_name,
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
-- Migration: Add message column to user_notifications table
|
|
2
2
|
-- message: short text summary (required)
|
|
3
3
|
-- body: rich HTML content (optional)
|
|
4
|
-
|
|
5
4
|
ALTER TABLE public.user_notifications
|
|
6
5
|
ADD COLUMN IF NOT EXISTS message TEXT NOT NULL DEFAULT '';
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_unique_signup_bonus_per_user ON public.user_token_ledger (owner_id)
|
|
8
|
+
WHERE
|
|
9
|
+
type = 'signup_bonus';
|
|
10
|
+
|
|
11
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_unique_welcome_bonus_per_user ON public.user_token_ledger (owner_id)
|
|
12
|
+
WHERE
|
|
13
|
+
type = 'gift'
|
|
14
|
+
AND meta->>'gift_type' = 'welcome_bonus';
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
-- Migration pour ajouter la colonne stockage dans user_profil
|
|
2
|
+
-- Date: 2026-01-22
|
|
3
|
+
|
|
4
|
+
-- Ajouter la colonne stockage (en GB) avec une valeur par défaut de 2 GB
|
|
5
|
+
ALTER TABLE public.user_profil
|
|
6
|
+
ADD COLUMN IF NOT EXISTS stockage DECIMAL(10,2) DEFAULT 2.00;
|
|
7
|
+
|
|
8
|
+
-- Ajouter un commentaire pour documenter la colonne
|
|
9
|
+
COMMENT ON COLUMN public.user_profil.stockage IS 'Espace de stockage alloué à l''utilisateur en gigaoctets (Go)';
|
|
10
|
+
|
|
11
|
+
-- Créer une fonction RPC pour calculer l'espace de stockage utilisé
|
|
12
|
+
CREATE OR REPLACE FUNCTION public.get_storage_usage(owner_id_param UUID)
|
|
13
|
+
RETURNS JSON
|
|
14
|
+
LANGUAGE plpgsql
|
|
15
|
+
SECURITY DEFINER
|
|
16
|
+
AS $$
|
|
17
|
+
DECLARE
|
|
18
|
+
allocated_gb DECIMAL(10,2);
|
|
19
|
+
allocated_bytes BIGINT;
|
|
20
|
+
used_bytes BIGINT := 0;
|
|
21
|
+
remaining_bytes BIGINT;
|
|
22
|
+
usage_percentage DECIMAL(5,2);
|
|
23
|
+
result JSON;
|
|
24
|
+
BEGIN
|
|
25
|
+
-- Récupérer l'espace alloué depuis user_profil
|
|
26
|
+
SELECT stockage INTO allocated_gb
|
|
27
|
+
FROM public.user_profil
|
|
28
|
+
WHERE owner_id = owner_id_param;
|
|
29
|
+
|
|
30
|
+
-- Si l'utilisateur n'a pas de profil, utiliser la valeur par défaut
|
|
31
|
+
IF allocated_gb IS NULL THEN
|
|
32
|
+
allocated_gb := 2.00;
|
|
33
|
+
END IF;
|
|
34
|
+
|
|
35
|
+
-- Convertir en bytes (1 GB = 1024^3 bytes)
|
|
36
|
+
allocated_bytes := (allocated_gb * 1024 * 1024 * 1024)::BIGINT;
|
|
37
|
+
|
|
38
|
+
-- Calculer l'espace utilisé via l'API Storage de Supabase
|
|
39
|
+
-- Note: Cette partie nécessitera un appel HTTP depuis l'API Next.js
|
|
40
|
+
-- car nous ne pouvons pas faire d'appels HTTP depuis une fonction PostgreSQL
|
|
41
|
+
-- Pour l'instant, on retourne 0 et l'API se chargera du calcul réel
|
|
42
|
+
used_bytes := 0;
|
|
43
|
+
|
|
44
|
+
-- Calculer l'espace restant
|
|
45
|
+
remaining_bytes := allocated_bytes - used_bytes;
|
|
46
|
+
|
|
47
|
+
-- Calculer le pourcentage d'utilisation
|
|
48
|
+
IF allocated_bytes > 0 THEN
|
|
49
|
+
usage_percentage := (used_bytes * 100.0 / allocated_bytes)::DECIMAL(5,2);
|
|
50
|
+
ELSE
|
|
51
|
+
usage_percentage := 0;
|
|
52
|
+
END IF;
|
|
53
|
+
|
|
54
|
+
-- Construire le résultat JSON
|
|
55
|
+
result := json_build_object(
|
|
56
|
+
'usedBytes', used_bytes,
|
|
57
|
+
'allocatedBytes', allocated_bytes,
|
|
58
|
+
'remainingBytes', remaining_bytes,
|
|
59
|
+
'allocatedGb', allocated_gb,
|
|
60
|
+
'usagePercentage', usage_percentage
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
RETURN result;
|
|
64
|
+
END;
|
|
65
|
+
$$;
|
|
66
|
+
|
|
67
|
+
-- Ajouter des commentaires sur la fonction
|
|
68
|
+
COMMENT ON FUNCTION public.get_storage_usage(UUID) IS 'Récupère les statistiques d''utilisation de l''espace de stockage pour un utilisateur';
|
|
69
|
+
|
|
70
|
+
-- Créer une fonction pour vérifier si l'espace de stockage est suffisant
|
|
71
|
+
CREATE OR REPLACE FUNCTION public.check_storage_available(
|
|
72
|
+
owner_id_param UUID,
|
|
73
|
+
required_bytes_param BIGINT
|
|
74
|
+
)
|
|
75
|
+
RETURNS BOOLEAN
|
|
76
|
+
LANGUAGE plpgsql
|
|
77
|
+
SECURITY DEFINER
|
|
78
|
+
AS $$
|
|
79
|
+
DECLARE
|
|
80
|
+
storage_info JSON;
|
|
81
|
+
remaining_bytes BIGINT;
|
|
82
|
+
BEGIN
|
|
83
|
+
-- Récupérer les informations de stockage
|
|
84
|
+
storage_info := public.get_storage_usage(owner_id_param);
|
|
85
|
+
|
|
86
|
+
-- Extraire l'espace restant
|
|
87
|
+
remaining_bytes := (storage_info->>'remainingBytes')::BIGINT;
|
|
88
|
+
|
|
89
|
+
-- Retourner true si l'espace est suffisant
|
|
90
|
+
RETURN remaining_bytes >= required_bytes_param;
|
|
91
|
+
END;
|
|
92
|
+
$$;
|
|
93
|
+
|
|
94
|
+
COMMENT ON FUNCTION public.check_storage_available(UUID, BIGINT) IS 'Vérifie si l''espace de stockage restant est suffisant pour une opération';
|
|
95
|
+
|
|
96
|
+
-- Créer un index sur la colonne stockage pour optimiser les requêtes
|
|
97
|
+
CREATE INDEX idx_user_profil_stockage ON public.user_profil(stockage);
|
|
98
|
+
|
|
99
|
+
-- Politique de sécurité pour permettre aux utilisateurs de voir leur propre usage
|
|
100
|
+
-- Supprimer d'abord la politique si elle existe
|
|
101
|
+
DROP POLICY IF EXISTS "Users can view their own storage usage" ON public.user_profil;
|
|
102
|
+
|
|
103
|
+
-- Créer la nouvelle politique
|
|
104
|
+
CREATE POLICY "Users can view their own storage usage" ON public.user_profil
|
|
105
|
+
FOR SELECT USING (auth.uid() = owner_id);
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
-- ============================================================================
|
|
2
|
+
-- Migration: Global Addons System (Storage, etc.)
|
|
3
|
+
-- Date: 2026-01-22
|
|
4
|
+
-- ============================================================================
|
|
5
|
+
-- This migration adds support for global addons (like storage) that can be:
|
|
6
|
+
-- 1. Shared across all apps (FoodAndShare, Audit, etc.)
|
|
7
|
+
-- 2. Purchased as subscription items alongside app-specific plans
|
|
8
|
+
-- 3. Managed via Stripe subscription items (1 subscription, multiple items)
|
|
9
|
+
-- ============================================================================
|
|
10
|
+
|
|
11
|
+
-- ===========================================================================
|
|
12
|
+
-- Table: public.global_addons
|
|
13
|
+
-- Catalog of global addons (storage packs, etc.) that work across all apps
|
|
14
|
+
-- ===========================================================================
|
|
15
|
+
CREATE TABLE IF NOT EXISTS public.global_addons (
|
|
16
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
17
|
+
|
|
18
|
+
-- Addon identification
|
|
19
|
+
addon_key text UNIQUE NOT NULL, -- e.g., "storage_8gb", "storage_50gb"
|
|
20
|
+
name text NOT NULL, -- Display name (e.g., "Stockage 8 GB")
|
|
21
|
+
description text,
|
|
22
|
+
|
|
23
|
+
-- Addon type (for future extensibility)
|
|
24
|
+
addon_type text NOT NULL DEFAULT 'storage' CHECK (addon_type IN ('storage', 'bandwidth', 'seats', 'other')),
|
|
25
|
+
|
|
26
|
+
-- Quota configuration
|
|
27
|
+
-- For storage: value in GB (e.g., 8, 50, 100)
|
|
28
|
+
-- For other types: custom unit
|
|
29
|
+
quota_value numeric(10,2) NOT NULL,
|
|
30
|
+
quota_unit text NOT NULL DEFAULT 'GB', -- GB, TB, users, etc.
|
|
31
|
+
|
|
32
|
+
-- Pricing display (flexible JSON for UI)
|
|
33
|
+
price_display jsonb DEFAULT '{"monthly": "4.90€/mois", "yearly": "Annuel -20%"}',
|
|
34
|
+
|
|
35
|
+
-- Stripe integration
|
|
36
|
+
stripe_price_id text, -- Monthly price ID
|
|
37
|
+
stripe_price_id_yearly text, -- Yearly price ID (optional)
|
|
38
|
+
|
|
39
|
+
-- Visibility
|
|
40
|
+
is_public boolean NOT NULL DEFAULT true,
|
|
41
|
+
is_active boolean NOT NULL DEFAULT true,
|
|
42
|
+
|
|
43
|
+
-- Optional metadata
|
|
44
|
+
metadata jsonb DEFAULT '{}',
|
|
45
|
+
|
|
46
|
+
-- Ordering
|
|
47
|
+
sort int NOT NULL DEFAULT 0,
|
|
48
|
+
|
|
49
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
50
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
-- RLS for global_addons
|
|
54
|
+
ALTER TABLE public.global_addons ENABLE ROW LEVEL SECURITY;
|
|
55
|
+
|
|
56
|
+
-- Public read access to public/active addons
|
|
57
|
+
DROP POLICY IF EXISTS global_addons_public_select ON public.global_addons;
|
|
58
|
+
CREATE POLICY global_addons_public_select ON public.global_addons
|
|
59
|
+
FOR SELECT TO authenticated, anon
|
|
60
|
+
USING (is_public = true AND is_active = true);
|
|
61
|
+
|
|
62
|
+
-- Admin full access (service_role bypasses RLS)
|
|
63
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON public.global_addons TO service_role;
|
|
64
|
+
|
|
65
|
+
-- Trigger updated_at
|
|
66
|
+
DROP TRIGGER IF EXISTS set_global_addons_updated_at ON public.global_addons;
|
|
67
|
+
CREATE TRIGGER set_global_addons_updated_at
|
|
68
|
+
BEFORE UPDATE ON public.global_addons
|
|
69
|
+
FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
|
|
70
|
+
|
|
71
|
+
-- Indexes
|
|
72
|
+
CREATE INDEX IF NOT EXISTS idx_global_addons_addon_key ON public.global_addons(addon_key);
|
|
73
|
+
CREATE INDEX IF NOT EXISTS idx_global_addons_addon_type ON public.global_addons(addon_type);
|
|
74
|
+
CREATE INDEX IF NOT EXISTS idx_global_addons_is_public ON public.global_addons(is_public);
|
|
75
|
+
CREATE INDEX IF NOT EXISTS idx_global_addons_is_active ON public.global_addons(is_active);
|
|
76
|
+
CREATE INDEX IF NOT EXISTS idx_global_addons_sort ON public.global_addons(sort);
|
|
77
|
+
|
|
78
|
+
-- ===========================================================================
|
|
79
|
+
-- Table: public.user_global_addons
|
|
80
|
+
-- User activations of global addons with Stripe subscription item tracking
|
|
81
|
+
-- ===========================================================================
|
|
82
|
+
CREATE TABLE IF NOT EXISTS public.user_global_addons (
|
|
83
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
84
|
+
|
|
85
|
+
-- User reference
|
|
86
|
+
owner_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
87
|
+
|
|
88
|
+
-- Addon reference
|
|
89
|
+
addon_id uuid NOT NULL REFERENCES public.global_addons(id) ON DELETE CASCADE,
|
|
90
|
+
|
|
91
|
+
-- Quantity (for scalable addons, e.g., 2x 8GB packs = 16GB)
|
|
92
|
+
quantity int NOT NULL DEFAULT 1 CHECK (quantity > 0),
|
|
93
|
+
|
|
94
|
+
-- Status
|
|
95
|
+
status text NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'trialing', 'canceled', 'past_due', 'incomplete', 'incomplete_expired', 'unpaid')),
|
|
96
|
+
|
|
97
|
+
-- Subscription period
|
|
98
|
+
current_period_start timestamptz,
|
|
99
|
+
current_period_end timestamptz,
|
|
100
|
+
|
|
101
|
+
-- Stripe references (for multi-item subscriptions)
|
|
102
|
+
stripe_customer_id text,
|
|
103
|
+
stripe_subscription_id text NOT NULL, -- The parent subscription
|
|
104
|
+
stripe_subscription_item_id text NOT NULL UNIQUE, -- The specific item within the subscription
|
|
105
|
+
|
|
106
|
+
-- Optional metadata
|
|
107
|
+
metadata jsonb DEFAULT '{}',
|
|
108
|
+
|
|
109
|
+
-- Timestamps
|
|
110
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
111
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
112
|
+
|
|
113
|
+
-- Constraint: one addon per user (unless we allow multiple quantities via quantity field)
|
|
114
|
+
UNIQUE(owner_id, addon_id)
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
-- RLS for user_global_addons
|
|
118
|
+
ALTER TABLE public.user_global_addons ENABLE ROW LEVEL SECURITY;
|
|
119
|
+
|
|
120
|
+
-- Users can only see their own addon activations
|
|
121
|
+
DROP POLICY IF EXISTS user_global_addons_owner_select ON public.user_global_addons;
|
|
122
|
+
CREATE POLICY user_global_addons_owner_select ON public.user_global_addons
|
|
123
|
+
FOR SELECT TO authenticated
|
|
124
|
+
USING (owner_id = (SELECT auth.uid()));
|
|
125
|
+
|
|
126
|
+
-- Only service_role can insert/update/delete (via webhooks)
|
|
127
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON public.user_global_addons TO service_role;
|
|
128
|
+
|
|
129
|
+
-- Trigger updated_at
|
|
130
|
+
DROP TRIGGER IF EXISTS set_user_global_addons_updated_at ON public.user_global_addons;
|
|
131
|
+
CREATE TRIGGER set_user_global_addons_updated_at
|
|
132
|
+
BEFORE UPDATE ON public.user_global_addons
|
|
133
|
+
FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
|
|
134
|
+
|
|
135
|
+
-- Indexes
|
|
136
|
+
CREATE INDEX IF NOT EXISTS idx_user_global_addons_owner_id ON public.user_global_addons(owner_id);
|
|
137
|
+
CREATE INDEX IF NOT EXISTS idx_user_global_addons_addon_id ON public.user_global_addons(addon_id);
|
|
138
|
+
CREATE INDEX IF NOT EXISTS idx_user_global_addons_status ON public.user_global_addons(status);
|
|
139
|
+
CREATE INDEX IF NOT EXISTS idx_user_global_addons_stripe_subscription_id ON public.user_global_addons(stripe_subscription_id);
|
|
140
|
+
CREATE INDEX IF NOT EXISTS idx_user_global_addons_stripe_subscription_item_id ON public.user_global_addons(stripe_subscription_item_id);
|
|
141
|
+
|
|
142
|
+
-- ===========================================================================
|
|
143
|
+
-- Function: get_user_limits
|
|
144
|
+
-- Returns aggregated limits (storage, tokens, etc.) for a user
|
|
145
|
+
-- Combines base limits + active addons + entitlements
|
|
146
|
+
-- ===========================================================================
|
|
147
|
+
CREATE OR REPLACE FUNCTION public.get_user_limits(owner_id_param UUID)
|
|
148
|
+
RETURNS JSON
|
|
149
|
+
LANGUAGE plpgsql
|
|
150
|
+
SECURITY DEFINER
|
|
151
|
+
SET search_path = public
|
|
152
|
+
AS $$
|
|
153
|
+
DECLARE
|
|
154
|
+
-- Base storage from user_profil
|
|
155
|
+
base_storage_gb DECIMAL(10,2);
|
|
156
|
+
|
|
157
|
+
-- Extra storage from billing plan features
|
|
158
|
+
plan_storage_extra_gb DECIMAL(10,2) := 0;
|
|
159
|
+
|
|
160
|
+
-- Storage from active addons
|
|
161
|
+
addon_storage_gb DECIMAL(10,2) := 0;
|
|
162
|
+
|
|
163
|
+
-- Total storage
|
|
164
|
+
total_storage_gb DECIMAL(10,2);
|
|
165
|
+
total_storage_bytes BIGINT;
|
|
166
|
+
|
|
167
|
+
-- Storage used (to be calculated by API)
|
|
168
|
+
storage_used_bytes BIGINT := 0;
|
|
169
|
+
|
|
170
|
+
-- Tokens (if available)
|
|
171
|
+
tokens_balance INT := NULL;
|
|
172
|
+
|
|
173
|
+
result JSON;
|
|
174
|
+
BEGIN
|
|
175
|
+
-- 1. Get base storage from user_profil
|
|
176
|
+
SELECT COALESCE(stockage, 2.00) INTO base_storage_gb
|
|
177
|
+
FROM public.user_profil
|
|
178
|
+
WHERE owner_id = owner_id_param;
|
|
179
|
+
|
|
180
|
+
-- If no profile, use default
|
|
181
|
+
IF base_storage_gb IS NULL THEN
|
|
182
|
+
base_storage_gb := 2.00;
|
|
183
|
+
END IF;
|
|
184
|
+
|
|
185
|
+
-- 2. Get extra storage from billing plan features
|
|
186
|
+
SELECT COALESCE(
|
|
187
|
+
(features->>'storage_extra')::DECIMAL(10,2),
|
|
188
|
+
0
|
|
189
|
+
) INTO plan_storage_extra_gb
|
|
190
|
+
FROM public.user_entitlements
|
|
191
|
+
WHERE owner_id = owner_id_param
|
|
192
|
+
AND status IN ('active', 'trialing')
|
|
193
|
+
LIMIT 1;
|
|
194
|
+
|
|
195
|
+
-- 3. Get storage from active global addons
|
|
196
|
+
SELECT COALESCE(
|
|
197
|
+
SUM(ga.quota_value * uga.quantity),
|
|
198
|
+
0
|
|
199
|
+
) INTO addon_storage_gb
|
|
200
|
+
FROM public.user_global_addons uga
|
|
201
|
+
JOIN public.global_addons ga ON ga.id = uga.addon_id
|
|
202
|
+
WHERE uga.owner_id = owner_id_param
|
|
203
|
+
AND uga.status IN ('active', 'trialing')
|
|
204
|
+
AND ga.addon_type = 'storage'
|
|
205
|
+
AND ga.is_active = true;
|
|
206
|
+
|
|
207
|
+
-- 4. Calculate total storage
|
|
208
|
+
total_storage_gb := base_storage_gb + plan_storage_extra_gb + addon_storage_gb;
|
|
209
|
+
total_storage_bytes := (total_storage_gb * 1024 * 1024 * 1024)::BIGINT;
|
|
210
|
+
|
|
211
|
+
-- 5. Try to get tokens balance (if table exists)
|
|
212
|
+
BEGIN
|
|
213
|
+
SELECT COALESCE(balance, 0) INTO tokens_balance
|
|
214
|
+
FROM public.user_token_quotas
|
|
215
|
+
WHERE owner_id = owner_id_param
|
|
216
|
+
LIMIT 1;
|
|
217
|
+
EXCEPTION WHEN undefined_table THEN
|
|
218
|
+
tokens_balance := NULL;
|
|
219
|
+
END;
|
|
220
|
+
|
|
221
|
+
-- 6. Build result JSON
|
|
222
|
+
result := json_build_object(
|
|
223
|
+
'storage_quota_gb', total_storage_gb,
|
|
224
|
+
'storage_quota_bytes', total_storage_bytes,
|
|
225
|
+
'storage_used_bytes', storage_used_bytes, -- Will be calculated by API
|
|
226
|
+
'storage_breakdown', json_build_object(
|
|
227
|
+
'base_gb', base_storage_gb,
|
|
228
|
+
'plan_extra_gb', plan_storage_extra_gb,
|
|
229
|
+
'addons_gb', addon_storage_gb
|
|
230
|
+
),
|
|
231
|
+
'tokens_balance', tokens_balance
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
RETURN result;
|
|
235
|
+
END;
|
|
236
|
+
$$;
|
|
237
|
+
|
|
238
|
+
-- Grant execution to service_role and authenticated users
|
|
239
|
+
GRANT EXECUTE ON FUNCTION public.get_user_limits(uuid) TO service_role, authenticated;
|
|
240
|
+
|
|
241
|
+
-- Add comment
|
|
242
|
+
COMMENT ON FUNCTION public.get_user_limits(uuid) IS 'Returns aggregated limits (storage, tokens) for a user including base + plan + addons';
|
|
243
|
+
|
|
244
|
+
-- ===========================================================================
|
|
245
|
+
-- Initial data: Create default storage addons catalog
|
|
246
|
+
-- ===========================================================================
|
|
247
|
+
INSERT INTO public.global_addons (addon_key, name, description, addon_type, quota_value, quota_unit, price_display, sort, is_public, is_active)
|
|
248
|
+
VALUES
|
|
249
|
+
('storage_8gb', 'Stockage +8 GB', 'Ajoutez 8 GB d''espace de stockage à votre compte', 'storage', 8.00, 'GB', '{"monthly": "4.90€/mois", "yearly": "49.90€/an"}', 1, true, true),
|
|
250
|
+
('storage_50gb', 'Stockage +50 GB', 'Ajoutez 50 GB d''espace de stockage à votre compte', 'storage', 50.00, 'GB', '{"monthly": "19.90€/mois", "yearly": "199.90€/an"}', 2, true, true),
|
|
251
|
+
('storage_100gb', 'Stockage +100 GB', 'Ajoutez 100 GB d''espace de stockage à votre compte', 'storage', 100.00, 'GB', '{"monthly": "34.90€/mois", "yearly": "349.90€/an"}', 3, true, true)
|
|
252
|
+
ON CONFLICT (addon_key) DO NOTHING;
|
|
253
|
+
|
|
254
|
+
-- ===========================================================================
|
|
255
|
+
-- Update get_storage_usage to use new get_user_limits function
|
|
256
|
+
-- ===========================================================================
|
|
257
|
+
CREATE OR REPLACE FUNCTION public.get_storage_usage(owner_id_param UUID)
|
|
258
|
+
RETURNS JSON
|
|
259
|
+
LANGUAGE plpgsql
|
|
260
|
+
SECURITY DEFINER
|
|
261
|
+
AS $$
|
|
262
|
+
DECLARE
|
|
263
|
+
limits_info JSON;
|
|
264
|
+
allocated_bytes BIGINT;
|
|
265
|
+
allocated_gb DECIMAL(10,2);
|
|
266
|
+
used_bytes BIGINT := 0;
|
|
267
|
+
remaining_bytes BIGINT;
|
|
268
|
+
usage_percentage DECIMAL(5,2);
|
|
269
|
+
result JSON;
|
|
270
|
+
BEGIN
|
|
271
|
+
-- Get aggregated limits from new function
|
|
272
|
+
limits_info := public.get_user_limits(owner_id_param);
|
|
273
|
+
|
|
274
|
+
-- Extract storage quota
|
|
275
|
+
allocated_gb := (limits_info->>'storage_quota_gb')::DECIMAL(10,2);
|
|
276
|
+
allocated_bytes := (limits_info->>'storage_quota_bytes')::BIGINT;
|
|
277
|
+
|
|
278
|
+
-- used_bytes will be calculated by API (requires HTTP call to storage API)
|
|
279
|
+
used_bytes := 0;
|
|
280
|
+
|
|
281
|
+
-- Calculate remaining
|
|
282
|
+
remaining_bytes := allocated_bytes - used_bytes;
|
|
283
|
+
|
|
284
|
+
-- Calculate percentage
|
|
285
|
+
IF allocated_bytes > 0 THEN
|
|
286
|
+
usage_percentage := (used_bytes * 100.0 / allocated_bytes)::DECIMAL(5,2);
|
|
287
|
+
ELSE
|
|
288
|
+
usage_percentage := 0;
|
|
289
|
+
END IF;
|
|
290
|
+
|
|
291
|
+
-- Build result
|
|
292
|
+
result := json_build_object(
|
|
293
|
+
'usedBytes', used_bytes,
|
|
294
|
+
'allocatedBytes', allocated_bytes,
|
|
295
|
+
'remainingBytes', remaining_bytes,
|
|
296
|
+
'allocatedGb', allocated_gb,
|
|
297
|
+
'usagePercentage', usage_percentage,
|
|
298
|
+
'breakdown', limits_info->'storage_breakdown'
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
RETURN result;
|
|
302
|
+
END;
|
|
303
|
+
$$;
|
|
304
|
+
|
|
305
|
+
COMMENT ON FUNCTION public.get_storage_usage(UUID) IS 'Récupère les statistiques d''utilisation de l''espace de stockage avec addons inclus';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
-- Enable pgvector extension for embedding operations
|
|
2
|
+
-- This extension is required for recipe search functionality
|
|
3
|
+
|
|
4
|
+
DO $$
|
|
5
|
+
BEGIN
|
|
6
|
+
IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'vector') THEN
|
|
7
|
+
RAISE NOTICE 'Extension vector not enabled (enable it in Supabase Dashboard)';
|
|
8
|
+
END IF;
|
|
9
|
+
END $$;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
-- Fix get_user_limits function to handle NULL values in calculations
|
|
2
|
+
-- Issue: When plan_storage_extra_gb is NULL, total becomes NULL (2 + NULL + 0 = NULL in SQL)
|
|
3
|
+
|
|
4
|
+
CREATE OR REPLACE FUNCTION public.get_user_limits(owner_id_param UUID)
|
|
5
|
+
RETURNS JSON
|
|
6
|
+
LANGUAGE plpgsql
|
|
7
|
+
SECURITY DEFINER
|
|
8
|
+
SET search_path = public
|
|
9
|
+
AS $$
|
|
10
|
+
DECLARE
|
|
11
|
+
-- Base storage from user_profil
|
|
12
|
+
base_storage_gb DECIMAL(10,2);
|
|
13
|
+
|
|
14
|
+
-- Extra storage from billing plan features
|
|
15
|
+
plan_storage_extra_gb DECIMAL(10,2) := 0;
|
|
16
|
+
|
|
17
|
+
-- Storage from active addons
|
|
18
|
+
addon_storage_gb DECIMAL(10,2) := 0;
|
|
19
|
+
|
|
20
|
+
-- Total storage
|
|
21
|
+
total_storage_gb DECIMAL(10,2);
|
|
22
|
+
total_storage_bytes BIGINT;
|
|
23
|
+
|
|
24
|
+
-- Storage used (to be calculated by API)
|
|
25
|
+
storage_used_bytes BIGINT := 0;
|
|
26
|
+
|
|
27
|
+
-- Tokens (if available)
|
|
28
|
+
tokens_balance INT := NULL;
|
|
29
|
+
|
|
30
|
+
result JSON;
|
|
31
|
+
BEGIN
|
|
32
|
+
-- 1. Get base storage from user_profil
|
|
33
|
+
SELECT COALESCE(stockage, 2.00) INTO base_storage_gb
|
|
34
|
+
FROM public.user_profil
|
|
35
|
+
WHERE owner_id = owner_id_param;
|
|
36
|
+
|
|
37
|
+
-- If no profile, use default
|
|
38
|
+
IF base_storage_gb IS NULL THEN
|
|
39
|
+
base_storage_gb := 2.00;
|
|
40
|
+
END IF;
|
|
41
|
+
|
|
42
|
+
-- 2. Get extra storage from billing plan features
|
|
43
|
+
SELECT COALESCE(
|
|
44
|
+
(features->>'storage_extra')::DECIMAL(10,2),
|
|
45
|
+
0
|
|
46
|
+
) INTO plan_storage_extra_gb
|
|
47
|
+
FROM public.user_entitlements
|
|
48
|
+
WHERE owner_id = owner_id_param
|
|
49
|
+
AND status IN ('active', 'trialing')
|
|
50
|
+
LIMIT 1;
|
|
51
|
+
|
|
52
|
+
-- 3. Get storage from active global addons
|
|
53
|
+
SELECT COALESCE(
|
|
54
|
+
SUM(ga.quota_value * uga.quantity),
|
|
55
|
+
0
|
|
56
|
+
) INTO addon_storage_gb
|
|
57
|
+
FROM public.user_global_addons uga
|
|
58
|
+
JOIN public.global_addons ga ON ga.id = uga.addon_id
|
|
59
|
+
WHERE uga.owner_id = owner_id_param
|
|
60
|
+
AND uga.status IN ('active', 'trialing')
|
|
61
|
+
AND ga.addon_type = 'storage'
|
|
62
|
+
AND ga.is_active = true;
|
|
63
|
+
|
|
64
|
+
-- 4. Calculate total storage (handle NULLs with COALESCE)
|
|
65
|
+
total_storage_gb := COALESCE(base_storage_gb, 0) + COALESCE(plan_storage_extra_gb, 0) + COALESCE(addon_storage_gb, 0);
|
|
66
|
+
total_storage_bytes := (total_storage_gb * 1024 * 1024 * 1024)::BIGINT;
|
|
67
|
+
|
|
68
|
+
-- 5. Try to get tokens balance (if table exists)
|
|
69
|
+
BEGIN
|
|
70
|
+
SELECT COALESCE(balance, 0) INTO tokens_balance
|
|
71
|
+
FROM public.user_token_quotas
|
|
72
|
+
WHERE owner_id = owner_id_param
|
|
73
|
+
LIMIT 1;
|
|
74
|
+
EXCEPTION WHEN undefined_table THEN
|
|
75
|
+
tokens_balance := NULL;
|
|
76
|
+
END;
|
|
77
|
+
|
|
78
|
+
-- 6. Build result JSON
|
|
79
|
+
result := json_build_object(
|
|
80
|
+
'storage_quota_gb', total_storage_gb,
|
|
81
|
+
'storage_quota_bytes', total_storage_bytes,
|
|
82
|
+
'storage_used_bytes', storage_used_bytes, -- Will be calculated by API
|
|
83
|
+
'storage_breakdown', json_build_object(
|
|
84
|
+
'base_gb', base_storage_gb,
|
|
85
|
+
'plan_extra_gb', plan_storage_extra_gb,
|
|
86
|
+
'addons_gb', addon_storage_gb
|
|
87
|
+
),
|
|
88
|
+
'tokens_balance', tokens_balance
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
RETURN result;
|
|
92
|
+
END;
|
|
93
|
+
$$;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
-- Migration pour corriger la boucle circulaire dans get_user_limits
|
|
2
|
+
-- Le problème : user_profil.stockage contient le total, mais était utilisé comme base
|
|
3
|
+
-- Solution : base_storage_gb est toujours 2.00 (constante)
|
|
4
|
+
-- Date: 2026-01-23 14:50
|
|
5
|
+
|
|
6
|
+
CREATE OR REPLACE FUNCTION public.get_user_limits(owner_id_param UUID)
|
|
7
|
+
RETURNS JSON
|
|
8
|
+
LANGUAGE plpgsql
|
|
9
|
+
SECURITY DEFINER
|
|
10
|
+
AS $$
|
|
11
|
+
DECLARE
|
|
12
|
+
-- Base storage (constant)
|
|
13
|
+
base_storage_gb DECIMAL(10,2);
|
|
14
|
+
|
|
15
|
+
-- Storage from billing plan
|
|
16
|
+
plan_storage_extra_gb DECIMAL(10,2);
|
|
17
|
+
|
|
18
|
+
-- Storage from addons
|
|
19
|
+
addon_storage_gb DECIMAL(10,2);
|
|
20
|
+
|
|
21
|
+
-- Total storage
|
|
22
|
+
total_storage_gb DECIMAL(10,2);
|
|
23
|
+
total_storage_bytes BIGINT;
|
|
24
|
+
|
|
25
|
+
-- Storage used (to be calculated by API)
|
|
26
|
+
storage_used_bytes BIGINT := 0;
|
|
27
|
+
|
|
28
|
+
-- Tokens (if available)
|
|
29
|
+
tokens_balance INT := NULL;
|
|
30
|
+
|
|
31
|
+
result JSON;
|
|
32
|
+
BEGIN
|
|
33
|
+
-- 1. Base storage is always 2 GB for all users (constant)
|
|
34
|
+
base_storage_gb := 2.00;
|
|
35
|
+
|
|
36
|
+
-- 2. Get extra storage from billing plan features
|
|
37
|
+
SELECT COALESCE(
|
|
38
|
+
(features->>'storage_extra')::DECIMAL(10,2),
|
|
39
|
+
0
|
|
40
|
+
) INTO plan_storage_extra_gb
|
|
41
|
+
FROM public.user_entitlements
|
|
42
|
+
WHERE owner_id = owner_id_param
|
|
43
|
+
AND status IN ('active', 'trialing')
|
|
44
|
+
LIMIT 1;
|
|
45
|
+
|
|
46
|
+
-- 3. Get storage from active global addons (with quantity)
|
|
47
|
+
SELECT COALESCE(
|
|
48
|
+
SUM(ga.quota_value * uga.quantity),
|
|
49
|
+
0
|
|
50
|
+
) INTO addon_storage_gb
|
|
51
|
+
FROM public.user_global_addons uga
|
|
52
|
+
JOIN public.global_addons ga ON ga.id = uga.addon_id
|
|
53
|
+
WHERE uga.owner_id = owner_id_param
|
|
54
|
+
AND uga.status IN ('active', 'trialing')
|
|
55
|
+
AND ga.addon_type = 'storage'
|
|
56
|
+
AND ga.is_active = true;
|
|
57
|
+
|
|
58
|
+
-- 4. Calculate total storage (handle NULLs with COALESCE)
|
|
59
|
+
total_storage_gb := COALESCE(base_storage_gb, 0) + COALESCE(plan_storage_extra_gb, 0) + COALESCE(addon_storage_gb, 0);
|
|
60
|
+
total_storage_bytes := (total_storage_gb * 1024 * 1024 * 1024)::BIGINT;
|
|
61
|
+
|
|
62
|
+
-- 5. Try to get tokens balance (if table exists)
|
|
63
|
+
BEGIN
|
|
64
|
+
SELECT COALESCE(balance, 0) INTO tokens_balance
|
|
65
|
+
FROM public.user_tokens
|
|
66
|
+
WHERE owner_id = owner_id_param;
|
|
67
|
+
EXCEPTION
|
|
68
|
+
WHEN undefined_table THEN
|
|
69
|
+
tokens_balance := NULL;
|
|
70
|
+
END;
|
|
71
|
+
|
|
72
|
+
-- 6. Build result JSON
|
|
73
|
+
result := json_build_object(
|
|
74
|
+
'storage_quota_gb', total_storage_gb,
|
|
75
|
+
'storage_quota_bytes', total_storage_bytes,
|
|
76
|
+
'storage_used_bytes', storage_used_bytes,
|
|
77
|
+
'storage_breakdown', json_build_object(
|
|
78
|
+
'base_gb', base_storage_gb,
|
|
79
|
+
'plan_extra_gb', plan_storage_extra_gb,
|
|
80
|
+
'addons_gb', addon_storage_gb
|
|
81
|
+
),
|
|
82
|
+
'tokens_balance', tokens_balance
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
RETURN result;
|
|
86
|
+
END;
|
|
87
|
+
$$;
|
|
88
|
+
|
|
89
|
+
COMMENT ON FUNCTION public.get_user_limits(UUID) IS 'Retourne les limites (storage, tokens) pour un utilisateur avec breakdown détaillé. Base storage est constant à 2 GB.';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
-- ============================================================================
|
|
2
|
+
-- Add username column to user_profil with unique constraint
|
|
3
|
+
-- Module: @lastbrain/module-auth
|
|
4
|
+
-- ============================================================================
|
|
5
|
+
|
|
6
|
+
-- Add username column
|
|
7
|
+
ALTER TABLE public.user_profil
|
|
8
|
+
ADD COLUMN IF NOT EXISTS username TEXT DEFAULT NULL;
|
|
9
|
+
|
|
10
|
+
-- Create unique index on lowercase username (case-insensitive)
|
|
11
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_user_profil_username_unique
|
|
12
|
+
ON public.user_profil (LOWER(username))
|
|
13
|
+
WHERE username IS NOT NULL;
|
|
14
|
+
|
|
15
|
+
-- Add comment
|
|
16
|
+
COMMENT ON COLUMN public.user_profil.username IS 'Unique username for public profile URL (case-insensitive)';
|
|
17
|
+
|
|
18
|
+
-- Function to validate username format
|
|
19
|
+
CREATE OR REPLACE FUNCTION validate_username(username_input TEXT)
|
|
20
|
+
RETURNS BOOLEAN AS $$
|
|
21
|
+
BEGIN
|
|
22
|
+
-- Username must be 3-30 characters, alphanumeric + underscore/dash only
|
|
23
|
+
RETURN username_input ~ '^[a-zA-Z0-9_-]{3,30}$';
|
|
24
|
+
END;
|
|
25
|
+
$$ LANGUAGE plpgsql IMMUTABLE;
|
|
26
|
+
|
|
27
|
+
-- Add check constraint for username format
|
|
28
|
+
ALTER TABLE public.user_profil
|
|
29
|
+
DROP CONSTRAINT IF EXISTS check_username_format;
|
|
30
|
+
|
|
31
|
+
ALTER TABLE public.user_profil
|
|
32
|
+
ADD CONSTRAINT check_username_format
|
|
33
|
+
CHECK (username IS NULL OR validate_username(username));
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../../src/web/auth/dashboard.tsx"],"names":[],"mappings":"AA8BA,wBAAgB,aAAa,mDAmM5B"}
|