@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.
Files changed (179) 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 +126 -11
  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/components/auth/dashboard.d.ts +1 -1
  74. package/dist/components/auth/dashboard.d.ts.map +1 -1
  75. package/dist/components/auth/dashboard.js +34 -7
  76. package/dist/index.d.ts +2 -0
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +2 -0
  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 +126 -11
  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/components/auth/dashboard.tsx +54 -13
  148. package/src/i18n/en.json +76 -8
  149. package/src/i18n/es.json +330 -0
  150. package/src/i18n/fr.json +74 -8
  151. package/src/index.ts +2 -0
  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 +0 -2
  178. package/dist/web/auth/dashboard.d.ts.map +0 -1
  179. 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
- -- If you have existing data, you might want to copy body to message:
9
- -- UPDATE public.user_notifications SET message = COALESCE(body, title) WHERE message = '';
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,2 +0,0 @@
1
- export declare function DashboardPage(): import("react/jsx-runtime").JSX.Element | null;
2
- //# sourceMappingURL=dashboard.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../../src/web/auth/dashboard.tsx"],"names":[],"mappings":"AA8BA,wBAAgB,aAAa,mDAmM5B"}