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.
+
+---