@striae-org/striae 6.1.7 → 7.0.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 (49) hide show
  1. package/.env.example +0 -26
  2. package/README.md +1 -2
  3. package/app/components/actions/image-manage.ts +17 -67
  4. package/functions/api/audit/[[path]].ts +9 -24
  5. package/functions/api/data/[[path]].ts +9 -24
  6. package/functions/api/image/[[path]].ts +14 -30
  7. package/functions/api/pdf/[[path]].ts +9 -24
  8. package/functions/api/user/[[path]].ts +20 -36
  9. package/package.json +143 -137
  10. package/scripts/deploy-all.sh +29 -10
  11. package/scripts/deploy-config/modules/env-utils.sh +0 -68
  12. package/scripts/deploy-config/modules/prompt.sh +4 -110
  13. package/scripts/deploy-config/modules/scaffolding.sh +5 -68
  14. package/scripts/deploy-config/modules/validation.sh +1 -30
  15. package/scripts/deploy-pages-secrets.sh +0 -9
  16. package/scripts/deploy-worker-secrets.sh +2 -8
  17. package/tsconfig.json +1 -1
  18. package/workers/audit-worker/package.json +2 -2
  19. package/workers/audit-worker/src/{audit-worker.example.ts → audit-worker.ts} +1 -17
  20. package/workers/audit-worker/src/config.ts +1 -6
  21. package/workers/audit-worker/src/types.ts +0 -1
  22. package/workers/audit-worker/wrangler.jsonc.example +2 -6
  23. package/workers/data-worker/package.json +3 -2
  24. package/workers/data-worker/src/config.ts +1 -6
  25. package/workers/data-worker/src/{data-worker.example.ts → data-worker.ts} +2 -18
  26. package/workers/data-worker/src/types.ts +0 -1
  27. package/workers/data-worker/wrangler.jsonc.example +2 -4
  28. package/workers/image-worker/package.json +2 -2
  29. package/workers/image-worker/src/handlers/delete-image.ts +0 -5
  30. package/workers/image-worker/src/handlers/mint-signed-url.ts +0 -5
  31. package/workers/image-worker/src/handlers/serve-image.ts +2 -5
  32. package/workers/image-worker/src/handlers/upload-image.ts +0 -5
  33. package/workers/image-worker/src/{image-worker.example.ts → image-worker.ts} +2 -15
  34. package/workers/image-worker/src/router.ts +2 -3
  35. package/workers/image-worker/src/security/signed-url.ts +2 -2
  36. package/workers/image-worker/src/types.ts +0 -1
  37. package/workers/image-worker/wrangler.jsonc.example +2 -1
  38. package/workers/pdf-worker/package.json +2 -2
  39. package/workers/pdf-worker/src/{pdf-worker.example.ts → pdf-worker.ts} +1 -23
  40. package/workers/pdf-worker/wrangler.jsonc.example +2 -1
  41. package/workers/user-worker/package.json +2 -2
  42. package/workers/user-worker/src/auth.ts +0 -7
  43. package/workers/user-worker/src/handlers/user-routes.ts +25 -39
  44. package/workers/user-worker/src/types.ts +0 -2
  45. package/workers/user-worker/src/{user-worker.example.ts → user-worker.ts} +15 -30
  46. package/workers/user-worker/wrangler.jsonc.example +2 -1
  47. package/wrangler.toml.example +22 -2
  48. package/worker-configuration.d.ts +0 -7509
  49. package/workers/image-worker/src/auth.ts +0 -7
