@striae-org/striae 7.0.0 → 7.0.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.
- package/app/components/canvas/canvas.module.css +12 -0
- package/app/components/canvas/canvas.tsx +26 -5
- package/package.json +1 -1
- package/workers/audit-worker/package.json +1 -1
- package/workers/audit-worker/wrangler.jsonc.example +1 -1
- package/workers/data-worker/package.json +1 -1
- package/workers/data-worker/wrangler.jsonc.example +1 -1
- package/workers/image-worker/package.json +1 -1
- package/workers/image-worker/src/handlers/delete-image.ts +5 -5
- package/workers/image-worker/src/handlers/mint-signed-url.ts +5 -5
- package/workers/image-worker/src/handlers/serve-image.ts +7 -7
- package/workers/image-worker/src/handlers/upload-image.ts +4 -4
- package/workers/image-worker/src/image-worker.ts +4 -4
- package/workers/image-worker/src/router.ts +11 -11
- package/workers/image-worker/src/types.ts +1 -1
- package/workers/image-worker/wrangler.jsonc.example +1 -1
- package/workers/pdf-worker/package.json +1 -1
- package/workers/pdf-worker/wrangler.jsonc.example +1 -1
- package/workers/user-worker/package.json +1 -1
- package/workers/user-worker/src/handlers/user-routes.ts +26 -34
- package/workers/user-worker/src/types.ts +13 -0
- package/workers/user-worker/src/user-worker.ts +18 -24
- package/workers/user-worker/wrangler.jsonc.example +1 -1
- package/wrangler.toml.example +1 -1
|
@@ -309,6 +309,18 @@
|
|
|
309
309
|
font-family: "Inter", sans-serif;
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
+
.noteSection {
|
|
313
|
+
flex: 1;
|
|
314
|
+
display: flex;
|
|
315
|
+
flex-direction: column;
|
|
316
|
+
overflow: hidden;
|
|
317
|
+
min-height: 0;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.noteSection + .noteSection {
|
|
321
|
+
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
|
322
|
+
}
|
|
323
|
+
|
|
312
324
|
.additionalNotesBox {
|
|
313
325
|
flex: 1;
|
|
314
326
|
overflow-y: auto;
|
|
@@ -424,12 +424,33 @@ export const Canvas = ({
|
|
|
424
424
|
</div>{/* end imageContainer */}
|
|
425
425
|
|
|
426
426
|
{/* Additional Notes - Right Panel */}
|
|
427
|
-
{activeAnnotations?.has('notes') &&
|
|
427
|
+
{activeAnnotations?.has('notes') &&
|
|
428
|
+
(annotationData?.leftAdditionalNotes || annotationData?.rightAdditionalNotes || annotationData?.additionalNotes) && (
|
|
428
429
|
<aside className={styles.notesPanel} aria-label="Additional notes">
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
430
|
+
{annotationData?.leftAdditionalNotes && (
|
|
431
|
+
<div className={styles.noteSection}>
|
|
432
|
+
<div className={styles.notesPanelHeader}>Left Item</div>
|
|
433
|
+
<div className={styles.additionalNotesBox}>
|
|
434
|
+
{annotationData.leftAdditionalNotes}
|
|
435
|
+
</div>
|
|
436
|
+
</div>
|
|
437
|
+
)}
|
|
438
|
+
{annotationData?.rightAdditionalNotes && (
|
|
439
|
+
<div className={styles.noteSection}>
|
|
440
|
+
<div className={styles.notesPanelHeader}>Right Item</div>
|
|
441
|
+
<div className={styles.additionalNotesBox}>
|
|
442
|
+
{annotationData.rightAdditionalNotes}
|
|
443
|
+
</div>
|
|
444
|
+
</div>
|
|
445
|
+
)}
|
|
446
|
+
{annotationData?.additionalNotes && (
|
|
447
|
+
<div className={styles.noteSection}>
|
|
448
|
+
<div className={styles.notesPanelHeader}>Notes</div>
|
|
449
|
+
<div className={styles.additionalNotesBox}>
|
|
450
|
+
{annotationData.additionalNotes}
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
453
|
+
)}
|
|
433
454
|
</aside>
|
|
434
455
|
)}
|
|
435
456
|
</div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@striae-org/striae",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Striae is a specialized, cloud-native platform designed to streamline forensic firearms identification by providing an intuitive environment for digital comparison image annotation, authenticated confirmations, and automated report generation.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { CreateResponse, Env } from '../types';
|
|
2
2
|
import { parseFileId } from '../utils/path-utils';
|
|
3
3
|
|
|
4
4
|
export async function handleImageDelete(
|
|
5
5
|
request: Request,
|
|
6
6
|
env: Env,
|
|
7
|
-
|
|
7
|
+
respond: CreateResponse
|
|
8
8
|
): Promise<Response> {
|
|
9
9
|
const fileId = parseFileId(new URL(request.url).pathname);
|
|
10
10
|
if (!fileId) {
|
|
11
|
-
return
|
|
11
|
+
return respond({ error: 'Image ID is required' }, 400);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
const existing = await env.STRIAE_FILES.head(fileId);
|
|
15
15
|
if (!existing) {
|
|
16
|
-
return
|
|
16
|
+
return respond({ error: 'File not found' }, 404);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
await env.STRIAE_FILES.delete(fileId);
|
|
20
|
-
return
|
|
20
|
+
return respond({ success: true });
|
|
21
21
|
}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
signSignedAccessPayload
|
|
6
6
|
} from '../security/signed-url';
|
|
7
7
|
import type {
|
|
8
|
-
|
|
8
|
+
CreateResponse,
|
|
9
9
|
Env,
|
|
10
10
|
SignedAccessPayload
|
|
11
11
|
} from '../types';
|
|
@@ -18,13 +18,13 @@ export async function handleSignedUrlMinting(
|
|
|
18
18
|
request: Request,
|
|
19
19
|
env: Env,
|
|
20
20
|
fileId: string,
|
|
21
|
-
|
|
21
|
+
respond: CreateResponse
|
|
22
22
|
): Promise<Response> {
|
|
23
23
|
requireSignedUrlConfig(env);
|
|
24
24
|
|
|
25
25
|
const existing = await env.STRIAE_FILES.head(fileId);
|
|
26
26
|
if (!existing) {
|
|
27
|
-
return
|
|
27
|
+
return respond({ error: 'File not found' }, 404);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
let requestedExpiresInSeconds: number | undefined;
|
|
@@ -58,7 +58,7 @@ export async function handleSignedUrlMinting(
|
|
|
58
58
|
console.error('Invalid IMAGE_SIGNED_URL_BASE_URL configuration', {
|
|
59
59
|
reason: error instanceof Error ? error.message : String(error)
|
|
60
60
|
});
|
|
61
|
-
return
|
|
61
|
+
return respond({ error: 'Signed URL base URL is misconfigured' }, 500);
|
|
62
62
|
}
|
|
63
63
|
} else {
|
|
64
64
|
baseUrl = new URL(request.url).origin;
|
|
@@ -66,7 +66,7 @@ export async function handleSignedUrlMinting(
|
|
|
66
66
|
|
|
67
67
|
const signedUrl = `${baseUrl}/${encodeURIComponent(fileId)}?st=${encodeURIComponent(signedToken)}`;
|
|
68
68
|
|
|
69
|
-
return
|
|
69
|
+
return respond({
|
|
70
70
|
success: true,
|
|
71
71
|
result: {
|
|
72
72
|
fileId,
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
requireEncryptionRetrievalConfig
|
|
4
4
|
} from '../security/key-registry';
|
|
5
5
|
import { requireSignedUrlConfig, verifySignedAccessToken } from '../security/signed-url';
|
|
6
|
-
import type {
|
|
6
|
+
import type { CreateResponse, Env } from '../types';
|
|
7
7
|
import { buildSafeContentDisposition } from '../utils/content-disposition';
|
|
8
8
|
import { extractEnvelope } from '../utils/storage-metadata';
|
|
9
9
|
|
|
@@ -11,7 +11,7 @@ export async function handleImageServing(
|
|
|
11
11
|
request: Request,
|
|
12
12
|
env: Env,
|
|
13
13
|
fileId: string,
|
|
14
|
-
|
|
14
|
+
respond: CreateResponse
|
|
15
15
|
): Promise<Response> {
|
|
16
16
|
const requestUrl = new URL(request.url);
|
|
17
17
|
const hasSignedToken = requestUrl.searchParams.has('st');
|
|
@@ -21,27 +21,27 @@ export async function handleImageServing(
|
|
|
21
21
|
requireSignedUrlConfig(env);
|
|
22
22
|
|
|
23
23
|
if (!signedToken || signedToken.trim().length === 0) {
|
|
24
|
-
return
|
|
24
|
+
return respond({ error: 'Invalid or expired signed URL token' }, 403);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const tokenValid = await verifySignedAccessToken(signedToken, fileId, env);
|
|
28
28
|
if (!tokenValid) {
|
|
29
|
-
return
|
|
29
|
+
return respond({ error: 'Invalid or expired signed URL token' }, 403);
|
|
30
30
|
}
|
|
31
31
|
} else {
|
|
32
|
-
return
|
|
32
|
+
return respond({ error: 'Unauthorized' }, 403);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
requireEncryptionRetrievalConfig(env);
|
|
36
36
|
|
|
37
37
|
const file = await env.STRIAE_FILES.get(fileId);
|
|
38
38
|
if (!file) {
|
|
39
|
-
return
|
|
39
|
+
return respond({ error: 'File not found' }, 404);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
const envelope = extractEnvelope(file);
|
|
43
43
|
if (!envelope) {
|
|
44
|
-
return
|
|
44
|
+
return respond({ error: 'Missing data-at-rest envelope metadata' }, 500);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
const encryptedData = await file.arrayBuffer();
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { encryptBinaryForStorage } from '../encryption-utils';
|
|
2
2
|
import { requireEncryptionUploadConfig } from '../security/key-registry';
|
|
3
|
-
import type {
|
|
3
|
+
import type { CreateResponse, Env } from '../types';
|
|
4
4
|
import { deriveFileKind } from '../utils/content-disposition';
|
|
5
5
|
|
|
6
6
|
export async function handleImageUpload(
|
|
7
7
|
request: Request,
|
|
8
8
|
env: Env,
|
|
9
|
-
|
|
9
|
+
respond: CreateResponse
|
|
10
10
|
): Promise<Response> {
|
|
11
11
|
requireEncryptionUploadConfig(env);
|
|
12
12
|
|
|
13
13
|
const formData = await request.formData();
|
|
14
14
|
const fileValue = formData.get('file');
|
|
15
15
|
if (!(fileValue instanceof Blob)) {
|
|
16
|
-
return
|
|
16
|
+
return respond({ error: 'Missing file upload payload' }, 400);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const fileBlob = fileValue;
|
|
@@ -44,7 +44,7 @@ export async function handleImageUpload(
|
|
|
44
44
|
}
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
-
return
|
|
47
|
+
return respond({
|
|
48
48
|
success: true,
|
|
49
49
|
errors: [],
|
|
50
50
|
messages: [],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { routeImageWorkerRequest } from './router';
|
|
2
|
-
import type { APIResponse, Env } from './types';
|
|
2
|
+
import type { APIResponse, CreateResponse, Env } from './types';
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const createWorkerResponse: CreateResponse = (data: APIResponse, status: number = 200): Response => new Response(
|
|
5
5
|
JSON.stringify(data),
|
|
6
6
|
{
|
|
7
7
|
status,
|
|
@@ -12,10 +12,10 @@ const createJsonResponse = (data: APIResponse, status: number = 200): Response =
|
|
|
12
12
|
export default {
|
|
13
13
|
async fetch(request: Request, env: Env): Promise<Response> {
|
|
14
14
|
try {
|
|
15
|
-
return await routeImageWorkerRequest(request, env,
|
|
15
|
+
return await routeImageWorkerRequest(request, env, createWorkerResponse);
|
|
16
16
|
} catch (error) {
|
|
17
17
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
18
|
-
return
|
|
18
|
+
return createWorkerResponse({ error: errorMessage }, 500);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
};
|
|
@@ -2,51 +2,51 @@ import { handleImageDelete } from './handlers/delete-image';
|
|
|
2
2
|
import { handleSignedUrlMinting } from './handlers/mint-signed-url';
|
|
3
3
|
import { handleImageServing } from './handlers/serve-image';
|
|
4
4
|
import { handleImageUpload } from './handlers/upload-image';
|
|
5
|
-
import type {
|
|
5
|
+
import type { CreateResponse, Env } from './types';
|
|
6
6
|
import { parsePathSegments } from './utils/path-utils';
|
|
7
7
|
|
|
8
8
|
export async function routeImageWorkerRequest(
|
|
9
9
|
request: Request,
|
|
10
10
|
env: Env,
|
|
11
|
-
|
|
11
|
+
respond: CreateResponse
|
|
12
12
|
): Promise<Response> {
|
|
13
13
|
const requestUrl = new URL(request.url);
|
|
14
14
|
const pathSegments = parsePathSegments(requestUrl.pathname);
|
|
15
15
|
if (!pathSegments) {
|
|
16
|
-
return
|
|
16
|
+
return respond({ error: 'Invalid image path encoding' }, 400);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
switch (request.method) {
|
|
20
20
|
case 'POST': {
|
|
21
21
|
if (pathSegments.length === 0) {
|
|
22
|
-
return handleImageUpload(request, env,
|
|
22
|
+
return handleImageUpload(request, env, respond);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
if (pathSegments.length === 2 && pathSegments[1] === 'signed-url') {
|
|
26
|
-
return handleSignedUrlMinting(request, env, pathSegments[0],
|
|
26
|
+
return handleSignedUrlMinting(request, env, pathSegments[0], respond);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
return
|
|
29
|
+
return respond({ error: 'Not found' }, 404);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
case 'GET': {
|
|
33
33
|
const fileId = pathSegments.length === 1 ? pathSegments[0] : null;
|
|
34
34
|
if (!fileId) {
|
|
35
|
-
return
|
|
35
|
+
return respond({ error: 'Image ID is required' }, 400);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
return handleImageServing(request, env, fileId,
|
|
38
|
+
return handleImageServing(request, env, fileId, respond);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
case 'DELETE': {
|
|
42
42
|
if (pathSegments.length !== 1) {
|
|
43
|
-
return
|
|
43
|
+
return respond({ error: 'Not found' }, 404);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
return handleImageDelete(request, env,
|
|
46
|
+
return handleImageDelete(request, env, respond);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
default:
|
|
50
|
-
return
|
|
50
|
+
return respond({ error: 'Method not allowed' }, 405);
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -3,49 +3,38 @@ import { readUserRecord, writeUserRecord } from '../storage/user-records';
|
|
|
3
3
|
import type {
|
|
4
4
|
AddCasesRequest,
|
|
5
5
|
AccountDeletionProgressEvent,
|
|
6
|
+
CreateResponse,
|
|
6
7
|
DeleteCasesRequest,
|
|
7
8
|
Env,
|
|
8
9
|
UserData,
|
|
9
10
|
UserRequestData
|
|
10
11
|
} from '../types';
|
|
11
12
|
|
|
12
|
-
function createJsonResponse(data: unknown, status: number = 200): Response {
|
|
13
|
-
return new Response(JSON.stringify(data), {
|
|
14
|
-
status,
|
|
15
|
-
headers: { 'Content-Type': 'application/json; charset=utf-8' }
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function createTextResponse(message: string, status: number): Response {
|
|
20
|
-
return new Response(message, {
|
|
21
|
-
status,
|
|
22
|
-
headers: { 'Content-Type': 'text/plain; charset=utf-8' }
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
13
|
export async function handleGetUser(
|
|
27
14
|
env: Env,
|
|
28
|
-
userUid: string
|
|
15
|
+
userUid: string,
|
|
16
|
+
respond: CreateResponse
|
|
29
17
|
): Promise<Response> {
|
|
30
18
|
try {
|
|
31
19
|
const userData = await readUserRecord(env, userUid);
|
|
32
20
|
if (userData === null) {
|
|
33
|
-
return
|
|
21
|
+
return respond({ error: 'User not found' }, 404);
|
|
34
22
|
}
|
|
35
23
|
|
|
36
|
-
return
|
|
24
|
+
return respond(userData);
|
|
37
25
|
} catch (error) {
|
|
38
26
|
const errorMessage = error instanceof Error ? error.message : 'Unknown user data read error';
|
|
39
27
|
console.error('Failed to get user data:', { uid: userUid, reason: errorMessage });
|
|
40
28
|
|
|
41
|
-
return
|
|
29
|
+
return respond({ error: 'Failed to get user data' }, 500);
|
|
42
30
|
}
|
|
43
31
|
}
|
|
44
32
|
|
|
45
33
|
export async function handleAddUser(
|
|
46
34
|
request: Request,
|
|
47
35
|
env: Env,
|
|
48
|
-
userUid: string
|
|
36
|
+
userUid: string,
|
|
37
|
+
respond: CreateResponse
|
|
49
38
|
): Promise<Response> {
|
|
50
39
|
try {
|
|
51
40
|
const requestData: UserRequestData = await request.json();
|
|
@@ -87,20 +76,21 @@ export async function handleAddUser(
|
|
|
87
76
|
|
|
88
77
|
await writeUserRecord(env, userUid, userData);
|
|
89
78
|
|
|
90
|
-
return
|
|
79
|
+
return respond(userData, existingUser !== null ? 200 : 201);
|
|
91
80
|
} catch {
|
|
92
|
-
return
|
|
81
|
+
return respond({ error: 'Failed to save user data' }, 500);
|
|
93
82
|
}
|
|
94
83
|
}
|
|
95
84
|
|
|
96
85
|
export async function handleDeleteUser(
|
|
97
86
|
env: Env,
|
|
98
|
-
userUid: string
|
|
87
|
+
userUid: string,
|
|
88
|
+
respond: CreateResponse
|
|
99
89
|
): Promise<Response> {
|
|
100
90
|
try {
|
|
101
91
|
const result = await executeUserDeletion(env, userUid);
|
|
102
92
|
|
|
103
|
-
return
|
|
93
|
+
return respond({
|
|
104
94
|
success: result.success,
|
|
105
95
|
message: result.message
|
|
106
96
|
});
|
|
@@ -109,10 +99,10 @@ export async function handleDeleteUser(
|
|
|
109
99
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
110
100
|
|
|
111
101
|
if (errorMessage === 'User not found') {
|
|
112
|
-
return
|
|
102
|
+
return respond({ error: 'User not found' }, 404);
|
|
113
103
|
}
|
|
114
104
|
|
|
115
|
-
return
|
|
105
|
+
return respond({
|
|
116
106
|
success: false,
|
|
117
107
|
message: 'Failed to delete user account'
|
|
118
108
|
}, 500);
|
|
@@ -170,13 +160,14 @@ export function handleDeleteUserWithProgress(
|
|
|
170
160
|
export async function handleAddCases(
|
|
171
161
|
request: Request,
|
|
172
162
|
env: Env,
|
|
173
|
-
userUid: string
|
|
163
|
+
userUid: string,
|
|
164
|
+
respond: CreateResponse
|
|
174
165
|
): Promise<Response> {
|
|
175
166
|
try {
|
|
176
167
|
const { cases = [] }: AddCasesRequest = await request.json();
|
|
177
168
|
const userData = await readUserRecord(env, userUid);
|
|
178
169
|
if (!userData) {
|
|
179
|
-
return
|
|
170
|
+
return respond({ error: 'User not found' }, 404);
|
|
180
171
|
}
|
|
181
172
|
|
|
182
173
|
const existingCases = userData.cases || [];
|
|
@@ -188,30 +179,31 @@ export async function handleAddCases(
|
|
|
188
179
|
userData.updatedAt = new Date().toISOString();
|
|
189
180
|
await writeUserRecord(env, userUid, userData);
|
|
190
181
|
|
|
191
|
-
return
|
|
182
|
+
return respond(userData);
|
|
192
183
|
} catch {
|
|
193
|
-
return
|
|
184
|
+
return respond({ error: 'Failed to add cases' }, 500);
|
|
194
185
|
}
|
|
195
186
|
}
|
|
196
187
|
|
|
197
188
|
export async function handleDeleteCases(
|
|
198
189
|
request: Request,
|
|
199
190
|
env: Env,
|
|
200
|
-
userUid: string
|
|
191
|
+
userUid: string,
|
|
192
|
+
respond: CreateResponse
|
|
201
193
|
): Promise<Response> {
|
|
202
194
|
try {
|
|
203
195
|
const { casesToDelete }: DeleteCasesRequest = await request.json();
|
|
204
196
|
const userData = await readUserRecord(env, userUid);
|
|
205
197
|
if (!userData) {
|
|
206
|
-
return
|
|
198
|
+
return respond({ error: 'User not found' }, 404);
|
|
207
199
|
}
|
|
208
200
|
|
|
209
|
-
userData.cases = userData.cases.filter((caseItem) => !casesToDelete.includes(caseItem.caseNumber));
|
|
201
|
+
userData.cases = (userData.cases || []).filter((caseItem) => !casesToDelete.includes(caseItem.caseNumber));
|
|
210
202
|
userData.updatedAt = new Date().toISOString();
|
|
211
203
|
await writeUserRecord(env, userUid, userData);
|
|
212
204
|
|
|
213
|
-
return
|
|
205
|
+
return respond(userData);
|
|
214
206
|
} catch {
|
|
215
|
-
return
|
|
207
|
+
return respond({ error: 'Failed to delete cases' }, 500);
|
|
216
208
|
}
|
|
217
209
|
}
|
|
@@ -28,6 +28,19 @@ export interface PrivateKeyRegistry {
|
|
|
28
28
|
|
|
29
29
|
export type DecryptionTelemetryOutcome = 'primary-hit' | 'fallback-hit' | 'all-failed';
|
|
30
30
|
|
|
31
|
+
export interface SuccessResponse {
|
|
32
|
+
success: boolean;
|
|
33
|
+
message?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ErrorResponse {
|
|
37
|
+
error: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type APIResponse = SuccessResponse | ErrorResponse | UserData;
|
|
41
|
+
|
|
42
|
+
export type CreateResponse = (data: APIResponse, status?: number) => Response;
|
|
43
|
+
|
|
31
44
|
export interface CaseItem {
|
|
32
45
|
caseNumber: string;
|
|
33
46
|
caseName?: string;
|
|
@@ -8,14 +8,12 @@ import {
|
|
|
8
8
|
handleDeleteUserWithProgress,
|
|
9
9
|
handleGetUser
|
|
10
10
|
} from './handlers/user-routes';
|
|
11
|
-
import type { Env } from './types';
|
|
11
|
+
import type { CreateResponse, Env } from './types';
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
});
|
|
18
|
-
}
|
|
13
|
+
const createWorkerResponse: CreateResponse = (data, status: number = 200): Response => new Response(
|
|
14
|
+
JSON.stringify(data),
|
|
15
|
+
{ status, headers: { 'Content-Type': 'application/json' } }
|
|
16
|
+
);
|
|
19
17
|
|
|
20
18
|
export default {
|
|
21
19
|
async fetch(request: Request, env: Env): Promise<Response> {
|
|
@@ -26,22 +24,22 @@ export default {
|
|
|
26
24
|
} else {
|
|
27
25
|
requireUserKvWriteConfig(env);
|
|
28
26
|
}
|
|
29
|
-
|
|
27
|
+
|
|
30
28
|
const url = new URL(request.url);
|
|
31
29
|
const parts = url.pathname.split('/');
|
|
32
30
|
const userUid = parts[1];
|
|
33
31
|
const isCasesEndpoint = parts[2] === USER_CASES_SEGMENT;
|
|
34
|
-
|
|
32
|
+
|
|
35
33
|
if (!userUid) {
|
|
36
|
-
return
|
|
34
|
+
return createWorkerResponse({ error: 'Not Found' }, 404);
|
|
37
35
|
}
|
|
38
36
|
|
|
39
37
|
// Handle regular cases endpoint
|
|
40
38
|
if (isCasesEndpoint) {
|
|
41
39
|
switch (request.method) {
|
|
42
|
-
case 'PUT': return handleAddCases(request, env, userUid);
|
|
43
|
-
case 'DELETE': return handleDeleteCases(request, env, userUid);
|
|
44
|
-
default: return
|
|
40
|
+
case 'PUT': return handleAddCases(request, env, userUid, createWorkerResponse);
|
|
41
|
+
case 'DELETE': return handleDeleteCases(request, env, userUid, createWorkerResponse);
|
|
42
|
+
default: return createWorkerResponse({ error: 'Method not allowed' }, 405);
|
|
45
43
|
}
|
|
46
44
|
}
|
|
47
45
|
|
|
@@ -50,24 +48,20 @@ export default {
|
|
|
50
48
|
const streamProgress = url.searchParams.get('stream') === 'true' || acceptsEventStream;
|
|
51
49
|
|
|
52
50
|
switch (request.method) {
|
|
53
|
-
case 'GET': return handleGetUser(env, userUid);
|
|
54
|
-
case 'PUT': return handleAddUser(request, env, userUid);
|
|
51
|
+
case 'GET': return handleGetUser(env, userUid, createWorkerResponse);
|
|
52
|
+
case 'PUT': return handleAddUser(request, env, userUid, createWorkerResponse);
|
|
55
53
|
case 'DELETE': return streamProgress
|
|
56
54
|
? handleDeleteUserWithProgress(env, userUid)
|
|
57
|
-
: handleDeleteUser(env, userUid);
|
|
58
|
-
default: return
|
|
55
|
+
: handleDeleteUser(env, userUid, createWorkerResponse);
|
|
56
|
+
default: return createWorkerResponse({ error: 'Method not allowed' }, 405);
|
|
59
57
|
}
|
|
60
58
|
} catch (error) {
|
|
61
59
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
62
|
-
if (errorMessage === 'Unauthorized') {
|
|
63
|
-
return createTextResponse('Forbidden', 403);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
60
|
if (errorMessage === 'User KV encryption is not fully configured') {
|
|
67
|
-
return
|
|
61
|
+
return createWorkerResponse({ error: errorMessage }, 500);
|
|
68
62
|
}
|
|
69
|
-
|
|
70
|
-
return
|
|
63
|
+
|
|
64
|
+
return createWorkerResponse({ error: 'Internal Server Error' }, 500);
|
|
71
65
|
}
|
|
72
66
|
}
|
|
73
67
|
};
|
package/wrangler.toml.example
CHANGED