@lastbrain/module-ai 0.1.18 → 0.1.19
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/dist/ai.build.config.d.ts.map +1 -1
- package/dist/ai.build.config.js +65 -1
- package/dist/api/admin/token-packs/[id].d.ts +28 -0
- package/dist/api/admin/token-packs/[id].d.ts.map +1 -0
- package/dist/api/admin/token-packs/[id].js +44 -0
- package/dist/api/admin/token-packs.d.ts +20 -0
- package/dist/api/admin/token-packs.d.ts.map +1 -0
- package/dist/api/admin/token-packs.js +57 -0
- package/dist/api/admin/user-token.js +2 -1
- package/dist/api/auth/create-checkout.d.ts +31 -0
- package/dist/api/auth/create-checkout.d.ts.map +1 -0
- package/dist/api/auth/create-checkout.js +93 -0
- package/dist/api/auth/generate-image.d.ts +3 -3
- package/dist/api/auth/generate-image.d.ts.map +1 -1
- package/dist/api/auth/generate-image.js +68 -27
- package/dist/api/auth/token-checkout.d.ts +12 -0
- package/dist/api/auth/token-checkout.d.ts.map +1 -0
- package/dist/api/auth/token-checkout.js +79 -0
- package/dist/api/auth/token-packs.d.ts +11 -0
- package/dist/api/auth/token-packs.d.ts.map +1 -0
- package/dist/api/auth/token-packs.js +29 -0
- package/dist/api/public/webhook.d.ts +2 -0
- package/dist/api/public/webhook.d.ts.map +1 -0
- package/dist/api/public/webhook.js +171 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/web/admin/AdminTokenPacksPage.d.ts +2 -0
- package/dist/web/admin/AdminTokenPacksPage.d.ts.map +1 -0
- package/dist/web/admin/AdminTokenPacksPage.js +127 -0
- package/dist/web/auth/TokenPage.d.ts.map +1 -1
- package/dist/web/auth/TokenPage.js +51 -3
- package/dist/web/components/ImageGenerative.d.ts +5 -2
- package/dist/web/components/ImageGenerative.d.ts.map +1 -1
- package/dist/web/components/ImageGenerative.js +51 -7
- package/package.json +2 -1
- package/supabase/migrations/20251201000000_token_packs.sql +73 -0
|
@@ -4,6 +4,18 @@ import { useState, useCallback } from "react";
|
|
|
4
4
|
import { Button, Chip, Progress, Card, CardBody, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Textarea, Select, SelectItem, addToast, } from "@lastbrain/ui";
|
|
5
5
|
import { Image as ImageIcon, Loader2, AlertCircle, Download, Wand2, } from "lucide-react";
|
|
6
6
|
import Image from "next/image";
|
|
7
|
+
// Coût en tokens par combinaison modèle/taille(/qualité)
|
|
8
|
+
const TOKENS_PER_IMAGE_KEY = {
|
|
9
|
+
"dall-e-2-256x256": 3000,
|
|
10
|
+
"dall-e-2-512x512": 4000,
|
|
11
|
+
"dall-e-2-1024x1024": 5000,
|
|
12
|
+
"dall-e-3-1024x1024-standard": 6000,
|
|
13
|
+
"dall-e-3-1024x1024-hd": 8000,
|
|
14
|
+
"dall-e-3-1024x1792-standard": 8000,
|
|
15
|
+
"dall-e-3-1024x1792-hd": 10000,
|
|
16
|
+
"dall-e-3-1792x1024-standard": 8000,
|
|
17
|
+
"dall-e-3-1792x1024-hd": 10000,
|
|
18
|
+
};
|
|
7
19
|
const imageStyles = [
|
|
8
20
|
{
|
|
9
21
|
key: "realistic",
|
|
@@ -34,7 +46,7 @@ const imageStyles = [
|
|
|
34
46
|
},
|
|
35
47
|
{ key: "pop-art", label: "Pop Art", description: "Style pop art vibrant" },
|
|
36
48
|
];
|
|
37
|
-
export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size = "1024x1024", quality = "standard", onChange, onError, className, disabled = false, apiEndpoint = "/api/ai/generate-image", showTokenBalance = true, label, description, defaultStyle = "realistic", hideStyleEditor = false, uploadPath, }) {
|
|
49
|
+
export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size = "1024x1024", quality = "standard", onChange, onError, className, disabled = false, apiEndpoint = "/api/ai/generate-image", showTokenBalance = true, label, description, defaultStyle = "realistic", hideStyleEditor = false, uploadPath, preview = true, inline = false, }) {
|
|
38
50
|
const [isGenerating, setIsGenerating] = useState(false);
|
|
39
51
|
const [generatedImage, setGeneratedImage] = useState(null);
|
|
40
52
|
const [error, setError] = useState(null);
|
|
@@ -44,6 +56,19 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
44
56
|
const [userPrompt, setUserPrompt] = useState(defaultPrompt);
|
|
45
57
|
const [selectedStyle, setSelectedStyle] = useState(defaultStyle);
|
|
46
58
|
const [selectedModel, setSelectedModel] = useState(model);
|
|
59
|
+
const [selectedSize, setSelectedSize] = useState(size);
|
|
60
|
+
const [selectedQuality, setSelectedQuality] = useState(quality);
|
|
61
|
+
// Calculer le coût estimé
|
|
62
|
+
const estimatedCost = () => {
|
|
63
|
+
let key = "";
|
|
64
|
+
if (selectedModel === "dall-e-2") {
|
|
65
|
+
key = `${selectedModel}-${selectedSize}`;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
key = `${selectedModel}-${selectedSize}-${selectedQuality}`;
|
|
69
|
+
}
|
|
70
|
+
return TOKENS_PER_IMAGE_KEY[key] || 0;
|
|
71
|
+
};
|
|
47
72
|
const handleGenerate = useCallback(async () => {
|
|
48
73
|
if (!userPrompt || isGenerating)
|
|
49
74
|
return;
|
|
@@ -59,8 +84,8 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
59
84
|
body: JSON.stringify({
|
|
60
85
|
prompt: enhancedPrompt,
|
|
61
86
|
model: selectedModel,
|
|
62
|
-
size,
|
|
63
|
-
quality,
|
|
87
|
+
size: selectedSize,
|
|
88
|
+
quality: selectedQuality,
|
|
64
89
|
uploadPath,
|
|
65
90
|
}),
|
|
66
91
|
});
|
|
@@ -70,11 +95,11 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
70
95
|
}
|
|
71
96
|
const data = await response.json();
|
|
72
97
|
const imageResponse = {
|
|
98
|
+
supabaseImageUrl: data.supabaseImageUrl,
|
|
73
99
|
imageUrl: data.imageUrl,
|
|
74
100
|
tokensUsed: data.tokensUsed || 0,
|
|
75
101
|
tokensRemaining: data.tokensRemaining || 0,
|
|
76
102
|
model: data.model || selectedModel,
|
|
77
|
-
cost: data.cost,
|
|
78
103
|
prompt: userPrompt,
|
|
79
104
|
};
|
|
80
105
|
setGeneratedImage(imageResponse);
|
|
@@ -109,8 +134,8 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
109
134
|
userPrompt,
|
|
110
135
|
selectedStyle,
|
|
111
136
|
selectedModel,
|
|
112
|
-
|
|
113
|
-
|
|
137
|
+
selectedSize,
|
|
138
|
+
selectedQuality,
|
|
114
139
|
apiEndpoint,
|
|
115
140
|
uploadPath,
|
|
116
141
|
onChange,
|
|
@@ -136,5 +161,24 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
136
161
|
console.error("Erreur lors du téléchargement:", error);
|
|
137
162
|
}
|
|
138
163
|
}, [generatedImage]);
|
|
139
|
-
return (_jsxs("div", { className: className, children: [_jsxs("div", { className: "flex flex-col gap-4", children: [label && _jsx("label", { className: "text-sm font-medium", children: label }), description && _jsx("p", { className: "text-sm text-gray-500", children: description }), generatedImage && !isGenerating && (_jsx(Card, { children: _jsxs(CardBody, { children: [_jsx("div", { className: "relative w-full aspect-square", children: _jsx(Image, { src: generatedImage.imageUrl, alt: generatedImage.prompt, fill: true, className: "object-contain rounded-lg", priority: true }) }), _jsxs("div", { className: "mt-4", children: [_jsxs("p", { className: "text-sm text-default-600", children: [_jsx("strong", { children: "Prompt:" }), " ", generatedImage.prompt] }), _jsxs("p", { className: "text-xs text-default-400 mt-1", children: [
|
|
164
|
+
return (_jsxs("div", { className: className, children: [_jsxs("div", { className: "flex flex-col gap-4", children: [label && _jsx("label", { className: "text-sm font-medium", children: label }), description && _jsx("p", { className: "text-sm text-gray-500", children: description }), generatedImage && !isGenerating && preview && (_jsx(Card, { children: _jsxs(CardBody, { children: [_jsx("div", { className: "relative w-full aspect-square", children: _jsx(Image, { src: generatedImage.imageUrl, alt: generatedImage.prompt, fill: true, className: "object-contain rounded-lg", priority: true }) }), _jsxs("div", { className: "mt-4", children: [_jsxs("p", { className: "text-sm text-default-600", children: [_jsx("strong", { children: "Prompt:" }), " ", generatedImage.prompt] }), _jsxs("p", { className: "text-xs text-default-400 mt-1", children: [selectedSize, " \u2022 ", selectedQuality] })] })] }) })), inline ? (_jsxs("div", { className: "space-y-4", children: [_jsx(Textarea, { label: "Description de l'image", placeholder: "D\u00E9crivez l'image que vous souhaitez g\u00E9n\u00E9rer...", value: userPrompt, onChange: (e) => setUserPrompt(e.target.value), minRows: 3, description: "Soyez pr\u00E9cis et d\u00E9taill\u00E9 pour de meilleurs r\u00E9sultats" }), !hideStyleEditor && (_jsxs(_Fragment, { children: [_jsx(Select, { label: "Style artistique", selectedKeys: [selectedStyle], onSelectionChange: (keys) => setSelectedStyle(Array.from(keys)[0]), description: "Le style influence l'apparence finale de l'image", children: imageStyles.map((style) => (_jsx(SelectItem, { textValue: style.label, children: _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { className: "font-medium", children: style.label }), _jsx("span", { className: "text-xs text-gray-500", children: style.description })] }) }, style.key))) }), _jsxs(Select, { label: "Mod\u00E8le IA", selectedKeys: [selectedModel], onSelectionChange: (keys) => {
|
|
165
|
+
const newModel = Array.from(keys)[0];
|
|
166
|
+
setSelectedModel(newModel);
|
|
167
|
+
// Ajuster la taille/qualité selon le modèle
|
|
168
|
+
if (newModel === "dall-e-2") {
|
|
169
|
+
setSelectedQuality("standard");
|
|
170
|
+
if (!["256x256", "512x512", "1024x1024"].includes(selectedSize)) {
|
|
171
|
+
setSelectedSize("1024x1024");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}, description: "DALL-E 3 offre la meilleure qualit\u00E9", children: [_jsx(SelectItem, { children: "DALL-E 3 (Haute qualit\u00E9)" }, "dall-e-3"), _jsx(SelectItem, { children: "DALL-E 2 (Standard)" }, "dall-e-2")] }), _jsx(Select, { label: "Taille de l'image", selectedKeys: [selectedSize], onSelectionChange: (keys) => setSelectedSize(Array.from(keys)[0]), description: "Tailles disponibles selon le mod\u00E8le", children: selectedModel === "dall-e-2" ? (_jsxs(_Fragment, { children: [_jsx(SelectItem, { children: "256\u00D7256 (3000 tokens)" }, "256x256"), _jsx(SelectItem, { children: "512\u00D7512 (4000 tokens)" }, "512x512"), _jsx(SelectItem, { children: "1024\u00D71024 (5000 tokens)" }, "1024x1024")] })) : (_jsxs(_Fragment, { children: [_jsx(SelectItem, { children: "1024\u00D71024 (6000-8000 tokens)" }, "1024x1024"), _jsx(SelectItem, { children: "1024\u00D71792 Portrait (8000-10000 tokens)" }, "1024x1792"), _jsx(SelectItem, { children: "1792\u00D71024 Paysage (8000-10000 tokens)" }, "1792x1024")] })) }), selectedModel === "dall-e-3" && (_jsxs(Select, { label: "Qualit\u00E9", selectedKeys: [selectedQuality], onSelectionChange: (keys) => setSelectedQuality(Array.from(keys)[0]), description: "HD offre plus de d\u00E9tails mais co\u00FBte plus de tokens", children: [_jsx(SelectItem, { children: "Standard" }, "standard"), _jsx(SelectItem, { children: "HD (Haute D\u00E9finition)" }, "hd")] })), _jsx("div", { className: "p-3 bg-primary-50 dark:bg-primary-900/20 rounded-lg", children: _jsxs("p", { className: "text-sm font-medium text-primary-700 dark:text-primary-300", children: ["Co\u00FBt estim\u00E9: ", estimatedCost().toLocaleString(), " tokens"] }) })] })), _jsx(Button, { color: "primary", onClick: handleGenerate, disabled: !userPrompt.trim() || disabled || isGenerating, startContent: isGenerating ? (_jsx(Loader2, { className: "animate-spin", size: 16 })) : (_jsx(Wand2, { size: 18 })), className: "w-full", children: isGenerating ? "Génération..." : "Générer" })] })) : (_jsx(Button, { color: "primary", onClick: () => setIsModalOpen(true), disabled: disabled || isGenerating, startContent: _jsx(Wand2, { size: 18 }), className: "w-full", children: isGenerating ? "Génération..." : "Générer une image" })), _jsxs("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [_jsx("div", { className: "flex items-center gap-2", children: showTokenBalance && tokenBalance !== null && (_jsxs(Chip, { size: "sm", variant: "flat", color: "success", children: [tokenBalance.toLocaleString(), " tokens restants"] })) }), generatedImage && (_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsxs(Chip, { size: "sm", variant: "flat", children: [generatedImage.tokensUsed, " tokens utilis\u00E9s"] }), _jsx(Chip, { size: "sm", variant: "flat", color: "primary", children: generatedImage.model }), preview && (_jsx(Button, { size: "sm", variant: "flat", onClick: handleDownload, startContent: _jsx(Download, { size: 16 }), children: "T\u00E9l\u00E9charger" }))] }))] }), isGenerating && (_jsxs("div", { className: "space-y-2", children: [_jsx(Progress, { size: "sm", isIndeterminate: true, "aria-label": "G\u00E9n\u00E9ration en cours...", className: "max-w-md" }), _jsx("p", { className: "text-sm text-default-500", children: "G\u00E9n\u00E9ration de l'image en cours... Cela peut prendre quelques instants." })] })), error && (_jsxs("div", { className: "flex items-center gap-2 text-danger text-sm p-3 bg-danger-50 rounded-md", children: [_jsx(AlertCircle, { size: 16 }), _jsx("span", { children: error })] }))] }), _jsx(Modal, { backdrop: "blur", isOpen: isModalOpen, onClose: () => setIsModalOpen(false), size: "2xl", children: _jsxs(ModalContent, { children: [_jsx(ModalHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ImageIcon, { size: 20 }), _jsx("span", { children: "G\u00E9n\u00E9rer une image avec l'IA" })] }) }), _jsx(ModalBody, { children: _jsxs("div", { className: "space-y-4", children: [_jsx(Textarea, { label: "Description de l'image", placeholder: "D\u00E9crivez l'image que vous souhaitez g\u00E9n\u00E9rer...", value: userPrompt, onChange: (e) => setUserPrompt(e.target.value), minRows: 3, description: "Soyez pr\u00E9cis et d\u00E9taill\u00E9 pour de meilleurs r\u00E9sultats" }), !hideStyleEditor && (_jsxs(_Fragment, { children: [_jsx(Select, { label: "Style artistique", selectedKeys: [selectedStyle], onSelectionChange: (keys) => setSelectedStyle(Array.from(keys)[0]), description: "Le style influence l'apparence finale de l'image", children: imageStyles.map((style) => (_jsx(SelectItem, { textValue: style.label, children: _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { className: "font-medium", children: style.label }), _jsx("span", { className: "text-xs text-gray-500", children: style.description })] }) }, style.key))) }), _jsxs(Select, { label: "Mod\u00E8le IA", selectedKeys: [selectedModel], onSelectionChange: (keys) => {
|
|
175
|
+
const newModel = Array.from(keys)[0];
|
|
176
|
+
setSelectedModel(newModel);
|
|
177
|
+
if (newModel === "dall-e-2") {
|
|
178
|
+
setSelectedQuality("standard");
|
|
179
|
+
if (!["256x256", "512x512", "1024x1024"].includes(selectedSize)) {
|
|
180
|
+
setSelectedSize("1024x1024");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}, description: "DALL-E 3 offre la meilleure qualit\u00E9", children: [_jsx(SelectItem, { children: "DALL-E 3 (Haute qualit\u00E9)" }, "dall-e-3"), _jsx(SelectItem, { children: "DALL-E 2 (Standard)" }, "dall-e-2")] }), _jsx(Select, { label: "Taille de l'image", selectedKeys: [selectedSize], onSelectionChange: (keys) => setSelectedSize(Array.from(keys)[0]), description: "Tailles disponibles selon le mod\u00E8le", children: selectedModel === "dall-e-2" ? (_jsxs(_Fragment, { children: [_jsx(SelectItem, { children: "256\u00D7256 (3000 tokens)" }, "256x256"), _jsx(SelectItem, { children: "512\u00D7512 (4000 tokens)" }, "512x512"), _jsx(SelectItem, { children: "1024\u00D71024 (5000 tokens)" }, "1024x1024")] })) : (_jsxs(_Fragment, { children: [_jsx(SelectItem, { children: "1024\u00D71024 (6000-8000 tokens)" }, "1024x1024"), _jsx(SelectItem, { children: "1024\u00D71792 Portrait (8000-10000 tokens)" }, "1024x1792"), _jsx(SelectItem, { children: "1792\u00D71024 Paysage (8000-10000 tokens)" }, "1792x1024")] })) }), selectedModel === "dall-e-3" && (_jsxs(Select, { label: "Qualit\u00E9", selectedKeys: [selectedQuality], onSelectionChange: (keys) => setSelectedQuality(Array.from(keys)[0]), description: "HD offre plus de d\u00E9tails mais co\u00FBte plus de tokens", children: [_jsx(SelectItem, { children: "Standard" }, "standard"), _jsx(SelectItem, { children: "HD (Haute D\u00E9finition)" }, "hd")] })), _jsx("div", { className: "p-3 bg-primary-50 dark:bg-primary-900/20 rounded-lg", children: _jsxs("p", { className: "text-sm font-medium text-primary-700 dark:text-primary-300", children: ["Co\u00FBt estim\u00E9: ", estimatedCost().toLocaleString(), " tokens"] }) })] })), generatedImage && (_jsxs("div", { className: "p-3 bg-gray-50 dark:bg-gray-800 rounded-lg", children: [_jsx("p", { className: "text-sm font-medium mb-2", children: "Aper\u00E7u de l'image actuelle:" }), _jsx("div", { className: "relative w-full", children: _jsx(Image, { src: generatedImage.imageUrl, alt: "Current", fill: true, className: "object-cover rounded" }) })] }))] }) }), _jsxs(ModalFooter, { children: [_jsx(Button, { variant: "light", onClick: () => setIsModalOpen(false), children: "Annuler" }), _jsx(Button, { color: "primary", onClick: handleGenerate, disabled: !userPrompt.trim() || isGenerating, startContent: isGenerating ? (_jsx(Loader2, { className: "animate-spin", size: 16 })) : (_jsx(ImageIcon, { size: 16 })), children: isGenerating ? "Génération..." : "Générer" })] })] }) })] }));
|
|
140
184
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lastbrain/module-ai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"description": "Module de génération IA (texte et images) avec gestion de tokens pour LastBrain",
|
|
5
5
|
"private": false,
|
|
6
6
|
"release": {
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"@heroui/react": "^2.4.23",
|
|
36
36
|
"@heroui/system": "^2.4.23",
|
|
37
37
|
"@heroui/theme": "^2.4.23",
|
|
38
|
+
"@lastbrain-labs/module-core-payment-pro": "^0.1.0",
|
|
38
39
|
"@lastbrain/core": "^0.1.0",
|
|
39
40
|
"@lastbrain/ui": "^0.1.0",
|
|
40
41
|
"lucide-react": "^0.554.0",
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
-- ===========================================================================
|
|
2
|
+
-- Module: @lastbrain/module-ai
|
|
3
|
+
-- Migration: 20251201000000_token_packs.sql
|
|
4
|
+
-- Description: Token packs for purchasing tokens via Stripe
|
|
5
|
+
-- ===========================================================================
|
|
6
|
+
|
|
7
|
+
-- ===========================================================================
|
|
8
|
+
-- Table: public.token_packs
|
|
9
|
+
-- Defines available token packs that users can purchase
|
|
10
|
+
-- ===========================================================================
|
|
11
|
+
CREATE TABLE IF NOT EXISTS public.token_packs (
|
|
12
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
13
|
+
name text NOT NULL, -- Display name (e.g., "100 Tokens")
|
|
14
|
+
description text NULL, -- Optional description
|
|
15
|
+
tokens integer NOT NULL, -- Number of tokens in pack
|
|
16
|
+
price_cents integer NOT NULL, -- Price in cents
|
|
17
|
+
currency text NOT NULL DEFAULT 'EUR', -- ISO currency code
|
|
18
|
+
stripe_price_id text NULL, -- Optional Stripe price ID for recurring
|
|
19
|
+
is_active boolean NOT NULL DEFAULT true, -- Whether pack is available for purchase
|
|
20
|
+
sort_order integer NOT NULL DEFAULT 0, -- Display order
|
|
21
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
22
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
-- ===========================================================================
|
|
26
|
+
-- Indexes
|
|
27
|
+
-- ===========================================================================
|
|
28
|
+
CREATE INDEX IF NOT EXISTS idx_token_packs_active_order
|
|
29
|
+
ON public.token_packs(is_active, sort_order);
|
|
30
|
+
|
|
31
|
+
-- ===========================================================================
|
|
32
|
+
-- Insert default token packs
|
|
33
|
+
-- ===========================================================================
|
|
34
|
+
INSERT INTO public.token_packs (name, description, tokens, price_cents, currency, sort_order)
|
|
35
|
+
VALUES
|
|
36
|
+
('Starter', 'Starter pack', 100000, 500, 'EUR', 1),
|
|
37
|
+
('Standard', 'Standard pack', 250000, 1000, 'EUR', 2),
|
|
38
|
+
('Premium', 'Premium pack', 600000, 2000, 'EUR', 3),
|
|
39
|
+
('Creator', 'Creator pack', 2000000, 5000, 'EUR', 4)
|
|
40
|
+
|
|
41
|
+
ON CONFLICT DO NOTHING;
|
|
42
|
+
|
|
43
|
+
-- ===========================================================================
|
|
44
|
+
-- RLS (Row Level Security)
|
|
45
|
+
-- ===========================================================================
|
|
46
|
+
ALTER TABLE public.token_packs ENABLE ROW LEVEL SECURITY;
|
|
47
|
+
|
|
48
|
+
-- Policy: Everyone can view active packs
|
|
49
|
+
DROP POLICY IF EXISTS token_packs_select_public ON public.token_packs;
|
|
50
|
+
CREATE POLICY token_packs_select_public ON public.token_packs
|
|
51
|
+
FOR SELECT
|
|
52
|
+
USING (is_active = true OR is_superadmin(auth.uid()));
|
|
53
|
+
|
|
54
|
+
-- Policy: Only superadmins can insert/update/delete
|
|
55
|
+
DROP POLICY IF EXISTS token_packs_admin_all ON public.token_packs;
|
|
56
|
+
CREATE POLICY token_packs_admin_all ON public.token_packs
|
|
57
|
+
FOR ALL
|
|
58
|
+
USING (is_superadmin(auth.uid()));
|
|
59
|
+
|
|
60
|
+
-- ===========================================================================
|
|
61
|
+
-- Trigger for updated_at
|
|
62
|
+
-- ===========================================================================
|
|
63
|
+
DROP TRIGGER IF EXISTS set_token_packs_updated_at ON public.token_packs;
|
|
64
|
+
CREATE TRIGGER set_token_packs_updated_at
|
|
65
|
+
BEFORE UPDATE ON public.token_packs
|
|
66
|
+
FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
|
|
67
|
+
|
|
68
|
+
-- ===========================================================================
|
|
69
|
+
-- Grants
|
|
70
|
+
-- ===========================================================================
|
|
71
|
+
GRANT SELECT ON public.token_packs TO anon;
|
|
72
|
+
GRANT SELECT ON public.token_packs TO authenticated;
|
|
73
|
+
GRANT ALL ON public.token_packs TO service_role;
|