feat: AI Kurzy module, project/service documents, services SQL import

- Add AI Kurzy module with courses, participants, and registrations management
- Add project documents and service documents features
- Add service folders for document organization
- Add SQL import queries for services from firmy.slovensko.ai
- Update todo notifications and group messaging
- Various API improvements and bug fixes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
richardtekula
2026-01-21 11:32:49 +01:00
parent d9f16ad0a6
commit 4089bb4be2
37 changed files with 7514 additions and 35 deletions

230
sql/01_schema_migration.sql Normal file
View File

@@ -0,0 +1,230 @@
-- ============================================================
-- COMPLETE SCHEMA MIGRATION FOR COOLIFY
-- Run this first to update the database schema
-- ============================================================
-- Create ENUMs for AI Kurzy (if not exist)
DO $$ BEGIN
CREATE TYPE "forma_kurzu_enum" AS ENUM('prezencne', 'online', 'hybridne');
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE "stav_registracie_enum" AS ENUM('potencialny', 'registrovany', 'potvrdeny', 'absolvoval', 'zruseny');
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE "typ_prilohy_enum" AS ENUM('certifikat', 'faktura', 'prihlaska', 'doklad_o_platbe', 'ine');
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
-- ============================================================
-- NEW TABLES
-- ============================================================
-- Chat Groups
CREATE TABLE IF NOT EXISTS "chat_groups" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" text NOT NULL,
"created_by_id" uuid REFERENCES "users"("id") ON DELETE SET NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
);
-- Chat Group Members
CREATE TABLE IF NOT EXISTS "chat_group_members" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"group_id" uuid NOT NULL REFERENCES "chat_groups"("id") ON DELETE CASCADE,
"user_id" uuid NOT NULL REFERENCES "users"("id") ON DELETE CASCADE,
"joined_at" timestamp DEFAULT now() NOT NULL,
"last_read_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "chat_group_member_unique" UNIQUE("group_id","user_id")
);
-- Group Messages
CREATE TABLE IF NOT EXISTS "group_messages" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"group_id" uuid NOT NULL REFERENCES "chat_groups"("id") ON DELETE CASCADE,
"sender_id" uuid REFERENCES "users"("id") ON DELETE SET NULL,
"content" text NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL
);
-- Push Subscriptions
CREATE TABLE IF NOT EXISTS "push_subscriptions" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" uuid NOT NULL REFERENCES "users"("id") ON DELETE CASCADE,
"endpoint" text NOT NULL,
"p256dh" text NOT NULL,
"auth" text NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "push_subscription_endpoint_unique" UNIQUE("user_id","endpoint")
);
-- Email Signatures
CREATE TABLE IF NOT EXISTS "email_signatures" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" uuid NOT NULL UNIQUE REFERENCES "users"("id") ON DELETE CASCADE,
"full_name" text,
"position" text,
"phone" text,
"email" text,
"company_name" text,
"website" text,
"is_enabled" boolean DEFAULT true NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
);
-- Services
CREATE TABLE IF NOT EXISTS "services" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" text NOT NULL,
"price" text NOT NULL,
"description" text,
"created_by" uuid REFERENCES "users"("id") ON DELETE SET NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
);
-- Service Folders
CREATE TABLE IF NOT EXISTS "service_folders" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" text NOT NULL,
"created_by" uuid REFERENCES "users"("id") ON DELETE SET NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
);
-- Service Documents
CREATE TABLE IF NOT EXISTS "service_documents" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"folder_id" uuid NOT NULL REFERENCES "service_folders"("id") ON DELETE CASCADE,
"file_name" text NOT NULL,
"original_name" text NOT NULL,
"file_path" text NOT NULL,
"file_type" text NOT NULL,
"file_size" integer NOT NULL,
"description" text,
"uploaded_by" uuid REFERENCES "users"("id") ON DELETE SET NULL,
"uploaded_at" timestamp DEFAULT now() NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL
);
-- Company Documents
CREATE TABLE IF NOT EXISTS "company_documents" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"company_id" uuid NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
"file_name" text NOT NULL,
"original_name" text NOT NULL,
"file_path" text NOT NULL,
"file_type" text NOT NULL,
"file_size" integer NOT NULL,
"description" text,
"uploaded_by" uuid REFERENCES "users"("id") ON DELETE SET NULL,
"uploaded_at" timestamp DEFAULT now() NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL
);
-- Project Documents
CREATE TABLE IF NOT EXISTS "project_documents" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"project_id" uuid NOT NULL REFERENCES "projects"("id") ON DELETE CASCADE,
"file_name" text NOT NULL,
"original_name" text NOT NULL,
"file_path" text NOT NULL,
"file_type" text NOT NULL,
"file_size" integer NOT NULL,
"description" text,
"uploaded_by" uuid REFERENCES "users"("id") ON DELETE SET NULL,
"uploaded_at" timestamp DEFAULT now() NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL
);
-- ============================================================
-- AI KURZY TABLES
-- ============================================================
-- Kurzy (Courses) - without dates (dates are per registration)
CREATE TABLE IF NOT EXISTS "kurzy" (
"id" serial PRIMARY KEY NOT NULL,
"nazov" varchar(255) NOT NULL,
"typ_kurzu" varchar(100) NOT NULL,
"popis" text,
"cena" numeric(10, 2) NOT NULL,
"max_kapacita" integer,
"aktivny" boolean DEFAULT true NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
-- Ucastnici (Participants)
CREATE TABLE IF NOT EXISTS "ucastnici" (
"id" serial PRIMARY KEY NOT NULL,
"titul" varchar(50),
"meno" varchar(100) NOT NULL,
"priezvisko" varchar(100) NOT NULL,
"email" varchar(255) NOT NULL UNIQUE,
"telefon" varchar(50),
"firma" varchar(255),
"mesto" varchar(100),
"ulica" varchar(255),
"psc" varchar(10),
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS "ucastnici_email_idx" ON "ucastnici" USING btree ("email");
-- Registracie (Registrations) - with dates
CREATE TABLE IF NOT EXISTS "registracie" (
"id" serial PRIMARY KEY NOT NULL,
"kurz_id" integer NOT NULL REFERENCES "kurzy"("id") ON DELETE CASCADE,
"ucastnik_id" integer NOT NULL REFERENCES "ucastnici"("id") ON DELETE CASCADE,
"datum_od" date,
"datum_do" date,
"forma_kurzu" "forma_kurzu_enum" DEFAULT 'prezencne' NOT NULL,
"pocet_ucastnikov" integer DEFAULT 1 NOT NULL,
"faktura_cislo" varchar(100),
"faktura_vystavena" boolean DEFAULT false NOT NULL,
"zaplatene" boolean DEFAULT false NOT NULL,
"stav" "stav_registracie_enum" DEFAULT 'registrovany' NOT NULL,
"poznamka" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS "registracie_kurz_ucastnik_idx" ON "registracie" USING btree ("kurz_id","ucastnik_id");
-- Prilohy (Attachments)
CREATE TABLE IF NOT EXISTS "prilohy" (
"id" serial PRIMARY KEY NOT NULL,
"registracia_id" integer NOT NULL REFERENCES "registracie"("id") ON DELETE CASCADE,
"nazov_suboru" varchar(255) NOT NULL,
"typ_prilohy" "typ_prilohy_enum" DEFAULT 'ine' NOT NULL,
"cesta_k_suboru" varchar(500) NOT NULL,
"mime_type" varchar(100),
"velkost_suboru" bigint,
"popis" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
-- ============================================================
-- ALTER EXISTING TABLES (add new columns)
-- ============================================================
-- Add completed_notified_at to todos (if not exists)
DO $$ BEGIN
ALTER TABLE "todos" ADD COLUMN "completed_notified_at" timestamp;
EXCEPTION WHEN duplicate_column THEN NULL;
END $$;
-- Make phone nullable in personal_contacts (if needed)
ALTER TABLE "personal_contacts" ALTER COLUMN "phone" DROP NOT NULL;
-- ============================================================
-- DONE
-- ============================================================
SELECT 'Schema migration completed successfully!' as status;

97
sql/02_ai_kurzy_data.sql Normal file
View File

@@ -0,0 +1,97 @@
-- ============================================================
-- AI KURZY DATA IMPORT
-- Run this after schema migration to import course data
-- ============================================================
-- Clear existing AI Kurzy data (optional - remove if you want to keep existing data)
DELETE FROM prilohy;
DELETE FROM registracie;
DELETE FROM ucastnici;
DELETE FROM kurzy;
-- Reset sequences
ALTER SEQUENCE kurzy_id_seq RESTART WITH 1;
ALTER SEQUENCE ucastnici_id_seq RESTART WITH 1;
ALTER SEQUENCE registracie_id_seq RESTART WITH 1;
ALTER SEQUENCE prilohy_id_seq RESTART WITH 1;
-- ============================================================
-- INSERT COURSES (without dates - dates are per registration)
-- ============================================================
INSERT INTO kurzy (nazov, typ_kurzu, cena, aktivny) VALUES
('AI 1+2 (2 dni) - 290€', 'AI', 290.00, true),
('AI 1 (1 deň) - 150€', 'AI', 150.00, true),
('AI 2 (1 deň) - 150€', 'AI', 150.00, true),
('AI v SEO (1 deň) - 150€', 'SEO', 150.00, true),
('AI I+II Marec 2026', 'AI', 290.00, true),
('AI I+II Apríl 2026', 'AI', 290.00, true);
-- ============================================================
-- INSERT PARTICIPANTS
-- ============================================================
INSERT INTO ucastnici (titul, meno, priezvisko, email, telefon, firma, mesto, ulica, psc) VALUES
(NULL, 'Martin', 'Sovák', 'info@energium.sk', '0918986172', 'energium sro', 'Bratislava', 'Topolcianska 5', '85105'),
(NULL, 'Michal', 'Farkaš', 'michal.farkas83@gmail.com', '0911209122', 'SLOVWELD', 'Dunajska Lužná', 'Mandlova 30', '90042'),
(NULL, 'Alena', 'Šranková', 'alena.srankova@gmail.com', '0917352580', NULL, 'Bratislava', 'Šándorova 1', '82103'),
(NULL, 'Katarina', 'Tomaníková', 'k.tomanikova@riseday.net', '0948 070 611', 'Classica Shipping Limited', 'Bratislava', 'Keltska 104', '85110'),
(NULL, 'Róbert', 'Brišák', 'robert.brisak@ss-nizna.sk', '0910583883', 'Spojená škola, Hattalova 471, 02743 Nižná', 'Nižná', 'Hattalova 471', '02743'),
(NULL, 'Marián', 'Bača', 'baca.marian@gmail.com', '0907994126', NULL, 'Petrovany', '8', '08253'),
('Mgr. MBA', 'Nikola', 'Horáčková', 'nikolahorackova11@gmail.com', '0918482184', NULL, 'Zákopčie', 'Zákopčie stred 12', '023 11'),
(NULL, 'Tomáš', 'Kupec', 'kupec.tom@gmail.com', '0911030190', 'Jamajka', 'Liptovská Sielnica', NULL, '032 23'),
(NULL, 'Anton', 'Považský', 'anton.povazsky@example.com', NULL, NULL, NULL, NULL, NULL);
-- ============================================================
-- INSERT REGISTRATIONS (with dates)
-- ============================================================
-- AI 1+2 (2 dni) - Februar 2026
INSERT INTO registracie (kurz_id, ucastnik_id, datum_od, datum_do, forma_kurzu, pocet_ucastnikov, faktura_vystavena, zaplatene, stav, poznamka)
SELECT k.id, u.id, '2026-02-02', '2026-02-03', 'prezencne', 1, true, false, 'registrovany', 'FA 2026020'
FROM kurzy k, ucastnici u WHERE k.nazov = 'AI 1+2 (2 dni) - 290€' AND u.email = 'info@energium.sk';
INSERT INTO registracie (kurz_id, ucastnik_id, datum_od, datum_do, forma_kurzu, pocet_ucastnikov, faktura_vystavena, zaplatene, stav, poznamka)
SELECT k.id, u.id, '2026-02-02', '2026-02-03', 'online', 1, true, true, 'registrovany', NULL
FROM kurzy k, ucastnici u WHERE k.nazov = 'AI 1+2 (2 dni) - 290€' AND u.email = 'alena.srankova@gmail.com';
INSERT INTO registracie (kurz_id, ucastnik_id, datum_od, datum_do, forma_kurzu, pocet_ucastnikov, faktura_vystavena, zaplatene, stav, poznamka)
SELECT k.id, u.id, '2026-02-02', '2026-02-03', 'prezencne', 1, true, true, 'registrovany', 'presunuta z oktobra, chce až január'
FROM kurzy k, ucastnici u WHERE k.nazov = 'AI 1+2 (2 dni) - 290€' AND u.email = 'k.tomanikova@riseday.net';
INSERT INTO registracie (kurz_id, ucastnik_id, datum_od, datum_do, forma_kurzu, pocet_ucastnikov, faktura_vystavena, zaplatene, stav, poznamka)
SELECT k.id, u.id, '2026-02-02', '2026-02-03', 'prezencne', 1, true, false, 'registrovany', 'FA 2026019'
FROM kurzy k, ucastnici u WHERE k.nazov = 'AI 1+2 (2 dni) - 290€' AND u.email = 'robert.brisak@ss-nizna.sk';
INSERT INTO registracie (kurz_id, ucastnik_id, datum_od, datum_do, forma_kurzu, pocet_ucastnikov, faktura_vystavena, zaplatene, stav, poznamka)
SELECT k.id, u.id, '2026-02-02', '2026-02-03', 'prezencne', 1, false, false, 'potencialny', 'vzdelávací poukaz'
FROM kurzy k, ucastnici u WHERE k.nazov = 'AI 1+2 (2 dni) - 290€' AND u.email = 'nikolahorackova11@gmail.com';
-- AI 1 (1 den) - Februar 2026
INSERT INTO registracie (kurz_id, ucastnik_id, datum_od, datum_do, forma_kurzu, pocet_ucastnikov, faktura_vystavena, zaplatene, stav, poznamka)
SELECT k.id, u.id, '2026-02-02', '2026-02-02', 'online', 1, true, true, 'registrovany', 'Fa 2025 338, Súhlasil so zmeneným termínom'
FROM kurzy k, ucastnici u WHERE k.nazov = 'AI 1 (1 deň) - 150€' AND u.email = 'michal.farkas83@gmail.com';
-- AI 2 (1 den) - Februar 2026
INSERT INTO registracie (kurz_id, ucastnik_id, datum_od, datum_do, forma_kurzu, pocet_ucastnikov, faktura_vystavena, zaplatene, stav, poznamka)
SELECT k.id, u.id, '2026-02-03', '2026-02-03', 'prezencne', 1, true, false, 'registrovany', 'Fa Gablasova'
FROM kurzy k, ucastnici u WHERE k.nazov = 'AI 2 (1 deň) - 150€' AND u.email = 'baca.marian@gmail.com';
-- AI v SEO - Februar 2026
INSERT INTO registracie (kurz_id, ucastnik_id, datum_od, datum_do, forma_kurzu, pocet_ucastnikov, faktura_vystavena, zaplatene, stav, poznamka)
SELECT k.id, u.id, '2026-02-13', '2026-02-13', 'prezencne', 1, true, false, 'registrovany', 'FA 2026021'
FROM kurzy k, ucastnici u WHERE k.nazov = 'AI v SEO (1 deň) - 150€' AND u.email = 'kupec.tom@gmail.com';
INSERT INTO registracie (kurz_id, ucastnik_id, datum_od, datum_do, forma_kurzu, pocet_ucastnikov, faktura_vystavena, zaplatene, stav, poznamka)
SELECT k.id, u.id, '2026-02-13', '2026-02-13', 'prezencne', 1, true, false, 'registrovany', NULL
FROM kurzy k, ucastnici u WHERE k.nazov = 'AI v SEO (1 deň) - 150€' AND u.email = 'anton.povazsky@example.com';
-- ============================================================
-- VERIFY DATA
-- ============================================================
SELECT 'Courses imported:' as info, COUNT(*) as count FROM kurzy;
SELECT 'Participants imported:' as info, COUNT(*) as count FROM ucastnici;
SELECT 'Registrations imported:' as info, COUNT(*) as count FROM registracie;
SELECT 'AI Kurzy data import completed successfully!' as status;

82
sql/03_cleanup_data.sql Normal file
View File

@@ -0,0 +1,82 @@
-- ============================================================
-- CLEANUP DATA - Clear test/development data
-- WARNING: This will DELETE data! Use with caution!
-- ============================================================
-- ============================================================
-- OPTION 1: SOFT CLEANUP (keeps structure, removes data)
-- ============================================================
-- Clear Todos and related
DELETE FROM todo_users;
DELETE FROM todos;
-- Clear Companies and related
DELETE FROM company_remind;
DELETE FROM company_users;
DELETE FROM company_documents;
DELETE FROM companies;
-- Clear Projects and related
DELETE FROM project_users;
DELETE FROM project_documents;
DELETE FROM projects;
-- Clear Notes
DELETE FROM notes;
-- Clear Time Entries
DELETE FROM time_entries;
-- Clear Timesheets
DELETE FROM timesheets;
-- Clear Events and related
DELETE FROM event_users;
DELETE FROM events;
-- Clear Messages (both direct and group)
DELETE FROM messages;
DELETE FROM group_messages;
DELETE FROM chat_group_members;
DELETE FROM chat_groups;
-- Clear Services
DELETE FROM service_documents;
DELETE FROM service_folders;
DELETE FROM services;
-- Clear Email related (contacts, emails) - BE CAREFUL
-- DELETE FROM emails;
-- DELETE FROM contacts;
-- Clear Push subscriptions
DELETE FROM push_subscriptions;
-- Clear Email signatures
DELETE FROM email_signatures;
-- ============================================================
-- OPTION 2: RESET SEQUENCES (optional)
-- ============================================================
-- If you have serial IDs and want to reset them:
-- ALTER SEQUENCE todos_id_seq RESTART WITH 1;
-- etc.
-- ============================================================
-- VERIFY CLEANUP
-- ============================================================
SELECT 'Cleanup results:' as info;
SELECT 'Todos:' as table_name, COUNT(*) as remaining FROM todos;
SELECT 'Companies:' as table_name, COUNT(*) as remaining FROM companies;
SELECT 'Projects:' as table_name, COUNT(*) as remaining FROM projects;
SELECT 'Notes:' as table_name, COUNT(*) as remaining FROM notes;
SELECT 'Events:' as table_name, COUNT(*) as remaining FROM events;
SELECT 'Messages:' as table_name, COUNT(*) as remaining FROM messages;
SELECT 'Chat Groups:' as table_name, COUNT(*) as remaining FROM chat_groups;
SELECT 'Time Entries:' as table_name, COUNT(*) as remaining FROM time_entries;
SELECT 'Services:' as table_name, COUNT(*) as remaining FROM services;
SELECT 'Cleanup completed!' as status;

46
sql/README.md Normal file
View File

@@ -0,0 +1,46 @@
# SQL Migration Scripts
## Pouzitie na Coolify
### 1. Pripojenie k databaze cez terminal
```bash
# Na Coolify serveri najdi PostgreSQL container a pripoj sa
docker exec -it <postgres_container_id> psql -U <username> -d <database>
# Alebo ak mas pristup cez SSH:
psql -h localhost -U <username> -d <database>
```
### 2. Poradie spustenia scriptov
**DOLEZITE: Spusti scripty v tomto poradi!**
```sql
-- 1. Najprv schema migration (vytvori nove tabulky a stlpce)
\i /path/to/01_schema_migration.sql
-- 2. Potom AI Kurzy data (vlozi kurzy a ucastnikov)
\i /path/to/02_ai_kurzy_data.sql
-- 3. Volitelne: Cleanup existujucich dat (POZOR - maze data!)
\i /path/to/03_cleanup_data.sql
```
### 3. Alternativne - copy/paste
Mozes tiez otvorit subory a copy/paste obsah priamo do psql terminalu.
### Popis suborov
| Subor | Popis |
|-------|-------|
| `01_schema_migration.sql` | Vytvori vsetky nove tabulky (chat groups, push notifications, AI kurzy, atd.) a prida nove stlpce do existujucich tabuliek |
| `02_ai_kurzy_data.sql` | Importuje kurzy a ucastnikov z CSV - 6 kurzov, 9 ucastnikov |
| `03_cleanup_data.sql` | Vymaze test data z todos, companies, projects, notes, events, messages |
### Poznamky
- `01_schema_migration.sql` je bezpecny - pouziva `IF NOT EXISTS` takze nevytvori duplicity
- `02_ai_kurzy_data.sql` najprv ZMAZE existujuce AI kurzy data!
- `03_cleanup_data.sql` ZMAZE data! Pouzi opatrne!