From c3c42ec1e49ce64a3fd3cfedd1e17d8681df3645 Mon Sep 17 00:00:00 2001 From: richardtekula Date: Wed, 28 Jan 2026 10:39:44 +0100 Subject: [PATCH] docs: Add README with full backend documentation and smoke test checklist Co-Authored-By: Claude Opus 4.5 --- README.md | 480 +++++++++++++++++++++++++++++++++++++++++++++++++ whattotest.txt | 15 ++ 2 files changed, 495 insertions(+) create mode 100644 README.md create mode 100644 whattotest.txt diff --git a/README.md b/README.md new file mode 100644 index 0000000..57491e6 --- /dev/null +++ b/README.md @@ -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. diff --git a/whattotest.txt b/whattotest.txt new file mode 100644 index 0000000..262ced6 --- /dev/null +++ b/whattotest.txt @@ -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