@beechcms/api 0.4.0-preview.8 → 0.4.0

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 (89) hide show
  1. package/assets/dashboard/BeechLogo.svg +18 -18
  2. package/assets/dashboard/BeechLogoLIght.svg +48 -48
  3. package/assets/dashboard/assets/index-CewtCjom.css +1 -0
  4. package/assets/dashboard/assets/index-FQ6JhvRH.js +554 -0
  5. package/assets/dashboard/beechLogoDark.svg +48 -48
  6. package/assets/dashboard/index.html +18 -18
  7. package/assets/dashboard/sol.svg +3 -3
  8. package/assets/dashboard/undraw_enter_nwx3.svg +36 -36
  9. package/migrations/0000_v040_base.sql +213 -213
  10. package/package.json +2 -2
  11. package/src/auth/constants.ts +10 -10
  12. package/src/auth/login.ts +91 -91
  13. package/src/auth/refresh.ts +127 -127
  14. package/src/factory.ts +347 -303
  15. package/src/features/content/constants.ts +10 -0
  16. package/src/features/content/handlers/create.ts +153 -0
  17. package/src/features/content/handlers/delete.ts +76 -0
  18. package/src/features/content/handlers/facets.ts +45 -0
  19. package/src/features/content/handlers/get.ts +116 -0
  20. package/src/features/content/handlers/list.ts +88 -0
  21. package/src/features/content/handlers/update.ts +207 -0
  22. package/src/features/content/index.ts +20 -0
  23. package/src/features/draft/draft.handler.ts +272 -198
  24. package/src/features/draft/index.ts +1 -1
  25. package/src/features/email/email.provider.ts +38 -38
  26. package/src/features/email/email.service.ts +80 -80
  27. package/src/features/email/email.types.ts +98 -98
  28. package/src/features/email/index.ts +28 -28
  29. package/src/features/email/providers/resend.ts +63 -63
  30. package/src/features/email/templates/password-changed.ts +59 -59
  31. package/src/features/email/templates/password-reset.ts +64 -64
  32. package/src/features/email/templates/shell.ts +92 -93
  33. package/src/features/notifications/index.ts +1 -1
  34. package/src/features/notifications/notifications.handler.ts +130 -88
  35. package/src/features/password-reset/index.ts +15 -15
  36. package/src/features/password-reset/request.ts +106 -88
  37. package/src/features/password-reset/reset.ts +128 -110
  38. package/src/features/rotate-field/index.ts +1 -1
  39. package/src/features/rotate-field/rotate-field.handler.ts +132 -82
  40. package/src/features/rotate-field/rotate-field.schema.ts +13 -9
  41. package/src/features/schema/schema.handler.ts +16 -16
  42. package/src/features/settings/settings.handler.ts +414 -249
  43. package/src/features/setup/index.ts +96 -59
  44. package/src/features/stats/index.ts +1 -1
  45. package/src/features/stats/stats.handler.ts +458 -395
  46. package/src/index.ts +24 -11
  47. package/src/media-utils.ts +78 -78
  48. package/src/middleware/repository.middleware.ts +24 -0
  49. package/src/middleware/storage.middleware.ts +21 -0
  50. package/src/middleware.ts +67 -67
  51. package/src/public/access-policy.ts +23 -23
  52. package/src/public/api-key-middleware.ts +53 -53
  53. package/src/public/index.ts +12 -12
  54. package/src/public/problem-details.ts +48 -42
  55. package/src/public/public-add.ts +166 -183
  56. package/src/public/public-edit.ts +158 -183
  57. package/src/public/public-errors.ts +15 -15
  58. package/src/public/public-read.ts +216 -217
  59. package/src/public/public-routes.ts +84 -31
  60. package/src/public/query-builder.ts +152 -241
  61. package/src/public/rate-limit-middleware.ts +42 -42
  62. package/src/public/response-builder.ts +26 -26
  63. package/src/public/sanitize.ts +65 -65
  64. package/src/public/slug-utils.ts +14 -14
  65. package/src/search-utils.ts +192 -192
  66. package/src/search.ts +72 -72
  67. package/src/shared/activity-logger.ts +79 -79
  68. package/src/shared/apply-policies.ts +63 -63
  69. package/src/shared/base.repository.d1.ts +28 -0
  70. package/src/shared/content-utils.ts +82 -108
  71. package/src/shared/content.repository.d1.ts +382 -0
  72. package/src/shared/fts-sync.ts +4 -4
  73. package/src/shared/idempotency.repository.d1.ts +56 -0
  74. package/src/shared/media.repository.d1.ts +64 -0
  75. package/src/shared/notification-service.ts +56 -56
  76. package/src/shared/query-utils.ts +137 -137
  77. package/src/shared/storage/factory.ts +40 -0
  78. package/src/shared/storage/r2-binding-bucket.ts +81 -0
  79. package/src/shared/storage/s3-bucket.ts +163 -0
  80. package/src/shared/storage-utils.ts +36 -36
  81. package/src/shared/system-stats.repository.d1.ts +44 -0
  82. package/src/types.ts +46 -36
  83. package/src/upload.ts +179 -335
  84. package/src/widget.ts +349 -349
  85. package/assets/dashboard/assets/index-CC-jbp6g.js +0 -554
  86. package/assets/dashboard/assets/index-CQODXprH.css +0 -1
  87. package/src/content.ts +0 -502
  88. package/src/features/draft/draft.test.ts +0 -315
  89. package/src/features/rotate-field/rotate-field.test.ts +0 -297
