104 lines
4.2 KiB
Markdown
104 lines
4.2 KiB
Markdown
# Architecture
|
|
|
|
## Overview
|
|
|
|
dwellops-platform is a TypeScript-first monorepo for a modern HOA management platform. It is designed for:
|
|
|
|
- **Self-hosted single-tenant** deployments (one HOA per server).
|
|
- **Future SaaS multi-tenant** evolution without major structural changes.
|
|
|
|
## Workspace layout
|
|
|
|
```
|
|
apps/
|
|
api/ — Fastify REST API
|
|
web/ — Next.js 16 frontend (App Router)
|
|
packages/
|
|
config/ — shared config (ESLint, Prettier, Stylelint, tsconfig, Vitest)
|
|
types/ — TypeScript-only domain types
|
|
schemas/ — Zod schemas (reusable across frontend/backend)
|
|
db/ — Prisma client + data access boundary
|
|
i18n/ — locale helpers
|
|
ui/ — shared React UI primitives (CSS Modules + design tokens)
|
|
test-utils/ — test factories, render helpers
|
|
```
|
|
|
|
## Dependency rules
|
|
|
|
- `apps/api` → `@dwellops/db`, `@dwellops/types`, `@dwellops/schemas`
|
|
- `apps/web` → `@dwellops/ui`, `@dwellops/types`, `@dwellops/schemas`, `@dwellops/i18n`
|
|
- **Prisma is only ever imported from `@dwellops/db`** — never directly in apps.
|
|
- `packages/types` has zero runtime dependencies.
|
|
- `packages/schemas` depends only on Zod.
|
|
|
|
## Frontend structure (`apps/web/src`)
|
|
|
|
| Layer | Description | Rules |
|
|
| ------------- | ------------------------------------- | ------------------------------- |
|
|
| `components/` | Pure presentational building blocks | No API calls, no business logic |
|
|
| `widgets/` | Composed UI units with local behavior | May hold local state |
|
|
| `views/` | Page-level server compositions | Orchestrates data + layout |
|
|
|
|
All styling uses **CSS Modules + PostCSS**. Tailwind is explicitly forbidden.
|
|
Design tokens live in `packages/ui/src/tokens/tokens.css` as CSS custom properties.
|
|
|
|
## Backend structure (`apps/api/src`)
|
|
|
|
| Directory | Description |
|
|
| ----------- | --------------------------------------------------------------- |
|
|
| `plugins/` | Fastify plugins (cors, swagger, auth, etc.) |
|
|
| `modules/` | Feature modules (health, auth, hoa, …) each with routes + tests |
|
|
| `services/` | Business logic services |
|
|
| `lib/` | Utilities: env validation, error types, logger, auth instance |
|
|
|
|
**Route handlers are thin**: validate → check auth/permissions → call service → shape response.
|
|
|
|
## Authentication
|
|
|
|
Powered by [Better Auth](https://better-auth.com).
|
|
|
|
| Method | Status |
|
|
| -------------- | ------------------------------------- |
|
|
| Magic link | ✅ Enabled by default |
|
|
| Passkeys | ✅ Enabled by default |
|
|
| OIDC | ⚙️ Optional (set `OIDC_ENABLED=true`) |
|
|
| Email/password | ❌ Disabled |
|
|
|
|
Auth state is resolved in a Fastify plugin (`plugins/auth.ts`) on every request
|
|
and attached as `request.session`. Route-level enforcement uses the `requireAuth`
|
|
preHandler helper.
|
|
|
|
## Authorization
|
|
|
|
Roles: `ADMIN`, `BOARD_MEMBER`, `TREASURER`, `OWNER`, `TENANT`, `VIEWER`.
|
|
|
|
A `Membership` record connects a `User` to an `Hoa` with a `Role`, optionally
|
|
scoped to a `Unit`. This enables future multi-tenant expansion: a user can hold
|
|
different roles in different HOAs.
|
|
|
|
## Database
|
|
|
|
PostgreSQL + Prisma. All access goes through `@dwellops/db`.
|
|
|
|
Core models:
|
|
|
|
- `User`, `Session`, `Account`, `Verification` — Better Auth required
|
|
- `Hoa` — homeowners association
|
|
- `Unit` — dwelling unit within an HOA
|
|
- `Membership` — user ↔ HOA ↔ role ↔ optional unit
|
|
- `AuditLog` — immutable record of sensitive actions
|
|
|
|
## Internationalization
|
|
|
|
All user-facing strings use [next-intl](https://next-intl.dev). Translation files:
|
|
|
|
- `apps/web/messages/<locale>.json` — aggregated messages (do not edit directly)
|
|
- `src/.../translations.json` — component-local translation fragments
|
|
- `scripts/aggregate-translations.ts` — merges component files into the messages file
|
|
|
|
## Audit logging
|
|
|
|
`AuditService` (`apps/api/src/services/audit.service.ts`) records sensitive
|
|
actions to the `AuditLog` table. It never throws — audit failures are logged
|
|
but do not block primary operations.
|