@@ -2,7 +2,6 @@ import type { PDFGenerationData, PDFGenerationRequest, ReportModule, ReportPdfOp
2
2
  import { getAuditTrailPdfOptions, isAuditTrailReportMode, renderAuditTrailReport } from './audit-trail-report';
3
3
 
4
4
  interface Env {
5
- PDF_WORKER_AUTH: string;
6
5
  ACCOUNT_ID?: string;
7
6
  BROWSER_API_TOKEN?: string;
8
7
  }
@@ -40,15 +39,6 @@ const reportModuleLoaders: Record<string, () => Promise<ReportModule>> = {
40
39
 
41
40
  };
42
41
 
43
- const corsHeaders: Record<string, string> = {
44
- 'Access-Control-Allow-Origin': 'PAGES_CUSTOM_DOMAIN',
45
- 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
46
- 'Access-Control-Allow-Headers': 'Content-Type, X-Custom-Auth-Key',
47
- };
48
-
49
- const hasValidHeader = (request: Request, env: Env): boolean =>
50
- request.headers.get('X-Custom-Auth-Key') === env.PDF_WORKER_AUTH;
51
-
52
42
  function isTimeoutError(error: unknown): boolean {
53
43
  return error instanceof Error && (
54
44
  error.name === 'AbortError' ||
@@ -60,7 +50,7 @@ function isTimeoutError(error: unknown): boolean {
60
50
  function jsonResponse(body: unknown, status: number): Response {
61
51
  return new Response(JSON.stringify(body), {
62
52
  status,
63
- headers: { ...corsHeaders, 'content-type': 'application/json' },
53
+ headers: { 'content-type': 'application/json' },
64
54
  });
65
55
  }
66
56
 
@@ -190,10 +180,6 @@ async function renderPdfViaRestEndpoint(env: Env, html: string, pdfOptions: Repo
190
180
  responseHeaders.set('cache-control', 'no-store');
191
181
  }
192
182
 
193
- for (const [headerName, headerValue] of Object.entries(corsHeaders)) {
194
- responseHeaders.set(headerName, headerValue);
195
- }
196
-
197
183
  return new Response(endpointResponse.body, {
198
184
  status: endpointResponse.status,
199
185
  statusText: endpointResponse.statusText,
@@ -203,14 +189,6 @@ async function renderPdfViaRestEndpoint(env: Env, html: string, pdfOptions: Repo
203
189
 
204
190
  export default {
205
191
  async fetch(request: Request, env: Env): Promise<Response> {
206
- if (request.method === 'OPTIONS') {
207
- return new Response(null, { headers: corsHeaders });
208
- }
209
-
210
- if (!hasValidHeader(request, env)) {
211
- return jsonResponse({ error: 'Forbidden' }, 403);
212
- }
213
-
214
192
  if (request.method === 'POST') {
215
193
  try {
216
194
  const payload = await request.json() as unknown;
@@ -2,7 +2,8 @@
2
2
  "name": "PDF_WORKER_NAME",
3
3
  "account_id": "ACCOUNT_ID",
4
4
  "main": "src/pdf-worker.ts",
5
- "compatibility_date": "2026-04-20",
5
+ "workers_dev": false,
6
+ "compatibility_date": "2026-04-21",
6
7
  "compatibility_flags": [
7
8
  "nodejs_compat"
8
9
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "user-worker",
3
- "version": "6.1.7",
3
+ "version": "7.0.0",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "deploy": "wrangler deploy",
@@ -8,6 +8,6 @@
8
8
  "start": "wrangler dev"
9
9
  },
10
10
  "devDependencies": {
11
- "wrangler": "^4.84.0"
11
+ "wrangler": "^4.84.1"
12
12
  }
13
13
  }
@@ -1,12 +1,5 @@
1
1
  import type { Env } from './types';
2
2
 
3
- export async function authenticate(request: Request, env: Env): Promise<void> {
4
- const authKey = request.headers.get('X-Custom-Auth-Key');
5
- if (authKey !== env.USER_DB_AUTH) {
6
- throw new Error('Unauthorized');
7
- }
8
- }
9
-
10
3
  export function requireUserKvReadConfig(env: Env): void {
11
4
  const hasLegacyPrivateKey = typeof env.USER_KV_ENCRYPTION_PRIVATE_KEY === 'string' && env.USER_KV_ENCRYPTION_PRIVATE_KEY.trim().length > 0;
12
5
  const hasRegistryPrivateKeys = typeof env.USER_KV_ENCRYPTION_KEYS_JSON === 'string' && env.USER_KV_ENCRYPTION_KEYS_JSON.trim().length > 0;
@@ -5,56 +5,47 @@ import type {
5
5
  AccountDeletionProgressEvent,
6
6
  DeleteCasesRequest,
7
7
  Env,
8
- ResponseHeaders,
9
8
  UserData,
10
9
  UserRequestData
11
10
  } from '../types';
12
11
 
13
- function createJsonResponse(data: unknown, headers: ResponseHeaders, status: number = 200): Response {
12
+ function createJsonResponse(data: unknown, status: number = 200): Response {
14
13
  return new Response(JSON.stringify(data), {
15
14
  status,
16
- headers: {
17
- ...headers,
18
- 'Content-Type': 'application/json; charset=utf-8'
19
- }
15
+ headers: { 'Content-Type': 'application/json; charset=utf-8' }
20
16
  });
21
17
  }
22
18
 
23
- function createTextResponse(message: string, headers: ResponseHeaders, status: number): Response {
19
+ function createTextResponse(message: string, status: number): Response {
24
20
  return new Response(message, {
25
21
  status,
26
- headers: {
27
- ...headers,
28
- 'Content-Type': 'text/plain; charset=utf-8'
29
- }
22
+ headers: { 'Content-Type': 'text/plain; charset=utf-8' }
30
23
  });
31
24
  }
32
25
 
33
26
  export async function handleGetUser(
34
27
  env: Env,
35
- userUid: string,
36
- corsHeaders: ResponseHeaders
28
+ userUid: string
37
29
  ): Promise<Response> {
38
30
  try {
39
31
  const userData = await readUserRecord(env, userUid);
40
32
  if (userData === null) {
41
- return createTextResponse('User not found', corsHeaders, 404);
33
+ return createTextResponse('User not found', 404);
42
34
  }
43
35
 
44
- return createJsonResponse(userData, corsHeaders);
36
+ return createJsonResponse(userData);
45
37
  } catch (error) {
46
38
  const errorMessage = error instanceof Error ? error.message : 'Unknown user data read error';
47
39
  console.error('Failed to get user data:', { uid: userUid, reason: errorMessage });
48
40
 
49
- return createTextResponse('Failed to get user data', corsHeaders, 500);
41
+ return createTextResponse('Failed to get user data', 500);
50
42
  }
51
43
  }
52
44
 
53
45
  export async function handleAddUser(
54
46
  request: Request,
55
47
  env: Env,
56
- userUid: string,
57
- corsHeaders: ResponseHeaders
48
+ userUid: string
58
49
  ): Promise<Response> {
59
50
  try {
60
51
  const requestData: UserRequestData = await request.json();
@@ -96,16 +87,15 @@ export async function handleAddUser(
96
87
 
97
88
  await writeUserRecord(env, userUid, userData);
98
89
 
99
- return createJsonResponse(userData, corsHeaders, existingUser !== null ? 200 : 201);
90
+ return createJsonResponse(userData, existingUser !== null ? 200 : 201);
100
91
  } catch {
101
- return createTextResponse('Failed to save user data', corsHeaders, 500);
92
+ return createTextResponse('Failed to save user data', 500);
102
93
  }
103
94
  }
104
95
 
105
96
  export async function handleDeleteUser(
106
97
  env: Env,
107
- userUid: string,
108
- corsHeaders: ResponseHeaders
98
+ userUid: string
109
99
  ): Promise<Response> {
110
100
  try {
111
101
  const result = await executeUserDeletion(env, userUid);
@@ -113,29 +103,27 @@ export async function handleDeleteUser(
113
103
  return createJsonResponse({
114
104
  success: result.success,
115
105
  message: result.message
116
- }, corsHeaders);
106
+ });
117
107
  } catch (error) {
118
108
  console.error('Delete user error:', error);
119
109
  const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
120
110
 
121
111
  if (errorMessage === 'User not found') {
122
- return createTextResponse('User not found', corsHeaders, 404);
112
+ return createTextResponse('User not found', 404);
123
113
  }
124
114
 
125
115
  return createJsonResponse({
126
116
  success: false,
127
117
  message: 'Failed to delete user account'
128
- }, corsHeaders, 500);
118
+ }, 500);
129
119
  }
130
120
  }
131
121
 
132
122
  export function handleDeleteUserWithProgress(
133
123
  env: Env,
134
- userUid: string,
135
- corsHeaders: ResponseHeaders
124
+ userUid: string
136
125
  ): Response {
137
- const sseHeaders: ResponseHeaders = {
138
- ...corsHeaders,
126
+ const sseHeaders = {
139
127
  'Content-Type': 'text/event-stream; charset=utf-8',
140
128
  'Cache-Control': 'no-cache, no-transform',
141
129
  Connection: 'keep-alive'
@@ -182,14 +170,13 @@ export function handleDeleteUserWithProgress(
182
170
  export async function handleAddCases(
183
171
  request: Request,
184
172
  env: Env,
185
- userUid: string,
186
- corsHeaders: ResponseHeaders
173
+ userUid: string
187
174
  ): Promise<Response> {
188
175
  try {
189
176
  const { cases = [] }: AddCasesRequest = await request.json();
190
177
  const userData = await readUserRecord(env, userUid);
191
178
  if (!userData) {
192
- return createTextResponse('User not found', corsHeaders, 404);
179
+ return createTextResponse('User not found', 404);
193
180
  }
194
181
 
195
182
  const existingCases = userData.cases || [];
@@ -201,31 +188,30 @@ export async function handleAddCases(
201
188
  userData.updatedAt = new Date().toISOString();
202
189
  await writeUserRecord(env, userUid, userData);
203
190
 
204
- return createJsonResponse(userData, corsHeaders);
191
+ return createJsonResponse(userData);
205
192
  } catch {
206
- return createTextResponse('Failed to add cases', corsHeaders, 500);
193
+ return createTextResponse('Failed to add cases', 500);
207
194
  }
208
195
  }
209
196
 
210
197
  export async function handleDeleteCases(
211
198
  request: Request,
212
199
  env: Env,
213
- userUid: string,
214
- corsHeaders: ResponseHeaders
200
+ userUid: string
215
201
  ): Promise<Response> {
216
202
  try {
217
203
  const { casesToDelete }: DeleteCasesRequest = await request.json();
218
204
  const userData = await readUserRecord(env, userUid);
219
205
  if (!userData) {
220
- return createTextResponse('User not found', corsHeaders, 404);
206
+ return createTextResponse('User not found', 404);
221
207
  }
222
208
 
223
209
  userData.cases = userData.cases.filter((caseItem) => !casesToDelete.includes(caseItem.caseNumber));
224
210
  userData.updatedAt = new Date().toISOString();
225
211
  await writeUserRecord(env, userUid, userData);
226
212
 
227
- return createJsonResponse(userData, corsHeaders);
213
+ return createJsonResponse(userData);
228
214
  } catch {
229
- return createTextResponse('Failed to delete cases', corsHeaders, 500);
215
+ return createTextResponse('Failed to delete cases', 500);
230
216
  }
231
217
  }
@@ -1,9 +1,7 @@
1
1
  export interface Env {
2
- USER_DB_AUTH: string;
3
2
  USER_DB: KVNamespace;
4
3
  STRIAE_DATA: R2Bucket;
5
4
  STRIAE_FILES: R2Bucket;
6
- R2_KEY_SECRET: string;
7
5
  DATA_AT_REST_ENCRYPTION_PRIVATE_KEY?: string;
8
6
  DATA_AT_REST_ENCRYPTION_KEY_ID?: string;
9
7
  DATA_AT_REST_ENCRYPTION_KEYS_JSON?: string;
@@ -1,4 +1,4 @@
1
- import { authenticate, requireUserKvReadConfig, requireUserKvWriteConfig } from './auth';
1
+ import { requireUserKvReadConfig, requireUserKvWriteConfig } from './auth';
2
2
  import { USER_CASES_SEGMENT } from './config';
3
3
  import {
4
4
  handleAddCases,
@@ -10,31 +10,16 @@ import {
10
10
  } from './handlers/user-routes';
11
11
  import type { Env } from './types';
12
12
 
13
- const corsHeaders: Record<string, string> = {
14
- 'Access-Control-Allow-Origin': 'PAGES_CUSTOM_DOMAIN',
15
- 'Access-Control-Allow-Methods': 'GET, PUT, DELETE, OPTIONS',
16
- 'Access-Control-Allow-Headers': 'Content-Type, X-Custom-Auth-Key'
17
- };
18
-
19
- function createTextResponse(message: string, status: number, headers: Record<string, string>): Response {
13
+ function createTextResponse(message: string, status: number): Response {
20
14
  return new Response(message, {
21
15
  status,
22
- headers: {
23
- ...headers,
24
- 'Content-Type': 'text/plain; charset=utf-8'
25
- }
16
+ headers: { 'Content-Type': 'text/plain; charset=utf-8' }
26
17
  });
27
18
  }
28
19
 
29
20
  export default {
30
21
  async fetch(request: Request, env: Env): Promise<Response> {
31
- if (request.method === 'OPTIONS') {
32
- return new Response(null, { headers: corsHeaders });
33
- }
34
-
35
22
  try {
36
- await authenticate(request, env);
37
-
38
23
  // DELETE can mutate user KV data (for example /:uid/cases), so non-GET methods require write config.
39
24
  if (request.method === 'GET') {
40
25
  requireUserKvReadConfig(env);
@@ -48,15 +33,15 @@ export default {
48
33
  const isCasesEndpoint = parts[2] === USER_CASES_SEGMENT;
49
34
 
50
35
  if (!userUid) {
51
- return createTextResponse('Not Found', 404, corsHeaders);
36
+ return createTextResponse('Not Found', 404);
52
37
  }
53
38
 
54
39
  // Handle regular cases endpoint
55
40
  if (isCasesEndpoint) {
56
41
  switch (request.method) {
57
- case 'PUT': return handleAddCases(request, env, userUid, corsHeaders);
58
- case 'DELETE': return handleDeleteCases(request, env, userUid, corsHeaders);
59
- default: return createTextResponse('Method not allowed', 405, corsHeaders);
42
+ case 'PUT': return handleAddCases(request, env, userUid);
43
+ case 'DELETE': return handleDeleteCases(request, env, userUid);
44
+ default: return createTextResponse('Method not allowed', 405);
60
45
  }
61
46
  }
62
47
 
@@ -65,24 +50,24 @@ export default {
65
50
  const streamProgress = url.searchParams.get('stream') === 'true' || acceptsEventStream;
66
51
 
67
52
  switch (request.method) {
68
- case 'GET': return handleGetUser(env, userUid, corsHeaders);
69
- case 'PUT': return handleAddUser(request, env, userUid, corsHeaders);
53
+ case 'GET': return handleGetUser(env, userUid);
54
+ case 'PUT': return handleAddUser(request, env, userUid);
70
55
  case 'DELETE': return streamProgress
71
- ? handleDeleteUserWithProgress(env, userUid, corsHeaders)
72
- : handleDeleteUser(env, userUid, corsHeaders);
73
- default: return createTextResponse('Method not allowed', 405, corsHeaders);
56
+ ? handleDeleteUserWithProgress(env, userUid)
57
+ : handleDeleteUser(env, userUid);
58
+ default: return createTextResponse('Method not allowed', 405);
74
59
  }
75
60
  } catch (error) {
76
61
  const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
77
62
  if (errorMessage === 'Unauthorized') {
78
- return createTextResponse('Forbidden', 403, corsHeaders);
63
+ return createTextResponse('Forbidden', 403);
79
64
  }
80
65
 
81
66
  if (errorMessage === 'User KV encryption is not fully configured') {
82
- return createTextResponse(errorMessage, 500, corsHeaders);
67
+ return createTextResponse(errorMessage, 500);
83
68
  }
84
69
 
85
- return createTextResponse('Internal Server Error', 500, corsHeaders);
70
+ return createTextResponse('Internal Server Error', 500);
86
71
  }
87
72
  }
88
73
  };
@@ -2,7 +2,8 @@
2
2
  "name": "USER_WORKER_NAME",
3
3
  "account_id": "ACCOUNT_ID",
4
4
  "main": "src/user-worker.ts",
5
- "compatibility_date": "2026-04-20",
5
+ "workers_dev": false,
6
+ "compatibility_date": "2026-04-21",
6
7
  "compatibility_flags": [
7
8
  "nodejs_compat"
8
9
  ],
@@ -1,8 +1,28 @@
1
1
  #:schema node_modules/wrangler/config-schema.json
2
2
  name = "PAGES_PROJECT_NAME"
3
- compatibility_date = "2026-04-20"
3
+ compatibility_date = "2026-04-21"
4
4
  compatibility_flags = ["nodejs_compat"]
5
5
  pages_build_output_dir = "./build/client"
6
6
 
7
7
  [placement]
8
- mode = "smart"
8
+ mode = "smart"
9
+
10
+ [[services]]
11
+ binding = "USER_WORKER"
12
+ service = "USER_WORKER_NAME"
13
+
14
+ [[services]]
15
+ binding = "DATA_WORKER"
16
+ service = "DATA_WORKER_NAME"
17
+
18
+ [[services]]
19
+ binding = "AUDIT_WORKER"
20
+ service = "AUDIT_WORKER_NAME"
21
+
22
+ [[services]]
23
+ binding = "IMAGE_WORKER"
24
+ service = "IMAGES_WORKER_NAME"
25
+
26
+ [[services]]
27
+ binding = "PDF_WORKER"
28
+ service = "PDF_WORKER_NAME"