| Requirement | What Was Scoped | What Was Built | Status |
|---|---|---|---|
| Property Management | Add, edit, archive properties | Full CRUD, multi-type (Flat/House/HMO), portfolio view, PostGIS geospatial fields | ✓ Live |
| Tenancy Management | Agreements, dates, status | Full tenancy lifecycle, status engine, rent/deposit tracking, renewal tracking | ✓ Live |
| Rent Collection | Stripe payments, reminders | Stripe integration, payment history, overdue detection, Bull queue email reminders | ✓ Live |
| Compliance Tracking | 6 certificate types, Awaab's Law | All 6 types, expiry tracking, RAG status, automated alerts at 90/30/7 days, PDF export | ✓ Live |
| Maintenance & Repairs | Requests, contractor assignment | Full workflow (Open→Resolved), Awaab's deadlines, cost tracking, photo uploads | ✓ Live |
| Document Management | Upload, categorise, download | MinIO storage, presigned downloads, category tags, bulk export | ✓ Live |
| Financial Reporting | Rent roll, arrears, forecasting | Full financial dashboard, rent roll, arrears report, income/expense, CSV/PDF export | ✓ Live |
| Tenant Portal | View tenancy, pay rent, requests | Dedicated tenant interface, payment gateway, maintenance requests, notifications | ✓ Live |
| Notifications | In-app bell, email alerts | Real-time bell with unread count, email via Nodemailer, full history, mark-read | ✓ Live |
| Audit Logging | Immutable trail, searchable | Every action logged, admin panel with filters (user/action/entity/date), pagination | ✓ Live |
| Admin Panel | Not in original spec | User management (roles, activate/deactivate), audit log viewer — added proactively | ✓ Bonus |
| Subscription Tiers | Stripe subscriptions, RBAC | Schema built, Stripe webhook ready — UI config pending (Phase 2 item) | ⚡ Phase 2 |
| Layer | Technology | Version | Why This Choice |
|---|---|---|---|
| Frontend | Next.js (App Router) | 14 | Server-side rendering, React Server Components, SEO-ready. App Router gives layout nesting and streaming. |
| Backend API | NestJS | 10 | TypeScript-native, decorator-based, modular architecture. Built-in DI, Guards, Interceptors. Scales cleanly. |
| Language | TypeScript | 5 | End-to-end type safety. Shared types package ensures frontend/backend contract is never broken. |
| Database | PostgreSQL + PostGIS | 15 | Relational integrity for tenancy/payment data. PostGIS extension adds geospatial queries for future map features. |
| ORM | Prisma | 7 | Type-safe schema-as-code, auto-generated client, migration system. Single source of truth for DB shape. |
| Auth | JWT (access + refresh) | — | Stateless authentication. Access token (15 min) + refresh token (7 days) pattern. Bcrypt password hashing. |
| Payments | Stripe | — | Industry standard. Handles PCI compliance. Supports rent collection, subscriptions, deposits. |
| File Storage | MinIO | — | S3-compatible, self-hosted on the same VPS. No AWS dependency. Full data ownership. |
| Background Jobs | Bull + Redis | — | Queue-based job processing for email reminders, compliance alerts, PDF generation. Retries, scheduling. |
| Nodemailer | — | Sends via SMTP. Rent reminders, compliance expiry alerts, maintenance updates. | |
| Styling | Tailwind CSS | 3 | Utility-first. No CSS files to maintain. Consistent design tokens. Works perfectly with component patterns. |
| State (Frontend) | TanStack Query | 5 | Server state management. Caching, background refetch, optimistic updates. Replaces Redux for data fetching. |
| Reverse Proxy | Nginx | Alpine | Routes /api/* to NestJS, /* to Next.js. Cloudflare IP allowlist, rate limiting, security headers. |
| Containerisation | Docker + Compose | — | 6-service stack runs identically in dev and production. No environment drift. Kubernetes-ready when needed. |
| CDN / DDoS | Cloudflare | — | Proxy in front of VPS. Handles SSL termination, DDoS protection, firewall rules. |
/api/auth/* → NestJS, strict rate limit (5 req/min)/api/stripe/webhook → NestJS, no rate limit (Stripe needs raw body)/api/* → NestJS, standard rate limit (30 req/sec)/* → Next.js (SSR pages)| Model | Key Fields | Purpose |
|---|---|---|
| User | email, passwordHash, role, isActive, refreshTokenHash | All platform users (all roles) |
| Property | address, type, ownerId, managerId, geom (PostGIS), isActive | Portfolio of managed properties |
| Tenancy | propertyId, tenantId, startDate, endDate, rentAmount, status, depositAmount | Tenancy agreements |
| Payment | tenancyId, amount, dueDate, paidAt, stripePaymentId, status | Rent payment records |
| ComplianceItem | propertyId, type (Gas/EICR/EPC…), expiryDate, status, alertSent | Certificate tracking |
| MaintenanceRequest | propertyId, tenantId, status, priority, contractorId, AwaaabDeadline | Repair jobs |
| Model | Key Fields |
|---|---|
| Document | propertyId, uploadedBy, fileKey (MinIO), type, category, fileSize |
| AuditLog | userId, action, entity, entityId, metadata (JSON), ipAddress, createdAt |
| Notification | userId, type, title, message, read, relatedEntity |
| Subscription | userId, stripeSubscriptionId, plan, status, currentPeriodEnd |
| MaintenancePhoto | requestId, fileKey, uploadedBy, stage (before/after) |
feature.service.ts. This is where Prisma queries live.@Get/@Post/@Patch in feature.controller.ts with correct @Roles() guard.prisma/schema.prisma, run pnpm prisma migrate dev --name description locally.sudo docker compose build api from the docker/ directory.sudo docker compose up -d api — zero-downtime, migrations run automatically on startup.@UseGuards(JwtAuthGuard, RolesGuard) at class level. Add @Roles(UserRole.ADMIN) on specific methods to restrict access. Use @CurrentUser() decorator to get the calling user.PrismaService and call this.prisma.auditLog.create() after significant mutations. Include userId, action (e.g. "CREATE_PROPERTY"), entity, entityId.EmailService to send transactional emails. For scheduled/background jobs, inject @InjectQueue('notifications') and add to the Bull queue with a delay.src/lib/api/feature.ts using the apiClient axios wrapper. Always type the response.FeatureView.tsx in src/components/dashboard/. Use useQuery for data, useMutation for writes.page.tsx inside src/app/(dashboard)/feature/. Keep it thin — just renders the View component.sudo docker compose build web — Next.js builds to a standalone output inside the image.sudo docker compose up -d web — hot-swaps the web container, nginx continues serving.cn() utility for conditional classes. Brand blue: #2563EB. Gray scale from gray-50 to gray-900.useQuery({ queryKey: ['feature', id], queryFn: api.get }). For mutations use useMutation + queryClient.invalidateQueries to refresh on success./users/me. Use useQuery(['users', 'me']) from the same cache to get the role in any component.sm:, md:, lg:. Dashboard has a fixed sidebar — on mobile consider a collapsible drawer (Phase 2).docker/.env — compose variable substitution values.env (root) — all secrets, JWT keys, Stripe keysdocker/nginx/nginx.conf — IP allowlist, rate limitsapps/api/prisma/schema.prisma — single source of truth for DBapps/api/src/common/guards/ — JWT + RBAC guards, all routes depend on these| Variable Group | Variables | Where Used |
|---|---|---|
| Database | POSTGRES_PASSWORD, DATABASE_URL | API container, Prisma |
| JWT | JWT_SECRET, JWT_REFRESH_SECRET, JWT_EXPIRES_IN | NestJS auth module |
| Stripe | STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_PUBLISHABLE_KEY | API (payments module) |
| MinIO | MINIO_ACCESS_KEY, MINIO_SECRET_KEY, MINIO_BUCKET_NAME | API (documents module) |
| SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS | API (notifications module) | |
| Frontend | NEXT_PUBLIC_API_URL, NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY | Baked into Next.js build |
docker compose build web, not just a restart.nest-easy/.env — loaded by API/web containers via env_file: in compose. Contains ALL secrets.nest-easy/docker/.env — used by docker compose for ${VAR} substitution when building DATABASE_URL etc. Must stay in sync with root .env passwords..env file (should be in .gitignore)sk_live_ or whsec_ keys.env.example file in the repo shows all keys with placeholder values.server_name. Flip the platform to live branding with real Stripe keys.| # | Feature | Description | Value to Client | Effort |
|---|---|---|---|---|
| P1 | Subscription Billing UI | Landlord self-serve subscription management. Starter/Professional/Enterprise tiers. Upgrade/downgrade flows. Stripe Customer Portal embed. | Direct revenue generation for Nest Easy | Medium |
| P2 | Digital Tenancy Signing | DocuSign / HelloSign integration. Tenants sign agreements digitally. Audit trail on signatures. Auto-store in document library. | Eliminates paper entirely, faster onboarding | Medium |
| P3 | Property Map View | Map view of portfolio using PostGIS data already stored. Heat map of compliance status. Geographic grouping. | PostGIS schema already ready — UI work only | Low |
| P4 | Mobile App | React Native app (shares types from monorepo). Tenant app: payments, requests, notifications. Contractor app: job updates, evidence upload. | Field access for tenants and contractors | High |
| P5 | PDF Compliance Packs | One-click export of all compliance documents for a property as a branded PDF bundle. Puppeteer already installed. | Required for letting agent compliance audits | Low |
| P6 | Two-Factor Auth | TOTP (Google Authenticator) for admin/manager accounts. FIDO2 passkey support. SMS OTP fallback. | Security upgrade for high-value accounts | Medium |
| P7 | CI/CD Pipeline | GitHub Actions: lint → test → build → push Docker images → SSH deploy. Replaces manual SSH deployments. | Faster, safer deployments with audit trail | Low |
| P8 | White-Label Multi-Tenancy | Allow Nest Easy to re-sell the platform to other agents. Custom branding per tenant. Isolated data per organisation. | Platform becomes a SaaS product | High |