@beechcms/api 0.4.0-preview.11 → 0.4.0-preview.13

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 (86) 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 -325
  15. package/src/features/content/constants.ts +10 -10
  16. package/src/features/content/handlers/create.ts +153 -163
  17. package/src/features/content/handlers/delete.ts +76 -85
  18. package/src/features/content/handlers/facets.ts +45 -45
  19. package/src/features/content/handlers/get.ts +116 -116
  20. package/src/features/content/handlers/list.ts +88 -88
  21. package/src/features/content/handlers/update.ts +207 -216
  22. package/src/features/content/index.ts +20 -20
  23. package/src/features/draft/draft.handler.ts +272 -272
  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 -267
  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 -24
  47. package/src/media-utils.ts +78 -78
  48. package/src/middleware/repository.middleware.ts +24 -18
  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 -166
  56. package/src/public/public-edit.ts +158 -158
  57. package/src/public/public-errors.ts +15 -15
  58. package/src/public/public-read.ts +216 -216
  59. package/src/public/public-routes.ts +84 -31
  60. package/src/public/query-builder.ts +152 -152
  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 -28
  70. package/src/shared/content-utils.ts +82 -108
  71. package/src/shared/content.repository.d1.ts +382 -382
  72. package/src/shared/fts-sync.ts +4 -4
  73. package/src/shared/idempotency.repository.d1.ts +56 -45
  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 -41
  83. package/src/upload.ts +179 -331
  84. package/src/widget.ts +349 -349
  85. package/assets/dashboard/assets/index-9Ch2xWJr.js +0 -554
  86. package/assets/dashboard/assets/index-ye3325L9.css +0 -1
