Synclet is an AI-assisted Django/Vue reference application for exploring contact synchronization, typed APIs, validation, testing, and delivery workflows. Its deliberately small reference use case manages private contact lists and synchronizes contacts from CSV files. The feature is real enough to demonstrate architecture, authentication, validation, transactions, generated API types, UI states, automated tests, containers, and CI without pretending to be a complete SaaS product.
- Email/password registration, session login, logout, and current-user lookup with CSRF protection.
- User-isolated contact CRUD, search, ordering, and pagination.
- Atomic CSV import with file limits, row validation, deterministic duplicate handling, upserts, and persisted synchronization results.
- Dashboard, contact management, import results, and synchronization history in a responsive Vue UI.
- OpenAPI/Swagger documentation and generated TypeScript types used by one central API client.
- Ruff, pytest, Biome, TypeScript/Vue typechecks, Vitest, Playwright, Docker healthchecks, and CI.
- Correlated JSON request logs and Prometheus-compatible HTTP/synchronization metrics.
Synclet uses a modular Django monolith and a Vue single-page application. PostgreSQL is the only
stateful infrastructure dependency. Synchronous CSV processing is isolated behind a SyncProvider
interface so a background job or external provider can be added without changing contact ownership
rules.
flowchart LR
Browser[Vue SPA] -->|same-origin session + CSRF| API[Django REST API]
API --> Accounts[Accounts]
API --> Contacts[Contacts]
API --> Sync[Sync orchestration]
Sync --> CSV[CSV provider]
Accounts --> DB[(PostgreSQL)]
Contacts --> DB
Sync --> DB
API --> Schema[OpenAPI schema]
Schema --> Types[Generated TypeScript types]
Types --> Client[Typed API client]
Client --> Browser
See Architecture, API, Security, and ADR 0001 for the detailed decisions.
apps/
api/ Django project and domain logic
web/ Vue 3/Vite single-page application
packages/
api-client/ Typed, framework-independent browser API client
api-contract/ Committed OpenAPI schema and generated TypeScript types
config-biome/ Shared Biome configuration
config-typescript/ Shared strict TypeScript configurations
shared/ Framework-independent constants
ui/ Reusable project-specific Vue components
docs/
adr/ Architecture decision records
.github/workflows/ci.yml Continuous integration
Domain logic intentionally remains in apps/api; packages contain only genuinely reusable code.
Install mise and Docker with the Compose plugin. mise installs the pinned Node.js, pnpm, Python, uv, and Task versions; no globally installed project packages are required.
mise install
pnpm install
task setup
task docker:up
task db:migrate
task devCopy .env.example to .env before changing local defaults. Never reuse its development values in
production. The web application runs at http://localhost:5173, Django at
http://localhost:8000, Swagger at http://localhost:8000/api/docs/, and PostgreSQL at port 5432.
task dev starts Django and Vite together. Vite proxies /api to Django so cookies and CSRF behave
like the production same-origin topology.
To run the complete containerized stack instead:
task docker:fullThe web application is then available on http://localhost:8080. The full Compose profile applies
migrations before starting Gunicorn. The default task docker:up starts only PostgreSQL to avoid
conflicting with native development servers.
| Command | Purpose |
|---|---|
task setup |
Install locked dependencies and regenerate the API contract |
task dev |
Run Django and Vite concurrently |
task dev:api / task dev:web |
Run one development server |
task test |
Run pytest and all workspace unit tests |
task test:api / task test:web |
Run one unit-test suite |
task test:e2e |
Migrate and run Playwright against the real local stack |
task lint |
Run Ruff and Biome without rewriting files |
task format |
Apply Ruff and Biome formatting/fixes |
task typecheck |
Run Django checks and all TypeScript/Vue typechecks |
task build |
Run Django deployment checks and all production builds |
task api:openapi |
Regenerate and validate openapi.yaml |
task api:sync |
Regenerate OpenAPI and TypeScript API types |
task api:lock |
Refresh apps/api/uv.lock |
task api:shell |
Open the Django shell |
task api:manage -- <command> |
Run an arbitrary Django management command |
task db:migrate |
Apply migrations |
task db:reset |
Confirm, delete the named local DB volume, recreate, and migrate |
task docker:up / task docker:down |
Start or stop local infrastructure |
task docker:full |
Build and run the containerized full stack |
Django serializers and views are the source of truth. drf-spectacular writes
packages/api-contract/openapi.yaml; openapi-typescript generates
packages/api-contract/src/schema.d.ts. Applications must import API types from the contract or
client package rather than redefine them.
After any backend API change, run:
task api:syncCI repeats generation and fails if committed contract files drift. See API documentation.
- pytest uses PostgreSQL and covers auth/CSRF, isolation, CRUD, search, pagination, CSV validation, duplicates, upserts, rollback behavior, synchronization states, dashboard isolation, and errors.
- Vitest covers the API client's CSRF/multipart/error behavior and frontend auth/error state.
- Playwright registers a real user, creates a contact, imports CSV, and verifies synchronization history through live Django and Vite servers.
- CI additionally checks migrations, OpenAPI drift, production builds, and both Dockerfiles.
Run the normal quality gate with:
task lint
task typecheck
task test
task buildThe repository contains no real secrets. Production fails fast without DJANGO_SECRET_KEY. Session
cookies are HTTP-only, unsafe requests require a CSRF token, CORS/CSRF origins are explicit, user
ownership is applied at both queryset and import-service boundaries, uploads are limited and
validated, and API errors hide internal exceptions. Production should terminate TLS before Django
and set secure-cookie/redirect/HSTS values appropriately. Read Security before
deployment.
- CSV import is synchronous and capped at 5 MiB/10,000 rows; there is no Celery or Redis.
- CSV is the only provider. Google Contacts, Microsoft Graph, and CRM integrations are interfaces, not fake implementations.
- There is no email verification, password reset, rate limiter, audit log, organization model, or multi-factor authentication.
- Registration immediately creates a session. Accounts are single-user workspaces.
- Deployment manifests, managed secret stores, observability storage, backup automation, and zero-downtime migration orchestration are environment-specific and intentionally excluded.
- TypeScript 5.9.3 is pinned because
openapi-typescript7.13 declares TypeScript 5 compatibility; upgrading to TypeScript 7 waits for upstream support.
- Add password reset/email verification and request throttling.
- Move large imports to a durable queue while preserving
SyncProvidersemantics. - Add encrypted provider credentials and an OAuth lifecycle for one real provider.
- Add organization membership and role-based permissions if the product becomes collaborative.
- Add tracing, Prometheus alert rules/dashboards, backup/restore drills, deployment manifests, and dependency scanning for the chosen hosting platform.