@@ -1,59 +1,96 @@
1
- /// <reference types="@cloudflare/workers-types" />
2
- import { Hono } from 'hono'
3
- import bcrypt from 'bcryptjs'
4
- import type { Env, Variables } from '../../types'
5
-
6
- export const setupApp = new Hono<{ Bindings: Env; Variables: Variables }>()
7
-
8
- /** GET /auth/setup — tells the dashboard whether first-run setup is needed */
9
- setupApp.get('/auth/setup', async (c) => {
10
- const row = await c.env.DB
11
- .prepare('SELECT COUNT(*) as count FROM users')
12
- .first<{ count: number }>()
13
- return c.json({ needsSetup: (row?.count ?? 0) === 0 })
14
- })
15
-
16
- /** POST /auth/setup — creates the first admin; rejected once any user exists */
17
- setupApp.post('/auth/setup', async (c) => {
18
- const row = await c.env.DB
19
- .prepare('SELECT COUNT(*) as count FROM users')
20
- .first<{ count: number }>()
21
-
22
- if ((row?.count ?? 0) > 0) {
23
- return c.json(
24
- { type: 'https://beech.local/errors/setup-already-done', title: 'Setup already completed', status: 403 },
25
- 403
26
- )
27
- }
28
-
29
- let body: unknown
30
- try { body = await c.req.json() } catch {
31
- return c.json({ type: 'https://beech.local/errors/bad-request', title: 'Invalid JSON body', status: 400 }, 400)
32
- }
33
-
34
- if (!body || typeof body !== 'object') {
35
- return c.json({ type: 'https://beech.local/errors/bad-request', title: 'Invalid request', status: 400 }, 400)
36
- }
37
-
38
- const { email, password, name } = body as Record<string, unknown>
39
-
40
- if (typeof email !== 'string' || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
41
- return c.json({ type: 'https://beech.local/errors/validation', title: 'Valid email required', status: 422 }, 422)
42
- }
43
-
44
- if (typeof password !== 'string' || password.length < 8 || password.length > 128) {
45
- return c.json({ type: 'https://beech.local/errors/validation', title: 'Password must be 8–128 characters', status: 422 }, 422)
46
- }
47
-
48
- const passwordHash = await bcrypt.hash(password, 12)
49
- const id = crypto.randomUUID()
50
- const cleanEmail = email.trim().toLowerCase()
51
- const cleanName = typeof name === 'string' ? name.trim() : null
52
-
53
- await c.env.DB
54
- .prepare('INSERT INTO users (id, email, password_hash, role, name) VALUES (?, ?, ?, ?, ?)')
55
- .bind(id, cleanEmail, passwordHash, 'admin', cleanName)
56
- .run()
57
-
58
- return c.json({ success: true }, 201)
59
- })
1
+ /// <reference types="@cloudflare/workers-types" />
2
+ import { Hono } from 'hono'
3
+ import bcrypt from 'bcryptjs'
4
+ import type { Env, Variables } from '../../types'
5
+ import { publicProblem } from '../../public/problem-details'
6
+
7
+ const setupApp = new Hono<{ Bindings: Env; Variables: Variables }>()
8
+
9
+ /**
10
+ * GET /auth/setup
11
+ * Checks if the application needs an initial setup (i.e., if no users exist).
12
+ */
13
+ setupApp.get('/auth/setup', async (context) => {
14
+ const userCountResult = await context.env.DB
15
+ .prepare('SELECT COUNT(*) as count FROM users')
16
+ .first<{ count: number }>()
17
+
18
+ const needsInitialSetup = (userCountResult?.count ?? 0) === 0
19
+ return context.json({ needsSetup: needsInitialSetup })
20
+ })
21
+
22
+ /**
23
+ * POST /auth/setup
24
+ * Creates the first administrator account. This endpoint is disabled once at least one user exists.
25
+ */
26
+ setupApp.post('/auth/setup', async (context) => {
27
+ const userCountResult = await context.env.DB
28
+ .prepare('SELECT COUNT(*) as count FROM users')
29
+ .first<{ count: number }>()
30
+
31
+ if ((userCountResult?.count ?? 0) > 0) {
32
+ return publicProblem(context, {
33
+ type: 'setup-already-done',
34
+ title: 'Setup already completed',
35
+ status: 403,
36
+ detail: 'An administrator account already exists. Initial setup can only be performed once.'
37
+ })
38
+ }
39
+
40
+ let payload: unknown
41
+ try {
42
+ payload = await context.req.json()
43
+ } catch {
44
+ return publicProblem(context, {
45
+ type: 'bad-request',
46
+ title: 'Invalid JSON body',
47
+ status: 400,
48
+ detail: 'The request body could not be parsed as valid JSON.'
49
+ })
50
+ }
51
+
52
+ if (!payload || typeof payload !== 'object') {
53
+ return publicProblem(context, {
54
+ type: 'bad-request',
55
+ title: 'Invalid request',
56
+ status: 400,
57
+ detail: 'The request payload is missing or invalid.'
58
+ })
59
+ }
60
+
61
+ const { email, password, name } = payload as Record<string, unknown>
62
+
63
+ if (typeof email !== 'string' || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
64
+ return publicProblem(context, {
65
+ type: 'validation-error',
66
+ title: 'Valid email required',
67
+ status: 422,
68
+ detail: 'A valid email address is required for the administrator account.'
69
+ })
70
+ }
71
+
72
+ if (typeof password !== 'string' || password.length < 8 || password.length > 128) {
73
+ return publicProblem(context, {
74
+ type: 'validation-error',
75
+ title: 'Invalid password',
76
+ status: 422,
77
+ detail: 'Password must be between 8 and 128 characters long.'
78
+ })
79
+ }
80
+
81
+ const hashedPassword = await bcrypt.hash(password, 12)
82
+ const newUserId = crypto.randomUUID()
83
+ const normalizedEmail = email.trim().toLowerCase()
84
+ const normalizedName = typeof name === 'string' ? name.trim() : null
85
+
86
+ await context.env.DB
87
+ .prepare('INSERT INTO users (id, email, password_hash, role, name) VALUES (?, ?, ?, ?, ?)')
88
+ .bind(newUserId, normalizedEmail, hashedPassword, 'admin', normalizedName)
89
+ .run()
90
+
91
+ return context.json({ success: true }, 201)
92
+ })
93
+
94
+ export { setupApp }
95
+
96
+
@@ -1 +1 @@
1
- export { statsApp } from './stats.handler'
1
+ export { statsApp } from './stats.handler'