@striae-org/striae 7.0.0 → 7.1.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 (47) hide show
  1. package/.env.example +8 -14
  2. package/app/components/canvas/canvas.module.css +12 -0
  3. package/app/components/canvas/canvas.tsx +26 -5
  4. package/functions/api/_shared/lists-client.ts +39 -0
  5. package/functions/api/_shared/registration-allowlist.ts +5 -4
  6. package/functions/api/auth/can-register.ts +7 -2
  7. package/functions/api/pdf/[[path]].ts +4 -1
  8. package/functions/api/user/[[path]].ts +11 -5
  9. package/package.json +10 -8
  10. package/scripts/deploy-all.sh +3 -3
  11. package/scripts/deploy-config/modules/prompt.sh +43 -7
  12. package/scripts/deploy-config/modules/scaffolding.sh +19 -0
  13. package/scripts/deploy-config/modules/validation.sh +3 -0
  14. package/scripts/deploy-config.sh +0 -33
  15. package/scripts/deploy-pages-secrets.sh +1 -10
  16. package/scripts/deploy-worker-secrets.sh +19 -1
  17. package/scripts/install-workers.sh +4 -3
  18. package/scripts/update-markdown-versions.cjs +1 -0
  19. package/workers/audit-worker/package.json +2 -2
  20. package/workers/audit-worker/wrangler.jsonc.example +1 -1
  21. package/workers/data-worker/package.json +2 -2
  22. package/workers/data-worker/wrangler.jsonc.example +1 -1
  23. package/workers/image-worker/package.json +2 -2
  24. package/workers/image-worker/src/handlers/delete-image.ts +5 -5
  25. package/workers/image-worker/src/handlers/mint-signed-url.ts +5 -5
  26. package/workers/image-worker/src/handlers/serve-image.ts +7 -7
  27. package/workers/image-worker/src/handlers/upload-image.ts +4 -4
  28. package/workers/image-worker/src/image-worker.ts +4 -4
  29. package/workers/image-worker/src/router.ts +11 -11
  30. package/workers/image-worker/src/types.ts +1 -1
  31. package/workers/image-worker/wrangler.jsonc.example +1 -1
  32. package/workers/lists-worker/package.json +13 -0
  33. package/workers/lists-worker/src/lists-worker.ts +97 -0
  34. package/workers/lists-worker/src/types.ts +4 -0
  35. package/workers/lists-worker/wrangler.jsonc.example +23 -0
  36. package/workers/pdf-worker/package.json +2 -2
  37. package/workers/pdf-worker/wrangler.jsonc.example +1 -1
  38. package/workers/user-worker/package.json +2 -2
  39. package/workers/user-worker/src/handlers/user-routes.ts +26 -34
  40. package/workers/user-worker/src/types.ts +13 -0
  41. package/workers/user-worker/src/user-worker.ts +18 -24
  42. package/workers/user-worker/wrangler.jsonc.example +1 -1
  43. package/wrangler.toml.example +6 -2
  44. package/app/config-example/members.emails +0 -11
  45. package/app/config-example/primershear.emails +0 -6
  46. package/scripts/deploy-members-emails.sh +0 -102
  47. package/scripts/deploy-primershear-emails.sh +0 -101
@@ -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
- function createTextResponse(message: string, status: number): Response {
14
- return new Response(message, {
15
- status,
16
- headers: { 'Content-Type': 'text/plain; charset=utf-8' }
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 createTextResponse('Not Found', 404);
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 createTextResponse('Method not allowed', 405);
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 createTextResponse('Method not allowed', 405);
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 createTextResponse(errorMessage, 500);
61
+ return createWorkerResponse({ error: errorMessage }, 500);
68
62
  }
69
-
70
- return createTextResponse('Internal Server Error', 500);
63
+
64
+ return createWorkerResponse({ error: 'Internal Server Error' }, 500);
71
65
  }
72
66
  }
73
67
  };
@@ -3,7 +3,7 @@
3
3
  "account_id": "ACCOUNT_ID",
4
4
  "main": "src/user-worker.ts",
5
5
  "workers_dev": false,
6
- "compatibility_date": "2026-04-21",
6
+ "compatibility_date": "2026-04-25",
7
7
  "compatibility_flags": [
8
8
  "nodejs_compat"
9
9
  ],
