@etus/bhono-app 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/package.json +3 -2
  2. package/templates/base/.claude/commands/check-skill-rules.md +112 -29
  3. package/templates/base/.claude/commands/linear/implement-issue.md +383 -55
  4. package/templates/base/.claude/commands/ship.md +77 -13
  5. package/templates/base/.claude/hooks/package-lock.json +0 -419
  6. package/templates/base/.claude/hooks/skill-activation-prompt.ts +185 -113
  7. package/templates/base/.claude/hooks/skill-tool-guard.sh +6 -0
  8. package/templates/base/.claude/hooks/skill-tool-guard.ts +198 -0
  9. package/templates/base/.claude/scripts/validate-skill-rules.sh +55 -32
  10. package/templates/base/.claude/settings.json +18 -11
  11. package/templates/base/.claude/skills/skill-rules.json +326 -173
  12. package/templates/base/.env.example +3 -0
  13. package/templates/base/README.md +9 -7
  14. package/templates/base/config/eslint.config.js +1 -0
  15. package/templates/base/config/wrangler.json +16 -17
  16. package/templates/base/docs/SETUP-GUIDE.md +566 -0
  17. package/templates/base/docs/architecture/README.md +162 -8
  18. package/templates/base/docs/architecture/api-catalog.md +575 -0
  19. package/templates/base/docs/architecture/c4-component.md +309 -0
  20. package/templates/base/docs/architecture/c4-container.md +183 -0
  21. package/templates/base/docs/architecture/c4-context.md +106 -0
  22. package/templates/base/docs/architecture/dependencies.md +327 -0
  23. package/templates/base/docs/architecture/tech-debt.md +184 -0
  24. package/templates/base/package.json +20 -15
  25. package/templates/base/scripts/capture-prod-session.ts +2 -2
  26. package/templates/base/scripts/sync-template.sh +104 -0
  27. package/templates/base/src/server/db/sql.ts +24 -4
  28. package/templates/base/src/server/index.ts +1 -0
  29. package/templates/base/src/server/lib/audited-db.ts +10 -10
  30. package/templates/base/src/server/middleware/account.ts +1 -1
  31. package/templates/base/src/server/middleware/auth.ts +11 -11
  32. package/templates/base/src/server/middleware/rate-limit.ts +3 -6
  33. package/templates/base/src/server/routes/auth/handlers.ts +5 -5
  34. package/templates/base/src/server/routes/auth/test-login.ts +9 -9
  35. package/templates/base/src/server/routes/index.ts +9 -0
  36. package/templates/base/src/server/routes/invitations/handlers.ts +6 -6
  37. package/templates/base/src/server/routes/openapi.ts +1 -1
  38. package/templates/base/src/server/services/accounts.ts +9 -9
  39. package/templates/base/src/server/services/audits.ts +12 -12
  40. package/templates/base/src/server/services/auth.ts +15 -15
  41. package/templates/base/src/server/services/invitations.ts +16 -16
  42. package/templates/base/src/server/services/users.ts +13 -13
  43. package/templates/base/src/shared/types/api.ts +66 -198
  44. package/templates/base/tests/e2e/auth.setup.ts +1 -1
  45. package/templates/base/tests/unit/server/auth/guards.test.ts +1 -1
  46. package/templates/base/tests/unit/server/middleware/auth.test.ts +273 -0
  47. package/templates/base/tests/unit/server/routes/auth/handlers.test.ts +111 -0
  48. package/templates/base/tests/unit/server/routes/users/handlers.test.ts +69 -5
  49. package/templates/base/tests/unit/server/services/accounts.test.ts +148 -0
  50. package/templates/base/tests/unit/server/services/audits.test.ts +219 -0
  51. package/templates/base/tests/unit/server/services/auth.test.ts +480 -3
  52. package/templates/base/tests/unit/server/services/invitations.test.ts +178 -0
  53. package/templates/base/tests/unit/server/services/users.test.ts +363 -8
  54. package/templates/base/tests/unit/shared/schemas.test.ts +1 -1
  55. package/templates/base/vite.config.ts +3 -1
  56. package/templates/base/.github/workflows/test.yml +0 -127
  57. package/templates/base/.husky/pre-push +0 -26
  58. package/templates/base/auth-setup-error.png +0 -0
  59. package/templates/base/pnpm-lock.yaml +0 -8052
  60. package/templates/base/tests/e2e/_auth/.gitkeep +0 -0
  61. package/templates/base/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,575 @@
