This project has been created as part of the 42 curriculum by mobouifr, oer-refa, soel-mou, csouita, hichokri.
A production-ready CRM platform built as the ft_transcendence Surprise capstone at 42 School
- Description
- Team Information
- Project Management
- Technical Stack
- Database Schema
- Features List
- Modules
- Individual Contributions
- Instructions
- Resources
Mycel is a full-stack web application that gives independent freelancers a single workspace to run their business. Instead of switching between separate tools, everything is in one place: client records, project tracking, a four-mode calendar, revenue analytics, sticky notes, and an AI assistant that reads the user's actual CRM data before responding.
The name references mycelium — the underground fungal network that connects and feeds ecosystems. The goal of the platform mirrors that: connect every part of a freelancer's workflow into one coherent, living system.
| Category | What it does |
|---|---|
| Authentication | Email/password login, 42 OAuth SSO, TOTP two-factor authentication, JWT stored in HttpOnly cookies |
| Client Management | Full CRUD, server-side search, multi-column sorting, cursor-based pagination |
| Project Management | Status lifecycle (Active → Completed / Paused / Cancelled), priority levels, budget and deadline tracking |
| Dashboard | Drag-and-drop widget grid with real-time SSE updates — Revenue KPI, Activity Heatmap, Activity Feed, Project Status Bar, Data Graph, Calendar, Notes, Next Deadline |
| Calendar & Reminders | Four view modes (Month / Week / Day / Lane), event scheduling with CRM entity tagging, pinnable colour-coded notes |
| AI Chatbot | DeepSeek LLM with live CRM context injected into every request, token-streamed to the browser, Markdown and code rendering |
| Gamification | XP and level progression triggered by CRM actions; one-time achievements and collectible badges |
| Notifications | Real-time bell badge via SSE, read/unread tracking, deep-link navigation to the related entity |
| Internationalisation | Full English / French / Spanish UI, browser language auto-detection, runtime language switching |
| Monitoring | Prometheus metrics endpoint, Grafana dashboards, PostgreSQL exporter, nightly automated database backups |
Roles: Product Owner · Tech Lead · DevOps · Backend · Frontend
Owned the product vision and backlog, wrote feature acceptance criteria, and made final scope decisions under deadline pressure. Led evaluation readiness: maintained the module checklist, ran defense rehearsals, and verified all graded criteria worked end-to-end. On the backend, designed the server-side sorting and cursor-based pagination systems, wrote the full test suite (74 tests across 8 suites), identified and fixed 6 critical bugs, performed a dead-code audit removing 23 unused files, and maintained the Docker Compose configuration. On the frontend, built the drag-and-drop dashboard widget grid, the four-mode calendar and reminders system, the chatbot UI, and the core routing and layout infrastructure.
Roles: Backend Lead · Project Manager / Scrum Master
Organized team meetings, tracked milestones against the evaluation checklist, and managed daily communication across Discord, WhatsApp, and Linear. Built the complete authentication layer: email/password login with bcrypt, JWT in HttpOnly cookies, 42 OAuth via passport-42, and TOTP two-factor authentication. Designed and implemented the real-time SSE infrastructure using an RxJS Subject in PrismaService as an in-process event bus shared across all modules. Developed all dashboard analytics endpoints: revenue KPI, activity heatmap, activity feed, 6-month project/client graph, and the calendar and reminders backend.
Roles: DevOps · Backend
Designed and maintained the full Docker Compose stack: Nginx reverse proxy with HTTPS, PostgreSQL, backend, frontend, Prometheus, Grafana, PostgreSQL exporter, and the automated backup service. Owned the Prisma schema and all database migrations. Built the AI chatbot module: DeepSeek API integration, live CRM context assembly (10 most recent clients and active projects injected into the system prompt), SSE token streaming via NestJS Observable, and per-user rate limiting. Set up Prometheus scraping, Grafana dashboard provisioning, and automated nightly database backups. Implemented the full internationalisation pipeline and maintained the Makefile (20+ targets).
Roles: Backend Developer
Built the Gamification module end-to-end: XP award values per action type, level threshold calculations (xpForLevel(L) = 500 × L²), and the GamificationService called after successful CRM writes. Implemented UserAchievement and UserBadge creation with a unique (userId, type) constraint ensuring each achievement is earned only once per user. Contributed to the Notifications SSE architecture and resolved a race condition that produced duplicate achievement records under rapid concurrent actions.
Roles: Frontend Developer
Built all Client and Project pages: list views with search, sort headers, and pagination controls; detail views; create and edit modal forms. Implemented the React Router background-location modal pattern so navigating to a modal keeps the list rendered and blurred behind it, and returning from the modal does not trigger a full list reload. Integrated Zod schemas with React Hook Form for client-side validation on all CRM forms.
Each developer owned one or more feature domains end-to-end, covering both frontend and backend. Domain boundaries were kept explicit to avoid merge conflicts during parallel development.
Branching strategy: personal branches per developer (OthmaneBranch, Solayman, SouitaBack, montassir, Hiba) merged into the backDevops integration branch, then promoted to main after Tech Lead review.
Cadence: weekly stand-ups to surface blockers and reprioritise against the evaluation checklist, plus full end-to-end rehearsals before the defense date.
| Purpose | Tool |
|---|---|
| Version control | Git + GitHub (solacode-SC/freelancer-crm-final-project) |
| Task tracking | Linear + GitHub branch-per-feature naming |
| Communication | Discord + WhatsApp + Linear comments |
| API testing | Postman, cURL |
| Database inspection | Prisma Studio (make studio) |
| Container management | Docker Desktop / CLI |
| Technology | Version | Why it was chosen |
|---|---|---|
| React | 19.2.0 | Industry-standard component model; concurrent mode; large ecosystem |
| TypeScript | ~5.9.3 | End-to-end type safety across API boundaries via shared DTOs |
| Vite | 7.3.1 | Sub-second HMR; tree-shaking; manual chunk splitting per page group |
| Tailwind CSS | 4.2.1 | Utility-first styling enables fast iteration without style conflicts between developers |
| React Router | 7.13.1 | Background-location pattern keeps list pages mounted behind modal overlays |
| React Hook Form + Zod | 7.72.0 / 4.3.6 | Uncontrolled forms with schema-level validation; minimal re-renders |
| Axios | 1.13.6 | Interceptors for unified error handling and auth; typed responses |
| i18next + react-i18next | 24.2.2 | Runtime language switching without page reload; browser language detection |
| react-grid-layout | 2.2.2 | Drag-and-drop resizable dashboard widget grid |
| react-markdown + rehype-highlight | 10.1.0 / 7.0.2 | Renders chatbot Markdown responses with syntax-highlighted code blocks |
| Technology | Version | Why it was chosen |
|---|---|---|
| NestJS | 10.x | Modular DI architecture maps directly to CRM domains; Guards, Pipes, Interceptors reduce boilerplate |
| TypeScript | 5.0.0 | Shared type surface with the frontend |
| Prisma ORM | 5.21.1 | Type-safe database client generated from schema; migration history; zero SQL injection surface |
| @nestjs/passport | — | Strategy pattern for Local, JWT, and 42 OAuth with a consistent guard interface |
| @nestjs/throttler | 6.5.0 | Declarative rate limiting at route or global level |
| RxJS | 7.8.0 | Subject-based reactive event bus for SSE streams — filter once, fan out to many consumers |
| otplib | 12.0.1 | TOTP secret generation and verification per RFC 6238 |
| @willsoto/nestjs-prometheus | 6.1.0 | Prometheus metrics at /api/metrics with NestJS DI integration |
| Component | Technology | Notes |
|---|---|---|
| Containerisation | Docker + Docker Compose v2 | Dev and production configurations |
| Reverse proxy | Nginx | HTTPS termination, API proxy, static asset serving |
| Monitoring | Prometheus v2.51.0 + Grafana 10.4.0 | Pre-provisioned dashboards via Docker volume mount |
| Database metrics | postgres-exporter v0.15.0 | DB-level connection and query metrics exported to Prometheus |
| Backups | Alpine cron + pg_dump | Nightly at 02:00 UTC, gzip-compressed to a named Docker volume |
| Dev HTTPS | OpenSSL self-signed cert | Generated via make ssl-dev-cert on first run |
NestJS over plain Express. The module/service/controller structure enforced clear ownership boundaries — each team member owned their module directory. Dependency injection made unit testing straightforward because mocking PrismaService required no global test setup.
PostgreSQL over MongoDB. CRM data is inherently relational: User → Client → Project → Notification. Foreign-key constraints and cascade deletes enforce data integrity at the database level instead of in application code.
SSE over WebSockets. Dashboard and notification updates are strictly server-to-client. SSE avoids the overhead of a bidirectional WebSocket channel, works through firewalls without special configuration, and integrates natively with the browser EventSource API. The RxJS Subject in PrismaService acts as an in-process bus: any write calls .next(), and each SSE endpoint subscribes and filters for its relevant models.
DeepSeek over OpenAI. The DeepSeek API is OpenAI-compatible, significantly cheaper for streaming completions, and fast enough for real-time token delivery. The chatbot injects the user's live CRM data into the system prompt at request time, enabling business-specific responses rather than generic answers.
Every record in the database is scoped to a User by a foreign key with cascade delete. This guarantees complete data isolation between users and ensures that deleting an account removes all associated data automatically.
| Field | Type | Constraints |
|---|---|---|
| id | String (UUID) | Primary key, default: cuid() |
| String | Unique | |
| username | String | default: "User" |
| name, phone, businessName, businessAddress, logoUrl | String? | Nullable |
| defaultCurrency | String | default: "USD" |
| taxRate | Decimal(5,2) | default: 0 |
| password, passwordHash | String? | Nullable — local auth only |
| intraId | String? | Unique; 42 OAuth identifier |
| isTwoFactorEnabled | Boolean | default: false |
| twoFactorSecret | String? | Encrypted TOTP seed |
| xp | Int | default: 0 |
| level | Int | default: 1 |
| createdAt, updatedAt | DateTime | Auto-managed |
| Field | Type | Constraints |
|---|---|---|
| id | String (UUID) | Primary key |
| name | String | Required |
| email, phone, company, notes | String? | Nullable |
| userId | String | FK → User (cascade delete) |
| createdAt, updatedAt | DateTime | Auto-managed |
| Field | Type | Constraints |
|---|---|---|
| id | String (UUID) | Primary key |
| title | String | Required |
| description | String? | Nullable |
| status | Enum | ACTIVE / COMPLETED / PAUSED / CANCELLED, default: ACTIVE |
| priority | Enum | HIGH / MEDIUM / LOW, default: MEDIUM |
| budget | Decimal(10,2) | default: 0 |
| deadline | DateTime? | Nullable |
| userId | String | FK → User (cascade delete) |
| clientId | String | FK → Client (cascade delete) |
| createdAt, updatedAt | DateTime | Auto-managed |
| Field | Type | Notes |
|---|---|---|
| id | String (UUID) | Primary key |
| message | String | Required |
| title | String? | Optional heading |
| read | Boolean | default: false |
| type | String | "info" / "success" / "warning" / "error" |
| targetType, targetId | String? | Deep-link to related entity (e.g., "client", "project") |
| userId | String | FK → User (cascade delete) |
| Field | Type | Notes |
|---|---|---|
| id | String (UUID) | Primary key |
| title, content | String | Required |
| tags | String[] | PostgreSQL array |
| color | String | UI colour key, default: "default" |
| pinned | Boolean | default: false |
| userId | String | FK → User (cascade delete) |
| Field | Type | Notes |
|---|---|---|
| id | String (UUID) | Primary key |
| title, date, time | String | Required |
| endDate, endTime | String? | Optional |
| description, eventType, priority, location, externalLink | String? | Optional metadata |
| clientTag, projectTag | String? | Links to CRM entities by name |
| userId | String | FK → User (cascade delete) |
| Field | Type | Notes |
|---|---|---|
| id | String (UUID) | Primary key |
| type | String | e.g., "FIRST_PROJECT", "HIGH_ROLLER" |
| name | String | Display label |
| earnedAt | DateTime | default: now() |
| userId | String | FK → User (cascade delete) |
| — | Unique constraint | (userId, type) — each achievement earned only once per user |
All features are userId-scoped and require authentication.
| Feature | Developer(s) | Description |
|---|---|---|
| Email / password auth | oer-refa | Registration and login with bcrypt-hashed passwords (12 salt rounds). JWT returned in an HttpOnly, Secure, SameSite=Lax cookie. |
| 42 OAuth | oer-refa | passport-42 strategy. On callback, creates a new user or links to an existing account by email. Configurable redirect URI. |
| Two-Factor Auth (TOTP) | oer-refa | otplib generates a per-user TOTP secret. qrcode renders a scannable QR code. TOTP code is verified server-side before the JWT is issued. |
| JWT guard | oer-refa, mobouifr | JwtAuthGuard protects all private routes. Token extracted from the jwt cookie and validated by JwtStrategy. |
| Global rate limiting | mobouifr | @nestjs/throttler configured globally at 10,000 requests per 5-minute window. |
| Feature | Developer(s) | Description |
|---|---|---|
| CRUD | hichokri, mobouifr | Create, view, edit, and delete client records. Deleting a client cascades to all linked projects. |
| Server-side search | mobouifr | Case-insensitive full-name search via Prisma contains filter. |
| Server-side sorting | mobouifr | Any column sortable via sortBy and sortOrder query params (ASC/DESC). |
| Cursor-based pagination | mobouifr | take + optional cursor params. Default page size 50, capped at 200. |
| Detail view | hichokri | Full client profile with a linked projects tab. |
| Modal overlay routing | hichokri | Create / edit / detail modals use React Router's background-location pattern. The list stays rendered and blurred behind the modal. |
| Feature | Developer(s) | Description |
|---|---|---|
| CRUD | hichokri, mobouifr | Full lifecycle management. Deleting a project cascades to its notifications. |
| Status management | hichokri | ACTIVE, COMPLETED, PAUSED, CANCELLED — displayed as colour-coded badges. |
| Priority levels | hichokri | HIGH, MEDIUM, LOW with visual indicators. |
| Budget & deadline | hichokri | Decimal budget field and optional DateTime deadline. |
| Server-side sorting + pagination | mobouifr | Same system as clients — consistent API contract across all list endpoints. |
| Feature | Developer(s) | Description |
|---|---|---|
| Configurable widget grid | mobouifr | react-grid-layout powers drag-to-reorder and resizable widgets. Layout saved per user and persists across sessions. |
| Preset layouts | mobouifr | Compact, Standard, and Analytics presets selectable in one click. |
| Widget picker | mobouifr | Modal to add, remove, or reset widgets from the grid. |
| Real-time SSE refresh | oer-refa, csouita | PrismaService.globalMutation$ Subject emits on every write. Dashboard filters for Project, Client, Note, Event, and UserAchievement model events. |
| Revenue KPI | oer-refa | Monthly revenue bar chart for the full year; current-month vs previous-month comparison; completed project count. |
| Activity heatmap | oer-refa | 52-week GitHub-style contribution calendar. Counts client creation, project creation, and project completion per day. |
| Activity feed | oer-refa | Unified chronological feed of recent clients, projects, and achievements (top 5). |
| Data graph | oer-refa | 6-month bar chart comparing projects vs clients created per month. |
| Project status bar | mobouifr | Grouped count of projects per status with a progress-bar visualisation. |
| Next deadline | mobouifr | Upcoming project with the nearest deadline. |
| Notes widget | mobouifr | Full note CRUD embedded as a dashboard widget. |
| Calendar widget | mobouifr | Mini view of the next scheduled calendar events. |
| Feature | Developer(s) | Description |
|---|---|---|
| 4-mode calendar | mobouifr | Month, Week, Day, and Lane (swim-lane by event type) views. Built without any third-party calendar library. |
| Event CRUD | mobouifr, oer-refa | Full create/edit/delete with title, date, time, end time, description, type, priority, location, and external link. |
| CRM entity tagging | mobouifr | Link events directly to existing clients or projects. Displayed as context badges on each event. |
| Sticky notes | mobouifr | Colour-coded, tag-based, pinnable notes with inline editing. Backed by a dedicated Note model. |
| Feature | Developer(s) | Description |
|---|---|---|
| DeepSeek LLM integration | soel-mou | Server-side request to DeepSeek API with the user's live CRM data (10 most recent clients and active projects) injected into the system prompt at request time. |
| SSE token streaming | soel-mou | Response streamed token-by-token from backend to frontend. User sees the reply appearing as it is generated. |
| Markdown rendering | soel-mou | react-markdown + rehype-highlight renders formatted text, tables, and syntax-highlighted code blocks in chatbot replies. |
| Per-user rate limiting | soel-mou, mobouifr | @Throttle limits chatbot requests to 10 per 5 minutes per user. A graceful error message is shown when the limit is reached. |
| Feature | Developer(s) | Description |
|---|---|---|
| XP system | csouita | XP awarded on CRM actions (creating a client, completing a project, etc.). |
| Level progression | csouita | Level calculated from cumulative XP: xpForLevel(L) = 500 × L². |
| Achievements | csouita | One-time milestones (e.g., FIRST_PROJECT, LOYAL_CLIENT_3). Unique per (userId, type). |
| Badges | csouita | Visual collectibles (e.g., HIGH_ROLLER, EARLY_BIRD) displayed on the Growth page. |
| Growth page | csouita, mobouifr | XP progress ring, level stats, and achievement card gallery. |
| Feature | Developer(s) | Description |
|---|---|---|
| Event-driven creation | mobouifr, csouita | Notifications created after successful client and project CRUD operations. |
| Real-time bell badge | mobouifr, csouita | SSE subscription filters globalMutation$ for the Notification model. Unread count badge updates instantly. |
| Bell dropdown | mobouifr | Last 10 notifications with type icons, read status, and relative timestamps. |
| Mark as read | mobouifr | Individual PATCH /:id/read or bulk PATCH /read-all. |
| Delete | mobouifr | Individual delete or DELETE / to clear all notifications. |
| Deep-link navigation | mobouifr | Clicking a notification with targetType + targetId routes to the linked client or project. |
| Feature | Developer(s) | Description |
|---|---|---|
| Profile editing | oer-refa | Username, name, email, phone, business name, address, default currency, and tax rate. |
| Password change | oer-refa | Requires current password. Re-hashes the new password with bcrypt before saving. |
| 2FA setup and teardown | oer-refa | QR code generation → TOTP scan → code verification → enable. Reverse path to disable. |
| Theme selection | mobouifr | Multiple visual themes persisted across sessions. |
| Feature | Developer(s) | Description |
|---|---|---|
| EN / FR / ES translations | soel-mou | Full UI translated in three locale JSON files (~25 KB each). All user-facing strings use translation keys. |
| Browser language detection | soel-mou | i18next-browser-languagedetector selects the language on first visit based on the browser's Accept-Language header. |
| Manual switcher | soel-mou | LanguageSwitcher component calls i18n.changeLanguage() at runtime without a page reload. |
| Feature | Developer(s) | Description |
|---|---|---|
| Prometheus metrics | soel-mou | HTTP request count, latency histograms, and custom counters exposed at /api/metrics. |
| Grafana dashboards | soel-mou | Pre-provisioned via Docker volume mount. Visualises API latency, request rates, and DB metrics. |
| PostgreSQL exporter | soel-mou | Exports DB-level metrics (connections, query timing) to Prometheus. |
| Health endpoint | mobouifr | GET /api/health used by Docker healthcheck. Probes the database connection. |
| Automated DB backups | soel-mou | Alpine cron runs pg_dump daily at 02:00 UTC. Output is gzip-compressed to a named Docker volume. |
| Manual restore | soel-mou | make db-restore FILE=<path> with an interactive confirmation prompt before restoring. |
| Feature | Developer(s) | Description |
|---|---|---|
| Privacy Policy | mobouifr | Accessible from the application footer. Contains data handling information relevant to the CRM context. |
| Terms of Service | mobouifr | Accessible from the application footer. Covers usage terms relevant to the platform. |
Scoring: Major module = 2 pts · Minor module = 1 pt · Minimum required to pass = 14 pts
| # | Module | Category | Type | Pts | How it was implemented | Developer(s) |
|---|---|---|---|---|---|---|
| 1 | Backend Framework (NestJS) | Web | Minor | 1 | NestJS 10.x with one module per CRM domain, dependency injection, Guards, Pipes, and Interceptors. Controllers and services are fully decoupled. | oer-refa |
| 2 | ORM (Prisma) | Web | Minor | 1 | Prisma 5.21.1 schema-first ORM. All queries go through a type-safe generated client. Migration history tracked in prisma/migrations. Prisma Studio available via make studio. |
oer-refa |
| 3 | OAuth 2.0 — 42 Intranet | User Management | Minor | 1 | passport-42 strategy intercepts the 42 callback, exchanges the code for a token, fetches the intra profile, and either finds an existing user by intraId or creates a new one. Configurable callback URL via env var. |
oer-refa |
| 4 | Two-Factor Authentication | User Management | Minor | 1 | otplib generates a per-user RFC 6238 TOTP secret. qrcode produces a scannable URI. Server verifies the 6-digit code before issuing a JWT stored as an HttpOnly cookie. Routes: /2fa/generate, /2fa/turn-on, /2fa/turn-off, /2fa/authenticate. |
oer-refa |
| 5 | LLM System Interface | Artificial Intelligence | Major | 2 | ChatbotModule calls the DeepSeek API with a system prompt including the user's live CRM data (10 most recent clients + active projects). Responses stream token-by-token via SSE using NestJS Observable. @Throttle enforces 10 req / 5 min per user. react-markdown + rehype-highlight renders the output. | soel-mou |
| 6 | Monitoring System (Prometheus + Grafana) | DevOps | Major | 2 | @willsoto/nestjs-prometheus exposes /api/metrics. GlobalMetricsMiddleware records HTTP method, route, status code, and response time. postgres-exporter collects DB metrics. Custom Grafana dashboards and alert rules provisioned via Docker volume. |
soel-mou |
| 7 | Health Check, Backups & Disaster Recovery | DevOps | Minor | 1 | Alpine cron container runs pg_dump nightly at 02:00 UTC. Output is gzip-compressed into a named Docker volume. make db-restore FILE=<path> requires interactive confirmation before restoring. |
soel-mou |
| 8 | Frontend Framework (React) | Web | Minor | 1 | React 19.2 SPA built with Vite 7. Lazy-loaded routes with React.lazy + Suspense. Background-location modal overlay pattern. React Router v7 handles all client-side navigation. | mobouifr, hichokri |
| 9 | Custom Design System | Web | Minor | 1 | 20+ shared reusable components exported from a single index: Button, Input, Select, Modal, Table, FormWrapper, LoadingSpinner, NotificationBell, WidgetCard, WidgetGrid, CalendarWidget, LanguageSwitcher, and more. Unified Tailwind design tokens for colour, typography, and spacing. | mobouifr, hichokri |
| 10 | Multiple Languages (i18n) | Accessibility & i18n | Minor | 1 | i18next + i18next-browser-languagedetector. Three complete locale JSON files (EN/FR/ES). All user-facing strings use translation keys. LanguageSwitcher calls i18n.changeLanguage() at runtime with no page reload. |
soel-mou |
| 11 | Advanced Search, Filtering, Sorting & Pagination | Web | Minor | 1 | All list endpoints (/clients, /projects, /notifications) accept take, cursor, sortBy, and sortOrder query params. Prisma translates these to orderBy + cursor pagination. Frontend table header clicks dispatch sort state. Maximum page size capped at 200. |
mobouifr |
| 12 | Additional Browser Support | Accessibility & i18n | Minor | 1 | Tested and verified on Google Chrome (primary), Mozilla Firefox, and Microsoft Edge. No browser-specific layout breaks or console errors observed across all core user flows. | mobouifr, hichokri |
| 13 | Gamification System | Gaming & UX | Minor | 1 | GamificationService awards XP on CRM actions. Level thresholds follow xpForLevel(L) = 500 × L². Achievements and badges stored with a unique (userId, type) constraint. XP progress bar and earned achievements displayed on the Growth page. |
csouita |
| 14 | Notification System | Web | Minor | 1 | Full CRUD: create, list (cursor-paginated), count unread, mark as read, mark all as read, delete, delete all. SSE bell badge updates in real time. Deep-link navigation to related entities. 28 dedicated tests. | mobouifr, csouita |
| 15 | Customizable Dashboard | Modules of Choice | Minor | 1 | react-grid-layout powers a fully drag-and-drop, resizable widget grid. Users choose from 8 distinct widgets via a Widget Picker modal. Three preset layout templates (Compact / Standard / Analytics). Layout persists across sessions. Mobile fallback replaces drag with reorder buttons. All widgets consume live SSE data. | mobouifr, hichokri |
| 16 | User Activity Analytics & Insights | User Management | Minor | 1 | 52-week GitHub-style activity heatmap tracking CRM actions per day with colour-coded intensity and hover tooltips. Unified activity feed with type badges and relative timestamps. Revenue KPI with month-over-month trend comparison, best-month highlight, and average revenue calculation. All analytics are userId-scoped and update in real time via SSE. | mobouifr, oer-refa, csouita |
| 17 | Calendar & Reminders System | Modules of Choice | Minor | 1 | Fully custom calendar built without any calendar library — no FullCalendar, no React Big Calendar. Four distinct view modes (Month, Week, Day, Lane) with correct date arithmetic across all month and week boundaries. Full CRUD for events with CRM-native client and project tagging. Sticky-notes system backed by a dedicated Note model. Deep-link support from notifications via ?date=YYYY-MM-DD. |
mobouifr, hichokri |
| Count | Points | |
|---|---|---|
| Major modules | 2 | 4 pts |
| Minor modules | 15 | 15 pts |
| Total implemented | 17 | 19 pts |
| Minimum required to pass | — | 14 pts |
| Bonus points (beyond 14) | — | 5 pts |
Module 15 — Customizable Dashboard (Minor)
A widget that displays fixed charts would not qualify as a module. What makes this substantial is the combination of capabilities built together: persistent Cartesian layout coordinates saved per user in the database, a fully drag-and-drop and resizable interface powered by react-grid-layout, a widget picker modal, three preset layout templates that users can apply in one click, a mobile fallback with up/down reorder controls, and eight distinct live-data widgets each consuming real-time SSE streams. No pre-built dashboard framework was used. The result is a configurable, persistent workspace that adapts to each freelancer's priorities — which is the core value proposition of the CRM itself.
Module 17 — Calendar & Reminders System (Minor)
This was built from scratch without any calendar library. Four distinct view modes required separate date-arithmetic implementations: month boundary overflow, ISO week-start normalisation, day-view time-slot rendering, and swim-lane layout grouped by event type. All four modes share one state machine and one data source, which required careful normalisation to UTC midnight before any arithmetic to avoid timezone-driven off-by-one errors. Events link natively to CRM clients and projects via dedicated tag fields, persisted in the backend. The sticky-notes system adds a separate persistence layer with its own Prisma model, full CRUD, colour coding, tagging, and pin state. Deep-link support routes users from notification clicks directly to a specific calendar date. The scope — four distinct views, full event CRUD with CRM integration, a notes system, and deep-linking — justifies the Minor module point.
Backend
- Designed and implemented server-side sorting (multi-column, configurable ASC/DESC) and cursor-based pagination for
/clients,/projects, and/notificationsusing PrismaorderBy+ cursor params withtakeand a default page size of 50 capped at 200. - Built the complete backend test suite: 74 tests across 8 test suites covering all service methods and controller routes. Converted SSE tests from async-with-timer to synchronous to eliminate Jest parallel-worker force-exit warnings caused by dangling setTimeout chains in Observable listeners.
- Fixed six backend bugs found through a systematic audit:
- SSE model filter on the dashboard was not scoped — all database mutations (including unrelated models) were reaching dashboard clients.
- Achievement type mapping used the string
"note"instead of"achievement", causing incorrect achievement records. - A notification was created before its associated delete completed, producing phantom notifications if the delete subsequently failed.
- Revenue chart was using
createdAtinstead ofupdatedAtfor project completion date, causing incorrect month attribution. updateNotereturned HTTP 200 on a missing record instead of 404.- A
weeklyRevenuevariable accumulated silently in a loop without being used or returned anywhere.
- Performed a full dead-code audit: removed 23 unused files including test scripts, placeholder pages, empty entity classes, dead components, and an unregistered interceptor. Zero TypeScript errors after removal.
- Built and maintained the notifications module end-to-end: bell dropdown close behaviour, delete-all endpoint, deep-link navigation by targetType and targetId, unread count badge, and 28 dedicated tests.
- Maintained Docker Compose configuration and
.env.exampledocumentation.
Frontend
- Built the dashboard widget grid using react-grid-layout: drag-to-reorder, resizable widgets via handles, three preset layout templates (Compact / Standard / Analytics), a widget picker modal, and per-user layout persistence across sessions.
- Developed the Calendar and Reminders UI: Month, Week, Day, and Lane view modes with correct date arithmetic across all boundaries; event creation and edit modal; mini navigation calendar for date selection; sticky-note panel with colour coding, tagging, and pinning.
- Implemented the chatbot widget: input box, streaming message display with character-by-character SSE rendering, Markdown output rendering via react-markdown, and action-button parsing.
- Built the frontend routing structure and the responsive application layout.
Challenge (backend). Jest force-exit warnings only appeared with parallel workers — running with --runInBand was silent. The root cause was setTimeout timers inside SSE tests that kept worker processes alive past afterAll teardown. Since Subject.next() emissions are synchronous, replacing the timer with a direct Subject completion in afterEach resolved the warning entirely without changing test logic or coverage.
Challenge (frontend). The four-mode calendar required one state machine that produced four different renderings. Managing week and day boundaries across month edges required normalising all dates to UTC midnight before arithmetic, because using local Date objects without normalisation caused off-by-one errors on boundaries where DST transitions occurred.
- Implemented the complete authentication layer: local email/password login with bcrypt (12 salt rounds), JWT signed and returned as an HttpOnly cookie, 42 OAuth via passport-42, and TOTP two-factor authentication with QR code generation.
- Designed and built the real-time SSE infrastructure:
globalMutation$Subject inPrismaServiceas an in-process singleton event bus. Any write operation calls.next(). Each SSE endpoint subscribes and filters for its relevant models. - Developed all dashboard analytics endpoints: revenue KPI (full-year monthly chart with current vs previous month comparison), activity heatmap (365-day contribution grid), unified activity feed, 6-month project/client trend chart, notes CRUD, and events CRUD.
- Built the Calendar and Reminders backend and integrated SSE updates for calendar event mutations.
Challenge. The globalMutation$ Subject needed to be a shared singleton across all modules. NestJS DI defaults can create independent instances per injection context, meaning endpoints would subscribe to different Subject instances and miss events. Setting PrismaService to DEFAULT scope explicitly ensured all modules shared one stream.
- Designed and maintained the full Docker Compose stack: Nginx with HTTPS, PostgreSQL, NestJS backend, Vite frontend, Prometheus, Grafana, PostgreSQL exporter, and the backup cron service — all on an isolated Docker bridge network.
- Owned the Prisma schema and all database migrations. No other developer edited
schema.prismadirectly. - Built the AI Chatbot module: DeepSeek API integration, live CRM context assembly by querying the user's 10 most recent clients and active projects at request time, SSE token streaming via NestJS Observable, react-markdown rendering on the frontend, and per-user throttling.
- Set up Prometheus scraping configuration, Grafana dashboard provisioning via Docker volume mount, and PostgreSQL alert rules.
- Implemented the internationalisation pipeline: three locale JSON files (EN/FR/ES), i18next configuration, browser language detection, and the LanguageSwitcher component.
- Wrote and maintained the Makefile with 20+ targets covering dev, prod, database operations, SSL certificate generation, and monitoring.
Challenge. The initial chatbot implementation sent the user's entire database as context to the LLM on every request. This caused token limit errors for users with many records and added significant latency. Reducing the context to the 10 most recent clients and active projects with only the key fields compressed the average payload by approximately 90% while preserving the quality of business-relevant responses.
- Implemented the Gamification module: XP award values per CRM action type, level threshold calculations using
xpForLevel(L) = 500 × L², and the GamificationService methods called from other services after successful writes. - Built UserAchievement and UserBadge creation with a unique
(userId, type)constraint in the Prisma schema, ensuring each milestone is earned only once per user. - Contributed to the Notifications SSE architecture and resolved integration conflicts between the gamification service and the shared
globalMutation$Subject.
Challenge. Two rapid sequential CRM actions — for example, creating two clients within milliseconds — could trigger concurrent gamification writes and produce duplicate achievement records before the first write was committed. Replacing the check-then-insert pattern with Prisma upsert using the (userId, type) unique constraint as the conflict key eliminated the race window entirely.
- Built all Client pages: list view with search bar, sort headers, and pagination controls; detail view with full profile and linked projects tab; create form modal; edit form modal.
- Built all Project pages with the same structure; implemented the status badge component and priority indicator.
- Implemented the React Router background-location modal pattern: two
<Routes>blocks inAppRoutes— one for the background list, one for the modal overlay — so the list page stays rendered and blurred behind the modal and is not remounted on return. - Integrated Zod schemas with React Hook Form on all CRM forms, covering required fields, email format validation, and phone pattern matching.
Challenge. The background-location modal pattern required accessing location.state?.background, which TypeScript rejects without an explicit type annotation on location.state. Using as any was the obvious shortcut but was avoided. Instead, a typed LocationState interface was defined and passed to useLocation<LocationState>(), resolving the type error cleanly without suppressing type checking.
| Tool | Minimum Version | How to verify | Download |
|---|---|---|---|
| Docker Engine | 24.x | docker --version |
docs.docker.com |
| Docker Compose | v2.x (plugin) | docker compose version |
Included with Docker Desktop |
| Git | Any recent | git --version |
git-scm.com |
| make | Any | make --version |
Pre-installed on Linux/macOS |
| Node.js (for local tests only) | 20+ | node --version |
nodejs.org |
git clone https://github.com/solacode-SC/freelancer-crm-final-project.git
cd freelancer-crm-final-projectcp .env.example .envOpen .env and fill in the required values:
| Variable | Required | How to obtain |
|---|---|---|
JWT_SECRET |
Yes | node -e "console.log(require('crypto').randomBytes(64).toString('hex'))" |
POSTGRES_PASSWORD |
Recommended | Replace the default with any strong string |
FORTYTWO_CLIENT_ID |
For 42 OAuth | Register at profile.intra.42.fr/oauth/applications. Callback: http://localhost:3089/auth/callback |
FORTYTWO_CLIENT_SECRET |
For 42 OAuth | Same registration page |
DEEPSEEK_API_KEY |
For AI chatbot | platform.deepseek.com |
GF_SECURITY_ADMIN_PASSWORD |
Recommended | Grafana admin password |
All other variables have working defaults for local development.
make upThis single command will:
- Generate a self-signed HTTPS certificate for
localhost(first run only — takes ~2 seconds) - Build all Docker images
- Start PostgreSQL, the NestJS backend, the React/Vite frontend, Adminer, Prometheus, Grafana, and the backup service
- Apply Prisma database migrations automatically on backend startup
Wait until the backend confirms it is running:
backend-1 | [Nest] LOG [NestApplication] Nest application successfully started
The first build takes 2–4 minutes while Docker pulls base images. Subsequent starts are faster.
| Service | URL | Notes |
|---|---|---|
| Application | https://localhost | Accept the self-signed certificate warning on first visit |
| Backend API | http://localhost:3001/api | Health check at /api/health |
| Adminer | http://localhost:8080 | System: PostgreSQL · Server: postgres |
| Grafana | http://localhost:3002 | Login with credentials from .env |
| Prometheus | http://localhost:9090 | No authentication required |
The Privacy Policy and Terms of Service are accessible from the application footer on every page.
make down| Command | Description |
|---|---|
make up |
Build and start all services |
make down |
Stop all services |
make restart |
Rebuild and restart everything |
make logs |
Stream live logs from all containers |
make status |
Show container status |
make migrate |
Apply pending Prisma migrations |
make generate |
Regenerate Prisma client TypeScript types |
make seed |
Load sample data into the database |
make studio |
Open Prisma Studio in the browser |
make db-shell |
Open a psql console inside PostgreSQL |
make db-backup |
Trigger a manual database backup |
make db-restore FILE=<path> |
Restore from a backup file (interactive confirmation required) |
cd backend
npm install
npm testExpected output:
Test Suites: 8 passed, 8 total
Tests: 74 passed, 74 total
Time: ~5s
| Symptom | Likely Cause | Fix |
|---|---|---|
Can't reach database server at localhost:5432 |
PostgreSQL container not running | make down && make up |
Environment variable not found: DATABASE_URL |
.env file missing |
cp .env.example .env |
Table 'public.User' does not exist |
Migrations not applied | make migrate |
Prisma Client is not generated |
Stale generated types | cd backend && npm install && make generate |
| Browser shows certificate warning | Self-signed dev certificate | Click "Advanced → Proceed to localhost" |
Port 3001 already in use |
Another process on that port | make down then lsof -i :3001 and kill -9 <PID> |
Port 443 already in use |
Another HTTPS server running | Stop the conflicting process or change FRONTEND_PORT in .env |