@@ -1,127 +1,127 @@
1
- /// <reference types="@cloudflare/workers-types" />
2
- import { SignJWT } from 'jose'
3
-
4
- /** Secondi in un giorno (per calcolo scadenza) */
5
- const SECONDS_PER_DAY = 24 * 60 * 60
6
-
7
- export type JwtTokenOptions = {
8
- issuer?: string
9
- audience?: string
10
- }
11
-
12
- export type RefreshTokenRecord = {
13
- id: string
14
- user_id: string
15
- token_hash: string
16
- expires_at: number
17
- created_at: number
18
- revoked_at: number | null
19
- }
20
-
21
- /** Genera un refresh token sicuro (UUID v4) - usa Web Crypto API globale */
22
- export function generateRefreshToken(): string {
23
- return crypto.randomUUID()
24
- }
25
-
26
- /**
27
- * Hash SHA-256 del refresh token per storage sicuro.
28
- * Usa Web Crypto API (crypto.subtle.digest).
29
- */
30
- export async function hashRefreshToken(token: string): Promise<string> {
31
- const encoder = new TextEncoder()
32
- const data = encoder.encode(token)
33
- const hashBuffer = await crypto.subtle.digest('SHA-256', data)
34
- const hashBytes = Array.from(new Uint8Array(hashBuffer))
35
- return hashBytes.map((byte) => byte.toString(16).padStart(2, '0')).join('')
36
- }
37
-
38
- /**
39
- * Salva refresh token in DB (solo hash, mai in chiaro).
40
- * @param expiresInDays - Giorni di validità (default 7)
41
- */
42
- export async function saveRefreshToken(
43
- db: D1Database,
44
- userId: string,
45
- token: string,
46
- expiresInDays: number = 7
47
- ): Promise<void> {
48
- const id = crypto.randomUUID()
49
- const tokenHash = await hashRefreshToken(token)
50
- const expiresAt = Math.floor(Date.now() / 1000) + expiresInDays * SECONDS_PER_DAY
51
-
52
- await db.prepare(
53
- `INSERT INTO refresh_tokens (id, user_id, token_hash, expires_at)
54
- VALUES (?, ?, ?, ?)`
55
- ).bind(id, userId, tokenHash, expiresAt).run()
56
- }
57
-
58
- /**
59
- * Valida refresh token: verifica hash in DB, scadenza e che non sia revocato.
60
- * @returns { valid, userId? } - userId presente solo se valido
61
- */
62
- export async function validateRefreshToken(
63
- db: D1Database,
64
- token: string
65
- ): Promise<{ valid: boolean; userId?: string }> {
66
- const tokenHash = await hashRefreshToken(token)
67
- const now = Math.floor(Date.now() / 1000)
68
-
69
- const row = await db.prepare(
70
- `SELECT user_id, expires_at, revoked_at
71
- FROM refresh_tokens
72
- WHERE token_hash = ? LIMIT 1`
73
- ).bind(tokenHash).first<RefreshTokenRecord>()
74
-
75
- if (!row) return { valid: false }
76
- if (row.revoked_at !== null) return { valid: false }
77
- if (row.expires_at < now) return { valid: false }
78
-
79
- return { valid: true, userId: row.user_id }
80
- }
81
-
82
- /**
83
- * Revoca un refresh token impostando revoked_at.
84
- * Il token non potrà più essere usato per il refresh.
85
- */
86
- export async function revokeRefreshToken(
87
- db: D1Database,
88
- token: string
89
- ): Promise<boolean> {
90
- const tokenHash = await hashRefreshToken(token)
91
- const now = Math.floor(Date.now() / 1000)
92
-
93
- const result = await db.prepare(
94
- `UPDATE refresh_tokens
95
- SET revoked_at = ?
96
- WHERE token_hash = ?
97
- AND revoked_at IS NULL
98
- AND expires_at >= ?`
99
- ).bind(now, tokenHash, now).run()
100
-
101
- const changes = (result as unknown as { meta?: { changes?: number } })?.meta?.changes ?? 0
102
- return changes > 0
103
- }
104
-
105
- /**
106
- * Genera JWT access token con scadenza breve (15 min).
107
- * Payload: sub (userId), email, name (opzionale). Algoritmo HS256.
108
- */
109
- export async function generateAccessToken(
110
- userId: string,
111
- email: string,
112
- secret: string,
113
- options: JwtTokenOptions = {},
114
- name?: string
115
- ): Promise<string> {
116
- const payload: Record<string, string> = { email }
117
- if (name) payload.name = name
118
- const secretBytes = new TextEncoder().encode(secret)
119
- let jwt = new SignJWT(payload)
120
- .setProtectedHeader({ alg: 'HS256', typ: 'JWT' })
121
- .setSubject(userId)
122
- .setIssuedAt()
123
- .setExpirationTime('15m')
124
- if (options.issuer) jwt = jwt.setIssuer(options.issuer)
125
- if (options.audience) jwt = jwt.setAudience(options.audience)
126
- return jwt.sign(secretBytes)
127
- }
1
+ /// <reference types="@cloudflare/workers-types" />
2
+ import { SignJWT } from 'jose'
3
+
4
+ /** Secondi in un giorno (per calcolo scadenza) */
5
+ const SECONDS_PER_DAY = 24 * 60 * 60
6
+
7
+ export type JwtTokenOptions = {
8
+ issuer?: string
9
+ audience?: string
10
+ }
11
+
12
+ export type RefreshTokenRecord = {
13
+ id: string
14
+ user_id: string
15
+ token_hash: string
16
+ expires_at: number
17
+ created_at: number
18
+ revoked_at: number | null
19
+ }
20
+
21
+ /** Genera un refresh token sicuro (UUID v4) - usa Web Crypto API globale */
22
+ export function generateRefreshToken(): string {
23
+ return crypto.randomUUID()
24
+ }
25
+
26
+ /**
27
+ * Hash SHA-256 del refresh token per storage sicuro.
28
+ * Usa Web Crypto API (crypto.subtle.digest).
29
+ */
30
+ export async function hashRefreshToken(token: string): Promise<string> {
31
+ const encoder = new TextEncoder()
32
+ const data = encoder.encode(token)
33
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data)
34
+ const hashBytes = Array.from(new Uint8Array(hashBuffer))
35
+ return hashBytes.map((byte) => byte.toString(16).padStart(2, '0')).join('')
36
+ }
37
+
38
+ /**
39
+ * Salva refresh token in DB (solo hash, mai in chiaro).
40
+ * @param expiresInDays - Giorni di validità (default 7)
41
+ */
42
+ export async function saveRefreshToken(
43
+ db: D1Database,
44
+ userId: string,
45
+ token: string,
46
+ expiresInDays: number = 7
47
+ ): Promise<void> {
48
+ const id = crypto.randomUUID()
49
+ const tokenHash = await hashRefreshToken(token)
50
+ const expiresAt = Math.floor(Date.now() / 1000) + expiresInDays * SECONDS_PER_DAY
51
+
52
+ await db.prepare(
53
+ `INSERT INTO refresh_tokens (id, user_id, token_hash, expires_at)
54
+ VALUES (?, ?, ?, ?)`
55
+ ).bind(id, userId, tokenHash, expiresAt).run()
56
+ }
57
+
58
+ /**
59
+ * Valida refresh token: verifica hash in DB, scadenza e che non sia revocato.
60
+ * @returns { valid, userId? } - userId presente solo se valido
61
+ */
62
+ export async function validateRefreshToken(
63
+ db: D1Database,
64
+ token: string
65
+ ): Promise<{ valid: boolean; userId?: string }> {
66
+ const tokenHash = await hashRefreshToken(token)
67
+ const now = Math.floor(Date.now() / 1000)
68
+
69
+ const row = await db.prepare(
70
+ `SELECT user_id, expires_at, revoked_at
71
+ FROM refresh_tokens
72
+ WHERE token_hash = ? LIMIT 1`
73
+ ).bind(tokenHash).first<RefreshTokenRecord>()
74
+
75
+ if (!row) return { valid: false }
76
+ if (row.revoked_at !== null) return { valid: false }
77
+ if (row.expires_at < now) return { valid: false }
78
+
79
+ return { valid: true, userId: row.user_id }
80
+ }
81
+
82
+ /**
83
+ * Revoca un refresh token impostando revoked_at.
84
+ * Il token non potrà più essere usato per il refresh.
85
+ */
86
+ export async function revokeRefreshToken(
87
+ db: D1Database,
88
+ token: string
89
+ ): Promise<boolean> {
90
+ const tokenHash = await hashRefreshToken(token)
91
+ const now = Math.floor(Date.now() / 1000)
92
+
93
+ const result = await db.prepare(
94
+ `UPDATE refresh_tokens
95
+ SET revoked_at = ?
96
+ WHERE token_hash = ?
97
+ AND revoked_at IS NULL
98
+ AND expires_at >= ?`
99
+ ).bind(now, tokenHash, now).run()
100
+
101
+ const changes = (result as unknown as { meta?: { changes?: number } })?.meta?.changes ?? 0
102
+ return changes > 0
103
+ }
104
+
105
+ /**
106
+ * Genera JWT access token con scadenza breve (15 min).
107
+ * Payload: sub (userId), email, name (opzionale). Algoritmo HS256.
108
+ */
109
+ export async function generateAccessToken(
110
+ userId: string,
111
+ email: string,
112
+ secret: string,
113
+ options: JwtTokenOptions = {},
114
+ name?: string
115
+ ): Promise<string> {
116
+ const payload: Record<string, string> = { email }
117
+ if (name) payload.name = name
118
+ const secretBytes = new TextEncoder().encode(secret)
119
+ let jwt = new SignJWT(payload)
120
+ .setProtectedHeader({ alg: 'HS256', typ: 'JWT' })
121
+ .setSubject(userId)
122
+ .setIssuedAt()
123
+ .setExpirationTime('15m')
124
+ if (options.issuer) jwt = jwt.setIssuer(options.issuer)
125
+ if (options.audience) jwt = jwt.setAudience(options.audience)
126
+ return jwt.sign(secretBytes)
127
+ }