update obsidian
This commit is contained in:
Vendored
+8
-12
@@ -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"
|
||||
]
|
||||
}
|
||||
+117
-113
@@ -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<br/>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<br/>Transactions)]
|
||||
end
|
||||
|
||||
|
||||
subgraph DAV[SabreDAV Server]
|
||||
CardDAV[(vCard Contacts)]
|
||||
CalDAV[(Calendar Events)]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
subgraph External[External Services]
|
||||
Gmail[Gmail SMTP<br/>smtp.gmail.com:587]
|
||||
SquareAPI[Square API<br/>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.
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
## 📜 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.
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user