226 lines
7.3 KiB
Markdown
226 lines
7.3 KiB
Markdown
|
|
# Cross-Cutting Concerns
|
||
|
|
|
||
|
|
Aspects that span all phases and must be maintained consistently throughout development.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## API Versioning
|
||
|
|
|
||
|
|
- All endpoints prefixed with `/api/v1/`
|
||
|
|
- Version is part of the URL, not headers
|
||
|
|
- When breaking changes are needed, introduce `/api/v2/` alongside v1
|
||
|
|
- Deprecation: v1 endpoints log warnings 3 months before removal
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Pagination
|
||
|
|
|
||
|
|
All list endpoints use **cursor-based pagination**:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Request
|
||
|
|
GET /api/v1/products?cursor=abc123&limit=20
|
||
|
|
|
||
|
|
// Response
|
||
|
|
{
|
||
|
|
"data": [...],
|
||
|
|
"pagination": {
|
||
|
|
"cursor": "def456", // Pass as next cursor, null = no more pages
|
||
|
|
"hasMore": true,
|
||
|
|
"total": 150 // Total count (optional, can be expensive)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
- Default `limit`: 20, max: 100
|
||
|
|
- Cursor is an opaque string (encoded `_id` or composite sort key)
|
||
|
|
- Prefer cursor over offset/skip for MongoDB performance
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Audit Trail
|
||
|
|
|
||
|
|
Every domain document includes:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
{
|
||
|
|
createdAt: Date; // Set on creation, never modified
|
||
|
|
updatedAt: Date; // Updated on every modification
|
||
|
|
createdBy: string; // userId who created
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
For sensitive operations (deletes, status transitions, admin actions), maintain an `AuditLog` collection:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
interface AuditLog {
|
||
|
|
id: string;
|
||
|
|
householdId: string;
|
||
|
|
userId: string;
|
||
|
|
action: string; // 'product.delete', 'pantry.transition', etc.
|
||
|
|
entityType: string; // 'Product', 'PantryItem', etc.
|
||
|
|
entityId: string;
|
||
|
|
changes?: Record<string, { from: any; to: any }>;
|
||
|
|
timestamp: Date;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Implemented as a NestJS interceptor that logs after successful mutations.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Error Handling
|
||
|
|
|
||
|
|
Consistent error response format across all endpoints:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
interface ApiError {
|
||
|
|
statusCode: number;
|
||
|
|
error: string; // HTTP status text
|
||
|
|
message: string; // Human-readable message
|
||
|
|
details?: any; // Validation errors, etc.
|
||
|
|
timestamp: string;
|
||
|
|
path: string;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Global exception filter in NestJS catches:
|
||
|
|
|
||
|
|
- `ValidationException` → 400
|
||
|
|
- `UnauthorizedException` → 401
|
||
|
|
- `ForbiddenException` → 403
|
||
|
|
- `NotFoundException` → 404
|
||
|
|
- `ConflictException` → 409 (e.g., duplicate barcode)
|
||
|
|
- `TooManyRequestsException` → 429 (LLM rate limit)
|
||
|
|
- Unhandled errors → 500 (log stack trace, return generic message)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Real-Time (WebSocket)
|
||
|
|
|
||
|
|
NestJS `@WebSocketGateway` with Socket.IO:
|
||
|
|
|
||
|
|
- **Authentication**: validate JWT on connection
|
||
|
|
- **Rooms**: one room per `householdId` — all household members receive household events
|
||
|
|
- **Namespaces**: optional per-feature namespaces (`/pantry`, `/shopping`)
|
||
|
|
|
||
|
|
Events introduced per phase:
|
||
|
|
|
||
|
|
| Phase | Events |
|
||
|
|
| ----- | ------------------------------------------------------------------------------------------------ |
|
||
|
|
| 3 | `pantry:item-added`, `pantry:item-updated`, `pantry:freshness-alert` |
|
||
|
|
| 5 | `shopping:item-checked`, `shopping:item-added`, `shopping:item-removed`, `shopping:list-updated` |
|
||
|
|
| 4 | `meal-plan:updated` |
|
||
|
|
|
||
|
|
Frontend pattern:
|
||
|
|
|
||
|
|
- Connect on app mount, join household room
|
||
|
|
- Use React context/Zustand store to distribute events to components
|
||
|
|
- Optimistic UI updates with server reconciliation
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Testing Strategy
|
||
|
|
|
||
|
|
### Unit Tests (per module, per phase)
|
||
|
|
|
||
|
|
- **Services**: business logic, calculations (nutrition calculator, freshness calculator, suggestion scoring)
|
||
|
|
- **Guards/Interceptors**: auth, household scoping
|
||
|
|
- **Mocking**: MongoDB operations mocked for service tests
|
||
|
|
- **Framework**: Jest
|
||
|
|
|
||
|
|
### Integration Tests (per module)
|
||
|
|
|
||
|
|
- **In-memory MongoDB** (`mongodb-memory-server`) or test containers
|
||
|
|
- Test full request → service → database → response cycle
|
||
|
|
- **Framework**: Supertest + Jest
|
||
|
|
|
||
|
|
### E2E Tests (per phase milestone)
|
||
|
|
|
||
|
|
- Full Docker Compose stack
|
||
|
|
- Automated script that exercises the happy path:
|
||
|
|
1. Register/login user
|
||
|
|
2. Create household
|
||
|
|
3. Add products
|
||
|
|
4. Create recipe
|
||
|
|
5. Stock pantry
|
||
|
|
6. Generate meal plan
|
||
|
|
7. Create shopping list
|
||
|
|
8. Check off items → add to pantry
|
||
|
|
- **Framework**: Supertest or Playwright (for web UI)
|
||
|
|
|
||
|
|
### Frontend Tests
|
||
|
|
|
||
|
|
- **Component tests**: React Testing Library
|
||
|
|
- **Hook tests**: `@testing-library/react-hooks`
|
||
|
|
- **E2E**: Playwright for critical flows (login, add product, create recipe)
|
||
|
|
|
||
|
|
### Test Coverage Targets
|
||
|
|
|
||
|
|
| Type | Target |
|
||
|
|
| ----------- | -------------------- |
|
||
|
|
| Unit | 80%+ |
|
||
|
|
| Integration | Key flows covered |
|
||
|
|
| E2E | Happy path per phase |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Mobile Readiness
|
||
|
|
|
||
|
|
Design decisions to facilitate React Native development later:
|
||
|
|
|
||
|
|
1. **All business logic in API**: the web frontend is a thin UI layer. Mobile will consume the same API.
|
||
|
|
2. **Shared types package**: `packages/shared` is platform-agnostic TypeScript. Mobile imports it directly.
|
||
|
|
3. **No SSR dependencies in shared code**: avoid Next.js-specific imports in `packages/shared`.
|
||
|
|
4. **API client layer**: `packages/web/src/services/api.ts` wraps fetch/axios with auth. Mobile will have its own but same pattern.
|
||
|
|
5. **WebSocket events**: same events work on mobile (Socket.IO has React Native support).
|
||
|
|
6. **Auth**: Keycloak has React Native OIDC libraries (`react-native-app-auth`).
|
||
|
|
7. **Image handling**: API accepts standard multipart uploads — works from any client.
|
||
|
|
8. **Push notifications**: Phase 3 starts with in-app notifications. Mobile phase adds Firebase Cloud Messaging (FCM) as a notification channel.
|
||
|
|
|
||
|
|
When the mobile phase begins:
|
||
|
|
|
||
|
|
- Add `packages/mobile` (React Native via Expo or bare workflow)
|
||
|
|
- Share from `packages/shared`
|
||
|
|
- Build mobile-optimized UI for key flows: pantry check, shopping list, quick add
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Security
|
||
|
|
|
||
|
|
- **Authentication**: All API routes require valid Keycloak JWT (except health check)
|
||
|
|
- **Authorization**: Household-scoped — users can only access data for their households
|
||
|
|
- **Input validation**: Zod schemas validate all inputs; NestJS validation pipe rejects invalid requests
|
||
|
|
- **Rate limiting**: per-IP and per-household (configurable)
|
||
|
|
- **CORS**: restricted to known origins
|
||
|
|
- **Helmet**: HTTP security headers via `@nestjs/helmet`
|
||
|
|
- **Secrets**: environment variables, never committed; `.env.example` documents required vars
|
||
|
|
- **Image uploads**: validate file type and size; store in local volume or S3-compatible storage
|
||
|
|
- **MongoDB**: authenticated access, least-privilege user for the app
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Performance
|
||
|
|
|
||
|
|
- **MongoDB indexes**: reviewed and optimized per phase (documented in each phase doc)
|
||
|
|
- **Query profiling**: enable MongoDB slow query log in dev; review before phase sign-off
|
||
|
|
- **Caching**: consider Redis for:
|
||
|
|
- Product search results (short TTL)
|
||
|
|
- Freshness rule lookups (rarely change)
|
||
|
|
- LLM response caching (identical inputs)
|
||
|
|
- **Denormalization**: nutrition on recipes, product names on pantry items — reduces lookups
|
||
|
|
- **Pagination**: cursor-based, no `skip()` for large collections
|
||
|
|
- **Compression**: gzip responses via NestJS middleware
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Observability (Future)
|
||
|
|
|
||
|
|
Not in initial phases, but plan for:
|
||
|
|
|
||
|
|
- **Structured logging**: Pino or Winston with JSON format
|
||
|
|
- **Health checks**: `/api/v1/health` returns status of MongoDB, Keycloak connectivity
|
||
|
|
- **Metrics**: Prometheus-compatible endpoint (NestJS has plugins)
|
||
|
|
- **Tracing**: OpenTelemetry for request tracing across services
|
||
|
|
- **Error tracking**: Sentry integration (optional, self-hosted instance possible)
|