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

48 KiB

CRM Server - Kompletná Dokumentácia

Obsah

  1. Prehľad projektu
  2. Štruktúra projektu
  3. Databázová schéma
  4. API Endpoints
  5. Middlewares
  6. Services
  7. Controllers
  8. Utility funkcie
  9. Validačné schémy
  10. Konfigurácia
  11. Autentizácia a bezpečnosť
  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

-- 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:

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

Error:

{
  "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

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

requireRole(...allowedRoles)
  • Overí či má user jednu z povolených rolí
  • Vracia 403 ak nemá oprávnenie
requireAdmin
  • Skratka pre requireRole('admin')
requireOwnerOrAdmin(getResourceUserId)
  • Admin má vždy prístup
  • Inak overí ownership cez callback funkciu

resourceAccessMiddleware.js

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
checkCompanyAccess  // = checkResourceAccess('company', 'companyId')
checkProjectAccess  // = checkResourceAccess('project', 'projectId')
checkTodoAccess     // = checkResourceAccess('todo', 'todoId')
getAccessibleResourceIds(resourceType, userId)
  • Vráti zoznam resource IDs ku ktorým má user prístup
  • Použité v service vrstvách pre filtrovanie
hasAccessToResource(resourceType, userId, resourceId)
  • Skontroluje či user má prístup k danému resource
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

loginRateLimiter
  • Login endpoint: 5 pokusov za 15 minút
apiRateLimiter
  • Všeobecné API: 1000 (dev) / 100 (prod) za 15 minút
sensitiveOperationLimiter
  • Citlivé operácie: 50 (dev) / 10 (prod) za 15 minút

validateInput.js

validateBody(schema)
  • Validuje request body podľa Zod schémy
validateQuery(schema)
  • Validuje query parametre
validateParams(schema)
  • Validuje URL parametre

Všetky vracajú 400 s detailmi chýb:

{
  "success": false,
  "error": {
    "message": "Validačná chyba",
    "statusCode": 400,
    "details": [
      { "field": "email", "message": "Neplatný email" }
    ]
  }
}

requireAccountId.js

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

Global Middlewares (/middlewares/global/)

errorHandler.js

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

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

notFound.js

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:

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

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

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

password.js

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

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

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

// 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

# 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

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

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

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

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:

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

Response:

{
  "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
// 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.