- 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>
1551 lines
48 KiB
Markdown
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.
|