chore: zalozenie databazy a uloziska na novom serveri

- supabase/schema.sql: idempotentny skript (tabulky, RLS, bucket newsletter-assets)
- supabase/assets + npm run db:upload-assets: staticke obrazky sablony
- adresa uloziska sa sklada z NEXT_PUBLIC_SUPABASE_URL namiesto pevneho projektu
- .env.example a navod na instalaciu v README

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub_HostacnyandClaude Opus 5.5 committed 2026-10-19 10:54:55 +02:00
1 parent eb92d30dc3
commit 5d1900740c
37 files changed
+178 -96

No files matched your search

+13
View File
@@ -0,0 +1,13 @@
# Skopírujte tento súbor ako .env.local a doplňte hodnoty.
# Supabase (Project Settings → API)
NEXT_PUBLIC_SUPABASE_URL=https://VAS-PROJEKT.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=
# service_role kľúč – len na serveri (nahrávanie obrázkov), nikdy ho nedávajte do kódu pre prehliadač
SUPABASE_SERVICE_ROLE_KEY=
# Brevo (SMTP & API → API Keys)
BREVO_API_KEY=
BREVO_SENDER_NAME=COOP Jednota
# E-mail odosielateľa musí byť v Brevo overený
BREVO_SENDER_EMAIL=
+3
View File
@@ -49,3 +49,6 @@ docs/
.vercel .vercel
.env* .env*
# vzor premenných prostredia sa commituje
!.env.example
+51 -21
View File
@@ -1,36 +1,66 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). # COOP Jednota – generátor newsletterov
## Getting Started Interný editor e-mailových newsletterov pre COOP Jednota: skladanie kampane z modulov
(hlavička, bannery, produktové sekcie, vernostné zľavy, letáky, recepty, pätička),
editor grafických cenoviek, export do ZIP a odoslanie kampane do Brevo.
First, run the development server: Technológie: Next.js 16, React 19, Tailwind CSS 4, Supabase (databáza + úložisko obrázkov), Brevo API.
## Inštalácia na nový server
### 1. Supabase projekt
1. Vytvorte nový projekt na [supabase.com](https://supabase.com) (alebo použite vlastný Supabase server).
2. **Databáza:** otvorte *SQL Editor*, vložte celý obsah [`supabase/schema.sql`](supabase/schema.sql) a kliknite *Run*.
Vytvoria sa tabuľky `templates` (kampane) a `badges` (šablóny cenoviek), prístupové pravidlá
a verejné úložisko obrázkov `newsletter-assets`. Skript sa dá bezpečne spustiť aj opakovane.
3. V *Project Settings → API* si skopírujte **Project URL**, **anon** kľúč a **service_role** kľúč.
### 2. Premenné prostredia
```bash ```bash
npm run dev cp .env.example .env.local
# or
yarn dev
# or
pnpm dev
# or
bun dev
``` ```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. Doplňte do `.env.local` údaje zo Supabase a Brevo (popis je priamo v súbore).
Na hostingu (Vercel, Docker…) nastavte rovnaké premenné v jeho nastaveniach.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. ### 3. Inštalácia a statické obrázky
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. ```bash
npm install
npm run db:upload-assets
```
## Learn More Druhý príkaz nahrá do úložiska `newsletter-assets` logá, ikony sociálnych sietí, štítky predajní
a ukážkové obrázky zo [`supabase/assets/`](supabase/assets/), ktoré šablóna newslettera používa.
To learn more about Next.js, take a look at the following resources: ### 4. Spustenie
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. ```bash
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. npm run dev # vývoj na http://localhost:3000
npm run build # produkčný build
npm run start # produkčný server
```
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! Pri prvom spustení s prázdnou databázou sa vytvorí nová kampaň z predvolenej šablóny
([`src/lib/defaultCampaign.ts`](src/lib/defaultCampaign.ts)) a automaticky sa uloží.
## Deploy on Vercel ## Štruktúra projektu
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. | Cesta | Obsah |
|---|---|
| `src/app/page.tsx` | Hlavná obrazovka editora (stav kampane, auto-save, náhľad) |
| `src/components/editor/` | Panel nastavení, editor cenoviek, modály (import, uložené kampane) |
| `src/lib/emailRenderer.ts` | Generovanie HTML e-mailu z kampane (náhľad aj export) |
| `src/lib/badgeUtils.ts` | Generovanie SVG cenoviek a premenné (`{cena}`, `{zlava}`, `{cena_s_kartou}`…) |
| `src/app/api/` | Serverové API: nahrávanie obrázkov, inline CSS, export a overenie kampane v Brevo |
| `supabase/schema.sql` | Schéma databázy a úložiska |
| `supabase/assets/` | Statické obrázky šablóny (nahrávajú sa cez `npm run db:upload-assets`) |
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. ## Poznámky
- Aplikácia nemá prihlasovanie a databáza je prístupná cez anon kľúč – je určená na interné použitie.
Ak bude verejne dostupná, treba doplniť autentifikáciu.
- Kampane uložené v pôvodnom Supabase projekte obsahujú odkazy na obrázky v pôvodnom úložisku.
Nové kampane a novo nahraté obrázky používajú úložisko z `.env.local`.
+2 -1
View File
@@ -6,7 +6,8 @@
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "eslint" "lint": "eslint",
"db:upload-assets": "node --env-file=.env.local scripts/upload-assets.mjs"
}, },
"dependencies": { "dependencies": {
"@supabase/supabase-js": "^2.117.1", "@supabase/supabase-js": "^2.117.1",
+45
View File
@@ -0,0 +1,45 @@
// Nahrá statické obrázky aplikácie (supabase/assets/) do Supabase Storage bucketu newsletter-assets.
// Spustenie: npm run db:upload-assets (číta NEXT_PUBLIC_SUPABASE_URL a SUPABASE_SERVICE_ROLE_KEY z .env.local)
// Existujúce súbory s rovnakým názvom sa prepíšu.
import { createClient } from '@supabase/supabase-js';
import { readdir, readFile } from 'node:fs/promises';
import path from 'node:path';
const BUCKET = 'newsletter-assets';
const ASSETS_DIR = path.join(process.cwd(), 'supabase', 'assets');
const CONTENT_TYPES = {
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.webp': 'image/webp'
};
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
if (!url || !serviceKey) {
console.error('Chýba NEXT_PUBLIC_SUPABASE_URL alebo SUPABASE_SERVICE_ROLE_KEY v .env.local');
process.exit(1);
}
const supabase = createClient(url, serviceKey);
const files = (await readdir(ASSETS_DIR)).filter((f) => CONTENT_TYPES[path.extname(f).toLowerCase()]);
let failed = 0;
for (const file of files) {
const body = await readFile(path.join(ASSETS_DIR, file));
const { error } = await supabase.storage.from(BUCKET).upload(file, body, {
contentType: CONTENT_TYPES[path.extname(file).toLowerCase()],
upsert: true
});
if (error) {
failed++;
console.error(`✗ ${file}: ${error.message}`);
} else {
console.log(`✓ ${file}`);
}
}
console.log(`\nNahraté ${files.length - failed}/${files.length} súborov do bucketu ${BUCKET}.`);
if (failed) process.exit(1);
+3 -2
View File
@@ -1,7 +1,8 @@
import { NewsletterCampaign, ProductItem } from '@/types/newsletter'; import { NewsletterCampaign, ProductItem } from '@/types/newsletter';
export const SUPABASE_ASSETS_BASE = // Verejné úložisko statických obrázkov (logá, ikony, štítky predajní...) v Supabase projekte z .env.
'https://dqexxfspkylqttrtxsww.supabase.co/storage/v1/object/public/newsletter-assets/'; // Súbory sú v supabase/assets/ a nahrajú sa príkazom: npm run db:upload-assets
export const SUPABASE_ASSETS_BASE = `${(process.env.NEXT_PUBLIC_SUPABASE_URL || '').replace(/\/$/, '')}/storage/v1/object/public/newsletter-assets/`;
export const DEFAULT_CAMPAIGN: NewsletterCampaign = { export const DEFAULT_CAMPAIGN: NewsletterCampaign = {
id: '', id: '',
Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 543 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

+61
View File
@@ -0,0 +1,61 @@
-- Schéma pre Modulárny generátor newsletterov (COOP Jednota)
--
-- Založenie databázy na novom Supabase projekte:
-- Supabase Dashboard → SQL Editor → vložiť celý tento súbor → Run.
-- Skript je idempotentný – dá sa spustiť opakovane bez straty dát.
-- 1. Tabuľka šablón / konceptov newslettera
-- data = { preheader, modules[] } – celý obsah kampane ako JSON
CREATE TABLE IF NOT EXISTS templates (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title TEXT NOT NULL DEFAULT 'Nový newsletter',
status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'ready', 'exported')),
data JSONB NOT NULL DEFAULT '{}'::jsonb,
brevo_campaign_id INTEGER,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Index pre rýchle zoradenie podľa dátumu úpravy
CREATE INDEX IF NOT EXISTS idx_templates_updated_at ON templates(updated_at DESC);
-- 2. Tabuľka šablón cenoviek (Badges)
-- layers_config = { layers[], width, align, paddingTop, paddingBottom }
-- (staršie záznamy môžu mať len pole vrstiev – aplikácia podporuje oba tvary)
CREATE TABLE IF NOT EXISTS badges (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
svg_background_url TEXT,
svg_raw TEXT,
layers_config JSONB NOT NULL DEFAULT '[]'::jsonb,
preview_image_url TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 3. Tabuľka uložených profilov mapovania importu (zatiaľ nevyužitá aplikáciou)
CREATE TABLE IF NOT EXISTS mapping_presets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
file_type TEXT NOT NULL CHECK (file_type IN ('csv', 'json', 'xml')),
mapping_rules JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 4. RLS (Row Level Security) – interný nástroj: povolený prístup cez anon kľúč
ALTER TABLE templates ENABLE ROW LEVEL SECURITY;
ALTER TABLE badges ENABLE ROW LEVEL SECURITY;
ALTER TABLE mapping_presets ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Allow public all on templates" ON templates;
DROP POLICY IF EXISTS "Allow public all on badges" ON badges;
DROP POLICY IF EXISTS "Allow public all on mapping_presets" ON mapping_presets;
CREATE POLICY "Allow public all on templates" ON templates FOR ALL USING (true) WITH CHECK (true);
CREATE POLICY "Allow public all on badges" ON badges FOR ALL USING (true) WITH CHECK (true);
CREATE POLICY "Allow public all on mapping_presets" ON mapping_presets FOR ALL USING (true) WITH CHECK (true);
-- 5. Úložisko obrázkov (Storage bucket) – verejne čitateľné, aby sa obrázky zobrazili v e-mailoch.
-- Nahrávanie ide cez /api/upload so service_role kľúčom (obchádza RLS), ďalšie politiky netreba.
INSERT INTO storage.buckets (id, name, public)
VALUES ('newsletter-assets', 'newsletter-assets', true)
ON CONFLICT (id) DO UPDATE SET public = true;
-72
View File
@@ -1,72 +0,0 @@
-- Schéma pre Modulárny generátor newsletterov (COOP Jednota)
-- 1. Tabuľka šablón / konceptov newslettera
CREATE TABLE IF NOT EXISTS templates (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title TEXT NOT NULL DEFAULT 'Nový newsletter',
status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'ready', 'exported')),
data JSONB NOT NULL DEFAULT '{}'::jsonb,
brevo_campaign_id INTEGER,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Index pre rýchle zoradenie podľa dátumu úpravy
CREATE INDEX IF NOT EXISTS idx_templates_updated_at ON templates(updated_at DESC);
-- 2. Tabuľka cenoviek (Badges)
CREATE TABLE IF NOT EXISTS badges (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
svg_background_url TEXT,
svg_raw TEXT,
layers_config JSONB NOT NULL DEFAULT '[]'::jsonb,
preview_image_url TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 3. Tabuľka uložených profilov mapovania (Data Mapper Presets)
CREATE TABLE IF NOT EXISTS mapping_presets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
file_type TEXT NOT NULL CHECK (file_type IN ('csv', 'json', 'xml')),
mapping_rules JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 4. Východiskové referenčné cenovky (Seed dát pre COOP Jednota)
INSERT INTO badges (name, svg_raw, layers_config) VALUES
(
'Žltá AKCIA',
'<svg viewBox="0 0 160 80" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="80" rx="8" fill="#ffd100"/><rect x="4" y="4" width="152" height="72" rx="6" fill="none" stroke="#e0001b" stroke-width="3"/></svg>',
'[
{"id": "l1", "type": "text", "text": "AKCIA", "x": 80, "y": 32, "fontSize": 24, "fontWeight": "900", "fill": "#e0001b", "align": "center"},
{"id": "l2", "type": "text", "text": "{zlava}", "x": 80, "y": 64, "fontSize": 22, "fontWeight": "800", "fill": "#001a33", "align": "center"}
]'::jsonb
),
(
'Červená SUPER CENA',
'<svg viewBox="0 0 160 80" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="80" rx="8" fill="#e0001b"/></svg>',
'[
{"id": "l1", "type": "text", "text": "SUPER CENA", "x": 80, "y": 34, "fontSize": 18, "fontWeight": "900", "fill": "#ffffff", "align": "center"},
{"id": "l2", "type": "text", "text": "{zlava}", "x": 80, "y": 64, "fontSize": 24, "fontWeight": "900", "fill": "#ffd100", "align": "center"}
]'::jsonb
),
(
'Percentuálna zľava (Kruh)',
'<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="48" fill="#e0001b" stroke="#ffd100" stroke-width="4"/></svg>',
'[
{"id": "l1", "type": "text", "text": "ZĽAVA", "x": 50, "y": 36, "fontSize": 13, "fontWeight": "800", "fill": "#ffffff", "align": "center"},
{"id": "l2", "type": "text", "text": "{zlava}%", "x": 50, "y": 68, "fontSize": 24, "fontWeight": "900", "fill": "#ffd100", "align": "center"}
]'::jsonb
)
ON CONFLICT DO NOTHING;
-- 5. Nastavenie RLS (Row Level Security) - pre interný nástroj povolíme verejný prístup cez anon kľúč
ALTER TABLE templates ENABLE ROW LEVEL SECURITY;
ALTER TABLE badges ENABLE ROW LEVEL SECURITY;
ALTER TABLE mapping_presets ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Allow public all on templates" ON templates FOR ALL USING (true) WITH CHECK (true);
CREATE POLICY "Allow public all on badges" ON badges FOR ALL USING (true) WITH CHECK (true);
CREATE POLICY "Allow public all on mapping_presets" ON mapping_presets FOR ALL USING (true) WITH CHECK (true);