Files
crm-server/DOCUMENTATION.md
richardtekula 232b8608e5 docs: Add cron jobs and notifications documentation
- Add section 12: Cron Jobs a Notifikácie
- Update project structure with cron/ folder
- Add admin trigger-notifications endpoint to API docs
- Add notification env variables to config section
- Include examples, logs, and extension guide

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-15 16:04:54 +01:00

1551 lines
48 KiB
Markdown

# 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.