obsidian 1

This commit is contained in:
2025-11-03 23:25:04 +00:00
parent 24257f56b7
commit 2b7ec96d37
17 changed files with 1455 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
{}
+1
View File
@@ -0,0 +1 @@
{}
+33
View File
@@ -0,0 +1,33 @@
{
"file-explorer": true,
"global-search": true,
"switcher": true,
"graph": true,
"backlink": true,
"canvas": true,
"outgoing-link": true,
"tag-pane": true,
"footnotes": false,
"properties": false,
"page-preview": true,
"daily-notes": true,
"templates": true,
"note-composer": true,
"command-palette": true,
"slash-command": false,
"editor-status": true,
"bookmarks": true,
"markdown-importer": false,
"zk-prefixer": false,
"random-note": false,
"outline": true,
"word-count": true,
"slides": false,
"audio-recorder": false,
"workspaces": false,
"file-recovery": true,
"publish": false,
"sync": true,
"bases": true,
"webviewer": false
}
+177
View File
@@ -0,0 +1,177 @@
{
"main": {
"id": "118cffd7cdee3b11",
"type": "split",
"children": [
{
"id": "83169ae72c9bc357",
"type": "tabs",
"children": [
{
"id": "6f192f9477d55538",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Crussell/Crussell Nails.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Crussell Nails"
}
}
]
}
],
"direction": "vertical"
},
"left": {
"id": "53a883d203e9233d",
"type": "split",
"children": [
{
"id": "490f9de080de140d",
"type": "tabs",
"children": [
{
"id": "42aca4a7881f98cd",
"type": "leaf",
"state": {
"type": "file-explorer",
"state": {
"sortOrder": "alphabetical",
"autoReveal": false
},
"icon": "lucide-folder-closed",
"title": "Files"
}
},
{
"id": "3f5a2398495786b0",
"type": "leaf",
"state": {
"type": "search",
"state": {
"query": "",
"matchingCase": false,
"explainSearch": false,
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical"
},
"icon": "lucide-search",
"title": "Search"
}
},
{
"id": "332fce93850753ed",
"type": "leaf",
"state": {
"type": "bookmarks",
"state": {},
"icon": "lucide-bookmark",
"title": "Bookmarks"
}
}
]
}
],
"direction": "horizontal",
"width": 300
},
"right": {
"id": "2750d7726f904ef3",
"type": "split",
"children": [
{
"id": "8cf8aee55a46981b",
"type": "tabs",
"children": [
{
"id": "5c7ce50806d60318",
"type": "leaf",
"state": {
"type": "backlink",
"state": {
"file": "Crussell/Crussell Nails.md",
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical",
"showSearch": false,
"searchQuery": "",
"backlinkCollapsed": false,
"unlinkedCollapsed": true
},
"icon": "links-coming-in",
"title": "Backlinks for Crussell Nails"
}
},
{
"id": "77c2a28112c134e9",
"type": "leaf",
"state": {
"type": "outgoing-link",
"state": {
"file": "Crussell/Crussell Nails.md",
"linksCollapsed": false,
"unlinkedCollapsed": true
},
"icon": "links-going-out",
"title": "Outgoing links from Crussell Nails"
}
},
{
"id": "0f217e57cf9fb6d9",
"type": "leaf",
"state": {
"type": "tag",
"state": {
"sortOrder": "frequency",
"useHierarchy": true,
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-tags",
"title": "Tags"
}
},
{
"id": "013dd65f19607fbd",
"type": "leaf",
"state": {
"type": "outline",
"state": {
"file": "Crussell/Crussell Nails.md",
"followCursor": false,
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-list",
"title": "Outline of Crussell Nails"
}
}
]
}
],
"direction": "horizontal",
"width": 300,
"collapsed": true
},
"left-ribbon": {
"hiddenItems": {
"switcher:Open quick switcher": false,
"graph:Open graph view": false,
"canvas:Create new canvas": false,
"daily-notes:Open today's daily note": false,
"templates:Insert template": false,
"command-palette:Open command palette": false,
"bases:Create new base": false
}
},
"active": "6f192f9477d55538",
"lastOpenFiles": [
"Crussell/Backend/bookings.md",
"Crussell/Crussell Nails.md"
]
}
+1
View File
@@ -0,0 +1 @@
{}
+3
View File
@@ -0,0 +1,3 @@
{
"nativeMenus": false
}
+33
View File
@@ -0,0 +1,33 @@
{
"file-explorer": true,
"global-search": true,
"switcher": true,
"graph": true,
"backlink": true,
"canvas": true,
"outgoing-link": true,
"tag-pane": true,
"footnotes": false,
"properties": false,
"page-preview": true,
"daily-notes": true,
"templates": true,
"note-composer": true,
"command-palette": true,
"slash-command": false,
"editor-status": true,
"bookmarks": true,
"markdown-importer": false,
"zk-prefixer": false,
"random-note": false,
"outline": true,
"word-count": true,
"slides": false,
"audio-recorder": false,
"workspaces": false,
"file-recovery": true,
"publish": false,
"sync": true,
"bases": true,
"webviewer": false
}
+22
View File
@@ -0,0 +1,22 @@
{
"collapse-filter": true,
"search": "",
"showTags": false,
"showAttachments": false,
"hideUnresolved": false,
"showOrphans": true,
"collapse-color-groups": true,
"colorGroups": [],
"collapse-display": true,
"showArrow": false,
"textFadeMultiplier": 0,
"nodeSizeMultiplier": 1,
"lineSizeMultiplier": 1,
"collapse-forces": true,
"centerStrength": 0.518713248970312,
"repelStrength": 10,
"linkStrength": 1,
"linkDistance": 250,
"scale": 1,
"close": true
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,12 @@
{
"id": "obsidian-excalidraw-plugin",
"name": "Excalidraw",
"version": "2.16.1",
"minAppVersion": "1.5.7",
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
"author": "Zsolt Viczian",
"authorUrl": "https://excalidraw-obsidian.online",
"fundingUrl": "https://ko-fi.com/zsolt",
"helpUrl": "https://github.com/zsviczian/obsidian-excalidraw-plugin#readme",
"isDesktopOnly": false
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,11 @@
{
"id": "obsidian-kanban",
"name": "Kanban",
"version": "2.0.51",
"minAppVersion": "1.0.0",
"description": "Create markdown-backed Kanban boards in Obsidian.",
"author": "mgmeyers",
"authorUrl": "https://github.com/mgmeyers/obsidian-kanban",
"helpUrl": "https://publish.obsidian.md/kanban/Obsidian+Kanban+Plugin",
"isDesktopOnly": false
}
File diff suppressed because one or more lines are too long
+179
View File
@@ -0,0 +1,179 @@
{
"main": {
"id": "c62d1e0ec7a60717",
"type": "split",
"children": [
{
"id": "d3b27c1f4c52d7a6",
"type": "tabs",
"children": [
{
"id": "78ea01679f328555",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Crussell Nails.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Crussell Nails"
}
}
]
}
],
"direction": "vertical"
},
"left": {
"id": "8bed8be933c79f20",
"type": "split",
"children": [
{
"id": "516959ec91abc174",
"type": "tabs",
"children": [
{
"id": "0f6fde9af78dd264",
"type": "leaf",
"state": {
"type": "file-explorer",
"state": {
"sortOrder": "alphabetical",
"autoReveal": false
},
"icon": "lucide-folder-closed",
"title": "Files"
}
},
{
"id": "356f344c2f4f81e6",
"type": "leaf",
"state": {
"type": "search",
"state": {
"query": "",
"matchingCase": false,
"explainSearch": false,
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical"
},
"icon": "lucide-search",
"title": "Search"
}
},
{
"id": "bf7cf19fe90f2f37",
"type": "leaf",
"state": {
"type": "bookmarks",
"state": {},
"icon": "lucide-bookmark",
"title": "Bookmarks"
}
}
]
}
],
"direction": "horizontal",
"width": 300,
"collapsed": true
},
"right": {
"id": "ae3733891099a958",
"type": "split",
"children": [
{
"id": "132c2d38d8dc839a",
"type": "tabs",
"children": [
{
"id": "0078a932529c043e",
"type": "leaf",
"state": {
"type": "backlink",
"state": {
"file": "Welcome.md",
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical",
"showSearch": false,
"searchQuery": "",
"backlinkCollapsed": false,
"unlinkedCollapsed": true
},
"icon": "links-coming-in",
"title": "Backlinks for Welcome"
}
},
{
"id": "c07138738d9a2d61",
"type": "leaf",
"state": {
"type": "outgoing-link",
"state": {
"file": "Welcome.md",
"linksCollapsed": false,
"unlinkedCollapsed": true
},
"icon": "links-going-out",
"title": "Outgoing links from Welcome"
}
},
{
"id": "23f7b34ce74479f8",
"type": "leaf",
"state": {
"type": "tag",
"state": {
"sortOrder": "frequency",
"useHierarchy": true,
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-tags",
"title": "Tags"
}
},
{
"id": "ec428ed62aa1165b",
"type": "leaf",
"state": {
"type": "outline",
"state": {
"file": "Welcome.md",
"followCursor": false,
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-list",
"title": "Outline of Welcome"
}
}
]
}
],
"direction": "horizontal",
"width": 300,
"collapsed": true
},
"left-ribbon": {
"hiddenItems": {
"switcher:Open quick switcher": false,
"graph:Open graph view": false,
"canvas:Create new canvas": false,
"daily-notes:Open today's daily note": false,
"templates:Insert template": false,
"command-palette:Open command palette": false,
"bases:Create new base": false
}
},
"active": "78ea01679f328555",
"lastOpenFiles": [
"Untitled.base",
"Crussell Nails.md",
"2025-10-07.md"
]
}
+522
View File
@@ -0,0 +1,522 @@
# Booking API - cURL Examples (Revised)
## User Endpoints (Requires Authentication)
-----
### 1\. Create Booking
Creates a new booking with the specified services and preferred time.
```bash
curl -X POST http://localhost:8080/api/bookings \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{
"start_time": "2025-10-25T14:00:00Z",
"service_ids": ["service-uuid-1", "service-uuid-2"],
"notes": "Please use the side entrance"
}'
```
**Response (201 Created):**
```json
{
"id": "booking-uuid",
"user_id": "user-uuid",
"start_time": "2025-10-25T14:00:00Z",
"status": "pending",
"notes": "Please use the side entrance",
"created_at": "2025-10-21T10:30:00Z",
"updated_at": "2025-10-21T10:30:00Z",
"created_by": "user-uuid"
}
```
-----
### 2\. Get User Booking by ID
Retrieves a specific booking for the authenticated user.
```bash
curl -X GET http://localhost:8080/api/bookings/booking-uuid \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
```
**Response (200 OK):**
*The response now consistently includes all joined data: `services` and `payments`.*
```json
{
"id": "booking-uuid",
"user_id": "user-uuid",
"start_time": "2025-10-25T14:00:00Z",
"status": "pending",
"notes": "Please use the side entrance",
"created_at": "2025-10-21T10:30:00Z",
"updated_at": "2025-10-21T10:30:00Z",
"created_by": "user-uuid",
"services": [
{
"booking_id": "booking-uuid",
"service_id": "service-uuid-1",
"override_price": null,
"override_duration_minutes": null,
"service_name": "Haircut",
"base_price": 25.00
}
],
"payments": [
{
"id": "payment-uuid",
"booking_id": "booking-uuid",
"payment_type": "deposit",
"payment_method": "card",
"status": "completed",
"amount": 10.00,
"is_vat_applicable": true,
"vat_rate": 20.0,
"vat_amount": 2.00,
"net_amount": 8.00
}
]
}
```
-----
### 3\. Get All User Bookings
Retrieves a list of all bookings associated with the authenticated user.
```bash
curl -X GET http://localhost:8080/api/bookings \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
```
**Response (200 OK):**
```json
[
{
"id": "booking-uuid-1",
"user_id": "user-uuid",
"start_time": "2025-10-25T14:00:00Z",
"status": "pending",
"updated_at": "2025-10-21T10:30:00Z"
},
{
"id": "booking-uuid-2",
"user_id": "user-uuid",
"start_time": "2025-10-26T10:00:00Z",
"status": "confirmed",
"updated_at": "2025-10-22T09:00:00Z"
}
]
```
-----
### 4\. Edit Booking (Change Start Time and/or Notes)
Updates the `start_time` and/or `notes` for a booking.
```bash
curl -X PUT http://localhost:8080/api/bookings/booking-uuid \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{
"start_time": "2025-10-25T15:00:00Z",
"notes": "Please use the front entrance this time"
}'
```
**Response (200 OK):**
```json
{
"id": "booking-uuid",
"user_id": "user-uuid",
"start_time": "2025-10-25T15:00:00Z",
"status": "pending",
"notes": "Please use the front entrance this time",
"created_at": "2025-10-21T10:30:00Z",
"updated_at": "2025-10-21T10:40:00Z",
"created_by": "user-uuid"
}
```
-----
### 5\. Delete/Cancel Booking
This endpoint performs a **hard delete** if no payments exist, or a **soft delete (cancel)** if payments exist, requiring a `reason`.
#### A. Hard Delete (No Payments)
```bash
# Hard delete when no payments exist
curl -X DELETE http://localhost:8080/api/bookings/booking-uuid \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
```
**Response (200 OK):**
```json
{
"message": "Booking deleted successfully",
"id": "booking-uuid"
}
```
#### B. Cancel Booking (With Payments)
```bash
# Soft delete/cancel when payments exist - requires reason
curl -X DELETE http://localhost:8080/api/bookings/booking-uuid \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{
"reason": "client_cancelled"
}'
```
**Valid reasons:** `client_cancelled`, `we_cancelled`, `re-schedule`, `no_show`
**Response (200 OK):**
```json
{
"message": "Booking cancelled successfully",
"id": "booking-uuid",
"status": "client_cancelled"
}
```
-----
-----
## Admin Endpoints (Requires Authentication + Admin Role)
-----
### 6\. Get All Admin Bookings
Retrieves a paginated list of all bookings in the system.
```bash
curl -X GET 'http://localhost:8080/api/admin/bookings?limit=50&offset=0' \
-H "Authorization: Bearer YOUR_ADMIN_JWT_TOKEN"
```
**Response (200 OK):**
```json
[
{
"id": "booking-uuid-a",
"user_id": "user-uuid-1",
"start_time": "2025-10-25T14:00:00Z",
"status": "confirmed",
"updated_at": "2025-10-21T10:45:00Z"
},
{
"id": "booking-uuid-b",
"user_id": "user-uuid-2",
"start_time": "2025-10-26T10:00:00Z",
"status": "pending",
"updated_at": "2025-10-22T09:00:00Z"
}
]
```
-----
### 7\. Search Admin Bookings
Retrieves a filtered list of bookings based on query parameters.
```bash
# Search for all 'pending' bookings for a specific user ID
curl -X GET 'http://localhost:8080/api/admin/bookings/search?status=pending&user_id=user-uuid-1&start_date=2025-10-01' \
-H "Authorization: Bearer YOUR_ADMIN_JWT_TOKEN"
```
**Response (200 OK):**
*Same list format as 'Get All Admin Bookings'*
-----
### 8\. Get Admin Bookings by User ID
Retrieves all bookings for a single, specified user.
```bash
curl -X GET http://localhost:8080/api/admin/bookings/user/user-uuid \
-H "Authorization: Bearer YOUR_ADMIN_JWT_TOKEN"
```
**Response (200 OK):**
*Same list format as 'Get All Admin Bookings'*
-----
### 9\. Get Admin Booking by ID
Retrieves a specific booking including all service and payment details.
```bash
curl -X GET http://localhost:8080/api/admin/bookings/booking-uuid \
-H "Authorization: Bearer YOUR_ADMIN_JWT_TOKEN"
```
**Response (200 OK):**
*Full booking object, including `services` and `payments` arrays.*
```json
{
"id": "booking-uuid",
"user_id": "user-uuid",
"start_time": "2025-10-25T14:00:00Z",
"status": "confirmed",
"notes": "Please use the side entrance",
"created_at": "2025-10-21T10:30:00Z",
"updated_at": "2025-10-21T10:45:00Z",
"created_by": "admin-uuid",
"services": [ ... ],
"payments": [ ... ]
}
```
-----
### 10\. Get Admin Booking Summary
Retrieves the booking, user details, services, payments, and financial totals in a single, comprehensive response.
```bash
curl -X GET http://localhost:8080/api/admin/bookings/booking-uuid/summary \
-H "Authorization: Bearer YOUR_ADMIN_JWT_TOKEN"
```
**Response (200 OK):**
```json
{
"booking": {
"id": "booking-uuid",
"user_id": "user-uuid",
"start_time": "2025-10-25T14:00:00Z",
"status": "confirmed",
"notes": "Please use the side entrance",
"created_at": "2025-10-21T10:30:00Z",
"updated_at": "2025-10-21T10:45:00Z",
"created_by": "admin-uuid"
},
"user": {
"first_name": "John",
"last_name": "Doe",
"email": "john.doe@example.com",
"account_role": "client",
"loyalty_stamps": 5
// ... other user fields
},
"services": [
{
"service_name": "Haircut",
"base_price": 25.00,
"override_price": 20.00
// ... other service fields
}
],
"payments": [
{
"id": "payment-uuid",
"payment_type": "deposit",
"status": "completed",
"amount": 10.00,
"vat_rate": 20.0
// ... other payment fields
}
],
"total_amount": 20.00,
"amount_paid": 10.00,
"amount_due": 10.00,
"duration_minutes": 25
}
```
-----
### 11\. Progress Booking Status
Moves the booking to the next status in its lifecycle (e.g., from `confirmed` to `in_progress`).
```bash
curl -X PUT http://localhost:8080/api/admin/bookings/booking-uuid/progress \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ADMIN_JWT_TOKEN" \
-d '{
"status": "in_progress"
}'
```
**Valid statuses:** `pending`, `confirmed`, `in_progress`, `completed`, `client_cancelled`, `we_cancelled`, `re-schedule`, `no_show`
**Response (200 OK):**
```json
{
"id": "booking-uuid",
"user_id": "user-uuid",
"start_time": "2025-10-25T14:00:00Z",
"status": "in_progress",
"notes": "Please use the side entrance",
"created_at": "2025-10-21T10:30:00Z",
"updated_at": "2025-10-25T14:05:00Z",
"created_by": "admin-uuid"
}
```
-----
### 12\. Confirm Booking (With/Without Overrides)
Confirms a `pending` booking, optionally applying price/duration overrides and updating notes.
#### A. Confirm with Overrides
```bash
curl -X POST http://localhost:8080/api/admin/bookings/booking-uuid/confirm \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ADMIN_JWT_TOKEN" \
-d '{
"service_overrides": [
{
"service_id": "service-uuid-1",
"override_price": 20.00,
"override_duration_minutes": 25
}
],
"notes": "Confirmed by phone. Applied special discount."
}'
```
#### B. Confirm with Notes Update Only
```bash
curl -X POST http://localhost:8080/api/admin/bookings/booking-uuid/confirm \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ADMIN_JWT_TOKEN" \
-d '{
"notes": "Confirmed via email"
}'
```
**Response (200 OK):**
```json
{
"id": "booking-uuid",
"user_id": "user-uuid",
"start_time": "2025-10-25T14:00:00Z",
"status": "confirmed",
"notes": "Confirmed by phone. Applied special discount.",
"created_at": "2025-10-21T10:30:00Z",
"updated_at": "2025-10-21T10:50:00Z",
"created_by": "admin-uuid"
}
```
-----
### 13\. Add Payment to Booking (New Endpoint)
Adds a new payment record to the specified booking.
```bash
curl -X POST http://localhost:8080/api/admin/bookings/booking-uuid/payments \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ADMIN_JWT_TOKEN" \
-d '{
"payment_type": "final",
"payment_method": "cash",
"status": "completed",
"amount": 15.00,
"is_vat_applicable": false,
"invoice_number": 1042
}'
```
**Valid Payment Types:** `deposit`, `final`
**Valid Payment Methods:** `card`, `cash`, `bank_transfer`
**Valid Payment Statuses:** `pending`, `completed`, `failed`
**Response (201 Created):**
*Returns the newly created payment object.*
```json
{
"id": "new-payment-uuid",
"booking_id": "booking-uuid",
"payment_type": "final",
"payment_method": "cash",
"status": "completed",
"amount": 15.00,
"is_vat_applicable": false,
"vat_rate": null,
"vat_amount": null,
"net_amount": 15.00,
"invoice_number": 1042,
"created_at": "2025-10-22T12:00:00Z",
"updated_at": "2025-10-22T12:00:00Z",
"created_by": "admin-uuid"
}
```
-----
### 14\. Remove Payment from Booking (New Endpoint)
Deletes a specific payment record associated with a booking.
```bash
curl -X DELETE http://localhost:8080/api/admin/bookings/booking-uuid/payments/payment-uuid-to-delete \
-H "Authorization: Bearer YOUR_ADMIN_JWT_TOKEN"
```
**Response (200 OK):**
```json
{
"message": "Payment removed successfully",
"id": "payment-uuid-to-delete"
}
```
-----
## Error Responses
| Status Code | Example Response | Description |
| :--- | :--- | :--- |
| **400 Bad Request** | `{"error": "Start time is required"}` | Missing or invalid required fields. |
| **401 Unauthorized** | `{"error": "Authentication required"}` | Missing or expired JWT token. |
| **403 Forbidden** | `{"error": "Admin access required"}` | Attempting to access an admin endpoint without the necessary role. |
| **404 Not Found** | `{"error": "Booking not found or access denied"}` | Resource does not exist or user/admin does not have permission to view it. |
| **500 Internal Server Error** | `{"error": "Internal server error"}` | Unhandled server error. |
-----
## Notes
1. Replace `YOUR_JWT_TOKEN` with a standard user's JWT token.
2. Replace `YOUR_ADMIN_JWT_TOKEN` with an admin user's JWT token.
3. Replace UUIDs (`booking-uuid`, `service-uuid-1`, etc.) with actual IDs from your database.
4. All timestamps should be in **RFC3339 format (ISO 8601)**, e.g., `"2025-10-25T14:00:00Z"`.
5. Start times must be in the future when creating or editing bookings.
6. Bookings can only be **hard-deleted** (Endpoint 5A) if they have no associated payments. Otherwise, a `reason` must be supplied to cancel the booking (Endpoint 5B).
7. The **Confirm Booking** endpoint (12) only works on bookings with status `pending`.
+295
View File
@@ -0,0 +1,295 @@
## ✅ / ⏳ 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
- [ ] Admin endpoints
- [ ] 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)
- [ ] Auth screens
- [ ] Admin dashboard
### Infrastructure
- [x] `.env` config
- [x] Docker Compose full stack
- [ ] nginx reverse proxy
- [ ] Monitoring/logging
- [ ] CI/CD pipeline
### Integrations
- [x] CardDAV sync for contacts
- [ ] Email/SMS reminders
- [ ] Payment provider
- [ ] Loyalty tracking frontend
---
## 📐 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]
end
%% Connections
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
}
```
### 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
- Saves user profile and creates vCard in CardDAV
```go
hash, _ := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
tx, _ := db.DB.Begin(r.Context())
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))
```
### CardDAV Integration
Each registered user generates a `.vcf` file in SabreDAV, ensuring external calendar and contact apps stay in sync.
```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
}
```
---
## 🎨 Frontend Implementation
### API Proxy
Prevents CORS issues by proxying all API calls through SvelteKit.
```ts
// routes/api/[...path]/+server.ts
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
});
return new Response(backendRes.body, {
status: backendRes.status,
headers: backendRes.headers
});
}
```
### Booking Flow
**Step 1**: Select services
**Step 2**: Choose date/time
**Step 3**: Enter details
**Step 4**: Payment (TODO)
**Calendar Component:**
```svelte
<Calendar
type="single"
bind:value={selectedDate}
isDateUnavailable={(date) => bookedDates.some(d => d.compare(date) === 0)}
/>
```
**Time Slot Generator:**
```ts
function generateTimeSlots(duration: number) {
const slots = []
for (let hour = 9; hour < 17; hour++) {
for (let minute = 0; minute < 60; minute += 15) {
slots.push(`${hour.toString().padStart(2,'0')}:${minute.toString().padStart(2,'0')}`);
}
}
return slots
}
```
---
## 🎯 Next Steps
### Bookings
- Add `bookings` table: user_id, service, date, time, status
- Backend: `/api/bookings` (create, list, cancel)
- Frontend: wire booking wizard → backend
### Payments
- Integrate Stripe or Square SDK
- Secure checkout at Step 4
### Admin Dashboard
- View/manage bookings
- User management
- Loyalty overview
### Security
- Move JWT secret → env
- HTTPS everywhere
- Harden API roles
---