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>
No files matched your search
@@ -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=
|
||||
@@ -49,3 +49,6 @@ docs/
|
||||
|
||||
.vercel
|
||||
.env*
|
||||
|
||||
# vzor premenných prostredia sa commituje
|
||||
!.env.example
|
||||
@@ -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
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
cp .env.example .env.local
|
||||
```
|
||||
|
||||
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.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
```bash
|
||||
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`.
|
||||
@@ -6,7 +6,8 @@
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint"
|
||||
"lint": "eslint",
|
||||
"db:upload-assets": "node --env-file=.env.local scripts/upload-assets.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@supabase/supabase-js": "^2.117.1",
|
||||
|
||||
@@ -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);
|
||||
@@ -1,7 +1,8 @@
|
||||
import { NewsletterCampaign, ProductItem } from '@/types/newsletter';
|
||||
|
||||
export const SUPABASE_ASSETS_BASE =
|
||||
'https://dqexxfspkylqttrtxsww.supabase.co/storage/v1/object/public/newsletter-assets/';
|
||||
// Verejné úložisko statických obrázkov (logá, ikony, štítky predajní...) v Supabase projekte z .env.
|
||||
// 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 = {
|
||||
id: '',
|
||||
|
||||
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 543 KiB |
|
After Width: | Height: | Size: 529 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 173 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 170 KiB |
|
After Width: | Height: | Size: 191 KiB |
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 181 KiB |
|
After Width: | Height: | Size: 204 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 211 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 61 KiB |
@@ -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;
|
||||
@@ -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);
|
||||