@beechcms/api 0.4.0-preview.9 → 0.4.1

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 (140) hide show
  1. package/assets/dashboard/BeechLogo.svg +18 -18
  2. package/assets/dashboard/BeechLogoLIght.svg +48 -48
  3. package/assets/dashboard/assets/index-CH13idU1.js +554 -0
  4. package/assets/dashboard/assets/index-CewtCjom.css +1 -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/bcrypt-hash-provider.ts +20 -0
  12. package/src/auth/constants.ts +10 -10
  13. package/src/auth/generate-refresh-token.test.ts +19 -0
  14. package/src/auth/hash-provider.test.ts +46 -0
  15. package/src/auth/in-memory-hash-provider.ts +13 -0
  16. package/src/auth/jose-token-service.ts +55 -0
  17. package/src/auth/login.test.ts +92 -0
  18. package/src/auth/login.ts +74 -91
  19. package/src/auth/refresh.ts +5 -127
  20. package/src/auth/static-token-service.ts +18 -0
  21. package/src/auth/token-service.test.ts +82 -0
  22. package/src/factory.ts +339 -303
  23. package/src/features/content/constants.ts +10 -0
  24. package/src/features/content/handlers/create.ts +157 -0
  25. package/src/features/content/handlers/delete.ts +80 -0
  26. package/src/features/content/handlers/facets.ts +45 -0
  27. package/src/features/content/handlers/get.ts +116 -0
  28. package/src/features/content/handlers/list.ts +88 -0
  29. package/src/features/content/handlers/update.ts +211 -0
  30. package/src/features/content/index.ts +20 -0
  31. package/src/features/draft/draft.handler.ts +283 -198
  32. package/src/features/draft/index.ts +1 -1
  33. package/src/features/email/email.provider.ts +38 -38
  34. package/src/features/email/email.service.ts +80 -80
  35. package/src/features/email/email.types.ts +98 -98
  36. package/src/features/email/index.ts +28 -28
  37. package/src/features/email/providers/resend.ts +63 -63
  38. package/src/features/email/templates/password-changed.ts +59 -59
  39. package/src/features/email/templates/password-reset.ts +64 -64
  40. package/src/features/email/templates/shell.ts +92 -93
  41. package/src/features/notifications/index.ts +1 -1
  42. package/src/features/notifications/notifications.handler.ts +101 -88
  43. package/src/features/password-reset/index.ts +15 -15
  44. package/src/features/password-reset/request.ts +82 -88
  45. package/src/features/password-reset/reset.ts +92 -110
  46. package/src/features/rotate-field/index.ts +1 -1
  47. package/src/features/rotate-field/rotate-field.handler.ts +128 -82
  48. package/src/features/rotate-field/rotate-field.schema.ts +13 -9
  49. package/src/features/schema/schema.handler.ts +16 -16
  50. package/src/features/settings/settings.handler.ts +301 -249
  51. package/src/features/setup/index.ts +91 -59
  52. package/src/features/stats/index.ts +1 -1
  53. package/src/features/stats/stats.handler.ts +430 -395
  54. package/src/index.ts +24 -11
  55. package/src/media-utils.ts +78 -78
  56. package/src/middleware/auth-providers.middleware.ts +32 -0
  57. package/src/middleware/observability.middleware.ts +52 -0
  58. package/src/middleware/rate-limit.middleware.ts +41 -0
  59. package/src/middleware/repository.middleware.ts +60 -0
  60. package/src/middleware/storage.middleware.ts +21 -0
  61. package/src/middleware.ts +47 -67
  62. package/src/public/access-policy.ts +23 -23
  63. package/src/public/api-key-middleware.ts +53 -53
  64. package/src/public/index.ts +12 -12
  65. package/src/public/problem-details.ts +48 -42
  66. package/src/public/public-add.ts +156 -183
  67. package/src/public/public-edit.ts +159 -183
  68. package/src/public/public-errors.ts +15 -15
  69. package/src/public/public-read.ts +216 -217
  70. package/src/public/public-routes.ts +84 -31
  71. package/src/public/query-builder.test.ts +220 -0
  72. package/src/public/query-builder.ts +152 -241
  73. package/src/public/rate-limit-middleware.ts +30 -42
  74. package/src/public/response-builder.ts +26 -26
  75. package/src/public/sanitize.ts +65 -65
  76. package/src/public/slug-utils.ts +14 -14
  77. package/src/rate-limit/cloudflare-rate-limiter.test.ts +26 -0
  78. package/src/rate-limit/cloudflare-rate-limiter.ts +11 -0
  79. package/src/rate-limit/in-memory-rate-limiter.test.ts +33 -0
  80. package/src/rate-limit/in-memory-rate-limiter.ts +13 -0
  81. package/src/rate-limit/no-op-rate-limiter.test.ts +18 -0
  82. package/src/rate-limit/no-op-rate-limiter.ts +7 -0
  83. package/src/search-utils.test.ts +207 -0
  84. package/src/search-utils.ts +209 -192
  85. package/src/search.ts +61 -72
  86. package/src/shared/apply-policies.test.ts +77 -0
  87. package/src/shared/apply-policies.ts +63 -63
  88. package/src/shared/background-notification-service.test.ts +58 -0
  89. package/src/shared/background-notification-service.ts +48 -0
  90. package/src/shared/base.repository.d1.ts +28 -0
  91. package/src/shared/content-utils.test.ts +161 -0
  92. package/src/shared/content-utils.ts +82 -108
  93. package/src/shared/content.repository.d1.test.ts +312 -0
  94. package/src/shared/content.repository.d1.ts +382 -0
  95. package/src/shared/d1-activity-log.repository.test.ts +136 -0
  96. package/src/shared/d1-activity-log.repository.ts +101 -0
  97. package/src/shared/d1-activity-logger.test.ts +82 -0
  98. package/src/shared/d1-activity-logger.ts +63 -0
  99. package/src/shared/d1-analytics.repository.test.ts +74 -0
  100. package/src/shared/d1-analytics.repository.ts +81 -0
  101. package/src/shared/d1-content-scan.repository.ts +29 -0
  102. package/src/shared/d1-notification.repository.test.ts +124 -0
  103. package/src/shared/d1-notification.repository.ts +114 -0
  104. package/src/shared/d1-password-reset-token.repository.test.ts +77 -0
  105. package/src/shared/d1-password-reset-token.repository.ts +52 -0
  106. package/src/shared/d1-search.repository.test.ts +83 -0
  107. package/src/shared/d1-search.repository.ts +84 -0
  108. package/src/shared/d1-session.repository.test.ts +121 -0
  109. package/src/shared/d1-session.repository.ts +98 -0
  110. package/src/shared/d1-user.repository.test.ts +147 -0
  111. package/src/shared/d1-user.repository.ts +109 -0
  112. package/src/shared/d1-widget.repository.test.ts +217 -0
  113. package/src/shared/d1-widget.repository.ts +337 -0
  114. package/src/shared/fixed-clock.ts +21 -0
  115. package/src/shared/fts-sync.ts +4 -4
  116. package/src/shared/idempotency.repository.d1.test.ts +79 -0
  117. package/src/shared/idempotency.repository.d1.ts +56 -0
  118. package/src/shared/in-memory-activity-logger.ts +15 -0
  119. package/src/shared/in-memory-notification-service.ts +15 -0
  120. package/src/shared/media.repository.d1.test.ts +103 -0
  121. package/src/shared/media.repository.d1.ts +64 -0
  122. package/src/shared/query-utils.ts +137 -137
  123. package/src/shared/request-utils.ts +22 -0
  124. package/src/shared/sequential-id-generator.ts +22 -0
  125. package/src/shared/storage/factory.ts +40 -0
  126. package/src/shared/storage/r2-binding-bucket.ts +81 -0
  127. package/src/shared/storage/s3-bucket.ts +163 -0
  128. package/src/shared/storage-utils.ts +36 -36
  129. package/src/shared/system-stats.repository.d1.test.ts +54 -0
  130. package/src/shared/system-stats.repository.d1.ts +44 -0
  131. package/src/types.ts +63 -36
  132. package/src/upload.ts +186 -335
  133. package/src/widget.ts +208 -349
  134. package/assets/dashboard/assets/index-CC-jbp6g.js +0 -554
  135. package/assets/dashboard/assets/index-CQODXprH.css +0 -1
  136. package/src/content.ts +0 -502
  137. package/src/features/draft/draft.test.ts +0 -315
  138. package/src/features/rotate-field/rotate-field.test.ts +0 -297
  139. package/src/shared/activity-logger.ts +0 -79
  140. package/src/shared/notification-service.ts +0 -56
