LetsChat je webová aplikace postavená na Next.js 14, která umožňuje soukromou textovou komunikaci mezi dvěma účastníky. Aplikace využívá unikátní hybridní model, který kombinuje anonymní přístup s prémiovými funkcemi pro registrované uživatele.
Jádrem aplikace je systém párování pomocí jednoho sdíleného kódu, který zajišťuje soukromí a jednoduchost.
- Vytvoření Kartičky (pouze pro přihlášené): Registrovaný uživatel může vygenerovat nový, unikátní 5místný kód. Tento kód slouží jako "klíč" k jedné chatovací místnosti.
- Připojení do Chatu (pro kohokoli):
- První osoba, která zadá platný kód, vstoupí do místnosti a je označena jako "Účastník A".
- Druhá osoba, která zadá ten samý kód, se připojí a je označena jako "Účastník B".
- Jakmile jsou v místnosti dva účastníci, chat se uzamkne a nikdo další se nemůže připojit.
- Anonymita a Bezpečnost:
- Všichni uživatelé vstupují do chatu anonymně.
- Při prvním vstupu si mohou volitelně nastavit 5místný PIN, který chrání jejich přístup k dané konverzaci.
- Uložení Chatu: Anonymní uživatel si může konverzaci "přivlastnit" tím, že se zaregistruje nebo přihlásí. Chat se mu tak uloží na jeho účet.
- Hybridní Model: Anonymní vstup pro všechny, pokročilé funkce (vytváření kódů) pro registrované.
- Párování 1-na-1: Každý chat je určen pouze pro dva účastníky.
- Ochrana PINem: Možnost zabezpečit si svůj anonymní vstup do chatu.
- Komunikace v reálném čase: Využití Supabase Realtime pro okamžité doručování zpráv.
- Claiming Chatů: Možnost uložit si anonymní konverzaci k registrovanému účtu.
- Detailní Logování: Server-side logování klíčových operací pro bezpečnost a analýzu.
- Frontend: Next.js 14 (App Router), React 18, TypeScript
- Stylování: Tailwind CSS
- Backend & Databáze: Supabase (Auth, Realtime, Database, Edge Functions)
- Notifikace:
react-hot-toast - Ikony:
lucide-react - UUID Generování:
uuid
- Nainstalovaný Node.js (v18.x nebo novější) a npm.
- Vytvořený projekt na Supabase.
- Ve svém Supabase projektu otevřete SQL Editor.
- Zkopírujte a spusťte celý SQL skript níže. Tím se vytvoří všechny potřebné tabulky a funkce.
-- Smazání starých tabulek pro čistý start (doporučeno, pokud začínáte znovu)
DROP TABLE IF EXISTS public.room_rows CASCADE;
DROP TABLE IF EXISTS public.room_participants CASCADE;
DROP TABLE IF EXISTS public.rooms CASCADE;
DROP TABLE IF EXISTS public.codes CASCADE;
DROP TABLE IF EXISTS public.logs CASCADE;
DROP TABLE IF EXISTS public.users CASCADE;
DROP TABLE IF EXISTS public.profiles CASCADE; -- Smazání i staré tabulky, pokud existuje
-- 1. Tabulka pro uživatele
CREATE TABLE public.users (
id UUID NOT NULL PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
lang VARCHAR(5) DEFAULT 'cs',
status SMALLINT DEFAULT 1, -- 0: neaktivní, 1: čeká na autorizaci, 2: aktivní
email VARCHAR(255) UNIQUE NOT NULL,
token VARCHAR(255) UNIQUE,
fname VARCHAR(255) NOT NULL,
sname VARCHAR(255) NOT NULL,
sex SMALLINT, -- 1: muž, 2: žena, null: neuvedeno
nickname VARCHAR(255),
birth DATE,
photo VARCHAR(255),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- RLS (Row Level Security) pro uživatele
ALTER TABLE public.users ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view their own profile." ON public.users FOR SELECT USING (auth.uid() = id);
CREATE POLICY "Users can update their own profile." ON public.users FOR UPDATE USING (auth.uid() = id);
-- Trigger pro automatické vytvoření profilu po registraci v auth.users
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO public.users (id, email, fname, sname, sex, token)
VALUES (NEW.id, NEW.email, NEW.raw_user_meta_data->>'fname', NEW.raw_user_meta_data->>'sname', (NEW.raw_user_meta_data->>'sex')::smallint, gen_random_uuid());
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Smazání starého triggeru, pokud existuje, a vytvoření nového
DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE PROCEDURE public.handle_new_user();
-- 2. Tabulka pro párové kódy
CREATE TABLE public.codes (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
code VARCHAR(5) NOT NULL UNIQUE,
linked_to BIGINT REFERENCES public.codes(id),
used SMALLINT DEFAULT 0, -- 0: nepoužit, 1: použit
user_id UUID REFERENCES public.users(id) ON DELETE SET NULL, -- Kdo kód vlastní
pin_hash TEXT, -- Hash 5místného PINu
anonymous_hash TEXT, -- Hash pro ověření anonymního uživatele bez PINu
date_first TIMESTAMPTZ,
date_last TIMESTAMPTZ,
used_count INT DEFAULT 0,
room_id BIGINT, -- Propojeno níže
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX ON public.codes(code);
CREATE INDEX ON public.codes(used);
-- 3. Tabulka pro chatovací místnosti
CREATE TABLE public.rooms (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
status SMALLINT DEFAULT 1, -- 1: aktivní, 0: neaktivní
name VARCHAR(255),
description VARCHAR(255),
date_create TIMESTAMPTZ DEFAULT NOW(),
date_last TIMESTAMPTZ
);
-- Přidání cizího klíče zpět do 'codes'
ALTER TABLE public.codes ADD CONSTRAINT fk_room_id FOREIGN KEY (room_id) REFERENCES public.rooms(id) ON DELETE SET NULL;
-- 4. Tabulka pro účastníky v místnostech
CREATE TABLE public.room_participants (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
room_id BIGINT NOT NULL REFERENCES public.rooms(id) ON DELETE CASCADE,
user_id UUID REFERENCES public.users(id) ON DELETE SET NULL, -- Pro registrované
anonymous_id UUID, -- Pro anonymní
code_id BIGINT NOT NULL REFERENCES public.codes(id), -- Kód, kterým vstoupil
role VARCHAR(50),
joined_at TIMESTAMPTZ DEFAULT NOW(),
profile_visible BOOLEAN DEFAULT FALSE
);
-- 5. Tabulka pro zprávy
CREATE TABLE public.room_rows (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
room_id BIGINT NOT NULL REFERENCES public.rooms(id) ON DELETE CASCADE,
participant_id BIGINT NOT NULL REFERENCES public.room_participants(id) ON DELETE CASCADE,
content TEXT,
image VARCHAR(255),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 6. Tabulka pro logování
CREATE TABLE public.logs (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
created_at TIMESTAMPTZ DEFAULT NOW(),
lang VARCHAR(5),
ip_address INET,
user_agent TEXT,
module VARCHAR(100),
operation VARCHAR(100),
data JSONB,
error TEXT
);
-- Základní RLS pravidla
ALTER TABLE public.rooms ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Allow authenticated user access" ON public.rooms FOR ALL USING (auth.role() = 'authenticated');
CREATE POLICY "Allow anonymous read access" ON public.rooms FOR SELECT USING (auth.role() = 'anon');
ALTER TABLE public.room_participants ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Allow authenticated user access" ON public.room_participants FOR ALL USING (auth.role() = 'authenticated');
CREATE POLICY "Allow anonymous read access" ON public.room_participants FOR SELECT USING (auth.role() = 'anon');
ALTER TABLE public.room_rows ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Allow authenticated user access" ON public.room_rows FOR ALL USING (auth.role() = 'authenticated');
CREATE POLICY "Allow anonymous read access" ON public.room_rows FOR SELECT USING (auth.role() = 'anon');Po vložení SQL skriptu doporučuji otestovat novou DB funkci finalize_join pomocí přiloženého skriptu:
PowerShell (lokálně, NE do VCS):
$env:SUPABASE_SERVICE_ROLE_KEY = "<your_service_role_key>"
$env:NEXT_PUBLIC_SUPABASE_URL = "https://..."
node scripts/test-finalize-join.js A1234 00000000-0000-0000-0000-000000000000Skript zavolá RPC a vypíše status a odpověď. Pokud funkce vyhodí chybu (např. invalid_code, pin_required), dostanete textovou chybovou zprávu.
- Instalace závislostí: V terminálu, ve složce projektu, spusťte:
npm install
- Konfigurace prostředí: Zkopírujte
.env.local.exampledo nového souboru.env.locala doplňte své klíče ze Supabase.NEXT_PUBLIC_SUPABASE_URL="VASE_SUPABASE_URL" NEXT_PUBLIC_SUPABASE_ANON_KEY="VAS_SUPABASE_ANON_KLIC"
npm run devAplikace poběží na http://localhost:3000.
.
├── public/
├── supabase/
│ └── functions/
│ ├── _shared/
│ ├── create-card/
│ ├── finalize-join/
│ └── get-code-status/
├── src/
│ ├── app/
│ │ ├── (auth)/
│ │ ├── chat/[chatId]/
│ │ ├── join/[code]/
│ │ ├── dashboard/
│ │ └── page.tsx
│ ├── components/
│ ├── contexts/
│ ├── hooks/
│ └── lib/
├── .env.local.example
├── README.md
└── package.json