@striae-org/striae 3.2.2 → 4.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.
- package/.env.example +1 -1
- package/app/components/actions/case-export/core-export.ts +5 -2
- package/app/components/actions/case-export/download-handlers.ts +51 -3
- package/app/components/actions/case-import/confirmation-import.ts +65 -40
- package/app/components/actions/case-import/confirmation-package.ts +86 -0
- package/app/components/actions/case-import/image-operations.ts +20 -49
- package/app/components/actions/case-import/index.ts +1 -0
- package/app/components/actions/case-import/orchestrator.ts +13 -3
- package/app/components/actions/case-import/storage-operations.ts +54 -89
- package/app/components/actions/case-import/validation.ts +7 -111
- package/app/components/actions/case-import/zip-processing.ts +44 -2
- package/app/components/actions/case-manage.ts +15 -27
- package/app/components/actions/confirm-export.ts +44 -13
- package/app/components/actions/generate-pdf.ts +3 -7
- package/app/components/actions/image-manage.ts +63 -129
- package/app/components/button/button.module.css +12 -8
- package/app/components/form/form-button.tsx +1 -1
- package/app/components/form/form.module.css +9 -0
- package/app/components/public-signing-key-modal/public-signing-key-modal.module.css +163 -49
- package/app/components/public-signing-key-modal/public-signing-key-modal.tsx +365 -88
- package/app/components/sidebar/case-export/case-export.tsx +13 -60
- package/app/components/sidebar/case-import/case-import.tsx +18 -6
- package/app/components/sidebar/case-import/hooks/useFilePreview.ts +6 -4
- package/app/components/sidebar/case-import/utils/file-validation.ts +57 -2
- package/app/components/sidebar/cases/case-sidebar.tsx +122 -52
- package/app/components/sidebar/cases/cases.module.css +101 -18
- package/app/components/sidebar/notes/notes.module.css +33 -13
- package/app/components/sidebar/sidebar.module.css +0 -2
- package/app/components/user/delete-account.tsx +7 -7
- package/app/components/user/manage-profile.tsx +1 -1
- package/app/components/user/mfa-phone-update.tsx +15 -12
- package/app/config-example/config.json +2 -8
- package/app/hooks/useInactivityTimeout.ts +2 -5
- package/app/root.tsx +96 -65
- package/app/routes/auth/login.tsx +132 -11
- package/app/routes/auth/route.ts +4 -3
- package/app/routes/striae/striae.tsx +4 -8
- package/app/services/audit/audit-api-client.ts +40 -0
- package/app/services/audit/audit-worker-client.ts +14 -17
- package/app/styles/root.module.css +13 -101
- package/app/tailwind.css +9 -2
- package/app/utils/SHA256.ts +5 -1
- package/app/utils/auth.ts +5 -32
- package/app/utils/confirmation-signature.ts +5 -1
- package/app/utils/data-api-client.ts +43 -0
- package/app/utils/data-operations.ts +59 -75
- package/app/utils/export-verification.ts +353 -0
- package/app/utils/image-api-client.ts +130 -0
- package/app/utils/pdf-api-client.ts +43 -0
- package/app/utils/permissions.ts +10 -23
- package/app/utils/signature-utils.ts +74 -4
- package/app/utils/user-api-client.ts +90 -0
- package/functions/api/_shared/firebase-auth.ts +255 -0
- package/functions/api/audit/[[path]].ts +150 -0
- package/functions/api/data/[[path]].ts +141 -0
- package/functions/api/image/[[path]].ts +127 -0
- package/functions/api/pdf/[[path]].ts +110 -0
- package/functions/api/user/[[path]].ts +196 -0
- package/package.json +8 -4
- package/public/favicon.ico +0 -0
- package/public/icon-256.png +0 -0
- package/public/icon-512.png +0 -0
- package/public/manifest.json +39 -0
- package/public/shortcut.png +0 -0
- package/public/social-image.png +0 -0
- package/react-router.config.ts +5 -0
- package/scripts/deploy-all.sh +22 -8
- package/scripts/deploy-config.sh +143 -148
- package/scripts/deploy-pages-secrets.sh +231 -0
- package/scripts/deploy-worker-secrets.sh +1 -1
- package/workers/audit-worker/wrangler.jsonc.example +1 -8
- package/workers/data-worker/wrangler.jsonc.example +1 -8
- package/workers/image-worker/wrangler.jsonc.example +1 -8
- package/workers/keys-worker/wrangler.jsonc.example +2 -9
- package/workers/pdf-worker/scripts/generate-assets.js +94 -0
- package/workers/pdf-worker/src/assets/icon-256.png +0 -0
- package/workers/pdf-worker/wrangler.jsonc.example +1 -8
- package/workers/user-worker/src/user-worker.example.ts +121 -41
- package/workers/user-worker/wrangler.jsonc.example +1 -8
- package/wrangler.toml.example +1 -1
- package/app/styles/legal-pages.module.css +0 -113
- package/public/favicon.svg +0 -9
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { verifyFirebaseIdentityFromRequest } from '../_shared/firebase-auth';
|
|
2
|
+
|
|
3
|
+
interface PdfProxyContext {
|
|
4
|
+
request: Request;
|
|
5
|
+
env: Env;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const SUPPORTED_METHODS = new Set(['POST', 'OPTIONS']);
|
|
9
|
+
|
|
10
|
+
function textResponse(message: string, status: number): Response {
|
|
11
|
+
return new Response(message, {
|
|
12
|
+
status,
|
|
13
|
+
headers: {
|
|
14
|
+
'Cache-Control': 'no-store',
|
|
15
|
+
'Content-Type': 'text/plain; charset=utf-8'
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeWorkerBaseUrl(workerDomain: string): string {
|
|
21
|
+
if (typeof workerDomain !== 'string' || workerDomain.trim().length === 0) {
|
|
22
|
+
throw new Error('Invalid worker domain');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const trimmedDomain = workerDomain.trim().replace(/\/+$/, '');
|
|
26
|
+
if (trimmedDomain.startsWith('http://') || trimmedDomain.startsWith('https://')) {
|
|
27
|
+
return trimmedDomain;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return `https://${trimmedDomain}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function extractProxyPath(url: URL): string | null {
|
|
34
|
+
const routePrefix = '/api/pdf';
|
|
35
|
+
if (!url.pathname.startsWith(routePrefix)) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const remainder = url.pathname.slice(routePrefix.length);
|
|
40
|
+
return remainder.length > 0 ? remainder : '/';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const onRequest = async ({ request, env }: PdfProxyContext): Promise<Response> => {
|
|
44
|
+
if (!SUPPORTED_METHODS.has(request.method)) {
|
|
45
|
+
return textResponse('Method not allowed', 405);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (request.method === 'OPTIONS') {
|
|
49
|
+
return new Response(null, {
|
|
50
|
+
status: 204,
|
|
51
|
+
headers: {
|
|
52
|
+
'Allow': 'POST, OPTIONS',
|
|
53
|
+
'Cache-Control': 'no-store'
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const identity = await verifyFirebaseIdentityFromRequest(request, env);
|
|
59
|
+
if (!identity) {
|
|
60
|
+
return textResponse('Unauthorized', 401);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const requestUrl = new URL(request.url);
|
|
64
|
+
const proxyPath = extractProxyPath(requestUrl);
|
|
65
|
+
if (!proxyPath) {
|
|
66
|
+
return textResponse('Not Found', 404);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!env.PDF_WORKER_DOMAIN || !env.PDF_WORKER_AUTH) {
|
|
70
|
+
return textResponse('PDF service not configured', 502);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const pdfWorkerBaseUrl = normalizeWorkerBaseUrl(env.PDF_WORKER_DOMAIN);
|
|
74
|
+
const upstreamUrl = `${pdfWorkerBaseUrl}${proxyPath}${requestUrl.search}`;
|
|
75
|
+
|
|
76
|
+
const upstreamHeaders = new Headers();
|
|
77
|
+
const contentTypeHeader = request.headers.get('Content-Type');
|
|
78
|
+
if (contentTypeHeader) {
|
|
79
|
+
upstreamHeaders.set('Content-Type', contentTypeHeader);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const acceptHeader = request.headers.get('Accept');
|
|
83
|
+
if (acceptHeader) {
|
|
84
|
+
upstreamHeaders.set('Accept', acceptHeader);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
upstreamHeaders.set('X-Custom-Auth-Key', env.PDF_WORKER_AUTH);
|
|
88
|
+
|
|
89
|
+
let upstreamResponse: Response;
|
|
90
|
+
try {
|
|
91
|
+
upstreamResponse = await fetch(upstreamUrl, {
|
|
92
|
+
method: request.method,
|
|
93
|
+
headers: upstreamHeaders,
|
|
94
|
+
body: request.body
|
|
95
|
+
});
|
|
96
|
+
} catch {
|
|
97
|
+
return textResponse('Upstream PDF service unavailable', 502);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const responseHeaders = new Headers(upstreamResponse.headers);
|
|
101
|
+
if (!responseHeaders.has('Cache-Control')) {
|
|
102
|
+
responseHeaders.set('Cache-Control', 'no-store');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return new Response(upstreamResponse.body, {
|
|
106
|
+
status: upstreamResponse.status,
|
|
107
|
+
statusText: upstreamResponse.statusText,
|
|
108
|
+
headers: responseHeaders
|
|
109
|
+
});
|
|
110
|
+
};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { verifyFirebaseIdentityFromRequest } from '../_shared/firebase-auth';
|
|
2
|
+
|
|
3
|
+
interface UserProxyContext {
|
|
4
|
+
request: Request;
|
|
5
|
+
env: Env;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const SUPPORTED_METHODS = new Set(['GET', 'PUT', 'DELETE', 'OPTIONS']);
|
|
9
|
+
const USER_EXISTS_PATH_PREFIX = '/exists/';
|
|
10
|
+
|
|
11
|
+
function textResponse(message: string, status: number): Response {
|
|
12
|
+
return new Response(message, {
|
|
13
|
+
status,
|
|
14
|
+
headers: {
|
|
15
|
+
'Cache-Control': 'no-store',
|
|
16
|
+
'Content-Type': 'text/plain; charset=utf-8'
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function jsonResponse(payload: Record<string, unknown>, status: number = 200): Response {
|
|
22
|
+
return new Response(JSON.stringify(payload), {
|
|
23
|
+
status,
|
|
24
|
+
headers: {
|
|
25
|
+
'Cache-Control': 'no-store',
|
|
26
|
+
'Content-Type': 'application/json; charset=utf-8'
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function normalizeWorkerBaseUrl(workerDomain: string): string {
|
|
32
|
+
if (typeof workerDomain !== 'string' || workerDomain.trim().length === 0) {
|
|
33
|
+
throw new Error('Invalid worker domain');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const trimmedDomain = workerDomain.trim().replace(/\/+$/, '');
|
|
37
|
+
if (trimmedDomain.startsWith('http://') || trimmedDomain.startsWith('https://')) {
|
|
38
|
+
return trimmedDomain;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return `https://${trimmedDomain}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function extractProxyPath(url: URL): string | null {
|
|
45
|
+
const routePrefix = '/api/user';
|
|
46
|
+
if (!url.pathname.startsWith(routePrefix)) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const remainder = url.pathname.slice(routePrefix.length);
|
|
51
|
+
return remainder.length > 0 ? remainder : '/';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function extractUserIdFromProxyPath(proxyPath: string): string | null {
|
|
55
|
+
const firstSegment = proxyPath.split('/').filter(Boolean)[0];
|
|
56
|
+
if (!firstSegment) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
return decodeURIComponent(firstSegment);
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function extractExistenceCheckUserId(proxyPath: string): string | null {
|
|
68
|
+
if (!proxyPath.startsWith(USER_EXISTS_PATH_PREFIX)) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const remainder = proxyPath.slice(USER_EXISTS_PATH_PREFIX.length);
|
|
73
|
+
const firstSegment = remainder.split('/').filter(Boolean)[0];
|
|
74
|
+
if (!firstSegment) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
return decodeURIComponent(firstSegment);
|
|
80
|
+
} catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const onRequest = async ({ request, env }: UserProxyContext): Promise<Response> => {
|
|
86
|
+
if (!SUPPORTED_METHODS.has(request.method)) {
|
|
87
|
+
return textResponse('Method not allowed', 405);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (request.method === 'OPTIONS') {
|
|
91
|
+
return new Response(null, {
|
|
92
|
+
status: 204,
|
|
93
|
+
headers: {
|
|
94
|
+
'Allow': 'GET, PUT, DELETE, OPTIONS',
|
|
95
|
+
'Cache-Control': 'no-store'
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const identity = await verifyFirebaseIdentityFromRequest(request, env);
|
|
101
|
+
if (!identity) {
|
|
102
|
+
return textResponse('Unauthorized', 401);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const requestUrl = new URL(request.url);
|
|
106
|
+
const proxyPath = extractProxyPath(requestUrl);
|
|
107
|
+
if (!proxyPath) {
|
|
108
|
+
return textResponse('Not Found', 404);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!env.USER_WORKER_DOMAIN || !env.USER_DB_AUTH) {
|
|
112
|
+
return textResponse('User service not configured', 502);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const userWorkerBaseUrl = normalizeWorkerBaseUrl(env.USER_WORKER_DOMAIN);
|
|
116
|
+
|
|
117
|
+
const existenceCheckUserId = extractExistenceCheckUserId(proxyPath);
|
|
118
|
+
if (existenceCheckUserId !== null) {
|
|
119
|
+
if (request.method !== 'GET') {
|
|
120
|
+
return textResponse('Method not allowed', 405);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let existenceResponse: Response;
|
|
124
|
+
try {
|
|
125
|
+
existenceResponse = await fetch(
|
|
126
|
+
`${userWorkerBaseUrl}/${encodeURIComponent(existenceCheckUserId)}`,
|
|
127
|
+
{
|
|
128
|
+
method: 'GET',
|
|
129
|
+
headers: {
|
|
130
|
+
'Accept': 'application/json',
|
|
131
|
+
'X-Custom-Auth-Key': env.USER_DB_AUTH
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
} catch {
|
|
136
|
+
return textResponse('Upstream user service unavailable', 502);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (existenceResponse.status === 404) {
|
|
140
|
+
return jsonResponse({ exists: false });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!existenceResponse.ok) {
|
|
144
|
+
return textResponse('Upstream user service unavailable', 502);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return jsonResponse({ exists: true });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const requestedUserId = extractUserIdFromProxyPath(proxyPath);
|
|
151
|
+
if (!requestedUserId) {
|
|
152
|
+
return textResponse('Missing user identifier', 400);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (requestedUserId !== identity.uid) {
|
|
156
|
+
return textResponse('Forbidden', 403);
|
|
157
|
+
}
|
|
158
|
+
const upstreamUrl = `${userWorkerBaseUrl}${proxyPath}${requestUrl.search}`;
|
|
159
|
+
|
|
160
|
+
const upstreamHeaders = new Headers();
|
|
161
|
+
const contentTypeHeader = request.headers.get('Content-Type');
|
|
162
|
+
if (contentTypeHeader) {
|
|
163
|
+
upstreamHeaders.set('Content-Type', contentTypeHeader);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const acceptHeader = request.headers.get('Accept');
|
|
167
|
+
if (acceptHeader) {
|
|
168
|
+
upstreamHeaders.set('Accept', acceptHeader);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
upstreamHeaders.set('X-Custom-Auth-Key', env.USER_DB_AUTH);
|
|
172
|
+
|
|
173
|
+
const shouldForwardBody = request.method !== 'GET' && request.method !== 'HEAD';
|
|
174
|
+
|
|
175
|
+
let upstreamResponse: Response;
|
|
176
|
+
try {
|
|
177
|
+
upstreamResponse = await fetch(upstreamUrl, {
|
|
178
|
+
method: request.method,
|
|
179
|
+
headers: upstreamHeaders,
|
|
180
|
+
body: shouldForwardBody ? request.body : undefined
|
|
181
|
+
});
|
|
182
|
+
} catch {
|
|
183
|
+
return textResponse('Upstream user service unavailable', 502);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const responseHeaders = new Headers(upstreamResponse.headers);
|
|
187
|
+
if (!responseHeaders.has('Cache-Control')) {
|
|
188
|
+
responseHeaders.set('Cache-Control', 'no-store');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return new Response(upstreamResponse.body, {
|
|
192
|
+
status: upstreamResponse.status,
|
|
193
|
+
statusText: upstreamResponse.statusText,
|
|
194
|
+
headers: responseHeaders
|
|
195
|
+
});
|
|
196
|
+
};
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@striae-org/striae",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "Striae is a cloud-native
|
|
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",
|
|
7
7
|
"homepage": "https://www.striae.org",
|
|
8
8
|
"repository": {
|
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
"annotation",
|
|
25
25
|
"react",
|
|
26
26
|
"cloudflare-workers",
|
|
27
|
+
"authenticated",
|
|
28
|
+
"confirmations",
|
|
27
29
|
"chain-of-custody",
|
|
28
30
|
"audit-trail"
|
|
29
31
|
],
|
|
@@ -44,6 +46,7 @@
|
|
|
44
46
|
"app/entry.server.tsx",
|
|
45
47
|
"app/root.tsx",
|
|
46
48
|
"app/tailwind.css",
|
|
49
|
+
"react-router.config.ts",
|
|
47
50
|
"functions/",
|
|
48
51
|
"public/",
|
|
49
52
|
"scripts/",
|
|
@@ -51,8 +54,8 @@
|
|
|
51
54
|
"workers/*/src/*.example.ts",
|
|
52
55
|
"workers/*/src/*.example.js",
|
|
53
56
|
"workers/*/src/*.ts",
|
|
54
|
-
"workers
|
|
55
|
-
"workers
|
|
57
|
+
"workers/pdf-worker/scripts/*.js",
|
|
58
|
+
"workers/pdf-worker/src/assets/icon-256.png",
|
|
56
59
|
"!workers/*/src/*worker.ts",
|
|
57
60
|
"workers/pdf-worker/src/assets/generated-assets.ts",
|
|
58
61
|
"workers/pdf-worker/src/formats/format-striae.ts",
|
|
@@ -98,6 +101,7 @@
|
|
|
98
101
|
"install-workers": "bash ./scripts/install-workers.sh",
|
|
99
102
|
"deploy-workers": "npm run deploy-workers:audit && npm run deploy-workers:data && npm run deploy-workers:image && npm run deploy-workers:keys && npm run deploy-workers:pdf && npm run deploy-workers:user",
|
|
100
103
|
"deploy-workers:secrets": "bash ./scripts/deploy-worker-secrets.sh",
|
|
104
|
+
"deploy-pages:secrets": "bash ./scripts/deploy-pages-secrets.sh",
|
|
101
105
|
"deploy-pages": "bash ./scripts/deploy-pages.sh",
|
|
102
106
|
"deploy-workers:audit": "cd workers/audit-worker && npm run deploy",
|
|
103
107
|
"deploy-workers:data": "cd workers/data-worker && npm run deploy",
|
package/public/favicon.ico
CHANGED
|
Binary file
|
package/public/icon-256.png
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "/",
|
|
3
|
+
"name": "Striae: A Firearms Examiner's Comparison Companion",
|
|
4
|
+
"short_name": "Striae",
|
|
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
|
+
"start_url": "/",
|
|
7
|
+
"scope": "/",
|
|
8
|
+
"display": "standalone",
|
|
9
|
+
"orientation": "any",
|
|
10
|
+
"lang": "en-US",
|
|
11
|
+
"dir": "ltr",
|
|
12
|
+
"theme_color": "#377087",
|
|
13
|
+
"background_color": "#f5f5f5",
|
|
14
|
+
"categories": [
|
|
15
|
+
"business",
|
|
16
|
+
"productivity",
|
|
17
|
+
"utilities"
|
|
18
|
+
],
|
|
19
|
+
"icons": [
|
|
20
|
+
{
|
|
21
|
+
"src": "shortcut.png",
|
|
22
|
+
"sizes": "64x64",
|
|
23
|
+
"type": "image/png",
|
|
24
|
+
"purpose": "any"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"src": "icon-256.png",
|
|
28
|
+
"sizes": "256x256",
|
|
29
|
+
"type": "image/png",
|
|
30
|
+
"purpose": "any"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"src": "icon-512.png",
|
|
34
|
+
"sizes": "512x512",
|
|
35
|
+
"type": "image/png",
|
|
36
|
+
"purpose": "any"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
Binary file
|
|
Binary file
|
package/scripts/deploy-all.sh
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
# 2. Worker dependencies installation
|
|
9
9
|
# 3. Workers (all 7 workers)
|
|
10
10
|
# 4. Worker secrets/environment variables
|
|
11
|
-
# 5. Pages
|
|
11
|
+
# 5. Pages secrets/environment variables
|
|
12
|
+
# 6. Pages (frontend)
|
|
12
13
|
|
|
13
14
|
set -e
|
|
14
15
|
set -o pipefail
|
|
@@ -66,6 +67,7 @@ require_command wrangler
|
|
|
66
67
|
assert_file_exists "$SCRIPT_DIR/deploy-config.sh"
|
|
67
68
|
assert_file_exists "$SCRIPT_DIR/install-workers.sh"
|
|
68
69
|
assert_file_exists "$SCRIPT_DIR/deploy-worker-secrets.sh"
|
|
70
|
+
assert_file_exists "$SCRIPT_DIR/deploy-pages-secrets.sh"
|
|
69
71
|
assert_file_exists "package.json"
|
|
70
72
|
|
|
71
73
|
if [ ! -f ".env" ] && [ ! -f ".env.example" ]; then
|
|
@@ -77,7 +79,7 @@ echo -e "${GREEN}✅ Preflight checks passed${NC}"
|
|
|
77
79
|
echo ""
|
|
78
80
|
|
|
79
81
|
# Step 1: Configuration Setup
|
|
80
|
-
echo -e "${PURPLE}Step 1/
|
|
82
|
+
echo -e "${PURPLE}Step 1/6: Configuration Setup${NC}"
|
|
81
83
|
echo "------------------------------"
|
|
82
84
|
echo -e "${YELLOW}⚙️ Setting up configuration files and replacing placeholders...${NC}"
|
|
83
85
|
if ! bash "$SCRIPT_DIR/deploy-config.sh"; then
|
|
@@ -90,7 +92,7 @@ run_config_checkpoint
|
|
|
90
92
|
echo ""
|
|
91
93
|
|
|
92
94
|
# Step 2: Install Worker Dependencies
|
|
93
|
-
echo -e "${PURPLE}Step 2/
|
|
95
|
+
echo -e "${PURPLE}Step 2/6: Installing Worker Dependencies${NC}"
|
|
94
96
|
echo "----------------------------------------"
|
|
95
97
|
echo -e "${YELLOW}📦 Installing npm dependencies for all workers...${NC}"
|
|
96
98
|
if ! bash "$SCRIPT_DIR/install-workers.sh"; then
|
|
@@ -101,7 +103,7 @@ echo -e "${GREEN}✅ All worker dependencies installed successfully${NC}"
|
|
|
101
103
|
echo ""
|
|
102
104
|
|
|
103
105
|
# Step 3: Deploy Workers
|
|
104
|
-
echo -e "${PURPLE}Step 3/
|
|
106
|
+
echo -e "${PURPLE}Step 3/6: Deploying Workers${NC}"
|
|
105
107
|
echo "----------------------------"
|
|
106
108
|
echo -e "${YELLOW}🔧 Deploying all 7 Cloudflare Workers...${NC}"
|
|
107
109
|
if ! npm run deploy-workers; then
|
|
@@ -112,7 +114,7 @@ echo -e "${GREEN}✅ All workers deployed successfully${NC}"
|
|
|
112
114
|
echo ""
|
|
113
115
|
|
|
114
116
|
# Step 4: Deploy Worker Secrets
|
|
115
|
-
echo -e "${PURPLE}Step 4/
|
|
117
|
+
echo -e "${PURPLE}Step 4/6: Deploying Worker Secrets${NC}"
|
|
116
118
|
echo "-----------------------------------"
|
|
117
119
|
echo -e "${YELLOW}🔐 Deploying worker environment variables...${NC}"
|
|
118
120
|
if ! bash "$SCRIPT_DIR/deploy-worker-secrets.sh"; then
|
|
@@ -122,8 +124,19 @@ fi
|
|
|
122
124
|
echo -e "${GREEN}✅ Worker secrets deployed successfully${NC}"
|
|
123
125
|
echo ""
|
|
124
126
|
|
|
125
|
-
# Step 5: Deploy Pages
|
|
126
|
-
echo -e "${PURPLE}Step 5/
|
|
127
|
+
# Step 5: Deploy Pages Secrets
|
|
128
|
+
echo -e "${PURPLE}Step 5/6: Deploying Pages Secrets${NC}"
|
|
129
|
+
echo "----------------------------------"
|
|
130
|
+
echo -e "${YELLOW}🔐 Deploying Pages environment variables...${NC}"
|
|
131
|
+
if ! bash "$SCRIPT_DIR/deploy-pages-secrets.sh"; then
|
|
132
|
+
echo -e "${RED}❌ Pages secrets deployment failed!${NC}"
|
|
133
|
+
exit 1
|
|
134
|
+
fi
|
|
135
|
+
echo -e "${GREEN}✅ Pages secrets deployed successfully${NC}"
|
|
136
|
+
echo ""
|
|
137
|
+
|
|
138
|
+
# Step 6: Deploy Pages
|
|
139
|
+
echo -e "${PURPLE}Step 6/6: Deploying Pages${NC}"
|
|
127
140
|
echo "--------------------------"
|
|
128
141
|
echo -e "${YELLOW}🌐 Building and deploying Pages...${NC}"
|
|
129
142
|
if ! npm run deploy-pages; then
|
|
@@ -142,11 +155,12 @@ echo -e "${BLUE}Deployed Components:${NC}"
|
|
|
142
155
|
echo " ✅ Worker dependencies (npm install)"
|
|
143
156
|
echo " ✅ 7 Cloudflare Workers"
|
|
144
157
|
echo " ✅ Worker environment variables"
|
|
158
|
+
echo " ✅ Pages environment variables"
|
|
145
159
|
echo " ✅ Cloudflare Pages frontend"
|
|
146
160
|
echo ""
|
|
147
161
|
echo -e "${BLUE}Next Steps:${NC}"
|
|
148
162
|
echo " 1. Test your application endpoints"
|
|
149
163
|
echo " 2. Verify all services are working"
|
|
150
|
-
echo " 3.
|
|
164
|
+
echo " 3. Verify worker and Pages secrets are set as expected"
|
|
151
165
|
echo ""
|
|
152
166
|
echo -e "${GREEN}✨ Your Striae application is now fully deployed!${NC}"
|