1
+ # API Catalog - BHono Platform
2
+
3
+ > Complete REST API endpoint documentation with OpenAPI 3.0 support.
4
+
5
+ ## Overview
6
+
7
+ | Attribute | Value | Confidence |
8
+ |-----------|-------|------------|
9
+ | **Base URL** | `/api` (authenticated), `/auth` (public) | HIGH |
10
+ | **Format** | REST/JSON | HIGH |
11
+ | **Documentation** | OpenAPI 3.0 at `/api/doc` | HIGH |
12
+ | **Swagger UI** | Available at `/api/swagger` | HIGH |
13
+ | **Total Endpoints** | 30 | HIGH |
14
+
15
+ ## Authentication
16
+
17
+ All `/api/*` endpoints require authentication via session cookie (`__Host-sid`).
18
+
19
+ | Header/Cookie | Purpose |
20
+ |---------------|---------|
21
+ | `Cookie: __Host-sid=<session_id>` | Session authentication |
22
+ | `X-Account-Id` | Multi-tenant account context |
23
+
24
+ ---
25
+
26
+ ## Auth Endpoints [HIGH]
27
+
28
+ Base path: `/auth`
29
+
30
+ | Method | Path | Description | Auth Required |
31
+ |--------|------|-------------|---------------|
32
+ | `GET` | `/auth/login` | Initiate Google OAuth flow | No |
33
+ | `GET` | `/auth/callback` | OAuth callback handler | No |
34
+ | `POST` | `/auth/logout` | Destroy session | Yes |
35
+ | `GET` | `/auth/me` | Get current user info | Yes |
36
+ | `POST` | `/auth/refresh` | Refresh session token | Yes |
37
+ | `GET` | `/auth/invite/{token}` | Get invitation details | No |
38
+
39
+ ### GET /auth/login
40
+
41
+ Initiates Google OAuth 2.0 authorization flow.
42
+
43
+ **Response**: Redirect to Google OAuth consent screen
44
+
45
+ ```
46
+ 302 Found
47
+ Location: https://accounts.google.com/o/oauth2/v2/auth?...
48
+ ```
49
+
50
+ ### GET /auth/callback
51
+
52
+ Handles OAuth callback from Google.
53
+
54
+ **Query Parameters**:
55
+ | Parameter | Type | Description |
56
+ |-----------|------|-------------|
57
+ | `code` | string | Authorization code from Google |
58
+ | `state` | string | CSRF state token |
59
+
60
+ **Response**: Redirect to dashboard with session cookie set
61
+
62
+ ### POST /auth/logout
63
+
64
+ Destroys the current session.
65
+
66
+ **Response**:
67
+ ```json
68
+ {
69
+ "success": true
70
+ }
71
+ ```
72
+
73
+ ### GET /auth/me
74
+
75
+ Returns current authenticated user information.
76
+
77
+ **Response**:
78
+ ```json
79
+ {
80
+ "id": "01234567-89ab-cdef-0123-456789abcdef",
81
+ "email": "user@example.com",
82
+ "name": "John Doe",
83
+ "avatarUrl": "https://...",
84
+ "isSuperAdmin": false,
85
+ "accounts": [
86
+ {
87
+ "id": "account-uuid",
88
+ "name": "My Workspace",
89
+ "role": "ADMIN"
90
+ }
91
+ ]
92
+ }
93
+ ```
94
+
95
+ ### POST /auth/refresh
96
+
97
+ Refreshes the session token.
98
+
99
+ **Response**:
100
+ ```json
101
+ {
102
+ "success": true,
103
+ "expiresAt": "2025-01-15T12:00:00Z"
104
+ }
105
+ ```
106
+
107
+ ### GET /auth/invite/{token}
108
+
109
+ Gets invitation details for acceptance page.
110
+
111
+ **Path Parameters**:
112
+ | Parameter | Type | Description |
113
+ |-----------|------|-------------|
114
+ | `token` | string | Invitation token |
115
+
116
+ **Response**:
117
+ ```json
118
+ {
119
+ "id": "invitation-uuid",
120
+ "email": "invitee@example.com",
121
+ "accountName": "Workspace Name",
122
+ "inviterName": "John Doe",
123
+ "role": "EDITOR",
124
+ "expiresAt": "2025-01-20T12:00:00Z"
125
+ }
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Users Endpoints [HIGH]
131
+
132
+ Base path: `/api/users`
133
+
134
+ | Method | Path | Description | Auth | Permission |
135
+ |--------|------|-------------|------|------------|
136
+ | `GET` | `/api/users` | List users (paginated) | Yes | VIEWER+ |
137
+ | `GET` | `/api/users/{id}` | Get user by ID | Yes | VIEWER+ |
138
+ | `POST` | `/api/users` | Create user | Yes | ADMIN |
139
+ | `PATCH` | `/api/users/{id}` | Update user | Yes | ADMIN |
140
+ | `DELETE` | `/api/users/{id}` | Soft delete user | Yes | ADMIN |
141
+ | `POST` | `/api/users/{id}/restore` | Restore deleted user | Yes | ADMIN |
142
+ | `GET` | `/api/users/{id}/accounts` | Get user's accounts | Yes | VIEWER+ |
143
+ | `POST` | `/api/users/bulk/accounts` | Bulk add to accounts | Yes | ADMIN |
144
+
145
+ ### GET /api/users
146
+
147
+ List users with pagination.
148
+
149
+ **Query Parameters**:
150
+ | Parameter | Type | Default | Description |
151
+ |-----------|------|---------|-------------|
152
+ | `page` | integer | 1 | Page number |
153
+ | `limit` | integer | 20 | Items per page (max 100) |
154
+ | `search` | string | - | Search by name/email |
155
+ | `status` | string | - | Filter by status |
156
+
157
+ **Response**:
158
+ ```json
159
+ {
160
+ "data": [
161
+ {
162
+ "id": "user-uuid",
163
+ "email": "user@example.com",
164
+ "name": "John Doe",
165
+ "status": "active",
166
+ "createdAt": "2025-01-01T00:00:00Z"
167
+ }
168
+ ],
169
+ "pagination": {
170
+ "page": 1,
171
+ "limit": 20,
172
+ "total": 100,
173
+ "totalPages": 5
174
+ }
175
+ }
176
+ ```
177
+
178
+ ### POST /api/users
179
+
180
+ Create a new user.
181
+
182
+ **Request Body**:
183
+ ```json
184
+ {
185
+ "email": "newuser@example.com",
186
+ "name": "New User",
187
+ "role": "EDITOR"
188
+ }
189
+ ```
190
+
191
+ **Response**: `201 Created`
192
+ ```json
193
+ {
194
+ "id": "new-user-uuid",
195
+ "email": "newuser@example.com",
196
+ "name": "New User",
197
+ "status": "active",
198
+ "createdAt": "2025-01-01T00:00:00Z"
199
+ }
200
+ ```
201
+
202
+ ### PATCH /api/users/{id}
203
+
204
+ Update user properties.
205
+
206
+ **Request Body**:
207
+ ```json
208
+ {
209
+ "name": "Updated Name",
210
+ "status": "inactive"
211
+ }
212
+ ```
213
+
214
+ ### DELETE /api/users/{id}
215
+
216
+ Soft delete a user (sets `deleted_at`).
217
+
218
+ **Response**: `204 No Content`
219
+
220
+ ### POST /api/users/{id}/restore
221
+
222
+ Restore a soft-deleted user.
223
+
224
+ **Response**: `200 OK`
225
+
226
+ ---
227
+
228
+ ## Accounts Endpoints [HIGH]
229
+
230
+ Base path: `/api/accounts`
231
+
232
+ | Method | Path | Description | Auth | Permission |
233
+ |--------|------|-------------|------|------------|
234
+ | `GET` | `/api/accounts` | List accounts | Yes | VIEWER+ |
235
+ | `GET` | `/api/accounts/{id}` | Get account | Yes | VIEWER+ |
236
+ | `POST` | `/api/accounts` | Create account | Yes | Auth only |
237
+ | `PATCH` | `/api/accounts/{id}` | Update account | Yes | ADMIN |
238
+ | `DELETE` | `/api/accounts/{id}` | Soft delete account | Yes | ADMIN |
239
+ | `POST` | `/api/accounts/{id}/restore` | Restore account | Yes | ADMIN |
240
+
241
+ ### GET /api/accounts
242
+
243
+ List accounts the current user has access to.
244
+
245
+ **Query Parameters**:
246
+ | Parameter | Type | Default | Description |
247
+ |-----------|------|---------|-------------|
248
+ | `page` | integer | 1 | Page number |
249
+ | `limit` | integer | 20 | Items per page |
250
+
251
+ **Response**:
252
+ ```json
253
+ {
254
+ "data": [
255
+ {
256
+ "id": "account-uuid",
257
+ "name": "My Workspace",
258
+ "description": "Team workspace",
259
+ "domain": "myworkspace",
260
+ "memberCount": 5,
261
+ "createdAt": "2025-01-01T00:00:00Z"
262
+ }
263
+ ],
264
+ "pagination": {
265
+ "page": 1,
266
+ "limit": 20,
267
+ "total": 3,
268
+ "totalPages": 1
269
+ }
270
+ }
271
+ ```
272
+
273
+ ### POST /api/accounts
274
+
275
+ Create a new account (workspace).
276
+
277
+ **Request Body**:
278
+ ```json
279
+ {
280
+ "name": "New Workspace",
281
+ "description": "Description of the workspace",
282
+ "domain": "new-workspace"
283
+ }
284
+ ```
285
+
286
+ **Response**: `201 Created`
287
+
288
+ ---
289
+
290
+ ## Invitations Endpoints [HIGH]
291
+
292
+ Base path: `/api/invitations`
293
+
294
+ | Method | Path | Description | Auth | Permission |
295
+ |--------|------|-------------|------|------------|
296
+ | `POST` | `/api/invitations` | Send invitation | Yes | MANAGER+ |
297
+ | `GET` | `/api/invitations` | List invitations | Yes | MANAGER+ |
298
+ | `DELETE` | `/api/invitations/{id}` | Revoke invitation | Yes | MANAGER+ |
299
+
300
+ ### POST /api/invitations
301
+
302
+ Send a team invitation email.
303
+
304
+ **Request Body**:
305
+ ```json
306
+ {
307
+ "email": "newmember@example.com",
308
+ "role": "EDITOR",
309
+ "accountId": "account-uuid"
310
+ }
311
+ ```
312
+
313
+ **Response**: `201 Created`
314
+ ```json
315
+ {
316
+ "id": "invitation-uuid",
317
+ "email": "newmember@example.com",
318
+ "role": "EDITOR",
319
+ "status": "pending",
320
+ "expiresAt": "2025-01-08T00:00:00Z"
321
+ }
322
+ ```
323
+
324
+ ### GET /api/invitations
325
+
326
+ List pending invitations for an account.
327
+
328
+ **Query Parameters**:
329
+ | Parameter | Type | Description |
330
+ |-----------|------|-------------|
331
+ | `accountId` | string | Filter by account |
332
+ | `status` | string | Filter by status (pending, accepted, expired) |
333
+
334
+ ### DELETE /api/invitations/{id}
335
+
336
+ Revoke a pending invitation.
337
+
338
+ **Response**: `204 No Content`
339
+
340
+ ---
341
+
342
+ ## Audits Endpoints [HIGH]
343
+
344
+ Base path: `/api/audits`
345
+
346
+ | Method | Path | Description | Auth | Permission |
347
+ |--------|------|-------------|------|------------|
348
+ | `GET` | `/api/audits` | List audit logs | Yes | ANALYTICS/ADMIN |
349
+
350
+ ### GET /api/audits
351
+
352
+ Query audit logs with filtering.
353
+
354
+ **Query Parameters**:
355
+ | Parameter | Type | Description |
356
+ |-----------|------|-------------|
357
+ | `page` | integer | Page number |
358
+ | `limit` | integer | Items per page |
359
+ | `action` | string | Filter by action (CREATE, UPDATE, DELETE, LOGIN, LOGOUT) |
360
+ | `resourceType` | string | Filter by resource type |
361
+ | `resourceId` | string | Filter by resource ID |
362
+ | `userId` | string | Filter by user ID |
363
+ | `startDate` | string | Filter from date (ISO 8601) |
364
+ | `endDate` | string | Filter to date (ISO 8601) |
365
+
366
+ **Response**:
367
+ ```json
368
+ {
369
+ "data": [
370
+ {
371
+ "id": "audit-uuid",
372
+ "action": "UPDATE",
373
+ "resourceType": "user",
374
+ "resourceId": "user-uuid",
375
+ "userId": "actor-uuid",
376
+ "changes": {
377
+ "before": { "name": "Old Name" },
378
+ "after": { "name": "New Name" }
379
+ },
380
+ "ipAddress": "192.168.1.1",
381
+ "userAgent": "Mozilla/5.0...",
382
+ "transactionId": "txn-uuid",
383
+ "createdAt": "2025-01-01T12:00:00Z"
384
+ }
385
+ ],
386
+ "pagination": {
387
+ "page": 1,
388
+ "limit": 20,
389
+ "total": 150,
390
+ "totalPages": 8
391
+ }
392
+ }
393
+ ```
394
+
395
+ ---
396
+
397
+ ## Storage Endpoints [HIGH]
398
+
399
+ Base path: `/api/storage`
400
+
401
+ | Method | Path | Description | Auth | Permission |
402
+ |--------|------|-------------|------|------------|
403
+ | `POST` | `/api/storage/upload-url` | Get presigned upload URL | Yes | AUTHOR+ |
404
+ | `PUT` | `/api/storage/upload/{key}` | Upload file directly | Yes | AUTHOR+ |
405
+ | `DELETE` | `/api/storage/{key}` | Delete file | Yes | EDITOR+ |
406
+
407
+ ### POST /api/storage/upload-url
408
+
409
+ Generate a presigned URL for client-side upload.
410
+
411
+ **Request Body**:
412
+ ```json
413
+ {
414
+ "filename": "document.pdf",
415
+ "contentType": "application/pdf",
416
+ "size": 1048576
417
+ }
418
+ ```
419
+
420
+ **Response**:
421
+ ```json
422
+ {
423
+ "uploadUrl": "https://r2.cloudflarestorage.com/...",
424
+ "key": "accounts/uuid/files/uuid/document.pdf",
425
+ "expiresAt": "2025-01-01T12:15:00Z"
426
+ }
427
+ ```
428
+
429
+ ### PUT /api/storage/upload/{key}
430
+
431
+ Direct file upload (alternative to presigned URL).
432
+
433
+ **Headers**:
434
+ - `Content-Type`: File MIME type
435
+ - `Content-Length`: File size in bytes
436
+
437
+ **Response**: `200 OK`
438
+ ```json
439
+ {
440
+ "key": "accounts/uuid/files/uuid/document.pdf",
441
+ "url": "https://storage.example.com/...",
442
+ "size": 1048576
443
+ }
444
+ ```
445
+
446
+ ### DELETE /api/storage/{key}
447
+
448
+ Delete a file from R2 storage.
449
+
450
+ **Response**: `204 No Content`
451
+
452
+ ---
453
+
454
+ ## Health Endpoints [HIGH]
455
+
456
+ Base path: `/health`
457
+
458
+ | Method | Path | Description | Auth |
459
+ |--------|------|-------------|------|
460
+ | `GET` | `/health` | Basic health check | No |
461
+ | `GET` | `/health/ready` | Readiness probe | No |
462
+ | `GET` | `/health/live` | Liveness probe | No |
463
+
464
+ ### GET /health
465
+
466
+ Basic health check endpoint.
467
+
468
+ **Response**:
469
+ ```json
470
+ {
471
+ "status": "healthy",
472
+ "timestamp": "2025-01-01T12:00:00Z"
473
+ }
474
+ ```
475
+
476
+ ### GET /health/ready
477
+
478
+ Kubernetes readiness probe - checks database connectivity.
479
+
480
+ **Response**:
481
+ ```json
482
+ {
483
+ "status": "ready",
484
+ "checks": {
485
+ "database": "ok",
486
+ "kv": "ok"
487
+ }
488
+ }
489
+ ```
490
+
491
+ ### GET /health/live
492
+
493
+ Kubernetes liveness probe - basic process health.
494
+
495
+ **Response**:
496
+ ```json
497
+ {
498
+ "status": "alive"
499
+ }
500
+ ```
501
+
502
+ ---
503
+
504
+ ## Error Responses
505
+
506
+ All endpoints return consistent error responses:
507
+
508
+ ```json
509
+ {
510
+ "error": {
511
+ "code": "ERROR_CODE",
512
+ "message": "Human-readable error message",
513
+ "details": {}
514
+ }
515
+ }
516
+ ```
517
+
518
+ ### HTTP Status Codes
519
+
520
+ | Code | Meaning |
521
+ |------|---------|
522
+ | `200` | Success |
523
+ | `201` | Created |
524
+ | `204` | No Content |
525
+ | `400` | Bad Request (validation error) |
526
+ | `401` | Unauthorized (not authenticated) |
527
+ | `403` | Forbidden (insufficient permissions) |
528
+ | `404` | Not Found |
529
+ | `409` | Conflict (duplicate resource) |
530
+ | `429` | Too Many Requests (rate limited) |
531
+ | `500` | Internal Server Error |
532
+
533
+ ### Common Error Codes
534
+
535
+ | Code | Description |
536
+ |------|-------------|
537
+ | `VALIDATION_ERROR` | Request body/params failed validation |
538
+ | `UNAUTHORIZED` | No valid session |
539
+ | `FORBIDDEN` | Insufficient role/permissions |
540
+ | `NOT_FOUND` | Resource doesn't exist |
541
+ | `DUPLICATE_EMAIL` | Email already registered |
542
+ | `INVITATION_EXPIRED` | Invitation token expired |
543
+ | `RATE_LIMITED` | Too many requests |
544
+
545
+ ---
546
+
547
+ ## Rate Limiting
548
+
549
+ | Endpoint Type | Limit | Window |
550
+ |---------------|-------|--------|
551
+ | Auth endpoints | 10 req | 1 minute |
552
+ | API endpoints | 100 req | 1 minute |
553
+ | Upload endpoints | 20 req | 1 minute |
554
+
555
+ Rate limit headers:
556
+ ```
557
+ X-RateLimit-Limit: 100
558
+ X-RateLimit-Remaining: 95
559
+ X-RateLimit-Reset: 1704110400
560
+ ```
561
+
562
+ ---
563
+
564
+ ## OpenAPI Specification
565
+
566
+ Full OpenAPI 3.0 specification available at:
567
+
568
+ - **JSON**: `GET /api/doc`
569
+ - **Swagger UI**: `GET /api/swagger`
570
+
571
+ The specification includes:
572
+ - All request/response schemas (Zod-generated)
573
+ - Authentication requirements
574
+ - Example requests and responses
575
+ - Error schemas