@@ -1,6 +1,6 @@
1
1
  #:schema node_modules/wrangler/config-schema.json
2
2
  name = "PAGES_PROJECT_NAME"
3
- compatibility_date = "2026-04-21"
3
+ compatibility_date = "2026-04-25"
4
4
  compatibility_flags = ["nodejs_compat"]
5
5
  pages_build_output_dir = "./build/client"
6
6
 
@@ -25,4 +25,8 @@ service = "IMAGES_WORKER_NAME"
25
25
 
26
26
  [[services]]
27
27
  binding = "PDF_WORKER"
28
- service = "PDF_WORKER_NAME"
28
+ service = "PDF_WORKER_NAME"
29
+
30
+ [[services]]
31
+ binding = "LISTS_WORKER"
32
+ service = "LISTS_WORKER_NAME"
@@ -1,11 +0,0 @@
1
- # Registration gateway - authorized email addresses
2
- # One entry per line. Lines starting with # are ignored.
3
- # This file is untracked. Run: npm run deploy-members to push changes.
4
- #
5
- # Supported formats:
6
- # Exact email: analyst@organization.com
7
- # Domain wildcard: @organization.com (allows all emails from that domain)
8
- #
9
- # Examples:
10
- # analyst@organization.com
11
- # @striae.org
@@ -1,6 +0,0 @@
1
- # PrimerShear PDF format - authorized email addresses
2
- # One email per line. Lines starting with # are ignored.
3
- # This file is untracked. Run: npm run deploy-primershear to push changes.
4
- #
5
- # Example:
6
- # analyst@organization.com
@@ -1,102 +0,0 @@
1
- #!/bin/bash
2
-
3
- # ============================================
4
- # MEMBERS EMAIL LIST DEPLOYMENT SCRIPT
5
- # ============================================
6
- # Reads app/config/members.emails, updates REGISTRATION_EMAILS in .env,
7
- # then deploys that secret directly to Cloudflare Pages (production).
8
-
9
- set -e
10
- set -o pipefail
11
-
12
- RED='\033[0;31m'
13
- GREEN='\033[0;32m'
14
- YELLOW='\033[1;33m'
15
- BLUE='\033[0;34m'
16
- NC='\033[0m'
17
-
18
- echo -e "${BLUE}👥 Members Email List Deployment${NC}"
19
- echo "=================================="
20
-
21
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
22
- PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
23
- cd "$PROJECT_ROOT"
24
-
25
- trap 'echo -e "\n${RED}❌ deploy-members-emails.sh failed near line ${LINENO}${NC}"' ERR
26
-
27
- # ── Read emails file ──────────────────────────────────────────────────────────
28
-
29
- EMAILS_FILE="$PROJECT_ROOT/app/config/members.emails"
30
-
31
- if [ ! -f "$EMAILS_FILE" ]; then
32
- echo -e "${RED}❌ members.emails not found at: $EMAILS_FILE${NC}"
33
- echo -e "${YELLOW} Create it with one email address or @domain.com wildcard per line.${NC}"
34
- echo -e "${YELLOW} See app/config-example/members.emails for the format.${NC}"
35
- exit 1
36
- fi
37
-
38
- # Strip comment lines and blank lines, then join with commas
39
- # Use || true to avoid failure if paste gets no input (handles empty file gracefully)
40
- REGISTRATION_EMAILS=$(grep -v '^[[:space:]]*#' "$EMAILS_FILE" | grep -v '^[[:space:]]*$' | paste -sd ',' - || true)
41
-
42
- if [ -z "$REGISTRATION_EMAILS" ]; then
43
- echo -e "${YELLOW}⚠️ members.emails contains no active entries.${NC}"
44
- echo -e "${YELLOW} The secret will be set to an empty string, disabling the gateway (open registration).${NC}"
45
- fi
46
-
47
- ENTRY_COUNT=$(echo "$REGISTRATION_EMAILS" | tr ',' '\n' | grep -c '[^[:space:]]' || true)
48
- echo -e "${GREEN}✅ Loaded $ENTRY_COUNT entry(ies) from app/config/members.emails${NC}"
49
-
50
- # ── Update .env ───────────────────────────────────────────────────────────────
51
-
52
- ENV_FILE="$PROJECT_ROOT/.env"
53
-
54
- if [ ! -f "$ENV_FILE" ]; then
55
- echo -e "${RED}❌ .env not found. Run deploy-config first.${NC}"
56
- exit 1
57
- fi
58
-
59
- # Replace the REGISTRATION_EMAILS= line in .env (handles both empty and populated values)
60
- if grep -q '^REGISTRATION_EMAILS=' "$ENV_FILE"; then
61
- # Use a temp file to avoid sed -i portability issues across macOS/Linux
62
- local_tmp=$(mktemp)
63
- sed "s|^REGISTRATION_EMAILS=.*|REGISTRATION_EMAILS=${REGISTRATION_EMAILS}|" "$ENV_FILE" > "$local_tmp"
64
- mv "$local_tmp" "$ENV_FILE"
65
- echo -e "${GREEN}✅ Updated REGISTRATION_EMAILS in .env${NC}"
66
- else
67
- echo "" >> "$ENV_FILE"
68
- echo "REGISTRATION_EMAILS=${REGISTRATION_EMAILS}" >> "$ENV_FILE"
69
- echo -e "${GREEN}✅ Appended REGISTRATION_EMAILS to .env${NC}"
70
- fi
71
-
72
- # ── Deploy to Cloudflare Pages ────────────────────────────────────────────────
73
-
74
- if ! command -v wrangler > /dev/null 2>&1; then
75
- echo -e "${RED}❌ wrangler is not installed or not in PATH${NC}"
76
- exit 1
77
- fi
78
-
79
- source "$ENV_FILE"
80
-
81
- PAGES_PROJECT_NAME=$(echo "$PAGES_PROJECT_NAME" | tr -d '\r')
82
- if [ -z "$PAGES_PROJECT_NAME" ]; then
83
- echo -e "${RED}❌ PAGES_PROJECT_NAME is missing from .env${NC}"
84
- exit 1
85
- fi
86
-
87
- echo -e "${YELLOW} Setting REGISTRATION_EMAILS for production...${NC}"
88
- printf '%s' "$REGISTRATION_EMAILS" | wrangler pages secret put REGISTRATION_EMAILS \
89
- --project-name "$PAGES_PROJECT_NAME"
90
-
91
- echo -e "${GREEN}✅ REGISTRATION_EMAILS deployed to production${NC}"
92
-
93
- # Deploy Pages so the new secret takes effect immediately
94
- echo -e "\n${YELLOW}🚀 Building and deploying Pages to activate new secret...${NC}"
95
-
96
- if ! npm run deploy-pages; then
97
- echo -e "${RED}❌ Pages deployment failed${NC}"
98
- exit 1
99
- fi
100
- echo -e "${GREEN}✅ Pages deployment complete${NC}"
101
-
102
- echo -e "\n${GREEN}🎉 Members email list deployment complete!${NC}"
@@ -1,101 +0,0 @@
1
- #!/bin/bash
2
-
3
- # ============================================
4
- # PRIMERSHEAR EMAIL LIST DEPLOYMENT SCRIPT
5
- # ============================================
6
- # Reads app/config/primershear.emails, updates PRIMERSHEAR_EMAILS in .env,
7
- # then deploys that secret directly to Cloudflare Pages (production).
8
-
9
- set -e
10
- set -o pipefail
11
-
12
- RED='\033[0;31m'
13
- GREEN='\033[0;32m'
14
- YELLOW='\033[1;33m'
15
- BLUE='\033[0;34m'
16
- NC='\033[0m'
17
-
18
- echo -e "${BLUE}📧 PrimerShear Email List Deployment${NC}"
19
- echo "======================================"
20
-
21
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
22
- PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
23
- cd "$PROJECT_ROOT"
24
-
25
- trap 'echo -e "\n${RED}❌ deploy-primershear-emails.sh failed near line ${LINENO}${NC}"' ERR
26
-
27
- # ── Read emails file ──────────────────────────────────────────────────────────
28
-
29
- EMAILS_FILE="$PROJECT_ROOT/app/config/primershear.emails"
30
-
31
- if [ ! -f "$EMAILS_FILE" ]; then
32
- echo -e "${RED}❌ primershear.emails not found at: $EMAILS_FILE${NC}"
33
- echo -e "${YELLOW} Create it with one email address per line.${NC}"
34
- exit 1
35
- fi
36
-
37
- # Strip comment lines and blank lines, then join with commas
38
- # Use || true to avoid failure if paste gets no input (handles empty file gracefully)
39
- PRIMERSHEAR_EMAILS=$(grep -v '^[[:space:]]*#' "$EMAILS_FILE" | grep -v '^[[:space:]]*$' | paste -sd ',' - || true)
40
-
41
- if [ -z "$PRIMERSHEAR_EMAILS" ]; then
42
- echo -e "${YELLOW}⚠️ primershear.emails contains no active email addresses.${NC}"
43
- echo -e "${YELLOW} The secret will be set to an empty string, disabling the feature.${NC}"
44
- fi
45
-
46
- EMAIL_COUNT=$(echo "$PRIMERSHEAR_EMAILS" | tr ',' '\n' | grep -c '[^[:space:]]' || true)
47
- echo -e "${GREEN}✅ Loaded $EMAIL_COUNT email address(es) from app/config/primershear.emails${NC}"
48
-
49
- # ── Update .env ───────────────────────────────────────────────────────────────
50
-
51
- ENV_FILE="$PROJECT_ROOT/.env"
52
-
53
- if [ ! -f "$ENV_FILE" ]; then
54
- echo -e "${RED}❌ .env not found. Run deploy-config first.${NC}"
55
- exit 1
56
- fi
57
-
58
- # Replace the PRIMERSHEAR_EMAILS= line in .env (handles both empty and populated values)
59
- if grep -q '^PRIMERSHEAR_EMAILS=' "$ENV_FILE"; then
60
- # Use a temp file to avoid sed -i portability issues across macOS/Linux
61
- local_tmp=$(mktemp)
62
- sed "s|^PRIMERSHEAR_EMAILS=.*|PRIMERSHEAR_EMAILS=${PRIMERSHEAR_EMAILS}|" "$ENV_FILE" > "$local_tmp"
63
- mv "$local_tmp" "$ENV_FILE"
64
- echo -e "${GREEN}✅ Updated PRIMERSHEAR_EMAILS in .env${NC}"
65
- else
66
- echo "" >> "$ENV_FILE"
67
- echo "PRIMERSHEAR_EMAILS=${PRIMERSHEAR_EMAILS}" >> "$ENV_FILE"
68
- echo -e "${GREEN}✅ Appended PRIMERSHEAR_EMAILS to .env${NC}"
69
- fi
70
-
71
- # ── Deploy to Cloudflare Pages ────────────────────────────────────────────────
72
-
73
- if ! command -v wrangler > /dev/null 2>&1; then
74
- echo -e "${RED}❌ wrangler is not installed or not in PATH${NC}"
75
- exit 1
76
- fi
77
-
78
- source "$ENV_FILE"
79
-
80
- PAGES_PROJECT_NAME=$(echo "$PAGES_PROJECT_NAME" | tr -d '\r')
81
- if [ -z "$PAGES_PROJECT_NAME" ]; then
82
- echo -e "${RED}❌ PAGES_PROJECT_NAME is missing from .env${NC}"
83
- exit 1
84
- fi
85
-
86
- echo -e "${YELLOW} Setting PRIMERSHEAR_EMAILS for production...${NC}"
87
- printf '%s' "$PRIMERSHEAR_EMAILS" | wrangler pages secret put PRIMERSHEAR_EMAILS \
88
- --project-name "$PAGES_PROJECT_NAME"
89
-
90
- echo -e "${GREEN}✅ PRIMERSHEAR_EMAILS deployed to production${NC}"
91
-
92
- # Deploy Pages so the new secret takes effect immediately
93
- echo -e "\n${YELLOW}🚀 Building and deploying Pages to activate new secret...${NC}"
94
-
95
- if ! npm run deploy-pages; then
96
- echo -e "${RED}❌ Pages deployment failed${NC}"
97
- exit 1
98
- fi
99
- echo -e "${GREEN}✅ Pages deployment complete${NC}"
100
-
101
- echo -e "\n${GREEN}🎉 PrimerShear email list deployment complete!${NC}"