docs: Add README with full backend documentation and smoke test checklist
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
480
README.md
Normal file
480
README.md
Normal file
@@ -0,0 +1,480 @@
|
||||
# CRM Server
|
||||
|
||||
Node.js/Express backend for a full-featured CRM system with company/project management, time tracking, email integration (JMAP), internal messaging, calendar events, push notifications, and an AI courses module.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Runtime:** Node.js with ES modules
|
||||
- **Framework:** Express.js
|
||||
- **Database:** PostgreSQL via Drizzle ORM
|
||||
- **Auth:** JWT (access + refresh tokens), bcrypt passwords
|
||||
- **Email:** JMAP protocol
|
||||
- **Files:** Multer (disk + memory storage)
|
||||
- **Validation:** Zod schemas
|
||||
- **Notifications:** Web Push (VAPID)
|
||||
- **Exports:** ExcelJS for XLSX timesheets
|
||||
- **Cron:** node-cron for scheduled tasks
|
||||
- **Logging:** Winston
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
config/ # Database, env, upload configs
|
||||
controllers/ # HTTP request handlers
|
||||
cron/ # Scheduled jobs
|
||||
db/ # Schema definitions, migrations, seeds
|
||||
middlewares/ # Auth, security, validation middleware
|
||||
routes/ # Express route definitions
|
||||
services/ # Business logic layer
|
||||
utils/ # Shared utilities
|
||||
validators/ # Zod validation schemas
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Config (`src/config/`)
|
||||
|
||||
| File | Exports |
|
||||
|------|---------|
|
||||
| `database.js` | `db` (Drizzle instance), `pool` (pg connection pool) |
|
||||
| `env.js` | `env` object — validated env vars (DATABASE_URL, JWT_SECRET, JWT_REFRESH_SECRET, JMAP_URL, ENCRYPTION_KEY, VAPID keys, PORT) |
|
||||
| `upload.js` | `createUpload()` factory for multer configs, `ALLOWED_FILE_TYPES` constant. Pre-configured exports: `upload`, `timesheetUpload`, `companyDocumentUpload`, `projectDocumentUpload`, `serviceDocumentUpload`, `aiKurzyUpload` |
|
||||
|
||||
---
|
||||
|
||||
## Middlewares (`src/middlewares/`)
|
||||
|
||||
### Auth
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `authenticate` | Verify JWT from cookie or Authorization header, attach user to `req` |
|
||||
| `optionalAuthenticate` | Same but doesn't fail if no token present |
|
||||
| `requireAdmin` | Require admin role |
|
||||
| `requireMember` | Require member role (or admin) |
|
||||
| `checkResourceAccess(type)` | Check user has access to company/project/todo |
|
||||
| `getAccessibleResourceIds(type, userId)` | Get IDs of resources a user can access |
|
||||
|
||||
### Security
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `apiLimiter` | Rate limit: 100 req/15min |
|
||||
| `authLimiter` | Rate limit: 5 req/15min (login) |
|
||||
| `requireAccountId` | Validate `accountId` query param |
|
||||
| `sanitizeInput(input)` | Strip XSS from user input |
|
||||
|
||||
### Global
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `validateBody(schema)` | Validate `req.body` against Zod schema |
|
||||
| `errorHandler` | Global error handler, formats errors consistently |
|
||||
| `notFound` | 404 handler for unknown routes |
|
||||
|
||||
---
|
||||
|
||||
## Utils (`src/utils/`)
|
||||
|
||||
| File | Exports | Description |
|
||||
|------|---------|-------------|
|
||||
| `jwt.js` | `generateAccessToken`, `generateRefreshToken`, `generateTokenPair`, `verifyAccessToken`, `verifyRefreshToken` | JWT token operations (1h access, 7d refresh) |
|
||||
| `password.js` | `hashPassword`, `comparePassword`, `generateTempPassword` | bcrypt hashing + temp password generation |
|
||||
| `logger.js` | `logger` | Winston logger (file + console) |
|
||||
| `errors.js` | `AppError`, `NotFoundError`, `ValidationError`, `AuthenticationError`, `ForbiddenError`, `ConflictError` | Custom error classes with HTTP status codes |
|
||||
| `emailAccountHelper.js` | `encryptPassword`, `decryptPassword` | AES-256 encryption for stored email passwords |
|
||||
|
||||
---
|
||||
|
||||
## Validators (`src/validators/`)
|
||||
|
||||
| File | Schemas |
|
||||
|------|---------|
|
||||
| `auth.validators.js` | `loginSchema`, `setPasswordSchema`, `linkEmailSchema`, `createUserSchema` |
|
||||
| `crm.validators.js` | `createCompanySchema`, `updateCompanySchema`, `createProjectSchema`, `updateProjectSchema`, `createTodoSchema`, `updateTodoSchema`, `createNoteSchema`, `updateNoteSchema`, `createReminderSchema`, `updateReminderSchema`, `startTimeEntrySchema`, `stopTimeEntrySchema`, `updateTimeEntrySchema`, `createContactSchema` |
|
||||
| `email-account.validators.js` | `createEmailAccountSchema`, `updatePasswordSchema`, `toggleStatusSchema`, `signatureSchema`, `toggleSchema` |
|
||||
| `service.validators.js` | `serviceIdSchema`, `folderIdSchema`, `folderDocumentIdSchema`, `createFolderSchema`, `updateFolderSchema` |
|
||||
| `ai-kurzy.validators.js` | `createKurzSchema`, `updateKurzSchema`, `createUcastnikSchema`, `updateUcastnikSchema`, `createRegistraciaSchema`, `updateRegistraciaSchema`, `registracieQuerySchema`, `updateFieldSchema`, `prilohaIdSchema` |
|
||||
|
||||
---
|
||||
|
||||
## Services (`src/services/`)
|
||||
|
||||
### auth.service.js
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `loginWithTempPassword(username, password, ip, ua)` | Authenticate with temp or permanent password, return JWT pair |
|
||||
| `setNewPassword(userId, newPassword, auditCtx)` | Set permanent password, mark as changed |
|
||||
| `linkEmail(userId, email, password, auditCtx)` | Link email account via JMAP validation |
|
||||
| `skipEmailSetup(userId)` | Skip email onboarding step |
|
||||
| `logout(auditCtx)` | Log logout to audit trail |
|
||||
| `getUserById(userId)` | Get user with email accounts |
|
||||
|
||||
### admin.service.js
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `checkUsernameExists(username)` | Check if username is taken |
|
||||
| `createUser(username, firstName, lastName, role, email, password, auditCtx)` | Create user with auto-generated temp password |
|
||||
| `getAllUsers()` | List all users (no passwords) |
|
||||
| `getUserById(userId)` | Get user with email accounts |
|
||||
| `changeUserRole(userId, newRole, auditCtx)` | Change role (admin/member) |
|
||||
| `updateUser(userId, data)` | Update user name fields |
|
||||
| `resetUserPassword(userId)` | Generate new temp password |
|
||||
| `deleteUser(userId, auditCtx)` | Delete user and orphaned email accounts |
|
||||
|
||||
### company.service.js
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `getAllCompanies(search, userId, role)` | Get companies (members see only assigned) |
|
||||
| `getCompanyById(id)` | Get company with creator info |
|
||||
| `createCompany(userId, data, auditCtx)` | Create company, auto-assign creator |
|
||||
| `updateCompany(id, data, auditCtx)` | Update company |
|
||||
| `deleteCompany(id, auditCtx)` | Delete company |
|
||||
| `getCompanyWithRelations(id)` | Get with projects, todos, notes, reminders |
|
||||
| `getCompanyUsers(id)` | Get assigned users |
|
||||
| `assignUserToCompany(companyId, userId, addedBy, role, auditCtx)` | Add user to team |
|
||||
| `removeUserFromCompany(companyId, userId, auditCtx)` | Remove from team |
|
||||
| `updateUserRoleOnCompany(companyId, userId, role)` | Update team role |
|
||||
|
||||
### company-email.service.js
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `getCompanyEmailThreads(companyId, userId)` | Get email threads grouped by contact |
|
||||
| `getCompanyUnreadCounts(userId)` | Unread counts per company |
|
||||
|
||||
### company-document.service.js
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `getDocumentsByCompanyId(id)` | List company documents |
|
||||
| `uploadDocument({ companyId, userId, file, description })` | Upload document |
|
||||
| `getDocumentForDownload(companyId, docId)` | Get file path for download |
|
||||
| `deleteDocument(companyId, docId)` | Delete document and file |
|
||||
|
||||
### company-reminder.service.js
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `getRemindersByCompanyId(id)` | Get reminders for company |
|
||||
| `createReminder(companyId, data, auditCtx)` | Create reminder |
|
||||
| `updateReminder(companyId, id, data, auditCtx)` | Update reminder |
|
||||
| `deleteReminder(companyId, id, auditCtx)` | Delete reminder |
|
||||
| `getReminderSummary(userId, role)` | Checked/unchecked stats |
|
||||
| `getReminderCountsByCompany(userId, role)` | Counts grouped by company |
|
||||
| `getUpcomingReminders(userId, role)` | Upcoming with due dates |
|
||||
|
||||
### project.service.js
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `getAllProjects(search, companyId, userId, role)` | List with filters (members see assigned only) |
|
||||
| `getProjectById(id)` | Get with creator info |
|
||||
| `createProject(userId, data, auditCtx)` | Create and auto-assign creator |
|
||||
| `updateProject(id, data, auditCtx)` | Update project |
|
||||
| `deleteProject(id, auditCtx)` | Delete project |
|
||||
| `getProjectWithRelations(id)` | Get with todos, notes, timesheets, team |
|
||||
| `getProjectUsers(id)` | Get team members |
|
||||
| `assignUserToProject(projectId, userId, addedBy, role, auditCtx)` | Add to team |
|
||||
| `removeUserFromProject(projectId, userId, auditCtx)` | Remove from team |
|
||||
| `updateUserRoleOnProject(projectId, userId, role)` | Update role |
|
||||
|
||||
### project-document.service.js
|
||||
Same pattern as company-document: `getDocumentsByProjectId`, `uploadDocument`, `getDocumentForDownload`, `deleteDocument`.
|
||||
|
||||
### todo.service.js
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `getAllTodos(filters, userId, role)` | Get with filters (project, company, status, priority, assignedTo) |
|
||||
| `getTodoById(id)` | Get todo |
|
||||
| `getTodoWithRelations(id)` | Get with project, company, users, notes |
|
||||
| `createTodo(userId, data, auditCtx)` | Create and assign users (triggers push) |
|
||||
| `updateTodo(id, data, userId, auditCtx)` | Update and manage assignments |
|
||||
| `deleteTodo(id, auditCtx)` | Delete todo |
|
||||
| `getOverdueCount(userId, role)` | Count overdue |
|
||||
| `getCompletedByMeCount(userId)` | Count completed by user |
|
||||
| `getTodoCounts(userId, role)` | Combined counts for sidebar badges |
|
||||
| `markCompletedAsNotified(userId)` | Mark completed as notified |
|
||||
|
||||
### todo-notification.service.js
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `notifyNewTodoAssignment(userIds, title, todoId, excludeId)` | Push notify assigned users |
|
||||
| `notifyUpdatedTodoAssignment(userIds, title, todoId, excludeId)` | Push notify newly assigned users |
|
||||
|
||||
### note.service.js
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `getAllNotes(filters)` | Get with filters (company, project, todo, contact) |
|
||||
| `getNoteById(id)` | Get note |
|
||||
| `getNotesByCompanyId(id)` | Notes for company |
|
||||
| `getNotesByProjectId(id)` | Notes for project |
|
||||
| `createNote(userId, data, auditCtx)` | Create note |
|
||||
| `updateNote(id, data, auditCtx)` | Update note |
|
||||
| `deleteNote(id, auditCtx)` | Delete note |
|
||||
| `getUpcomingRemindersForUser(userId)` | Upcoming note reminders |
|
||||
| `markReminderAsSent(id)` | Mark reminder sent |
|
||||
| `searchNotes(term)` | Search with company/project info |
|
||||
|
||||
### time-tracking.service.js
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `startTimeEntry(userId, data, auditCtx)` | Start timer (auto-stops any running entry) |
|
||||
| `stopTimeEntry(entryId, userId, data, auditCtx)` | Stop and calculate duration |
|
||||
| `pauseTimeEntry(entryId, userId, auditCtx)` | Pause running entry |
|
||||
| `resumeTimeEntry(entryId, userId, auditCtx)` | Resume paused entry |
|
||||
| `getRunningTimeEntry(userId)` | Get active entry for user |
|
||||
| `getAllRunningTimeEntries()` | All active entries (admin) |
|
||||
| `getAllTimeEntries(userId, filters)` | Filtered time entries |
|
||||
| `getMonthlyTimeEntries(userId, year, month)` | Entries for month |
|
||||
| `generateMonthlyTimesheet(userId, year, month)` | Generate XLSX timesheet |
|
||||
| `generateCompanyTimesheet(userId, year, month, companyId)` | XLSX filtered by company |
|
||||
| `getTimeEntryById(id)` | Get single entry |
|
||||
| `getTimeEntryWithRelations(id)` | Entry with project/todo/company/user |
|
||||
| `updateTimeEntry(id, userCtx, data, auditCtx)` | Update entry |
|
||||
| `deleteTimeEntry(id, userCtx, auditCtx)` | Delete entry |
|
||||
| `getMonthlyStats(userId, year, month)` | Monthly stats |
|
||||
|
||||
### timesheet.service.js
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `uploadTimesheet({ userId, year, month, file, auditCtx })` | Upload timesheet file |
|
||||
| `getTimesheetsForUser(userId, filters)` | User's timesheets |
|
||||
| `getAllTimesheets(filters)` | All timesheets (admin) |
|
||||
| `getDownloadInfo(id, userCtx)` | File path with access check |
|
||||
| `deleteTimesheet(id, userCtx, auditCtx)` | Delete timesheet and file |
|
||||
|
||||
### contact.service.js
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `getContactsForEmailAccount(accountId)` | Get contacts for email account |
|
||||
| `addContact(accountId, jmapConfig, email, name, notes, userId)` | Add contact and sync JMAP emails |
|
||||
| `removeContact(contactId, accountId)` | Remove contact |
|
||||
| `updateContact(contactId, accountId, data)` | Update contact |
|
||||
| `linkCompanyToContact(contactId, accountId, companyId, auditCtx)` | Link company |
|
||||
| `unlinkCompanyFromContact(contactId, accountId)` | Unlink company |
|
||||
| `createCompanyFromContact(contactId, accountId, userId, data, auditCtx)` | Create company and link |
|
||||
|
||||
### personal-contact.service.js
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `listPersonalContacts(userId)` | List user's personal contacts |
|
||||
| `createPersonalContact(userId, data)` | Create contact |
|
||||
| `updatePersonalContact(id, userId, data)` | Update contact |
|
||||
| `deletePersonalContact(id, userId)` | Delete contact |
|
||||
| `getContactsByCompanyId(companyId)` | Contacts by company |
|
||||
|
||||
### crm-email.service.js
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `getEmailsForAccount(accountId)` | All emails for account |
|
||||
| `getEmailThread(accountId, threadId)` | Thread emails |
|
||||
| `searchEmails(accountId, term)` | Search by subject/from/to/body |
|
||||
| `getUnreadCountSummary(accountIds)` | Unread counts |
|
||||
| `markContactEmailsAsRead(contactId, accountId)` | Mark all read for contact |
|
||||
| `getContactEmailsWithUnread(accountId, contactId)` | Contact emails with status |
|
||||
| `markThreadAsRead(accountId, threadId)` | Mark thread read |
|
||||
|
||||
### email-account.service.js
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `getUserEmailAccounts(userId)` | Get accounts via many-to-many |
|
||||
| `getEmailAccountById(accountId, userId)` | Get with access check |
|
||||
| `getEmailAccountWithCredentials(accountId, userId)` | Get with decrypted password |
|
||||
| `getPrimaryEmailAccount(userId)` | Get primary account |
|
||||
| `createEmailAccount(userId, email, password)` | Create/link via JMAP discovery |
|
||||
| `updateEmailAccountPassword(accountId, userId, password)` | Update password |
|
||||
| `toggleEmailAccountStatus(accountId, userId, isActive)` | Enable/disable |
|
||||
| `setPrimaryEmailAccount(accountId, userId)` | Set as primary |
|
||||
| `removeUserFromEmailAccount(accountId, userId)` | Remove user access |
|
||||
|
||||
### email-signature.service.js
|
||||
`getSignature`, `upsertSignature`, `toggleSignature`, `formatSignatureText`, `deleteSignature` — manage per-user email signatures.
|
||||
|
||||
### message.service.js
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `getConversations(userId)` | All 1-on-1 conversations with unread counts |
|
||||
| `getConversation(userId, partnerId)` | Messages with user, mark as read |
|
||||
| `sendMessage(senderId, receiverId, content)` | Send direct message |
|
||||
| `deleteConversation(userId, partnerId)` | Soft delete conversation |
|
||||
| `getUnreadCount(userId)` | Total unread count |
|
||||
| `getChatUsers(userId)` | All users except self |
|
||||
|
||||
### group.service.js
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `createGroup(name, createdById, memberIds)` | Create group chat |
|
||||
| `getUserGroups(userId)` | Groups with unread counts |
|
||||
| `getGroupDetails(groupId, userId)` | Group with members |
|
||||
| `getGroupMessages(groupId, userId)` | Messages, update last read |
|
||||
| `sendGroupMessage(groupId, senderId, content)` | Send to group |
|
||||
| `addGroupMember(groupId, userId, requesterId)` | Add member |
|
||||
| `removeGroupMember(groupId, userId, requesterId)` | Remove member |
|
||||
| `updateGroupName(groupId, name, userId)` | Rename group |
|
||||
| `deleteGroup(groupId, userId)` | Delete (creator only) |
|
||||
|
||||
### push.service.js
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `getVapidPublicKey()` | Get VAPID public key |
|
||||
| `saveSubscription(userId, sub)` | Save push subscription |
|
||||
| `removeSubscription(userId, endpoint)` | Remove subscription |
|
||||
| `removeAllSubscriptions(userId)` | Remove all for user |
|
||||
| `hasActiveSubscription(userId)` | Check if subscribed |
|
||||
| `sendPushNotification(userId, payload)` | Send push to user |
|
||||
|
||||
### event.service.js
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `getCalendarData(year, month, userId, isAdmin)` | Calendar events + todos |
|
||||
| `getEventById(eventId, userId, isAdmin)` | Event with access check |
|
||||
| `createEvent(userId, data)` | Create and assign users |
|
||||
| `updateEvent(eventId, data)` | Update and reassign users |
|
||||
| `deleteEvent(eventId)` | Delete event |
|
||||
|
||||
### audit.service.js
|
||||
Provides `logLogin`, `logLogout`, `logPasswordChange`, `logEmailLink`, `logUserCreation`, `logRoleChange`, `logUserDeleted`, `logCompanyCreated`, `logCompanyUpdated`, `logCompanyDeleted`, `logCompanyUserAssigned`, `logCompanyUserRemoved`, `logProjectCreated`, `logProjectUpdated`, `logProjectDeleted`, `logProjectUserAssigned`, `logProjectUserRemoved`, and more. Each function records action, resource, details, IP, and user agent to the `auditLogs` table.
|
||||
|
||||
### service.service.js / service-folder.service.js / service-document.service.js
|
||||
Standard CRUD for services, folders, and folder documents. Same pattern: `getAll`, `getById`, `create`, `update`, `delete`.
|
||||
|
||||
### ai-kurzy/ (barrel exported from ai-kurzy.service.js)
|
||||
|
||||
**kurzy.service.js** — `getAllKurzy`, `getKurzById`, `createKurz`, `updateKurz`, `deleteKurz`, `getKurzyStats` (course CRUD + statistics)
|
||||
|
||||
**ucastnici.service.js** — `getAllUcastnici`, `getUcastnikById`, `createUcastnik`, `updateUcastnik`, `deleteUcastnik` (participant CRUD)
|
||||
|
||||
**registracie.service.js** — `getAllRegistracie`, `getRegistraciaById`, `createRegistracia`, `updateRegistracia`, `deleteRegistracia`, `getCombinedTableData`, `updateField`, `getPrilohyByRegistracia`, `createPriloha`, `deletePriloha` (registrations + combined table view + inline field editing + attachments)
|
||||
|
||||
### jmap/ (email protocol integration)
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `getJmapConfig(userId)` | Get JMAP config from user's primary email |
|
||||
| `getJmapConfigFromAccount(account)` | Build JMAP config from account object |
|
||||
| `discoverContactsFromJMAP(config, accountId, search, limit)` | Search and discover contacts |
|
||||
| `syncEmailsFromSender(config, accountId, contactId, email, opts)` | Sync emails from sender |
|
||||
| `markEmailAsRead(config, userId, jmapId, isRead)` | Mark read/unread on JMAP |
|
||||
| `sendEmail(config, userId, accountId, to, subject, body, inReplyTo, threadId)` | Send via JMAP |
|
||||
| `searchEmailsJMAP(config, accountId, query, limit, offset)` | Full-text JMAP search |
|
||||
|
||||
### status.service.js
|
||||
`getServerStatus()` — returns uptime, memory usage, CPU, database connection info.
|
||||
|
||||
---
|
||||
|
||||
## Controllers (`src/controllers/`)
|
||||
|
||||
Controllers are thin HTTP handlers. Each extracts params/body from `req`, calls the corresponding service, and returns JSON. After the refactoring, audit logging is handled inside services via the `auditContext` parameter (`{ userId, ipAddress, userAgent }`).
|
||||
|
||||
| Controller | Handles |
|
||||
|-----------|---------|
|
||||
| `auth.controller.js` | Login, logout, refresh token, set password, link email, session |
|
||||
| `admin.controller.js` | User CRUD (admin only), server status, trigger notifications |
|
||||
| `company.controller.js` | Company CRUD, email threads, unread counts |
|
||||
| `company-team.controller.js` | Company user assignments |
|
||||
| `company-note.controller.js` | Company notes CRUD |
|
||||
| `company-reminder.controller.js` | Company reminders CRUD + summary/upcoming |
|
||||
| `company-document.controller.js` | Company document upload/download/delete |
|
||||
| `project.controller.js` | Project CRUD, notes, team, documents |
|
||||
| `project-document.controller.js` | Project document upload/download/delete |
|
||||
| `todo.controller.js` | Todo CRUD, toggle, counts, overdue, my todos |
|
||||
| `note.controller.js` | Note CRUD, search, reminders |
|
||||
| `time-tracking.controller.js` | Start/stop/pause/resume timer, entries, stats, timesheet generation |
|
||||
| `timesheet.controller.js` | Timesheet upload/download/delete |
|
||||
| `contact.controller.js` | Email contacts, discover, link/create company |
|
||||
| `personal-contact.controller.js` | Personal contacts CRUD |
|
||||
| `crm-email.controller.js` | Email list, threads, search, sync, reply, read status |
|
||||
| `email-account.controller.js` | Email account CRUD, password, status, primary |
|
||||
| `email-signature.controller.js` | Signature CRUD, toggle, formatted output |
|
||||
| `message.controller.js` | 1-on-1 messaging, conversations, unread count |
|
||||
| `group.controller.js` | Group chat CRUD, messages, members |
|
||||
| `push.controller.js` | Push subscription, VAPID key, test push |
|
||||
| `event.controller.js` | Calendar events CRUD, notifications |
|
||||
| `audit.controller.js` | Audit log listing and filters (admin) |
|
||||
| `service.controller.js` | Services CRUD |
|
||||
| `service-folder.controller.js` | Service folders CRUD |
|
||||
| `service-document.controller.js` | Folder documents upload/download/delete |
|
||||
| `ai-kurzy.controller.js` | Courses, participants, registrations, combined table, attachments, stats |
|
||||
|
||||
---
|
||||
|
||||
## Routes (`src/routes/`)
|
||||
|
||||
All routes are prefixed with `/api`. Authentication is required unless noted.
|
||||
|
||||
| Prefix | Methods | Description |
|
||||
|--------|---------|-------------|
|
||||
| `/api/auth` | POST login/logout/refresh/set-password/link-email/skip-email, GET session/me | Auth flow |
|
||||
| `/api/admin` | GET/POST/PATCH/DELETE users, GET server-status, POST trigger-notifications | Admin only |
|
||||
| `/api/companies` | Full CRUD + nested notes, reminders, users, documents, email-threads | Company management |
|
||||
| `/api/projects` | Full CRUD + nested notes, users, documents | Project management |
|
||||
| `/api/todos` | Full CRUD + /my, /counts, /toggle, /overdue-count | Task management |
|
||||
| `/api/notes` | CRUD + /search, /my-reminders | Notes with reminders |
|
||||
| `/api/time-tracking` | start/stop/pause/resume, entries, monthly, generate timesheets | Time tracking |
|
||||
| `/api/timesheets` | Upload/download/delete, /my, /all | Timesheet files |
|
||||
| `/api/contacts` | CRUD + /discover, link/unlink/create company | Email contacts |
|
||||
| `/api/personal-contacts` | CRUD + /company/:companyId | Personal contacts |
|
||||
| `/api/emails` | List, search, sync, reply, threads, read status | Email operations |
|
||||
| `/api/email-accounts` | CRUD + password, status toggle, set-primary | Email accounts |
|
||||
| `/api/email-signature` | CRUD + toggle, formatted | Email signatures |
|
||||
| `/api/messages` | Conversations, send, unread, users | Direct messages |
|
||||
| `/api/groups` | CRUD + messages, members | Group chat |
|
||||
| `/api/push` | Subscribe/unsubscribe, VAPID key, status, test | Push notifications |
|
||||
| `/api/events` | CRUD + /notify | Calendar events (admin-managed) |
|
||||
| `/api/audit` | GET logs, actions, resources | Audit trail (admin) |
|
||||
| `/api/services` | CRUD + folders + folder documents | Service catalog |
|
||||
| `/api/ai-kurzy` | Courses, participants, registrations, combined table, attachments, stats | AI courses module |
|
||||
| `/api/users` | GET all users | Basic user list |
|
||||
|
||||
---
|
||||
|
||||
## Cron Jobs (`src/cron/`)
|
||||
|
||||
| Job | Schedule | Description |
|
||||
|-----|----------|-------------|
|
||||
| `notifyUpcomingEvents` | Every 15 min | Push notifications for events starting within 30 min |
|
||||
| `cleanupOldAuditLogs` | Daily 2:00 AM | Delete audit logs older than 90 days |
|
||||
|
||||
Manual triggers available via admin endpoints: `triggerEventNotifications()`, `triggerSingleEventNotification(eventId, adminUserId)`.
|
||||
|
||||
---
|
||||
|
||||
## Database Schema (`src/db/schema.js`)
|
||||
|
||||
34 tables organized by domain:
|
||||
|
||||
**Auth & Users:** `users`, `emailAccounts`, `userEmailAccounts`, `auditLogs`
|
||||
|
||||
**CRM Core:** `companies`, `companyUsers`, `companyReminders`, `companyDocuments`, `projects`, `projectUsers`, `projectDocuments`, `todos`, `todoUsers`, `notes`
|
||||
|
||||
**Time Tracking:** `timeEntries`, `timesheets`
|
||||
|
||||
**Email:** `contacts`, `personalContacts`, `emails`, `emailSignatures`
|
||||
|
||||
**Messaging:** `messages`, `chatGroups`, `chatGroupMembers`, `groupMessages`
|
||||
|
||||
**Calendar:** `events`, `eventUsers`
|
||||
|
||||
**Push:** `pushSubscriptions`
|
||||
|
||||
**Services:** `services`, `serviceFolders`, `serviceDocuments`
|
||||
|
||||
**AI Courses:** `kurzy`, `ucastnici`, `registracie`, `prilohy`
|
||||
|
||||
### Key Enums
|
||||
- `role`: admin, member
|
||||
- `projectStatus`: active, completed, on_hold, cancelled
|
||||
- `todoStatus`: pending, in_progress, completed, cancelled
|
||||
- `todoPriority`: low, medium, high, urgent
|
||||
- `companyStatus`: registered, lead, customer, inactive
|
||||
- `formaKurzu`: prezencne, online, hybridne
|
||||
- `stavRegistracie`: potencialny, registrovany, potvrdeny, absolvoval, zruseny
|
||||
- `typPrilohy`: certifikat, faktura, prihlaska, doklad_o_platbe, ine
|
||||
|
||||
---
|
||||
|
||||
## Access Control
|
||||
|
||||
- **Admin** — full access to all resources, user management, server status
|
||||
- **Member** — sees only companies/projects they are assigned to; todos filtered accordingly
|
||||
- Resource access checked via `checkResourceAccess` middleware and `getAccessibleResourceIds` helper
|
||||
- Creator is auto-assigned to companies and projects on creation
|
||||
|
||||
## Audit Logging
|
||||
|
||||
All write operations log to `auditLogs` via the `auditContext` pattern. Services receive `{ userId, ipAddress, userAgent }` and call the audit service internally. Tracked: logins, logouts, password changes, CRUD on companies/projects/todos/notes/timesheets, user assignments, admin actions.
|
||||
15
whattotest.txt
Normal file
15
whattotest.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
Refactoring Smoke Test Checklist
|
||||
=================================
|
||||
|
||||
1. [x] Start server (npm run dev) — does it boot without import errors?
|
||||
2. [x] Login — check audit log entry exists
|
||||
3. [x] Create a company — check audit log
|
||||
4. [x] Add a note to the company
|
||||
5. [x] Add a reminder to the company
|
||||
6. [x] Assign a user to the company
|
||||
7. [x] Create a project, assign user
|
||||
8. [x] Create a todo with assigned users — check push notification
|
||||
9. [x] Start/stop/pause/resume a timer
|
||||
10. [x] Generate a monthly timesheet XLSX — open and verify
|
||||
11. [x] Upload a file to a project (check size/type limits)
|
||||
12. [x] AI Kurzy: create course, participant, registration
|
||||
Reference in New Issue
Block a user