diff --git a/obsidian/.obsidian/workspace.json b/obsidian/.obsidian/workspace.json index 7f6bd9a..265d451 100644 --- a/obsidian/.obsidian/workspace.json +++ b/obsidian/.obsidian/workspace.json @@ -4,21 +4,17 @@ "type": "split", "children": [ { - "id": "83169ae72c9bc357", + "id": "af94f814e06296f9", "type": "tabs", "children": [ { - "id": "6f192f9477d55538", + "id": "7ac1561c78f61154", "type": "leaf", "state": { - "type": "markdown", - "state": { - "file": "Crussell/Crussell Nails.md", - "mode": "source", - "source": false - }, + "type": "empty", + "state": {}, "icon": "lucide-file", - "title": "Crussell Nails" + "title": "New tab" } } ] @@ -169,9 +165,9 @@ "bases:Create new base": false } }, - "active": "6f192f9477d55538", + "active": "7ac1561c78f61154", "lastOpenFiles": [ - "Crussell/Backend/bookings.md", - "Crussell/Crussell Nails.md" + "Crussell/Crussell Nails.md", + "Crussell/Backend/bookings.md" ] } \ No newline at end of file diff --git a/obsidian/Crussell/Crussell Nails.md b/obsidian/Crussell/Crussell Nails.md index 6c74d10..dc11221 100644 --- a/obsidian/Crussell/Crussell Nails.md +++ b/obsidian/Crussell/Crussell Nails.md @@ -1,86 +1,86 @@ - ## ✅ / ⏳ Project Checklist ### Backend (Go / Chi / Postgres) - - [x] JWT authentication (login, refresh, role verification) - [x] User registration with input validation - [x] Password hashing with bcrypt - [x] Middleware for auth/roles - [x] DB connection pooling - [ ] Booking endpoints + - ☐ `/api/bookings` (CRUD for users) + - ☐ `/api/admin/bookings` (list, search, user‑by‑user, progress, confirm) - [ ] Admin endpoints + - ☐ `/api/admin/services` (create, delete, list, toggle) + - ☐ `/api/admin/bookings` (list, search, user bookings, progress, confirm) + - ☐ `/api/admin/users` (list, view) – *handlers still to be implemented* - [ ] Unit tests - [ ] CI/CD ### Frontend (SvelteKit / Tailwind / shadcn) - - [x] Core pages (Home, Prices, Contact, Book) - [x] Booking wizard prototype - [x] Calendar/time slot selector - [ ] API integration for booking flow -- [ ] Payments (Stripe/Square) + *The wizard currently only shows a prototype alert. Wire it to the backend `/api/bookings` POST endpoint in the next iteration.* +- [ ] Payments (Stripe/Square) – *pending integration* - [ ] Auth screens - [ ] Admin dashboard ### Infrastructure - - [x] `.env` config - [x] Docker Compose full stack -- [ ] nginx reverse proxy -- [ ] Monitoring/logging -- [ ] CI/CD pipeline +- [ ] nginx reverse proxy – *still under review* +- [ ] Monitoring/logging – *no stack configured* +- [ ] CI/CD pipeline – *not yet defined* ### Integrations - - [x] CardDAV sync for contacts -- [ ] Email/SMS reminders -- [ ] Payment provider -- [ ] Loyalty tracking frontend +- [ ] Email/SMS reminders – *not yet implemented* +- [ ] Payment provider – *placeholder only* +- [ ] Loyalty tracking frontend – *no component yet* --- ## 📐 Current Architecture ### Overview Diagram - ```mermaid flowchart TD User([Customer]) Admin([Admin]) CardReader[Square Card Reader
Physical Terminal] - + subgraph CF[Cloudflare Protection] CDN[CDN / DDoS Protection] end - + subgraph Lightsail[AWS Lightsail Instance] subgraph NGINX[Nginx Reverse Proxy] Proxy[Port 443/80] end - + subgraph Frontend[SvelteKit Frontend] UI[Booking UI / Prices / Contact] AdminUI[Admin Dashboard] APIProxy[API Proxy Routes] end - + subgraph Backend[Go + Chi Backend] Router[chi Router] Auth[JWT Middleware] Handlers[API Handlers] end - + subgraph Database[PostgreSQL] DB[(Users / Bookings
Transactions)] end - + subgraph DAV[SabreDAV Server] CardDAV[(vCard Contacts)] CalDAV[(Calendar Events)] end end - + subgraph External[External Services] Gmail[Gmail SMTP
smtp.gmail.com:587] SquareAPI[Square API
Online Payments] @@ -90,94 +90,46 @@ flowchart TD User -->|HTTPS| CF Admin -->|HTTPS| CF CF --> Proxy - + Proxy --> UI Proxy --> AdminUI Proxy --> APIProxy - + UI --> APIProxy AdminUI --> APIProxy APIProxy --> Router - + Router --> Auth Auth --> Handlers - + Handlers --> DB Handlers --> CardDAV Handlers --> CalDAV Handlers -->|Send Emails| Gmail Handlers -->|Process Payments| SquareAPI - + Admin -.->|Sync Contacts/Calendar| DAV - + CardReader -->|Transaction Data| SquareAPI Handlers -.->|Poll Transactions| SquareAPI %% Force External Services to appear below Lightsail --> External - ``` + --- ## 🔧 Backend Implementation ### JWT Auth - -JWT uses **HS256** algorithm with 30-day expiry. Implemented via `go-chi/jwtauth`. - -**Code:** - -```go -// auth/jwt.go -var TokenAuth *jwtauth.JWTAuth - -func InitJWT(secret string) { - TokenAuth = jwtauth.New("HS256", []byte(secret), nil) -} - -func GenerateToken(userID string, role string) (string, error) { - _, token, err := TokenAuth.Encode(map[string]interface{}{ - "user_id": userID, - "role": role, - "exp": time.Now().Add(30 * 24 * time.Hour).Unix(), - }) - return token, err -} -``` +JWT uses **HS256** algorithm with 30‑day expiry. Implemented via `go-chi/jwtauth`. ### Middleware - Validates JWT and attaches user context. Supports role restrictions. -```go -// mw/auth.go -func RequireAuth(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - authHeader := r.Header.Get("Authorization") - if !strings.HasPrefix(authHeader, "Bearer ") { - http.Error(w, "missing token", http.StatusUnauthorized) - return - } - - token := strings.TrimPrefix(authHeader, "Bearer ") - userID, role, err := auth.VerifyToken(token, r.Context()) - if err != nil { - http.Error(w, "invalid token", http.StatusUnauthorized) - return - } - - ctx := context.WithValue(r.Context(), UserIDKey, userID) - ctx = context.WithValue(ctx, UserRoleKey, role) - next.ServeHTTP(w, r.WithContext(ctx)) - }) -} -``` - ### Registration Flow - The registration process: - - Validates names, email, phone, DOB - Rejects users under 16 years old - Hashes password with bcrypt @@ -191,30 +143,38 @@ defer tx.Rollback(r.Context()) _, err := tx.Exec(r.Context(), `INSERT INTO users (first_name, last_name, email, phone, dob, password_hash) VALUES ($1,$2,$3,$4,$5,$6)`, - req.FirstName, req.LastName, req.Email, req.Phone, dob, string(hash)) + req.FirstName, req.LastName, req.Email, req.Phone, dob, string(hash)) ``` + ### CardDAV Integration - -Each registered user generates a `.vcf` file in SabreDAV, ensuring external calendar and contact apps stay in sync. +Each registered user generates a `.vcf` file in SabreDAV, ensuring external calendar and contact apps stay in sync. The code now uses the internal `dav.BaseService.CreateContact` helper. ```go -func saveToCardDAV(userID, firstName, lastName, email, phone, dob string) error { - vcard := createVCard(userID, firstName, lastName, email, phone, dob) - url := fmt.Sprintf("http://nginx/dav/addressbooks/principals/default/default/%s.vcf", userID) - req, _ := http.NewRequest("PUT", url, bytes.NewBufferString(vcard)) - req.SetBasicAuth("admin", "admin") - _, err := http.DefaultClient.Do(req) - return err +func CreateCardDAVContact(service *dav.BaseService, addressBookID int, userID, firstName, lastName, email, phone, dob string) error { + input := dav.ContactInput{ + UserID: userID, + FirstName: firstName, + LastName: lastName, + Email: email, + Phone: phone, + DOB: dob, + } + return service.CreateContact(addressBookID, userID, input) } ``` +The address‑book ID is currently hard‑coded in the database (`dav_cards` table). A migration will expose the ID via a dedicated endpoint in the next sprint. + +### Admin Endpoints +*Handlers for `/api/admin/services`, `/api/admin/bookings`, and `/api/admin/users` are defined in the router, but the concrete implementation files are still empty. These will be fleshed out in the next sprint.* + + --- ## 🎨 Frontend Implementation ### API Proxy - Prevents CORS issues by proxying all API calls through SvelteKit. ```ts @@ -223,22 +183,23 @@ const BACKEND_URL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:8080'; async function proxyRequest(request: Request, path: string) { const url = `${BACKEND_URL}/api/${path}`; - const backendRes = await fetch(url, { - method: request.method, - headers: request.headers + const backendRes = await fetch(url, { + method: request.method, + headers: request.headers }); - return new Response(backendRes.body, { - status: backendRes.status, - headers: backendRes.headers + + // Forward everything transparently + return new Response(backendRes.body, { + status: backendRes.status, + headers: new Headers(backendRes.headers) }); } ``` ### Booking Flow - -**Step 1**: Select services -**Step 2**: Choose date/time -**Step 3**: Enter details +**Step 1**: Select services +**Step 2**: Choose date/time +**Step 3**: Enter details **Step 4**: Payment (TODO) **Calendar Component:** @@ -268,28 +229,71 @@ function generateTimeSlots(duration: number) { --- ## 🎯 Next Steps +- **Booking Integration** – wire the wizard to the backend `/api/bookings` POST endpoint. +- **Payments** – integrate Stripe or Square SDK; add a payment screen. +- **Admin Dashboard** – implement admin routes and UI. +- **Monitoring/Logging** – add a lightweight Prometheus/Grafana stack or use CloudWatch. +- **CI/CD Pipeline** – create GitHub Actions workflow for linting, testing, and container publishing. +- **Admin Handlers** – flesh out `/api/admin/services`, `/api/admin/bookings`, and `/api/admin/users` endpoints. +- **Email/SMS Reminders** – add scheduled job to send reminders. +- **Loyalty Tracking** – add a frontend component to display loyalty stamps. -### Bookings +--- -- Add `bookings` table: user_id, service, date, time, status -- Backend: `/api/bookings` (create, list, cancel) -- Frontend: wire booking wizard → backend +## 📦 Environment & Runtime -### Payments +| Variable | Purpose | Default / Example | +|----------|---------|-------------------| +| `JWT_SECRET_KEY` | Secret used to sign JWTs | **REQUIRED** – set in `.env` or Docker secrets | +| `DATABASE_URL` | PostgreSQL connection string | `postgres://user:pass@localhost:5432/crussell?sslmode=disable` | +| `VITE_BACKEND_URL` | Front‑end proxy target | `http://localhost:8080` (dev) | +| `VITE_SQUARE_ENV` | Square environment | `sandbox` or `production` | +| `VITE_STRIPE_KEY` | Stripe publishable key | `pk_test_...` | -- Integrate Stripe or Square SDK -- Secure checkout at Step 4 +> **Tip**: The `.env.example` file contains all required keys – copy it to `.env` and edit. -### Admin Dashboard +--- -- View/manage bookings -- User management -- Loyalty overview +## 🐳 Docker Compose -### Security +```yaml +# docker-compose.yml +services: + backend: + build: ./backend + environment: + - JWT_SECRET_KEY=supersecret + - DATABASE_URL=postgres://crussell:crussell@db:5432/crussell + depends_on: [db, dav] + dav: + image: sabredav/sabredav + environment: + - DAV_URL=http://dav:8000 + volumes: + - dav-data:/data + db: + image: postgres:15 + environment: + - POSTGRES_USER=crussell + - POSTGRES_PASSWORD=crussell + - POSTGRES_DB=crussell + nginx: + image: nginx:alpine + depends_on: [backend, dav] + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf + frontend: + build: ./frontend + environment: + - VITE_BACKEND_URL=http://backend:8080 +``` -- Move JWT secret → env -- HTTPS everywhere -- Harden API roles +> Run `docker compose up -d` to bring the stack up. ---- \ No newline at end of file +--- + +## 📜 GDPR & Data Retention + +The database schema includes a `anonymize_user()` function (see `init-script.sql`) that removes PII after a user’s account is closed. The code also contains a `export_all_user_data()` helper to support subject‑access requests. + +---