@@ -1,59 +1,91 @@
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 type { Env, Variables } from '../../types'
4
+ import { publicProblem } from '../../public/problem-details'
5
+
6
+ const setupApp = new Hono<{ Bindings: Env; Variables: Variables }>()
7
+
8
+ /**
9
+ * GET /auth/setup
10
+ * Checks if the application needs an initial setup (i.e., if no users exist).
11
+ */
12
+ setupApp.get('/auth/setup', async (context) => {
13
+ const userCount = await context.get('userRepository').countAll()
14
+ return context.json({ needsSetup: userCount === 0 })
15
+ })
16
+
17
+ /**
18
+ * POST /auth/setup
19
+ * Creates the first administrator account. This endpoint is disabled once at least one user exists.
20
+ */
21
+ setupApp.post('/auth/setup', async (context) => {
22
+ const userCount = await context.get('userRepository').countAll()
23
+
24
+ if (userCount > 0) {
25
+ return publicProblem(context, {
26
+ type: 'setup-already-done',
27
+ title: 'Setup already completed',
28
+ status: 403,
29
+ detail: 'An administrator account already exists. Initial setup can only be performed once.'
30
+ })
31
+ }
32
+
33
+ let payload: unknown
34
+ try {
35
+ payload = await context.req.json()
36
+ } catch {
37
+ return publicProblem(context, {
38
+ type: 'bad-request',
39
+ title: 'Invalid JSON body',
40
+ status: 400,
41
+ detail: 'The request body could not be parsed as valid JSON.'
42
+ })
43
+ }
44
+
45
+ if (!payload || typeof payload !== 'object') {
46
+ return publicProblem(context, {
47
+ type: 'bad-request',
48
+ title: 'Invalid request',
49
+ status: 400,
50
+ detail: 'The request payload is missing or invalid.'
51
+ })
52
+ }
53
+
54
+ const { email, password, name } = payload as Record<string, unknown>
55
+
56
+ if (typeof email !== 'string' || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
57
+ return publicProblem(context, {
58
+ type: 'validation-error',
59
+ title: 'Valid email required',
60
+ status: 422,
61
+ detail: 'A valid email address is required for the administrator account.'
62
+ })
63
+ }
64
+
65
+ if (typeof password !== 'string' || password.length < 8 || password.length > 128) {
66
+ return publicProblem(context, {
67
+ type: 'validation-error',
68
+ title: 'Invalid password',
69
+ status: 422,
70
+ detail: 'Password must be between 8 and 128 characters long.'
71
+ })
72
+ }
73
+
74
+ const passwordHash = await context.get('hashProvider').hash(password)
75
+ const normalizedEmail = email.trim().toLowerCase()
76
+ const normalizedName = typeof name === 'string' ? name.trim() : null
77
+
78
+ await context.get('userRepository').create({
79
+ id: context.get('idGenerator').uuid(),
80
+ email: normalizedEmail,
81
+ passwordHash,
82
+ role: 'admin',
83
+ name: normalizedName,
84
+ })
85
+
86
+ return context.json({ success: true }, 201)
87
+ })
88
+
89
+ export { setupApp }
90
+
91
+
@@ -1 +1 @@
1
- export { statsApp } from './stats.handler'
1
+ export { statsApp } from './stats.handler'