|
| 1 | +# AlianHub Architecture Guide |
| 2 | + |
| 3 | +**[← Back to main guide](../CLAUDE.md)** |
| 4 | + |
| 5 | +## High-Level Design |
| 6 | + |
| 7 | +``` |
| 8 | +┌─────────────────────────────────────────────────────────┐ |
| 9 | +│ Web Clients (Vue.js) │ |
| 10 | +│ Desktop (Electron) │ |
| 11 | +└───────────┬──────────────────────────┬──────────────────┘ |
| 12 | + │ │ |
| 13 | + ┌───────▼───────────────────────────▼──────┐ |
| 14 | + │ API Server (Express.js) — /api/v1, v2 │ |
| 15 | + │ - Controllers (50+ modules) │ |
| 16 | + │ - Routes, Middleware, Error Handling │ |
| 17 | + │ - Socket.io Event Emitter (LiveSync) │ |
| 18 | + │ - Scheduled Tasks (node-schedule) │ |
| 19 | + │ - Authentication (JWT, OAuth, Firebase)│ |
| 20 | + └───────┬──────────────────┬────────────────┘ |
| 21 | + │ │ |
| 22 | + ┌───────▼────────┐ ┌───▼──────────────┐ |
| 23 | + │ MongoDB │ │ Storage Layer │ |
| 24 | + │ (Multi-tenant)│ │ (Wasabi/Server) │ |
| 25 | + │ (Company-scoped) │ (S3-compatible) │ |
| 26 | + └────────────────┘ └──────────────────┘ |
| 27 | +``` |
| 28 | + |
| 29 | +## Data Flow |
| 30 | + |
| 31 | +1. **Request:** Client sends HTTP request to `/api/v{version}/endpoint` |
| 32 | +2. **Route Matching:** Express router matches request to module controller |
| 33 | +3. **Business Logic:** Controller (with chained middleware) processes request |
| 34 | +4. **Validation:** Input validation at API boundary |
| 35 | +5. **Database:** MongoDB query via `MongoDbCrudOpration(companyId, mongoObj, operation)` |
| 36 | +6. **Real-time:** Socket.io emits event for other connected clients |
| 37 | +7. **Cache:** In-memory cache invalidated after mutations |
| 38 | +8. **Response:** Returns `{ status, statusText, data }` JSON format |
| 39 | + |
| 40 | +## Design Patterns in Use |
| 41 | + |
| 42 | +### Module-Based Organization |
| 43 | +The codebase is organized into 50+ feature modules under `Modules/`. Each module encapsulates: |
| 44 | +- **Controller:** HTTP request handlers (endpoint logic) |
| 45 | +- **Routes:** Route definitions (registered via `exports.init(app)`) |
| 46 | +- **Helpers:** Business logic and utilities |
| 47 | +- **Schema:** Mongoose schema (if applicable) |
| 48 | + |
| 49 | +### Centralized MongoDB Abstraction |
| 50 | +All database queries go through a single function: |
| 51 | +```javascript |
| 52 | +const result = await MongoDbCrudOpration(companyId, mongoObj, 'findOne'); |
| 53 | +``` |
| 54 | + |
| 55 | +This prevents: |
| 56 | +- SQL-injection-like vulnerabilities |
| 57 | +- Inconsistent query patterns |
| 58 | +- Data scoping bugs (missing companyId) |
| 59 | + |
| 60 | +### Company-Scoped Multi-Tenancy |
| 61 | +Every operation includes `companyId` for strict data isolation: |
| 62 | +- **Database level:** All queries filtered by companyId |
| 63 | +- **API level:** companyId comes from JWT or request params |
| 64 | +- **Security:** Prevents accidental cross-company data access |
| 65 | + |
| 66 | +### Error Propagation via Middleware |
| 67 | +Errors don't throw — they're set on `req.errorMessageObject` and `next()` is called: |
| 68 | +```javascript |
| 69 | +try { |
| 70 | + // logic |
| 71 | +} catch (error) { |
| 72 | + req.errorMessageObject = { message: error.message, statusCode: 400 }; |
| 73 | + next(); // Passes to centralized error handler |
| 74 | +} |
| 75 | +``` |
| 76 | + |
| 77 | +### Cache Invalidation Pattern |
| 78 | +In-memory cache (node-cache) is invalidated after mutations: |
| 79 | +```javascript |
| 80 | +const cacheKey = `project:${projectId}`; |
| 81 | +MongoDbCrudOpration(...).then(() => { |
| 82 | + removeCache(cacheKey); // Clear stale data |
| 83 | +}); |
| 84 | +``` |
| 85 | + |
| 86 | +### Real-Time Events via Socket.io |
| 87 | +After data mutations, Socket.io broadcasts events to connected clients: |
| 88 | +```javascript |
| 89 | +require('../event/socketEventEmitter').emitEvent('TASK_UPDATED', { |
| 90 | + taskId, projectId, changes |
| 91 | +}); |
| 92 | +``` |
| 93 | + |
| 94 | +This enables LiveSync — all clients see updates in real-time. |
| 95 | + |
| 96 | +## API Versioning Strategy |
| 97 | + |
| 98 | +AlianHub maintains **v1 and v2 endpoints** for backward compatibility: |
| 99 | + |
| 100 | +- **v1 endpoints:** `/api/v1/projects/list` |
| 101 | +- **v2 endpoints:** `/api/v2/projects/list` (preferred) |
| 102 | + |
| 103 | +**Rules:** |
| 104 | +- v2 is the default for new routes |
| 105 | +- v1 remains for legacy clients |
| 106 | +- Never mix versions in a single module (consistency) |
| 107 | +- v2 may have enhanced request/response formats |
| 108 | + |
| 109 | +## Real-Time Architecture (Socket.io) |
| 110 | + |
| 111 | +Socket.io enables live updates when tasks/projects change: |
| 112 | + |
| 113 | +1. **Connection:** Client connects on page load |
| 114 | +2. **Event Listener:** Socket listens for events (`TASK_UPDATED`, `PROJECT_CREATED`, etc.) |
| 115 | +3. **Broadcast:** When mutation happens, server emits event to all connected clients |
| 116 | +4. **Update:** Client receives event and updates local state |
| 117 | + |
| 118 | +**Key Event Types:** |
| 119 | +- `TASK_CREATED`, `TASK_UPDATED`, `TASK_DELETED` |
| 120 | +- `PROJECT_CREATED`, `PROJECT_UPDATED`, `PROJECT_DELETED` |
| 121 | +- `COMMENT_ADDED` |
| 122 | +- `CHAT_MESSAGE` |
| 123 | + |
| 124 | +**Critical:** If you add a mutation, emit the corresponding event or other clients won't see the change. |
| 125 | + |
| 126 | +## Multi-Tenancy Implementation |
| 127 | + |
| 128 | +AlianHub supports running a single instance for multiple companies: |
| 129 | + |
| 130 | +1. **Database Isolation:** All collections include `companyId` field |
| 131 | +2. **Request Scoping:** companyId extracted from JWT or request params |
| 132 | +3. **Query Filtering:** Every MongoDB query filtered by companyId |
| 133 | +4. **Storage Scoping:** File uploads organized under company directory |
| 134 | +5. **API Scoping:** Each endpoint validates user belongs to that company |
| 135 | + |
| 136 | +**Security Critical:** |
| 137 | +- Missing `companyId` in query → data leak |
| 138 | +- Hardcoded `companyId` → wrong company access |
| 139 | +- JWT validation failure → authentication bypass |
| 140 | + |
| 141 | +## State Management Layers |
| 142 | + |
| 143 | +### Backend State |
| 144 | +- **Database:** Persistent state (projects, tasks, users) |
| 145 | +- **In-Memory Cache:** Frequently accessed data (projects, user settings) |
| 146 | +- **Socket.io State:** Connected client list |
| 147 | + |
| 148 | +### Frontend State |
| 149 | +- **Vuex/Pinia Store:** Global app state (current project, user, UI) |
| 150 | +- **Local Component State:** Component-level data (form inputs, modals) |
| 151 | +- **Server State:** Fetch from API when needed |
| 152 | + |
| 153 | +## Authentication Flow |
| 154 | + |
| 155 | +1. **Login:** User sends credentials → JWT generated |
| 156 | +2. **Token Storage:** Client stores JWT in localStorage |
| 157 | +3. **Request:** Client sends JWT in Authorization header |
| 158 | +4. **Validation:** Express middleware validates JWT |
| 159 | +5. **Scoping:** Extracts userId and companyId from JWT |
| 160 | +6. **Authorization:** Endpoint checks user has permission |
| 161 | + |
| 162 | +**Token Format:** Standard JWT with `userId`, `companyId`, `role` claims |
| 163 | + |
| 164 | +## Storage Architecture |
| 165 | + |
| 166 | +Two storage backends supported via abstraction layer: |
| 167 | + |
| 168 | +### Wasabi (S3-Compatible) |
| 169 | +- **Use case:** Cloud deployments, scalable file storage |
| 170 | +- **Configuration:** WASABI_ACCESS_KEY, WASABI_SECRET_ACCESS_KEY, USERPROFILEBUCKET |
| 171 | +- **Implementation:** common-storage/common-wasabi.js |
| 172 | + |
| 173 | +### Local File System |
| 174 | +- **Use case:** Self-hosted deployments, development |
| 175 | +- **Configuration:** STORAGE_TYPE="server" |
| 176 | +- **Implementation:** common-storage/common-server.js |
| 177 | + |
| 178 | +Both support: |
| 179 | +- Image resizing (Sharp) |
| 180 | +- File uploads |
| 181 | +- Presigned URLs (for temporary access) |
| 182 | +- Cleanup after deletion |
0 commit comments