# Phase 1: Foundation — Subplan ## Goal Stand up the monorepo skeleton with Docker Compose orchestration, a working FastAPI backend with database migrations and JWT authentication, a React frontend shell with auth pages and protected routing, and an admin seed script — so that a user can register, log in, see a dashboard shell, and an admin account exists. ## Prerequisites - Git repository initialized (done) - Docker and Docker Compose installed on the development machine - Node.js 20+ and Python 3.12+ available locally for development outside containers - `GeneralPlan.md`, `README.md`, `.gitignore` already in repo (done) --- ## Database Schema (Phase 1) ### `users` table | Column | Type | Constraints | |---|---|---| | id | UUID | PK, default gen_random_uuid() | | email | VARCHAR(255) | UNIQUE, NOT NULL | | username | VARCHAR(100) | UNIQUE, NOT NULL | | hashed_password | VARCHAR(255) | NOT NULL | | full_name | VARCHAR(255) | NULL | | role | VARCHAR(20) | NOT NULL, default 'user', CHECK IN ('user','admin') | | is_active | BOOLEAN | NOT NULL, default true | | max_chats | INTEGER | NOT NULL, default 10 | | oauth_provider | VARCHAR(50) | NULL | | oauth_provider_id | VARCHAR(255) | NULL | | telegram_chat_id | BIGINT | NULL | | avatar_url | VARCHAR(500) | NULL | | created_at | TIMESTAMPTZ | NOT NULL, default now() | | updated_at | TIMESTAMPTZ | NOT NULL, default now(), on-update trigger | Indexes: `ix_users_email` (unique), `ix_users_username` (unique). ### `sessions` table | Column | Type | Constraints | |---|---|---| | id | UUID | PK, default gen_random_uuid() | | user_id | UUID | FK -> users.id ON DELETE CASCADE, NOT NULL | | refresh_token_hash | VARCHAR(255) | NOT NULL | | device_info | VARCHAR(500) | NULL | | ip_address | VARCHAR(45) | NULL | | expires_at | TIMESTAMPTZ | NOT NULL | | created_at | TIMESTAMPTZ | NOT NULL, default now() | Indexes: `ix_sessions_user_id`, `ix_sessions_refresh_token_hash`. --- ## API Endpoint Signatures (Phase 1 Auth) All under `/api/v1/auth/`. | Method | Path | Request Body | Response | Notes | |---|---|---|---|---| | POST | `/register` | `{ email, username, password, full_name? }` | `{ user, access_token, refresh_token }` | Creates user + session. 201 | | POST | `/login` | `{ email, password, remember_me?: bool }` | `{ user, access_token, refresh_token }` | `remember_me` controls refresh expiry (30d vs 24h). 200 | | POST | `/refresh` | `{ refresh_token }` | `{ access_token, refresh_token }` | Rotates refresh token (old invalidated). 200 | | POST | `/logout` | `{ refresh_token }` | `204 No Content` | Deletes session row | | GET | `/me` | — | `UserResponse` | Requires valid access token. 200 | **Token details:** - Access token: JWT, HS256, 15-minute expiry, payload: `{ sub: user_id, role, exp, iat, jti }` - Refresh token: opaque 64-byte hex string; only its SHA-256 hash is stored in `sessions.refresh_token_hash` - Password hashing: bcrypt via `passlib[bcrypt]` --- ## Frontend Route Structure (Phase 1) | Path | Component | Auth Required | Notes | |---|---|---|---| | `/login` | LoginPage | No | Redirects to `/` if already authenticated | | `/register` | RegisterPage | No | Redirects to `/` if already authenticated | | `/` | DashboardPage | Yes | Wrapped in AppLayout (sidebar + header) | | `*` | NotFoundPage | No | 404 fallback | --- ## Tasks ### A. Project Infrastructure & Docker (Tasks 1–6) - [x] **A1.** Create root-level config files: `.env.example` (all env vars: `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`, `SECRET_KEY`, `BACKEND_CORS_ORIGINS`, `FIRST_ADMIN_EMAIL`, `FIRST_ADMIN_PASSWORD`, `FIRST_ADMIN_USERNAME`), `docker-compose.yml`, `docker-compose.dev.yml` (volume mounts + hot reload). - [x] **A2.** Create `nginx/nginx.conf` with reverse proxy rules: `/api/*` → `backend:8000`, `/` → `frontend:80`. Include WebSocket upgrade headers for future `/ws/*`. Add gzip, security headers, `client_max_body_size 50M`. - [x] **A.** Create `nginx/Dockerfile` (FROM nginx:1.25-alpine, copy nginx.conf). - [x] **A.** Create `backend/Dockerfile` (Python 3.12-slim, install deps from pyproject.toml, run uvicorn). Create `backend/pyproject.toml` with dependencies: fastapi, uvicorn[standard], sqlalchemy[asyncio], asyncpg, alembic, pydantic-settings, passlib[bcrypt], python-jose[cryptography], python-multipart, httpx. Dev deps: pytest, pytest-asyncio, httpx. - [x] **A.** Create `frontend/Dockerfile` (multi-stage: Node 20-alpine build stage + nginx:alpine serve stage). Create `frontend/package.json` with dependencies: react, react-dom, react-router-dom, axios, zustand, @tanstack/react-query, i18next, react-i18next, i18next-http-backend, i18next-browser-languagedetector, lucide-react, clsx, tailwind-merge, class-variance-authority. Dev deps: vite, @vitejs/plugin-react, typescript, tailwindcss, postcss, autoprefixer, @types/react, @types/react-dom. - [x] **A.** Verify `docker compose build` succeeds for all services and `docker compose up -d` starts postgres, backend, frontend, nginx. Backend health endpoint responds at `/api/v1/health`. Frontend serves through nginx. ### B. Backend Core Infrastructure (Tasks 7–13) - [x] **B.** Create `backend/app/__init__.py`, `backend/app/config.py` using pydantic-settings `BaseSettings`. Fields: `DATABASE_URL`, `SECRET_KEY`, `ACCESS_TOKEN_EXPIRE_MINUTES` (15), `REFRESH_TOKEN_EXPIRE_DAYS` (30), `REFRESH_TOKEN_EXPIRE_HOURS` (24), `CORS_ORIGINS`, `ENVIRONMENT`. - [x] **B.** Create `backend/app/database.py`: async engine, `async_sessionmaker`, `get_db` async generator, `Base = declarative_base()` with UUID pk mixin. - [x] **B.** Create `backend/app/models/__init__.py`, `backend/app/models/user.py`, `backend/app/models/session.py` matching the schema above. SQLAlchemy 2.0 `mapped_column` style. `updated_at` with `onupdate=func.now()`. - [x] **B0.** Set up Alembic: `backend/alembic.ini`, `backend/alembic/env.py` (async-compatible), `backend/alembic/script.py.mako`. Generate initial migration for users + sessions. - [x] **B1.** Create `backend/app/main.py`: `create_app()` factory. Lifespan runs Alembic `upgrade head` on startup. CORS middleware. Include API routers. `/api/v1/health` endpoint. - [x] **B2.** Create `backend/app/core/__init__.py`, `backend/app/core/security.py`: `hash_password`, `verify_password` (bcrypt), `create_access_token`, `decode_access_token`, `generate_refresh_token`, `hash_refresh_token` (SHA-256). - [x] **B3.** Create `backend/app/schemas/__init__.py`, `backend/app/schemas/auth.py`: `RegisterRequest`, `LoginRequest` (with `remember_me`), `RefreshRequest`, `TokenResponse`, `UserResponse`. Create `backend/app/schemas/common.py`: `MessageResponse`. ### C. Backend Auth Logic (Tasks 14–18) - [x] **C4.** Create `backend/app/services/__init__.py`, `backend/app/services/auth_service.py`: `register_user`, `login_user`, `refresh_tokens`, `logout_user`. Each manages session rows. Raises HTTPException on errors. - [x] **C5.** Create `backend/app/api/__init__.py`, `backend/app/api/deps.py`: `get_current_user` dependency (JWT → User), `require_admin` dependency. - [x] **C6.** Create `backend/app/api/v1/__init__.py`, `backend/app/api/v1/auth.py` router with 5 auth endpoints. - [x] **C7.** Create `backend/app/api/v1/router.py` to aggregate v1 sub-routers under `/api/v1`. Include in `main.py`. - [x] **C8.** Create `backend/scripts/seed_admin.py`: standalone async script, reads env, creates admin user if not exists. Idempotent. ### D. Frontend Infrastructure (Tasks 19–27) - [x] **D9.** Create `frontend/vite.config.ts` (React plugin, proxy `/api` to backend in dev), `frontend/tsconfig.json`, `frontend/tsconfig.node.json` with `@/` path alias. - [x] **D0.** Set up Tailwind CSS: `frontend/tailwind.config.ts`, `frontend/postcss.config.js`, `frontend/src/index.css` (Tailwind directives + shadcn CSS variables for dark mode). - [x] **D1.** Initialize shadcn/ui: `frontend/components.json`. Install components: Button, Input, Label, Card, DropdownMenu, Avatar, Sheet, Separator, Sonner. Place in `frontend/src/components/ui/`. - [x] **D2.** Set up i18next: `frontend/src/i18n.ts`, `frontend/public/locales/en/translation.json`, `frontend/public/locales/ru/translation.json` with keys for auth, layout, and common strings. - [x] **D3.** Create `frontend/src/api/client.ts`: Axios instance, request interceptor (attach token), response interceptor (401 → refresh → retry). Create `frontend/src/api/auth.ts` with typed API functions. - [x] **D4.** Create `frontend/src/stores/auth-store.ts` (Zustand, persisted): user, tokens, isAuthenticated, actions. Create `frontend/src/stores/ui-store.ts`: sidebarOpen, theme, persisted. - [x] **D5.** Create `frontend/src/components/shared/theme-provider.tsx`, `language-toggle.tsx`, `protected-route.tsx`. - [x] **D6.** Create `frontend/src/App.tsx`, `frontend/src/routes.tsx` (React Router v6), `frontend/src/main.tsx` entry point. - [x] **D7.** Set up TanStack Query: `frontend/src/lib/query-client.ts`. Wire `QueryClientProvider` in App. ### E. Frontend Auth Pages & Layout (Tasks 28–33) - [x] **E8.** Create `frontend/src/components/auth/login-form.tsx`: email + password + remember me + submit. Calls API, stores tokens, navigates to `/`. - [x] **E9.** Create `frontend/src/components/auth/register-form.tsx`: email, username, password, confirm password, full_name. Validation: email format, password min 8, passwords match, username 3-50 chars. - [x] **E0.** Create `frontend/src/pages/login.tsx` and `frontend/src/pages/register.tsx`: centered card layout, redirect if authenticated. - [x] **E1.** Create `frontend/src/components/layout/app-layout.tsx` (sidebar + main content), `frontend/src/components/layout/sidebar.tsx` (collapsible nav, placeholder items, mobile Sheet drawer). - [x] **E2.** Create `frontend/src/components/layout/header.tsx`: sidebar toggle, page title, language toggle, theme toggle, user dropdown with logout. - [x] **E3.** Create `frontend/src/pages/dashboard.tsx` (welcome card, placeholder stats), `frontend/src/pages/not-found.tsx` (404). ### F. Integration & Verification (Tasks 34–36) - [x] **F3.** Create `frontend/nginx.conf` for SPA fallback. Ensure multi-stage Dockerfile copies build output and nginx config. - [ ] **F35.** End-to-end smoke test: `docker compose up --build`. Verify: postgres + migrations, health endpoint, seed_admin, frontend loads, register, login, dashboard, logout, language toggle, theme toggle. - [x] **F3.** Write backend tests: `backend/tests/conftest.py` (async test client), `backend/tests/test_auth.py` (register, login, refresh, logout, invalid credentials, duplicate email, expired token). All pass with `pytest`. --- ## Files to Create ### Root - `.env.example` - `docker-compose.yml` - `docker-compose.dev.yml` ### nginx/ - `nginx/Dockerfile` - `nginx/nginx.conf` ### backend/ - `backend/Dockerfile` - `backend/pyproject.toml` - `backend/alembic.ini` - `backend/alembic/env.py` - `backend/alembic/script.py.mako` - `backend/alembic/versions/.py` - `backend/app/__init__.py` - `backend/app/main.py` - `backend/app/config.py` - `backend/app/database.py` - `backend/app/models/__init__.py` - `backend/app/models/user.py` - `backend/app/models/session.py` - `backend/app/schemas/__init__.py` - `backend/app/schemas/auth.py` - `backend/app/schemas/common.py` - `backend/app/core/__init__.py` - `backend/app/core/security.py` - `backend/app/services/__init__.py` - `backend/app/services/auth_service.py` - `backend/app/api/__init__.py` - `backend/app/api/deps.py` - `backend/app/api/v1/__init__.py` - `backend/app/api/v1/router.py` - `backend/app/api/v1/auth.py` - `backend/scripts/__init__.py` - `backend/scripts/seed_admin.py` - `backend/tests/__init__.py` - `backend/tests/conftest.py` - `backend/tests/test_auth.py` ### frontend/ - `frontend/Dockerfile` - `frontend/nginx.conf` - `frontend/package.json` - `frontend/vite.config.ts` - `frontend/tsconfig.json` - `frontend/tsconfig.node.json` - `frontend/tailwind.config.ts` - `frontend/postcss.config.js` - `frontend/components.json` - `frontend/index.html` - `frontend/src/main.tsx` - `frontend/src/App.tsx` - `frontend/src/routes.tsx` - `frontend/src/index.css` - `frontend/src/i18n.ts` - `frontend/src/vite-env.d.ts` - `frontend/src/lib/query-client.ts` - `frontend/src/lib/utils.ts` - `frontend/src/api/client.ts` - `frontend/src/api/auth.ts` - `frontend/src/stores/auth-store.ts` - `frontend/src/stores/ui-store.ts` - `frontend/src/components/ui/` (shadcn components) - `frontend/src/components/shared/theme-provider.tsx` - `frontend/src/components/shared/language-toggle.tsx` - `frontend/src/components/shared/protected-route.tsx` - `frontend/src/components/auth/login-form.tsx` - `frontend/src/components/auth/register-form.tsx` - `frontend/src/components/layout/app-layout.tsx` - `frontend/src/components/layout/sidebar.tsx` - `frontend/src/components/layout/header.tsx` - `frontend/src/pages/login.tsx` - `frontend/src/pages/register.tsx` - `frontend/src/pages/dashboard.tsx` - `frontend/src/pages/not-found.tsx` - `frontend/public/locales/en/translation.json` - `frontend/public/locales/ru/translation.json` --- ## Acceptance Criteria 1. `docker compose up --build` starts all 4 services (postgres, backend, frontend, nginx) without errors 2. `GET /api/v1/health` returns `{"status": "ok"}` 3. Alembic migration creates `users` and `sessions` tables with correct schema 4. `seed_admin.py` creates an admin user; re-running is idempotent 5. `POST /api/v1/auth/register` creates a new user and returns tokens 6. `POST /api/v1/auth/login` with valid credentials returns tokens; `remember_me=true` → 30-day refresh, `false` → 24-hour 7. `POST /api/v1/auth/refresh` rotates refresh token and returns new pair 8. `POST /api/v1/auth/logout` invalidates the session 9. `GET /api/v1/auth/me` with valid token returns user data; expired/invalid → 401 10. Frontend loads at `http://localhost`, unauthenticated users see login page 11. User can register, log in, and see dashboard with their name 12. Sidebar renders with placeholder navigation; header has theme toggle, language toggle, user menu with logout 13. Dark/light theme toggle works (persisted across reload) 14. Language toggle switches between English and Russian (persisted across reload) 15. Axios interceptor auto-refreshes access token on 401 and retries the request 16. All backend auth tests pass (`pytest`) --- ## Status **IN PROGRESS** — All code written. F35 (Docker smoke test) blocked: Docker not installed on this machine. Install Docker Desktop to complete end-to-end verification.