# AI Kurzy - Drizzle Schema **Databázová schéma pre Node.js backend s Drizzle ORM** --- ## 🗂️ Štruktúra Databázy **4 tabuľky:** ``` kurzy ←──┐ │ ├─→ registracie ←─→ ucastnici │ │ └───────────┴─→ prilohy ``` - **`kurzy`** - definície kurzov a termínov - **`ucastnici`** - osobné údaje účastníkov - **`registracie`** - väzba many-to-many (kto-kde-kedy + fakturácia) - **`prilohy`** - dokumenty (certifikáty, faktúry) pripnuté k registráciám --- ## 📄 Schéma: `src/db/schema.ts` ```typescript import { relations } from 'drizzle-orm'; import { pgTable, serial, varchar, text, numeric, date, integer, boolean, timestamp, pgEnum, bigint, uniqueIndex, } from 'drizzle-orm/pg-core'; // ============================================================================ // ENUM DEFINÍCIE // ============================================================================ export const formaKurzuEnum = pgEnum('forma_kurzu_enum', [ 'prezenčne', 'online', 'hybridne', ]); export const stavRegistracieEnum = pgEnum('stav_registracie_enum', [ 'potencialny', // Potenciálny záujemca 'registrovany', // Registrovaný 'potvrdeny', // Potvrdená účasť 'absolvoval', // Absolvoval kurz (odškolené) 'zruseny', // Zrušená registrácia ]); export const typPrilohyEnum = pgEnum('typ_prilohy_enum', [ 'certifikat', 'faktura', 'prihlaska', 'doklad_o_platbe', 'ine', ]); // ============================================================================ // TABUĽKA: kurzy // ============================================================================ export const kurzy = pgTable('kurzy', { id: serial('id').primaryKey(), // Základné informácie nazov: varchar('nazov', { length: 255 }).notNull(), typKurzu: varchar('typ_kurzu', { length: 100 }).notNull(), // "AI 1+2", "AI 1", "SEO" popis: text('popis'), // Cenník cena: numeric('cena', { precision: 10, scale: 2 }).notNull(), // Termín kurzu datumOd: date('datum_od', { mode: 'date' }).notNull(), datumDo: date('datum_do', { mode: 'date' }).notNull(), // Kapacita maxKapacita: integer('max_kapacita'), // NULL = neobmedzené // Stav kurzu aktivny: boolean('aktivny').default(true).notNull(), // Metadata createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), }); // ============================================================================ // TABUĽKA: ucastnici // ============================================================================ export const ucastnici = pgTable( 'ucastnici', { id: serial('id').primaryKey(), // Osobné údaje titul: varchar('titul', { length: 50 }), meno: varchar('meno', { length: 100 }).notNull(), priezvisko: varchar('priezvisko', { length: 100 }).notNull(), // Kontaktné údaje email: varchar('email', { length: 255 }).notNull().unique(), telefon: varchar('telefon', { length: 50 }), // Firemné údaje firma: varchar('firma', { length: 255 }), // Adresa mesto: varchar('mesto', { length: 100 }), ulica: varchar('ulica', { length: 255 }), psc: varchar('psc', { length: 10 }), // Metadata createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), }, (table) => ({ emailIdx: uniqueIndex('ucastnici_email_idx').on(table.email), }) ); // ============================================================================ // TABUĽKA: registracie // ============================================================================ export const registracie = pgTable( 'registracie', { id: serial('id').primaryKey(), // Foreign keys kurzId: integer('kurz_id') .notNull() .references(() => kurzy.id, { onDelete: 'cascade' }), ucastnikId: integer('ucastnik_id') .notNull() .references(() => ucastnici.id, { onDelete: 'cascade' }), // Forma účasti formaKurzu: formaKurzuEnum('forma_kurzu').default('prezenčne').notNull(), // Počet účastníkov (ak firma prihlasuje viacerých) pocetUcastnikov: integer('pocet_ucastnikov').default(1).notNull(), // Fakturácia fakturaCislo: varchar('faktura_cislo', { length: 100 }), fakturaVystavena: boolean('faktura_vystavena').default(false).notNull(), zaplatene: boolean('zaplatene').default(false).notNull(), // Stav registrácie stav: stavRegistracieEnum('stav').default('registrovany').notNull(), // Poznámky poznamka: text('poznamka'), // Metadata createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), }, (table) => ({ // Unikátne obmedzenie: jeden účastník sa nemôže prihlásiť na ten istý kurz viackrát uniqRegistracia: uniqueIndex('registracie_kurz_ucastnik_idx').on( table.kurzId, table.ucastnikId ), }) ); // ============================================================================ // TABUĽKA: prilohy // ============================================================================ export const prilohy = pgTable('prilohy', { id: serial('id').primaryKey(), // Foreign key registraciaId: integer('registracia_id') .notNull() .references(() => registracie.id, { onDelete: 'cascade' }), // Informácie o súbore nazovSuboru: varchar('nazov_suboru', { length: 255 }).notNull(), typPrilohy: typPrilohyEnum('typ_prilohy').default('ine').notNull(), // Úložisko cestaKSuboru: varchar('cesta_k_suboru', { length: 500 }).notNull(), mimeType: varchar('mime_type', { length: 100 }), velkostSuboru: bigint('velkost_suboru', { mode: 'number' }), // veľkosť v bytoch // Popis popis: text('popis'), // Metadata createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), }); // ============================================================================ // RELAČNÉ VÄZBY (Relations) // ============================================================================ // Kurzy Relations export const kurzyRelations = relations(kurzy, ({ many }) => ({ registracie: many(registracie), })); // Účastníci Relations export const ucastniciRelations = relations(ucastnici, ({ many }) => ({ registracie: many(registracie), })); // Registrácie Relations export const registracieRelations = relations(registracie, ({ one, many }) => ({ kurz: one(kurzy, { fields: [registracie.kurzId], references: [kurzy.id], }), ucastnik: one(ucastnici, { fields: [registracie.ucastnikId], references: [ucastnici.id], }), prilohy: many(prilohy), })); // Prílohy Relations export const prilohyRelations = relations(prilohy, ({ one }) => ({ registracia: one(registracie, { fields: [prilohy.registraciaId], references: [registracie.id], }), })); ``` --- ## 📘 TypeScript Typy ```typescript import { InferSelectModel, InferInsertModel } from 'drizzle-orm'; import { kurzy, ucastnici, registracie, prilohy } from './schema'; // SELECT typy (čítanie z DB) export type Kurz = InferSelectModel; export type Ucastnik = InferSelectModel; export type Registracia = InferSelectModel; export type Priloha = InferSelectModel; // INSERT typy (vkladanie do DB) export type NewKurz = InferInsertModel; export type NewUcastnik = InferInsertModel; export type NewRegistracia = InferInsertModel; export type NewPriloha = InferInsertModel; ``` --- ## 🔗 Vysvetlenie Relácií ### **1:N (One-to-Many)** ```typescript // Jeden kurz má VIAC registrácií kurzyRelations = relations(kurzy, ({ many }) => ({ registracie: many(registracie), })); ``` ### **N:1 (Many-to-One)** ```typescript // Viac registrácií patrí k JEDNÉMU kurzu registracieRelations = relations(registracie, ({ one }) => ({ kurz: one(kurzy, { fields: [registracie.kurzId], // FK references: [kurzy.id], // PK }), })); ``` ### **N:M (Many-to-Many)** ``` kurzy ↔ registracie ↔ ucastnici - Jeden kurz má viac účastníkov - Jeden účastník môže ísť na viac kurzov - Junction table: registracie ``` --- ## 💡 Použitie ### Základný query s relačnými dátami ```typescript import { db } from './db'; import { kurzy } from './db/schema'; import { eq } from 'drizzle-orm'; // Kurz s registráciami a účastníkmi const kurzDetail = await db.query.kurzy.findFirst({ where: eq(kurzy.id, 1), with: { registracie: { with: { ucastnik: true, prilohy: true, }, }, }, }); ``` --- **Vytvorené:** 2026-01-20 **Stack:** Node.js + Drizzle ORM + PostgreSQL + TypeScript