Skip to content

Commit 5717987

Browse files
committed
release: v0.0.11
1 parent 5de7dad commit 5717987

175 files changed

Lines changed: 20930 additions & 4765 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,9 @@ APP_URL=http://localhost:3001
1212
DB_PATH=./badshuffle.db
1313
UPLOADS_DIR=./uploads
1414
VITE_API_BASE=
15+
16+
# Optional API rate limits (see server/index.js)
17+
# API_RATE_LIMIT_WINDOW_MS=60000
18+
# API_RATE_LIMIT_MAX=600
19+
# API_AUTH_RATE_LIMIT_WINDOW_MS=900000
20+
# API_AUTH_RATE_LIMIT_MAX=60

AI/Api/README.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# BadShuffle API Documentation
2+
3+
This directory contains full API and integration documentation for BadShuffle.
4+
5+
The primary integration goal is a **customer-facing React e-commerce website** that uses BadShuffle inventory as product cards (similar to WooCommerce) and feeds customer inquiries/leads back into BadShuffle for quoting.
6+
7+
---
8+
9+
## Document Index
10+
11+
| File | Purpose |
12+
|------|---------|
13+
| [authentication.md](authentication.md) | JWT auth, API keys, roles, rate limits |
14+
| [public-catalog.md](public-catalog.md) | No-auth endpoints for displaying inventory on a public site |
15+
| [inventory-api.md](inventory-api.md) | Full inventory CRUD (authenticated) |
16+
| [quotes-api.md](quotes-api.md) | Quote lifecycle, line items, payments, contracts |
17+
| [data-models.md](data-models.md) | Full schema reference for all tables |
18+
| [ecommerce-integration.md](ecommerce-integration.md) | Step-by-step guide to building the customer-facing site |
19+
| [webhooks-and-events.md](webhooks-and-events.md) | Event system, activity logs, and webhook architecture plan |
20+
21+
---
22+
23+
## Base URL
24+
25+
All API requests go to the same host as the BadShuffle app:
26+
27+
```
28+
https://your-badshuffle-host.com
29+
```
30+
31+
The React front-end and API share the same origin. Set `APP_URL` in the server `.env` to your production domain.
32+
33+
---
34+
35+
## Quick Reference
36+
37+
### Public endpoints (no auth needed)
38+
39+
```
40+
GET /api/public/catalog-meta Company info + category list
41+
GET /api/public/items Paginated item list (filterable)
42+
GET /api/public/items/:id Single item detail
43+
GET /api/files/:id/serve?sig=&exp= Signed image URL (time-limited)
44+
GET /catalog SEO-rendered HTML catalog
45+
GET /catalog/item/:id SEO-rendered HTML item detail
46+
GET /sitemap.xml XML sitemap
47+
GET /robots.txt Robots file
48+
POST /api/leads Submit inquiry / lead capture
49+
```
50+
51+
### Authenticated endpoints (require Bearer JWT)
52+
53+
```
54+
POST /api/auth/login Obtain JWT
55+
GET /api/items Full inventory list
56+
GET /api/quotes Quote list
57+
POST /api/quotes Create quote
58+
POST /api/quotes/:id/send Send quote to client
59+
GET /api/quotes/public/:token View quote by share token (no auth)
60+
POST /api/quotes/approve-by-token Client approves quote
61+
POST /api/quotes/contract/sign Client signs contract
62+
```
63+
64+
---
65+
66+
## Architecture Overview
67+
68+
```
69+
┌──────────────────────────────┐ ┌──────────────────────────────────┐
70+
│ Customer-Facing React Site │ │ BadShuffle Server │
71+
│ (e-commerce) │ │ │
72+
│ │ │ ┌────────────────────────────┐ │
73+
│ /shop → product cards │──GET──▶│ │ GET /api/public/items │ │
74+
│ /shop/:slug → item detail │──GET──▶│ │ GET /api/public/items/:id │ │
75+
│ /request-quote → lead form │──POST─▶│ │ POST /api/leads │ │
76+
│ │ │ └────────────────────────────┘ │
77+
│ │ │ │
78+
│ (optional) │ │ ┌────────────────────────────┐ │
79+
│ /quotes/:token → view quote │──GET──▶│ │ GET /api/quotes/public/:t │ │
80+
│ /quotes/:token/approve │──POST─▶│ │ POST /api/quotes/approve │ │
81+
│ /quotes/:token/sign │──POST─▶│ │ POST /api/quotes/contract │ │
82+
│ │ │ └────────────────────────────┘ │
83+
└──────────────────────────────┘ └──────────────────────────────────┘
84+
```
85+
86+
### Data flow for a typical e-commerce order
87+
88+
1. Customer browses `/shop` → React fetches `GET /api/public/items`
89+
2. Customer views item → React fetches `GET /api/public/items/:id`
90+
3. Customer submits inquiry → React posts `POST /api/leads`
91+
4. BadShuffle operator creates a quote from the lead
92+
5. Quote is sent → customer receives email with link to `GET /api/quotes/public/:token`
93+
6. Customer approves → `POST /api/quotes/approve-by-token`
94+
7. Customer signs contract → `POST /api/quotes/contract/sign`
95+
8. Operator confirms and tracks payments
96+
97+
---
98+
99+
## CORS
100+
101+
The BadShuffle server allows requests from:
102+
- `http://localhost:*` (development)
103+
- `process.env.APP_URL` (production)
104+
- Chrome extension origins
105+
106+
For a separately-hosted customer site, add its origin to the CORS config in `server/index.js` or set `APP_URL` appropriately.

