# CRM Server - Kompletná Dokumentácia

## Obsah

1. [Prehľad projektu](#1-prehľad-projektu)
2. [Štruktúra projektu](#2-štruktúra-projektu)
3. [Databázová schéma](#3-databázová-schéma)
4. [API Endpoints](#4-api-endpoints)
5. [Middlewares](#5-middlewares)
6. [Services](#6-services)
7. [Controllers](#7-controllers)
8. [Utility funkcie](#8-utility-funkcie)
9. [Validačné schémy](#9-validačné-schémy)
10. [Konfigurácia](#10-konfigurácia)
11. [Autentizácia a bezpečnosť](#11-autentizácia-a-bezpečnosť)
12. [Cron Jobs a Notifikácie](#12-cron-jobs-a-notifikácie)

---

## 1. Prehľad projektu

CRM Server je backend aplikácia postavená na:
- **Express.js** - Web framework
- **Drizzle ORM** - Databázový ORM
- **PostgreSQL** - Databáza
- **JWT** - Autentizácia
- **Zod** - Validácia
- **JMAP** - Email integrácia

### Hlavné funkcie:
- Multi-user CRM s role-based access control (admin/member)
- Email integrácia cez JMAP API
- Time tracking s generovaním timesheetov (XLSX)
- Contact management s linkovaním na firmy
- Company/Project management s teamami
- Todo/Task management s priorítami
- Audit logging všetkých akcií
- Company reminders s due dates
- Meeting scheduling

---

## 2. Štruktúra projektu

```
crm-server/
├── src/
│   ├── config/
│   │   └── database.js              # Drizzle ORM + PostgreSQL konfigurácia
│   │
│   ├── db/
│   │   ├── schema.js                # Kompletná databázová schéma
│   │   └── seeds/
│   │       ├── admin.seed.js        # Seed pre admin usera
│   │       └── testuser.seed.js     # Seed pre testovacieho usera
│   │
│   ├── controllers/
│   │   ├── auth.controller.js
│   │   ├── admin.controller.js
│   │   ├── contact.controller.js
│   │   ├── personal-contact.controller.js
│   │   ├── company.controller.js
│   │   ├── company-reminder.controller.js
│   │   ├── project.controller.js
│   │   ├── todo.controller.js
│   │   ├── time-tracking.controller.js
│   │   ├── timesheet.controller.js
│   │   ├── meeting.controller.js
│   │   ├── note.controller.js
│   │   ├── crm-email.controller.js
│   │   ├── email-account.controller.js
│   │   └── audit.controller.js
│   │
│   ├── services/
│   │   ├── auth.service.js
│   │   ├── admin.service.js
│   │   ├── contact.service.js
│   │   ├── personal-contact.service.js
│   │   ├── company.service.js
│   │   ├── company-reminder.service.js
│   │   ├── company-email.service.js
│   │   ├── project.service.js
│   │   ├── todo.service.js
│   │   ├── time-tracking.service.js
│   │   ├── timesheet.service.js
│   │   ├── meeting.service.js
│   │   ├── note.service.js
│   │   ├── crm-email.service.js
│   │   ├── email-account.service.js
│   │   ├── email.service.js
│   │   ├── audit.service.js
│   │   ├── status.service.js
│   │   └── jmap/
│   │       ├── index.js             # Hlavný JMAP export
│   │       ├── client.js            # JMAP klient
│   │       ├── config.js            # JMAP konfigurácia
│   │       ├── discovery.js         # JMAP discovery
│   │       ├── operations.js        # JMAP operácie
│   │       ├── search.js            # JMAP vyhľadávanie
│   │       └── sync.js              # JMAP synchronizácia
│   │
│   ├── cron/
│   │   ├── index.js                 # Hlavný cron entry point
│   │   └── calendar/
│   │       ├── index.js             # Kalendárny cron scheduler
│   │       ├── email-template.js    # HTML email šablóna
│   │       └── event-notifier.js    # Logika notifikácií
│   │
│   ├── routes/
│   │   ├── auth.routes.js
│   │   ├── admin.routes.js
│   │   ├── contact.routes.js
│   │   ├── personal-contact.routes.js
│   │   ├── company.routes.js
│   │   ├── project.routes.js
│   │   ├── todo.routes.js
│   │   ├── time-tracking.routes.js
│   │   ├── timesheet.routes.js
│   │   ├── meeting.routes.js
│   │   ├── note.routes.js
│   │   ├── crm-email.routes.js
│   │   ├── email-account.routes.js
│   │   └── audit.routes.js
│   │
│   ├── middlewares/
│   │   ├── auth/
│   │   │   ├── authMiddleware.js           # JWT overenie
│   │   │   ├── roleMiddleware.js           # Role overenie
│   │   │   └── resourceAccessMiddleware.js # Resource access control
│   │   ├── security/
│   │   │   ├── rateLimiter.js              # Rate limiting
│   │   │   ├── validateInput.js            # Input validácia
│   │   │   └── requireAccountId.js         # Email account check
│   │   └── global/
│   │       ├── errorHandler.js             # Error handling
│   │       ├── validateBody.js             # Body validation
│   │       └── notFound.js                 # 404 handler
│   │
│   ├── utils/
│   │   ├── errors.js                # Custom error classes
│   │   ├── jwt.js                   # JWT management
│   │   ├── password.js              # Password & encryption
│   │   └── logger.js                # Logger utility
│   │
│   ├── validators/
│   │   ├── auth.validators.js       # Auth schémy (Zod)
│   │   ├── crm.validators.js        # CRM schémy (Zod)
│   │   └── email-account.validators.js
│   │
│   ├── scripts/
│   │   ├── seed-admin.js
│   │   ├── fresh-database.js
│   │   └── fix-wrong-contact-associations.js
│   │
│   ├── app.js                       # Express aplikácia
│   └── index.js                     # Entry point
│
├── uploads/                         # Nahrané súbory (timesheets)
├── __tests__/                       # Testy
├── package.json
├── drizzle.config.js
├── Dockerfile
└── .env
```

---

## 3. Databázová schéma

### Enums

```sql
-- Role používateľa
CREATE TYPE role AS ENUM ('admin', 'member');

-- Status projektu
CREATE TYPE project_status AS ENUM ('active', 'completed', 'on_hold', 'cancelled');

-- Status úlohy
CREATE TYPE todo_status AS ENUM ('pending', 'in_progress', 'completed', 'cancelled');

-- Priorita úlohy
CREATE TYPE todo_priority AS ENUM ('low', 'medium', 'high', 'urgent');
```

### Tabuľky

#### users
Používatelia systému.

| Stĺpec | Typ | Popis |
|--------|-----|-------|
| id | UUID | Primárny kľúč |
| username | VARCHAR(50) | Unikátne užívateľské meno |
| firstName | VARCHAR(100) | Krstné meno |
| lastName | VARCHAR(100) | Priezvisko |
| password | TEXT | Hash hesla |
| tempPassword | TEXT | Dočasné heslo |
| changedPassword | BOOLEAN | Či si zmenil heslo |
| role | ENUM | 'admin' alebo 'member' |
| lastLogin | TIMESTAMP | Posledné prihlásenie |
| createdAt | TIMESTAMP | Dátum vytvorenia |
| updatedAt | TIMESTAMP | Dátum aktualizácie |

#### email_accounts
Zdieľané email účty.

| Stĺpec | Typ | Popis |
|--------|-----|-------|
| id | UUID | Primárny kľúč |
| email | VARCHAR(255) | Email adresa |
| emailPassword | TEXT | Šifrované heslo (AES-256-GCM) |
| jmapAccountId | TEXT | JMAP account ID |
| isActive | BOOLEAN | Či je účet aktívny |
| createdAt | TIMESTAMP | Dátum vytvorenia |
| updatedAt | TIMESTAMP | Dátum aktualizácie |

#### user_email_accounts
M2M väzba: users ↔ email_accounts

| Stĺpec | Typ | Popis |
|--------|-----|-------|
| id | UUID | Primárny kľúč |
| userId | UUID | FK → users.id |
| emailAccountId | UUID | FK → email_accounts.id |
| isPrimary | BOOLEAN | Či je primárny účet |
| addedAt | TIMESTAMP | Dátum pridania |

#### companies
Firmy/spoločnosti.

| Stĺpec | Typ | Popis |
|--------|-----|-------|
| id | UUID | Primárny kľúč |
| name | VARCHAR(255) | Názov firmy |
| description | TEXT | Popis |
| address | TEXT | Adresa |
| city | VARCHAR(100) | Mesto |
| country | VARCHAR(100) | Krajina |
| phone | VARCHAR(50) | Telefón |
| email | VARCHAR(255) | Email |
| website | VARCHAR(255) | Web stránka |
| isActive | BOOLEAN | Či je aktívna |
| createdBy | UUID | FK → users.id |
| createdAt | TIMESTAMP | Dátum vytvorenia |
| updatedAt | TIMESTAMP | Dátum aktualizácie |

#### company_users
M2M väzba: companies ↔ users (tím firmy)

| Stĺpec | Typ | Popis |
|--------|-----|-------|
| id | UUID | Primárny kľúč |
| companyId | UUID | FK → companies.id (CASCADE) |
| userId | UUID | FK → users.id (CASCADE) |
| role | TEXT | Rola v tíme |
| addedBy | UUID | FK → users.id (SET NULL) |
| addedAt | TIMESTAMP | Dátum pridania |

**Unique constraint:** (companyId, userId)

#### company_remind
Pripomienky firmy.

| Stĺpec | Typ | Popis |
|--------|-----|-------|
| id | UUID | Primárny kľúč |
| companyId | UUID | FK → companies.id (CASCADE) |
| description | TEXT | Popis pripomienky |
| dueDate | TIMESTAMP | Termín |
| isChecked | BOOLEAN | Či je splnená |
| createdAt | TIMESTAMP | Dátum vytvorenia |
| updatedAt | TIMESTAMP | Dátum aktualizácie |

#### projects
Projekty.

| Stĺpec | Typ | Popis |
|--------|-----|-------|
| id | UUID | Primárny kľúč |
| name | VARCHAR(255) | Názov projektu |
| description | TEXT | Popis |
| companyId | UUID | FK → companies.id (SET NULL) |
| status | ENUM | Status projektu |
| startDate | DATE | Dátum začiatku |
| endDate | DATE | Dátum konca |
| createdBy | UUID | FK → users.id |
| createdAt | TIMESTAMP | Dátum vytvorenia |
| updatedAt | TIMESTAMP | Dátum aktualizácie |

#### project_users
M2M väzba: projects ↔ users (tím projektu)

| Stĺpec | Typ | Popis |
|--------|-----|-------|
| id | UUID | Primárny kľúč |
| projectId | UUID | FK → projects.id (CASCADE) |
| userId | UUID | FK → users.id (CASCADE) |
| role | TEXT | Rola v projekte |
| addedBy | UUID | FK → users.id (SET NULL) |
| addedAt | TIMESTAMP | Dátum pridania |

**Unique constraint:** (projectId, userId)

#### todos
Úlohy/tasky.

| Stĺpec | Typ | Popis |
|--------|-----|-------|
| id | UUID | Primárny kľúč |
| title | VARCHAR(255) | Názov úlohy |
| description | TEXT | Popis |
| projectId | UUID | FK → projects.id (SET NULL) |
| companyId | UUID | FK → companies.id (SET NULL) |
| status | ENUM | Status úlohy |
| priority | ENUM | Priorita |
| dueDate | TIMESTAMP | Termín |
| completedAt | TIMESTAMP | Dátum dokončenia |
| createdBy | UUID | FK → users.id |
| createdAt | TIMESTAMP | Dátum vytvorenia |
| updatedAt | TIMESTAMP | Dátum aktualizácie |

#### todo_users
M2M väzba: todos ↔ users (priradení k úlohe)

| Stĺpec | Typ | Popis |
|--------|-----|-------|
| id | UUID | Primárny kľúč |
| todoId | UUID | FK → todos.id (CASCADE) |
| userId | UUID | FK → users.id (CASCADE) |
| assignedBy | UUID | FK → users.id (SET NULL) |
| assignedAt | TIMESTAMP | Dátum priradenia |

**Unique constraint:** (todoId, userId)

#### contacts
Kontakty (patriace email účtu).

| Stĺpec | Typ | Popis |
|--------|-----|-------|
| id | UUID | Primárny kľúč |
| emailAccountId | UUID | FK → email_accounts.id (CASCADE) |
| companyId | UUID | FK → companies.id (SET NULL) |
| email | VARCHAR(255) | Email kontaktu |
| name | VARCHAR(255) | Meno kontaktu |
| notes | TEXT | Poznámky |
| addedBy | UUID | FK → users.id |
| addedAt | TIMESTAMP | Dátum pridania |
| createdAt | TIMESTAMP | Dátum vytvorenia |
| updatedAt | TIMESTAMP | Dátum aktualizácie |

#### personal_contacts
Osobné kontakty používateľa.

| Stĺpec | Typ | Popis |
|--------|-----|-------|
| id | UUID | Primárny kľúč |
| userId | UUID | FK → users.id (CASCADE) |
| firstName | VARCHAR(100) | Krstné meno |
| lastName | VARCHAR(100) | Priezvisko |
| phone | VARCHAR(50) | Telefón |
| email | VARCHAR(255) | Email |
| secondaryEmail | VARCHAR(255) | Sekundárny email |
| createdAt | TIMESTAMP | Dátum vytvorenia |
| updatedAt | TIMESTAMP | Dátum aktualizácie |

#### emails
Uložené emaily z JMAP.

| Stĺpec | Typ | Popis |
|--------|-----|-------|
| id | UUID | Primárny kľúč |
| emailAccountId | UUID | FK → email_accounts.id (CASCADE) |
| contactId | UUID | FK → contacts.id (SET NULL) |
| companyId | UUID | FK → companies.id (SET NULL) |
| jmapId | TEXT | JMAP ID emailu |
| messageId | TEXT | Message-ID header |
| threadId | TEXT | JMAP thread ID |
| inReplyTo | TEXT | In-Reply-To header |
| from | JSONB | Odosielateľ |
| to | JSONB | Príjemcovia |
| subject | TEXT | Predmet |
| body | TEXT | Telo emailu |
| isRead | BOOLEAN | Či je prečítaný |
| sentByUserId | UUID | FK → users.id (ak odoslané) |
| date | TIMESTAMP | Dátum emailu |
| createdAt | TIMESTAMP | Dátum vytvorenia |
| updatedAt | TIMESTAMP | Dátum aktualizácie |

#### notes
Poznámky (pripojené k rôznym entitám).

| Stĺpec | Typ | Popis |
|--------|-----|-------|
| id | UUID | Primárny kľúč |
| title | VARCHAR(255) | Titulok |
| content | TEXT | Obsah poznámky |
| companyId | UUID | FK → companies.id (SET NULL) |
| projectId | UUID | FK → projects.id (SET NULL) |
| todoId | UUID | FK → todos.id (SET NULL) |
| contactId | UUID | FK → contacts.id (SET NULL) |
| reminderAt | TIMESTAMP | Čas pripomienky |
| reminderSent | BOOLEAN | Či bola pripomienka odoslaná |
| createdBy | UUID | FK → users.id |
| createdAt | TIMESTAMP | Dátum vytvorenia |
| updatedAt | TIMESTAMP | Dátum aktualizácie |

#### time_entries
Sledovanie času.

| Stĺpec | Typ | Popis |
|--------|-----|-------|
| id | UUID | Primárny kľúč |
| userId | UUID | FK → users.id (CASCADE) |
| projectId | UUID | FK → projects.id (SET NULL) |
| todoId | UUID | FK → todos.id (SET NULL) |
| companyId | UUID | FK → companies.id (SET NULL) |
| startTime | TIMESTAMP | Čas začiatku |
| endTime | TIMESTAMP | Čas konca |
| duration | INTEGER | Trvanie v minútach |
| description | TEXT | Popis práce |
| isRunning | BOOLEAN | Či práve beží |
| isEdited | BOOLEAN | Či bol upravený |
| createdAt | TIMESTAMP | Dátum vytvorenia |
| updatedAt | TIMESTAMP | Dátum aktualizácie |

#### timesheets
Nahrané timesheets.

| Stĺpec | Typ | Popis |
|--------|-----|-------|
| id | UUID | Primárny kľúč |
| userId | UUID | FK → users.id (CASCADE) |
| projectId | UUID | FK → projects.id (SET NULL) |
| fileName | VARCHAR(255) | Názov súboru |
| filePath | TEXT | Cesta k súboru |
| fileType | VARCHAR(10) | 'pdf' alebo 'xlsx' |
| fileSize | INTEGER | Veľkosť v bytoch |
| year | INTEGER | Rok |
| month | INTEGER | Mesiac |
| isGenerated | BOOLEAN | Či bol generovaný |
| uploadedAt | TIMESTAMP | Dátum nahratia |
| createdAt | TIMESTAMP | Dátum vytvorenia |
| updatedAt | TIMESTAMP | Dátum aktualizácie |

#### meetings
Stretnutia (iba admin).

| Stĺpec | Typ | Popis |
|--------|-----|-------|
| id | UUID | Primárny kľúč |
| title | VARCHAR(255) | Názov stretnutia |
| description | TEXT | Popis |
| start | TIMESTAMP | Čas začiatku |
| end | TIMESTAMP | Čas konca |
| createdBy | UUID | FK → users.id |
| createdAt | TIMESTAMP | Dátum vytvorenia |
| updatedAt | TIMESTAMP | Dátum aktualizácie |

#### audit_logs
Audit trail všetkých akcií.

| Stĺpec | Typ | Popis |
|--------|-----|-------|
| id | UUID | Primárny kľúč |
| userId | UUID | FK → users.id (SET NULL) |
| action | VARCHAR(100) | Typ akcie |
| resource | VARCHAR(100) | Typ resource |
| resourceId | UUID | ID resource |
| oldValue | JSONB | Stará hodnota |
| newValue | JSONB | Nová hodnota |
| ipAddress | VARCHAR(45) | IP adresa |
| userAgent | TEXT | User agent |
| success | BOOLEAN | Či bola úspešná |
| errorMessage | TEXT | Chybová správa |
| createdAt | TIMESTAMP | Dátum vytvorenia |

---

## 4. API Endpoints

### Formát Response

**Success:**
```json
{
  "success": true,
  "data": { },
  "count": 10,
  "message": "Akcia úspešná"
}
```

**Error:**
```json
{
  "success": false,
  "error": {
    "message": "Chybová správa",
    "statusCode": 400,
    "details": [
      { "field": "username", "message": "Povinné pole" }
    ]
  }
}
```

### Authentication (`/api/auth`)

| Metóda | Endpoint | Popis | Auth | Rate Limit |
|--------|----------|-------|------|------------|
| POST | `/login` | Prihlásenie | Nie | 5/15min |
| POST | `/set-password` | Nastavenie hesla | Áno | - |
| POST | `/logout` | Odhlásenie | Áno | - |
| GET | `/session` | Aktuálna session | Áno | - |

### Admin (`/api/admin`)

| Metóda | Endpoint | Popis | Auth | Role |
|--------|----------|-------|------|------|
| GET | `/users` | Zoznam userov | Áno | Všetci |
| POST | `/users` | Vytvoriť usera | Áno | Admin |
| GET | `/users/:userId` | Detail usera | Áno | Admin |
| PATCH | `/users/:userId/role` | Zmeniť rolu | Áno | Admin |
| DELETE | `/users/:userId` | Zmazať usera | Áno | Admin |
| GET | `/server-status` | Server status | Áno | Admin |
| POST | `/trigger-notifications` | Manuálne spustiť notifikácie | Áno | Admin |

### Companies (`/api/companies`)

| Metóda | Endpoint | Popis | Auth | Role |
|--------|----------|-------|------|------|
| GET | `/` | Zoznam firiem | Áno | Všetci* |
| GET | `/:companyId` | Detail firmy | Áno | Access |
| POST | `/` | Vytvoriť firmu | Áno | Admin |
| PATCH | `/:companyId` | Upraviť firmu | Áno | Admin |
| DELETE | `/:companyId` | Zmazať firmu | Áno | Admin |
| GET | `/reminders/summary` | Súhrn pripomienok | Áno | Všetci* |
| GET | `/reminders/counts` | Počty pripomienok | Áno | Všetci* |
| GET | `/reminders/upcoming` | Budúce pripomienky | Áno | Všetci* |
| GET | `/email-unread` | Neprečítané emaily | Áno | Všetci |
| GET | `/:companyId/email-threads` | Email vlákna | Áno | Access |

**Company Notes:**

| Metóda | Endpoint | Popis | Auth | Role |
|--------|----------|-------|------|------|
| GET | `/:companyId/notes` | Poznámky firmy | Áno | Access |
| POST | `/:companyId/notes` | Pridať poznámku | Áno | Admin |
| PATCH | `/:companyId/notes/:noteId` | Upraviť poznámku | Áno | Admin |
| DELETE | `/:companyId/notes/:noteId` | Zmazať poznámku | Áno | Admin |

**Company Reminders:**

| Metóda | Endpoint | Popis | Auth | Role |
|--------|----------|-------|------|------|
| GET | `/:companyId/reminders` | Pripomienky firmy | Áno | Access |
| POST | `/:companyId/reminders` | Vytvoriť | Áno | Admin |
| PATCH | `/:companyId/reminders/:reminderId` | Upraviť | Áno | Admin |
| DELETE | `/:companyId/reminders/:reminderId` | Zmazať | Áno | Admin |

**Company Users (Team):**

| Metóda | Endpoint | Popis | Auth | Role |
|--------|----------|-------|------|------|
| GET | `/:companyId/users` | Členovia tímu | Áno | Access |
| POST | `/:companyId/users` | Pridať člena | Áno | Admin |
| PATCH | `/:companyId/users/:userId` | Upraviť rolu | Áno | Admin |
| DELETE | `/:companyId/users/:userId` | Odstrániť člena | Áno | Admin |

*\* Member vidí len firmy kde je priradený*

### Projects (`/api/projects`)

| Metóda | Endpoint | Popis | Auth | Role |
|--------|----------|-------|------|------|
| GET | `/` | Zoznam projektov | Áno | Všetci* |
| GET | `/:projectId` | Detail projektu | Áno | Access |
| POST | `/` | Vytvoriť projekt | Áno | Admin |
| PATCH | `/:projectId` | Upraviť projekt | Áno | Admin |
| DELETE | `/:projectId` | Zmazať projekt | Áno | Admin |

**Project Notes:**

| Metóda | Endpoint | Popis | Auth | Role |
|--------|----------|-------|------|------|
| GET | `/:projectId/notes` | Poznámky projektu | Áno | Access |
| POST | `/:projectId/notes` | Pridať poznámku | Áno | Admin |
| PATCH | `/:projectId/notes/:noteId` | Upraviť | Áno | Admin |
| DELETE | `/:projectId/notes/:noteId` | Zmazať | Áno | Admin |

**Project Users (Team):**

| Metóda | Endpoint | Popis | Auth | Role |
|--------|----------|-------|------|------|
| GET | `/:projectId/users` | Členovia projektu | Áno | Access |
| POST | `/:projectId/users` | Pridať člena | Áno | Admin |
| PATCH | `/:projectId/users/:userId` | Upraviť rolu | Áno | Admin |
| DELETE | `/:projectId/users/:userId` | Odstrániť člena | Áno | Admin |

*\* Member vidí len projekty kde je priradený alebo projekty firiem kde je priradený*

### Todos (`/api/todos`)

| Metóda | Endpoint | Popis | Auth | Role |
|--------|----------|-------|------|------|
| GET | `/` | Zoznam úloh | Áno | Všetci* |
| GET | `/:todoId` | Detail úlohy | Áno | Access |
| POST | `/` | Vytvoriť úlohu | Áno | Admin |
| PATCH | `/:todoId` | Upraviť úlohu | Áno | Admin |
| DELETE | `/:todoId` | Zmazať úlohu | Áno | Admin |
| PATCH | `/:todoId/toggle` | Prepnúť stav | Áno | Access |

*\* Member vidí len úlohy kde je priradený*

**Query parametre pre GET /:**
- `search` - Vyhľadávanie v title/description
- `projectId` - Filter podľa projektu
- `companyId` - Filter podľa firmy
- `assignedTo` - Filter podľa priradeného usera
- `status` - Filter podľa statusu (pending/in_progress/completed/cancelled)
- `completed` - true/false (alternatíva k status)
- `priority` - Filter podľa priority (low/medium/high/urgent)

### Contacts (`/api/contacts`)

| Metóda | Endpoint | Popis | Auth | Role |
|--------|----------|-------|------|------|
| GET | `/` | Zoznam kontaktov | Áno | Všetci |
| GET | `/discover` | Objaviť z JMAP | Áno | Všetci |
| POST | `/` | Pridať kontakt | Áno | Všetci |
| PATCH | `/:contactId` | Upraviť kontakt | Áno | Všetci |
| POST | `/:contactId/link-company` | Pripojiť firmu | Áno | Všetci |
| POST | `/:contactId/unlink-company` | Odpojiť firmu | Áno | Všetci |
| POST | `/:contactId/create-company` | Vytvoriť firmu | Áno | Všetci |
| DELETE | `/:contactId` | Zmazať kontakt | Áno | Všetci |

### Personal Contacts (`/api/personal-contacts`)

| Metóda | Endpoint | Popis | Auth | Role |
|--------|----------|-------|------|------|
| GET | `/` | Moje osobné kontakty | Áno | Všetci |
| POST | `/` | Vytvoriť | Áno | Všetci |
| PUT | `/:contactId` | Upraviť | Áno | Všetci |
| DELETE | `/:contactId` | Zmazať | Áno | Všetci |

### Time Tracking (`/api/time-tracking`)

| Metóda | Endpoint | Popis | Auth | Role |
|--------|----------|-------|------|------|
| POST | `/start` | Spustiť tracking | Áno | Všetci |
| POST | `/:entryId/stop` | Zastaviť tracking | Áno | Všetci |
| GET | `/running` | Bežiaci entry | Áno | Všetci |
| GET | `/running-all` | Všetky bežiace | Áno | Všetci |
| GET | `/` | Zoznam entries | Áno | Všetci |
| GET | `/month/:year/:month` | Mesačné entries | Áno | Všetci |
| POST | `/month/:year/:month/generate` | Generovať XLSX | Áno | Všetci |
| GET | `/stats/monthly/:year/:month` | Mesačné štatistiky | Áno | Všetci |
| GET | `/:entryId` | Detail entry | Áno | Všetci |
| GET | `/:entryId/details` | Entry s reláciami | Áno | Všetci |
| PATCH | `/:entryId` | Upraviť entry | Áno | Všetci |
| DELETE | `/:entryId` | Zmazať entry | Áno | Všetci |

### Timesheets (`/api/timesheets`)

| Metóda | Endpoint | Popis | Auth | Role |
|--------|----------|-------|------|------|
| POST | `/upload` | Nahrať timesheet | Áno | Všetci |
| GET | `/my` | Moje timesheets | Áno | Všetci |
| GET | `/all` | Všetky timesheets | Áno | Admin |
| GET | `/:timesheetId/download` | Stiahnuť súbor | Áno | Všetci |
| DELETE | `/:timesheetId` | Zmazať | Áno | Všetci |

### Meetings (`/api/meetings`)

| Metóda | Endpoint | Popis | Auth | Role |
|--------|----------|-------|------|------|
| GET | `/` | Zoznam meetingov | Áno | Všetci |
| GET | `/:meetingId` | Detail meetingu | Áno | Všetci |
| POST | `/` | Vytvoriť meeting | Áno | Admin |
| PUT | `/:meetingId` | Upraviť meeting | Áno | Admin |
| DELETE | `/:meetingId` | Zmazať meeting | Áno | Admin |

**Query parametre pre GET /:**
- `year` - Rok
- `month` - Mesiac

### Notes (`/api/notes`)

| Metóda | Endpoint | Popis | Auth | Role |
|--------|----------|-------|------|------|
| GET | `/` | Zoznam poznámok | Áno | Všetci |
| GET | `/my-reminders` | Moje pripomienky | Áno | Všetci |
| GET | `/:noteId` | Detail poznámky | Áno | Všetci |
| POST | `/` | Vytvoriť poznámku | Áno | Všetci |
| PATCH | `/:noteId` | Upraviť poznámku | Áno | Všetci |
| DELETE | `/:noteId` | Zmazať poznámku | Áno | Všetci |
| POST | `/:noteId/mark-reminder-sent` | Označiť odoslanú | Áno | Všetci |

### CRM Emails (`/api/emails`)

| Metóda | Endpoint | Popis | Auth | Role |
|--------|----------|-------|------|------|
| GET | `/` | Zoznam emailov | Áno | Všetci |
| GET | `/search` | Vyhľadávanie v DB | Áno | Všetci |
| GET | `/search-jmap` | JMAP full-text | Áno | Všetci |
| GET | `/unread-count` | Počet neprečítaných | Áno | Všetci |
| POST | `/sync` | Synchronizovať z JMAP | Áno | Všetci |
| GET | `/thread/:threadId` | Email vlákno | Áno | Všetci |
| POST | `/thread/:threadId/read` | Označiť prečítané | Áno | Všetci |
| POST | `/contact/:contactId/read` | Označiť od kontaktu | Áno | Všetci |
| POST | `/reply` | Odpovedať na email | Áno | Všetci |

**Väčšina vyžaduje `accountId` v query parametroch.**

### Email Accounts (`/api/email-accounts`)

| Metóda | Endpoint | Popis | Auth | Role | Rate Limit |
|--------|----------|-------|------|------|------------|
| GET | `/` | Moje email účty | Áno | Všetci | - |
| POST | `/` | Vytvoriť účet | Áno | Všetci | Sensitive |
| POST | `/:id/set-primary` | Nastaviť primárny | Áno | Všetci | - |
| DELETE | `/:id` | Zmazať účet | Áno | Všetci | Sensitive |

### Audit Logs (`/api/audit-logs`)

| Metóda | Endpoint | Popis | Auth | Role |
|--------|----------|-------|------|------|
| GET | `/` | Nedávne audit logy | Áno | Všetci |

### Health Check

| Metóda | Endpoint | Popis | Auth |
|--------|----------|-------|------|
| GET | `/health` | Health check | Nie |
| GET | `/` | API info | Nie |

---

## 5. Middlewares

### Auth Middlewares (`/middlewares/auth/`)

#### authMiddleware.js

```javascript
authenticate(req, res, next)
```
- Overí JWT token z Authorization header alebo cookies
- Načíta aktuálne user data z DB
- Pridá `req.user` a `req.userId`
- Vracia 401 ak token chýba/je neplatný/expiroval

#### roleMiddleware.js

```javascript
requireRole(...allowedRoles)
```
- Overí či má user jednu z povolených rolí
- Vracia 403 ak nemá oprávnenie

```javascript
requireAdmin
```
- Skratka pre `requireRole('admin')`

```javascript
requireOwnerOrAdmin(getResourceUserId)
```
- Admin má vždy prístup
- Inak overí ownership cez callback funkciu

#### resourceAccessMiddleware.js

```javascript
checkResourceAccess(resourceType, paramName)
```
- Univerzálny middleware pre kontrolu prístupu k resources
- Admin má prístup vždy
- Member len ak je priradený k resource

```javascript
checkCompanyAccess  // = checkResourceAccess('company', 'companyId')
checkProjectAccess  // = checkResourceAccess('project', 'projectId')
checkTodoAccess     // = checkResourceAccess('todo', 'todoId')
```

```javascript
getAccessibleResourceIds(resourceType, userId)
```
- Vráti zoznam resource IDs ku ktorým má user prístup
- Použité v service vrstvách pre filtrovanie

```javascript
hasAccessToResource(resourceType, userId, resourceId)
```
- Skontroluje či user má prístup k danému resource

```javascript
canAccessResource(resourceType, userId, resourceId, userRole)
```
- Kombinácia role check + access check

**Podporované resource typy:**
| Resource Type | Junction Table | Resource ID Column |
|--------------|----------------|-------------------|
| company | company_users | companyId |
| project | project_users | projectId |
| todo | todo_users | todoId |

### Security Middlewares (`/middlewares/security/`)

#### rateLimiter.js

```javascript
loginRateLimiter
```
- Login endpoint: 5 pokusov za 15 minút

```javascript
apiRateLimiter
```
- Všeobecné API: 1000 (dev) / 100 (prod) za 15 minút

```javascript
sensitiveOperationLimiter
```
- Citlivé operácie: 50 (dev) / 10 (prod) za 15 minút

#### validateInput.js

```javascript
validateBody(schema)
```
- Validuje request body podľa Zod schémy

```javascript
validateQuery(schema)
```
- Validuje query parametre

```javascript
validateParams(schema)
```
- Validuje URL parametre

Všetky vracajú 400 s detailmi chýb:
```json
{
  "success": false,
  "error": {
    "message": "Validačná chyba",
    "statusCode": 400,
    "details": [
      { "field": "email", "message": "Neplatný email" }
    ]
  }
}
```

#### requireAccountId.js

```javascript
requireAccountId(req, res, next)
```
- Kontroluje či má user `accountId` v query
- Potrebné pre email operácie

### Global Middlewares (`/middlewares/global/`)

#### errorHandler.js

```javascript
errorHandler(err, req, res, next)
```
- Globálny error handler (posledný middleware)
- Formátuje error response
- Loguje chyby
- Vracia príslušný status code

#### validateBody.js

```javascript
validateBodyMiddleware(req, res, next)
```
- Overí či je request body valid JSON

#### notFound.js

```javascript
notFoundHandler(req, res)
```
- Handler pre 404 (neexistujúce routes)

---

## 6. Services

### auth.service.js

| Funkcia | Popis |
|---------|-------|
| `login(username, password, ip, userAgent)` | Prihlásenie s temp/permanent heslom |
| `setPassword(userId, newPassword)` | Nastavenie nového hesla |
| `logout(userId)` | Odhlásenie (audit log) |
| `getUserById(userId)` | Získať usera podľa ID |

### admin.service.js

| Funkcia | Popis |
|---------|-------|
| `getAllUsers()` | Zoznam všetkých userov |
| `getUserById(userId)` | Detail usera |
| `createUser(data)` | Vytvoriť usera s temp heslom |
| `updateUserRole(userId, role)` | Zmeniť rolu usera |
| `deleteUser(userId)` | Zmazať usera |

### company.service.js

| Funkcia | Popis |
|---------|-------|
| `getAllCompanies(searchTerm, userId, userRole)` | Zoznam firiem (filtrované pre membera) |
| `getCompanyById(companyId)` | Detail firmy |
| `createCompany(data, userId)` | Vytvoriť firmu |
| `updateCompany(companyId, data)` | Upraviť firmu |
| `deleteCompany(companyId)` | Zmazať firmu |
| `getCompanyUsers(companyId)` | Členovia tímu |
| `addUserToCompany(companyId, userId, role, addedBy)` | Pridať člena |
| `updateUserRoleInCompany(companyId, userId, role)` | Upraviť rolu |
| `removeUserFromCompany(companyId, userId)` | Odstrániť člena |

### company-reminder.service.js

| Funkcia | Popis |
|---------|-------|
| `getRemindersByCompanyId(companyId)` | Pripomienky firmy |
| `createReminder(companyId, data)` | Vytvoriť pripomienku |
| `updateReminder(reminderId, data)` | Upraviť pripomienku |
| `deleteReminder(reminderId)` | Zmazať pripomienku |
| `getReminderSummary(userId, userRole)` | Súhrn pripomienok (filtrované) |
| `getReminderCountsByCompany(userId, userRole)` | Počty podľa firmy (filtrované) |
| `getUpcomingReminders(limit, userId, userRole)` | Budúce pripomienky (filtrované) |

### project.service.js

| Funkcia | Popis |
|---------|-------|
| `getAllProjects(searchTerm, companyId, userId, userRole)` | Zoznam projektov (filtrované) |
| `getProjectById(projectId)` | Detail projektu |
| `createProject(data, userId)` | Vytvoriť projekt |
| `updateProject(projectId, data)` | Upraviť projekt |
| `deleteProject(projectId)` | Zmazať projekt |
| `getProjectUsers(projectId)` | Členovia projektu |
| `assignUserToProject(projectId, userId, role, addedBy)` | Pridať člena |
| `updateProjectUserRole(projectId, userId, role)` | Upraviť rolu |
| `removeUserFromProject(projectId, userId)` | Odstrániť člena |

### todo.service.js

| Funkcia | Popis |
|---------|-------|
| `getAllTodos(filters, userId, userRole)` | Zoznam úloh (filtrované) |
| `getTodoById(todoId)` | Detail úlohy |
| `createTodo(userId, data)` | Vytvoriť úlohu |
| `updateTodo(todoId, data)` | Upraviť úlohu |
| `deleteTodo(todoId)` | Zmazať úlohu |
| `getTodoWithRelations(todoId)` | Úloha s reláciami |
| `getTodosByProjectId(projectId)` | Úlohy projektu |
| `getTodosByCompanyId(companyId)` | Úlohy firmy |
| `getTodosByUserId(userId)` | Úlohy usera |

### contact.service.js

| Funkcia | Popis |
|---------|-------|
| `getAllContacts(accountId, search, companyId)` | Zoznam kontaktov |
| `getContactById(contactId)` | Detail kontaktu |
| `createContact(data)` | Vytvoriť kontakt |
| `updateContact(contactId, data)` | Upraviť kontakt |
| `deleteContact(contactId)` | Zmazať kontakt |
| `linkCompanyToContact(contactId, companyId)` | Pripojiť firmu |
| `unlinkCompanyFromContact(contactId)` | Odpojiť firmu |

### personal-contact.service.js

| Funkcia | Popis |
|---------|-------|
| `getPersonalContacts(userId)` | Osobné kontakty usera |
| `createPersonalContact(userId, data)` | Vytvoriť |
| `updatePersonalContact(contactId, userId, data)` | Upraviť |
| `deletePersonalContact(contactId, userId)` | Zmazať |

### time-tracking.service.js

| Funkcia | Popis |
|---------|-------|
| `startTimeEntry(userId, data)` | Spustiť tracking |
| `stopTimeEntry(entryId, endTime)` | Zastaviť tracking |
| `getRunningEntry(userId)` | Bežiaci entry usera |
| `getAllRunningEntries()` | Všetky bežiace entries |
| `getTimeEntries(filters)` | Zoznam entries |
| `getTimeEntryById(entryId)` | Detail entry |
| `getTimeEntryWithRelations(entryId)` | Entry s reláciami |
| `updateTimeEntry(entryId, data)` | Upraviť entry |
| `deleteTimeEntry(entryId)` | Zmazať entry |
| `getMonthlyEntries(userId, year, month)` | Mesačné entries |
| `getMonthlyStats(userId, year, month)` | Mesačné štatistiky |
| `generateMonthlyTimesheet(userId, year, month)` | Generovať XLSX |

### timesheet.service.js

| Funkcia | Popis |
|---------|-------|
| `uploadTimesheet(userId, file, year, month, projectId)` | Nahrať |
| `getMyTimesheets(userId)` | Moje timesheets |
| `getAllTimesheets()` | Všetky timesheets |
| `getTimesheetById(timesheetId)` | Detail |
| `deleteTimesheet(timesheetId, userId)` | Zmazať |

### meeting.service.js

| Funkcia | Popis |
|---------|-------|
| `getMeetingsByMonth(year, month)` | Meetingy v mesiaci |
| `getMeetingById(meetingId)` | Detail meetingu |
| `createMeeting(data, userId)` | Vytvoriť |
| `updateMeeting(meetingId, data)` | Upraviť |
| `deleteMeeting(meetingId)` | Zmazať |

### note.service.js

| Funkcia | Popis |
|---------|-------|
| `getAllNotes(filters)` | Zoznam poznámok |
| `getNoteById(noteId)` | Detail poznámky |
| `createNote(data, userId)` | Vytvoriť |
| `updateNote(noteId, data)` | Upraviť |
| `deleteNote(noteId)` | Zmazať |
| `getMyReminders(userId)` | Moje pripomienky |
| `markReminderSent(noteId)` | Označiť odoslanú |

### crm-email.service.js

| Funkcia | Popis |
|---------|-------|
| `getEmails(accountId, filters)` | Zoznam emailov |
| `searchEmails(accountId, query)` | Vyhľadávanie v DB |
| `searchEmailsJmap(accountId, query)` | JMAP full-text |
| `getUnreadCount(accountId)` | Počet neprečítaných |
| `syncEmails(accountId)` | Synchronizovať z JMAP |
| `getEmailThread(accountId, threadId)` | Email vlákno |
| `markThreadAsRead(accountId, threadId)` | Označiť prečítané |
| `markContactEmailsAsRead(accountId, contactId)` | Označiť od kontaktu |
| `replyToEmail(accountId, userId, data)` | Odpovedať |

### email-account.service.js

| Funkcia | Popis |
|---------|-------|
| `getUserEmailAccounts(userId)` | Email účty usera |
| `createEmailAccount(userId, data)` | Vytvoriť účet |
| `setPrimaryAccount(userId, accountId)` | Nastaviť primárny |
| `deleteEmailAccount(userId, accountId)` | Zmazať účet |

### audit.service.js

| Funkcia | Popis |
|---------|-------|
| `logAction(data)` | Zaznamenať akciu |
| `logLogin(userId, success, ip, userAgent, error)` | Login log |
| `logLogout(userId, ip, userAgent)` | Logout log |
| `logPasswordChange(userId, ip, userAgent)` | Password change log |
| `logTodoCreated(userId, todoId, title, ip, userAgent)` | Todo created log |
| `logTodoDeleted(userId, todoId, title, ip, userAgent)` | Todo deleted log |
| `logTodoCompleted(userId, todoId, title, ip, userAgent)` | Todo completed log |
| `getRecentLogs(limit)` | Nedávne logy |

### status.service.js

| Funkcia | Popis |
|---------|-------|
| `getServerStatus()` | CPU, RAM, Disk, Network, Uptime |

### JMAP Services (`/services/jmap/`)

| Súbor | Funkcie |
|-------|---------|
| `client.js` | JMAP HTTP klient |
| `config.js` | JMAP konfigurácia |
| `discovery.js` | JMAP discovery (.well-known) |
| `operations.js` | Email/set, Mailbox/get |
| `search.js` | Email/query s full-text |
| `sync.js` | Synchronizácia emailov |

---

## 7. Controllers

Každý controller:
1. Extrahuje dáta z `req` (params, query, body, user)
2. Volá príslušný service
3. Formátuje a vracia response
4. Zachytáva a propaguje chyby cez `next(error)`

### Príklad controller funkcie:

```javascript
export const getAllCompanies = async (req, res, next) => {
  try {
    const { search } = req.query;
    const userId = req.user?.id;
    const userRole = req.user?.role;

    const companies = await companyService.getAllCompanies(search, userId, userRole);

    res.status(200).json({
      success: true,
      count: companies.length,
      data: companies,
    });
  } catch (error) {
    next(error);
  }
};
```

---

## 8. Utility funkcie

### errors.js

```javascript
class AppError extends Error {
  constructor(message, statusCode, details = null)
}

class ValidationError extends AppError {
  constructor(message, details = [])  // 400
}

class BadRequestError extends AppError {
  constructor(message)  // 400
}

class AuthenticationError extends AppError {
  constructor(message)  // 401
}

class ForbiddenError extends AppError {
  constructor(message)  // 403
}

class NotFoundError extends AppError {
  constructor(message)  // 404
}

class ConflictError extends AppError {
  constructor(message)  // 409
}

class RateLimitError extends AppError {
  constructor(message)  // 429
}

formatErrorResponse(error, includeStack = false)
```

### jwt.js

```javascript
generateAccessToken(payload)      // Expires: 1h
generateRefreshToken(payload)     // Expires: 7d
verifyAccessToken(token)          // Verify + decode
verifyRefreshToken(token)         // Verify + decode
generateTokenPair(user)           // Both tokens
```

### password.js

```javascript
hashPassword(password)            // bcrypt, 12 rounds
comparePassword(password, hash)   // Verify password
generateTempPassword(length = 12) // Random temp password
generateVerificationToken()       // UUID token
encryptPassword(text)             // AES-256-GCM encrypt
decryptPassword(encryptedText)    // AES-256-GCM decrypt
```

### logger.js

```javascript
logger.info(message, ...args)     // Blue
logger.success(message, ...args)  // Green
logger.warn(message, ...args)     // Yellow
logger.error(message, error)      // Red
logger.debug(message, ...args)    // Cyan (dev only)
logger.audit(message, ...args)    // Magenta
```

---

## 9. Validačné schémy

### auth.validators.js

```javascript
loginSchema = z.object({
  username: z.string().min(3).max(50),
  password: z.string().min(1)
})

setPasswordSchema = z.object({
  newPassword: z.string().min(8).max(100).regex(/[A-Z]/).regex(/[a-z]/).regex(/[0-9]/),
  confirmPassword: z.string()
}).refine(data => data.newPassword === data.confirmPassword)

createUserSchema = z.object({
  username: z.string().min(3).max(50),
  email: z.string().email().optional(),
  firstName: z.string().min(1).max(100),
  lastName: z.string().min(1).max(100),
  role: z.enum(['admin', 'member']).default('member')
})

changeRoleSchema = z.object({
  role: z.enum(['admin', 'member'])
})
```

### crm.validators.js

```javascript
// Company
createCompanySchema = z.object({
  name: z.string().min(1).max(255),
  description: z.string().optional(),
  address: z.string().optional(),
  city: z.string().max(100).optional(),
  country: z.string().max(100).optional(),
  phone: z.string().max(50).optional(),
  email: z.string().email().optional(),
  website: z.string().url().optional()
})

// Project
createProjectSchema = z.object({
  name: z.string().min(1).max(255),
  description: z.string().optional(),
  companyId: z.string().uuid().optional(),
  status: z.enum(['active', 'completed', 'on_hold', 'cancelled']).default('active'),
  startDate: z.string().optional(),
  endDate: z.string().optional()
})

// Todo
createTodoSchema = z.object({
  title: z.string().min(1).max(255),
  description: z.string().optional(),
  projectId: z.string().uuid().optional(),
  companyId: z.string().uuid().optional(),
  assignedUserIds: z.array(z.string().uuid()).optional(),
  status: z.enum(['pending', 'in_progress', 'completed', 'cancelled']).default('pending'),
  priority: z.enum(['low', 'medium', 'high', 'urgent']).default('medium'),
  dueDate: z.string().optional()
})

// Time Entry
startTimeEntrySchema = z.object({
  projectId: z.string().uuid().optional(),
  todoId: z.string().uuid().optional(),
  companyId: z.string().uuid().optional(),
  description: z.string().optional()
})

// Company Reminder
createCompanyReminderSchema = z.object({
  description: z.string().min(1),
  dueDate: z.string(),
  isChecked: z.boolean().default(false)
})
```

---

## 10. Konfigurácia

### .env

```bash
# Database
DB_HOST=localhost
DB_PORT=5432
DB_USER=admin
DB_PASSWORD=heslo123
DB_NAME=crm

# JWT
JWT_SECRET=tajny_kluc_pre_access_token
JWT_REFRESH_SECRET=tajny_kluc_pre_refresh_token
JWT_EXPIRES_IN=1h
JWT_REFRESH_EXPIRES_IN=7d

# Encryption
ENCRYPTION_SALT=salt_pre_enkrypciu_emailovych_hesiel
BCRYPT_ROUNDS=12

# CORS
CORS_ORIGIN=http://localhost:5173

# Notifikácie
NOTIFICATION_TIME=07:00                    # Čas odosielania (HH:mm)
NOTIFICATION_SENDER_EMAIL=riso@slovensko.ai # Email odosielateľa (musí byť v DB)
NOTIFICATION_TEST_MODE=false               # true = cron beží každú minútu

# Rate Limiting
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_LOGIN_MAX=5
RATE_LIMIT_MAX_REQUESTS=100

# Server
NODE_ENV=development
PORT=5000
```

### package.json scripts

```bash
npm run dev          # nodemon src/index.js
npm run start        # node src/index.js
npm run test         # Jest testy
npm run db:generate  # drizzle-kit generate
npm run db:push      # drizzle-kit push
npm run db:studio    # drizzle-kit studio
npm run db:seed      # Admin seed
npm run db:seed:testuser  # Test user seed
```

### drizzle.config.js

```javascript
export default {
  schema: './src/db/schema.js',
  out: './drizzle',
  dialect: 'postgresql',
  dbCredentials: {
    host: process.env.DB_HOST,
    port: process.env.DB_PORT,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME,
  },
};
```

---

## 11. Autentizácia a bezpečnosť

### Autentizačný flow

1. **Login** - User zadá username + temp heslo
2. **Validácia** - Kontrola temp/permanent hesla
3. **Token generovanie** - Access token (1h) + Refresh token (7d)
4. **Cookies** - httpOnly, secure, sameSite=strict
5. **Password change** - Povinný v prvej prihláške
6. **Email setup** - Nastavenie email accounts

### Security features

| Feature | Implementácia |
|---------|---------------|
| JWT tokeny | httpOnly cookies |
| Rate limiting | express-rate-limit |
| CORS | cors middleware |
| XSS protection | xss-clean |
| Security headers | Helmet.js |
| Password hashing | bcrypt (12 rounds) |
| Email password encryption | AES-256-GCM |
| Audit logging | Všetky akcie |
| RBAC | admin/member role |
| Resource access control | Junction tables |

### Role-based access control

| Akcia | Admin | Member |
|-------|-------|--------|
| Vytvoriť/upraviť/zmazať firmu | ✓ | ✗ |
| Vytvoriť/upraviť/zmazať projekt | ✓ | ✗ |
| Vytvoriť/upraviť/zmazať todo | ✓ | ✗ |
| Toggle todo (splnené) | ✓ | ✓ (ak priradený) |
| Zobraziť firmu | ✓ | ✓ (ak priradený) |
| Zobraziť projekt | ✓ | ✓ (ak priradený) |
| Zobraziť todo | ✓ | ✓ (ak priradený) |
| Spravovať tím | ✓ | ✗ |
| Server status | ✓ | ✗ |
| Vytvoriť/upraviť/zmazať meeting | ✓ | ✗ |
| Time tracking | ✓ | ✓ |
| Kontakty | ✓ | ✓ |
| Emaily | ✓ | ✓ |

---

## 12. Cron Jobs a Notifikácie

### Prehľad

Systém obsahuje automatické cron joby pre odosielanie emailových notifikácií. Cron jobs sa spúšťajú automaticky pri štarte servera.

### Štruktúra

```
src/cron/
├── index.js                    # Hlavný entry point - spúšťa všetky cron jobs
└── calendar/
    ├── index.js                # Kalendárny cron scheduler
    ├── email-template.js       # HTML email šablóna pre notifikácie
    └── event-notifier.js       # Logika pre odosielanie notifikácií
```

### Kalendárne notifikácie

#### Ako to funguje

1. **Cron beží každý deň** o čase nastavenom v `NOTIFICATION_TIME` (default: 07:00)
2. **Vyhľadá eventy** ktoré začínajú **zajtra** (00:00 - 23:59)
3. **Získa priradených používateľov** cez `eventUsers` junction tabuľku
4. **Zistí primárny email** každého používateľa z `userEmailAccounts`
5. **Odošle HTML email** cez JMAP z účtu nastaveného v `NOTIFICATION_SENDER_EMAIL`

#### Kedy sa notifikácie posielajú

| Event začína | Notifikácia |
|--------------|-------------|
| Včera / dnes (minulosť) | ❌ Nie |
| Zajtra | ✅ Áno |
| Pozajtra a neskôr | ❌ Nie |

#### Konfigurácia

```bash
# .env
NOTIFICATION_TIME=07:00                    # Čas odosielania (formát HH:mm)
NOTIFICATION_SENDER_EMAIL=riso@slovensko.ai # Email účet v databáze
NOTIFICATION_TEST_MODE=false               # true = cron beží každú minútu
```

**Dôležité:** `NOTIFICATION_SENDER_EMAIL` musí byť email účet uložený v tabuľke `email_accounts` s platným zašifrovaným heslom.

#### Testovací mód

Pre testovanie nastavte v `.env`:

```bash
NOTIFICATION_TEST_MODE=true
```

Cron bude bežať **každú minútu** namiesto raz denne. Po dokončení testovania nastavte späť na `false`.

#### Manuálne spustenie

Admin môže manuálne spustiť notifikácie cez API:

```bash
POST /api/admin/trigger-notifications
Authorization: Bearer <admin_token>
```

**Response:**
```json
{
  "success": true,
  "data": {
    "sent": 2,
    "failed": 0,
    "skipped": 1
  },
  "message": "Notifikácie odoslané: 2, neúspešné: 0, preskočené: 1"
}
```

- `sent` - Počet úspešne odoslaných emailov
- `failed` - Počet neúspešných odoslaní
- `skipped` - Počet preskočených (napr. používateľ nemá nastavený email)

### Email šablóna

Notifikačný email obsahuje:

- **Header** s logom CRM
- **Pozdrav** s menom používateľa
- **Karta eventu** s:
  - Typ (Stretnutie / Udalosť) - farebne odlíšené
  - Názov eventu
  - Popis (ak existuje)
  - Dátum a čas
- **Footer** s informáciou o automatickom generovaní

Email je responzívny a zobrazuje sa správne v rôznych email klientoch.

### Logy

Pri behu cron jobu sa zobrazujú logy:

```
[INFO] === Inicializujem cron jobs ===
[INFO] Nastavujem cron pre kalendárne notifikácie: 00 07 * * * (07:00 každý deň)
[SUCCESS] Kalendárny notifikačný cron naplánovaný: 07:00 každý deň (Europe/Bratislava)
[INFO] === Všetky cron jobs inicializované ===

# Pri spustení jobu:
[INFO] Cron job spustený - kontrolujem zajtrajšie udalosti
[INFO] === Spúšťam kontrolu zajtrajších udalostí ===
[INFO] Hľadám udalosti od 2025-12-16T00:00:00.000Z do 2025-12-16T23:59:59.999Z
[INFO] Nájdených 2 priradení udalostí na zajtra
[INFO] Unikátnych notifikácií na odoslanie: 2
[INFO] Odosielam notifikáciu pre admin (riso@slovensko.ai) - udalosť: Team meeting
[SUCCESS] Email úspešne odoslaný na riso@slovensko.ai
[INFO] === Hotovo: odoslaných 2, neúspešných 0, preskočených 0 ===
```

### Rozšírenie

Pre pridanie nových cron jobs:

1. Vytvorte nový folder v `src/cron/` (napr. `reminders/`)
2. Implementujte logiku podobne ako v `calendar/`
3. Exportujte start funkciu
4. Importujte a zavolajte v `src/cron/index.js`

```javascript
// src/cron/index.js
import { startCalendarNotificationCron } from './calendar/index.js';
import { startRemindersCron } from './reminders/index.js'; // nový

export const startAllCronJobs = () => {
  logger.info('=== Inicializujem cron jobs ===');

  startCalendarNotificationCron();
  startRemindersCron(); // nový

  logger.info('=== Všetky cron jobs inicializované ===');
};
```

---

## Záver

Táto dokumentácia poskytuje kompletný prehľad CRM Server backendu. Pre ďalšie informácie kontaktujte administrátora projektu.