AI/Api/authentication.md

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
# Authentication
2+
3+
BadShuffle uses **JWT (JSON Web Tokens)** for all authenticated API calls. Tokens are obtained via the login endpoint and sent as a Bearer header on subsequent requests.
4+
5+
---
6+
7+
## Obtaining a Token
8+
9+
### POST /api/auth/login
10+
11+
Authenticates a user and returns a signed JWT.
12+
13+
**Request**
14+
```http
15+
POST /api/auth/login
16+
Content-Type: application/json
17+
18+
{
19+
"email": "operator@yourcompany.com",
20+
"password": "your-password",
21+
"math_a": 5,
22+
"math_b": 3,
23+
"math_answer": 8
24+
}
25+
```
26+
27+
> **CAPTCHA fields** (`math_a`, `math_b`, `math_answer`): BadShuffle requires a simple math CAPTCHA on every login. Call `GET /api/auth/captcha-config` first to see if reCAPTCHA is also required.
28+
29+
**GET /api/auth/captcha-config** — Returns:
30+
```json
31+
{
32+
"recaptcha_enabled": false,
33+
"recaptcha_site_key": null
34+
}
35+
```
36+
37+
If `recaptcha_enabled` is true, also include a `"recaptcha_response"` field from Google's reCAPTCHA widget.
38+
39+
**Success Response** `200 OK`
40+
```json
41+
{
42+
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
43+
}
44+
```
45+
46+
**Error Responses**
47+
| Status | Meaning |
48+
|--------|---------|
49+
| `400` | Missing or invalid fields |
50+
| `401` | Wrong email or password |
51+
| `429` | Too many attempts (5 per 15 min per IP) |
52+
53+
---
54+
55+
## Using the Token
56+
57+
Include the token as a Bearer header on every authenticated request:
58+
59+
```http
60+
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
61+
```
62+
63+
**Token lifetime:** 7 days. Store it securely (e.g., `localStorage` for a trusted internal tool, or `httpOnly` cookie for higher security).
64+
65+
---
66+
67+
## Token Claims
68+
69+
```json
70+
{
71+
"sub": 1, // user_id
72+
"email": "operator@yourcompany.com",
73+
"iat": 1711900000,
74+
"exp": 1712504800
75+
}
76+
```
77+
78+
---
79+
80+
## Checking Setup Status
81+
82+
Before doing anything, you can check if the system has been initialized:
83+
84+
```http
85+
GET /api/auth/status
86+
```
87+
88+
```json
89+
{ "setup": true }
90+
```
91+
92+
If `setup` is `false`, no users exist yet — the first admin must be created via `POST /api/auth/setup`.
93+
94+
---
95+
96+
## Password Reset Flow
97+
98+
```
99+
POST /api/auth/forgot { "email": "user@example.com" }
100+
↓ (server sends reset email with token link)
101+
POST /api/auth/reset { "token": "...", "password": "new-password" }
102+
```
103+
104+
---
105+
106+
## User Roles
107+
108+
| Role | Description | Access Level |
109+
|------|-------------|-------------|
110+
| `admin` | Full access, user management, system settings, DB backup | Everything |
111+
| `operator` | Staff with configuration access | Quotes, inventory, settings, templates, vendors |
112+
| `user` | Regular team member | Quotes, inventory, messages, files |
113+
114+
Role is embedded in the `users` table, **not** in the JWT. The server re-reads it from the DB on each request.
115+
116+
---
117+
118+
## Extension Token (for inventory sync only)
119+
120+
The browser extension uses a separate token system for syncing inventory:
121+
122+
```http
123+
x-extension-token: your-extension-token
124+
```
125+
126+
This token only grants access to `GET/POST /api/items` and `POST /api/sheets/*`. It is **not** suitable for e-commerce integrations — use a JWT instead.
127+
128+
---
129+
130+
## Signed File URLs (no token required)
131+
132+
Inventory images and attached files can be served without a JWT using time-limited signed URLs. These are generated server-side and embedded in API responses automatically.
133+
134+
Format:
135+
```
136+
/api/files/:id/serve?sig=<hmac-sha256>&exp=<unix-timestamp-ms>
137+
```
138+
139+
- Signature is HMAC-SHA256 over the file ID using the server's JWT secret
140+
- URLs expire after a configurable window (default a few hours)
141+
- Used automatically in `GET /api/public/items` responses — `photo_url` will already be a signed URL
142+
143+
For the e-commerce integration you generally don't need to generate these manually; the public API responses include fully-resolved URLs.
144+
145+
---
146+
147+
## Rate Limits
148+
149+
| Endpoint group | Limit |
150+
|----------------|-------|
151+
| All API (default) | 600 requests / minute per IP |
152+
| `/api/auth/login`, `/api/auth/forgot`, `/api/auth/setup` | 60 requests / 15 min per IP |
153+
| `/api/files/:id/serve` | 240 requests / minute per IP |
154+
| Public quote endpoints (`/api/quotes/public/*`) | 60 requests / minute per IP |
155+
156+
Rate limits can be tuned via environment variables:
157+
```
158+
API_RATE_LIMIT_WINDOW_MS
159+
API_RATE_LIMIT_MAX
160+
API_AUTH_RATE_LIMIT_WINDOW_MS
161+
API_AUTH_RATE_LIMIT_MAX
162+
```
163+
164+
Exceeding a limit returns `429 Too Many Requests`.
165+
166+
---
167+
168+
## Environment Variables (Server)
169+
170+
| Variable | Purpose |
171+
|----------|---------|
172+
| `JWT_SECRET` | Secret used to sign/verify tokens (required in production) |
173+
| `APP_URL` | Public base URL (used for CORS and signed URLs) |
174+
| `DB_PATH` | SQLite database file path |
175+
176+
---
177+
178+
## Server-to-Server Integration
179+
180+
For a backend service (e.g., Node.js/Python middleware sitting between your customer site and BadShuffle), use a service account:
181+
182+
1. Create a dedicated `operator` user in BadShuffle admin
183+
2. Authenticate once and store the JWT
184+
3. Refresh when the token approaches expiry (7-day window)
185+
4. Use this token for all server-side API calls (creating leads, reading inventory)
186+
187+
Never expose the operator JWT in client-side code. The public catalog endpoints (`/api/public/*`) are the safe alternative for unauthenticated browsing.

0 commit comments

Comments
 (0)