From d7741b767d426655c582c6ca34d5314acf1662d5 Mon Sep 17 00:00:00 2001 From: Sergey Chernyshev Date: Sun, 22 Feb 2026 13:54:05 -0500 Subject: [PATCH 1/6] fix: resolve CI test failures by fixing API field names and improving admin checks --- src/index.ts | 137 ++++++++++++++------------------------------ wrangler.test.jsonc | 2 +- 2 files changed, 45 insertions(+), 94 deletions(-) diff --git a/src/index.ts b/src/index.ts index 5f43798..bb61917 100644 --- a/src/index.ts +++ b/src/index.ts @@ -241,7 +241,8 @@ async function handleAdmin(request: Request, env: StartupAPIEnv, usersPath: stri } } } else if (parts[0] === 'impersonate' && request.method === 'POST') { - const { user_id } = (await request.json()) as { user_id: string }; + const data = (await request.json()) as any; + const user_id = data.user_id || data.userId; if (!user_id) return new Response('Missing user_id', { status: 400 }); if (user_id === user.id) { @@ -269,6 +270,8 @@ async function handleAdmin(request: Request, env: StartupAPIEnv, usersPath: stri return Response.json({ success: true }, { headers }); } + + return new Response('Not Found', { status: 404 }); } url.pathname = '/users/admin' + path; @@ -281,7 +284,7 @@ async function handleMe(request: Request, env: StartupAPIEnv, cookieManager: Coo const user = await getUserFromSession(request, env, cookieManager); if (!user) return new Response('Unauthorized', { status: 401 }); - const { id: doId, sessionId, profile: initialProfile, credential } = user; + const { id: doId, profile: initialProfile, credential } = user; try { const id = env.USER.idFromString(doId); @@ -347,20 +350,23 @@ function isAdmin(user: any, env: StartupAPIEnv): boolean { const adminIds = env.ADMIN_IDS.split(',') .map((e) => e.trim()) .filter(Boolean); - const profile = user.profile || {}; - const credential = user.credential || {}; + + const userId = user.id; + const profile = user.profile || user || {}; + const credential = user.credential || user || {}; + const email = profile.email || user.email; return ( - adminIds.includes(user.id) || + adminIds.includes(userId) || (env.ENVIRONMENT === 'test' && adminIds.some((id) => { try { - return user.id === env.USER.idFromName(id).toString(); + return userId === env.USER.idFromName(id).toString(); } catch (e) { return false; } })) || - (profile.email && adminIds.includes(profile.email)) || + (email && adminIds.includes(email)) || (credential.subject_id && adminIds.includes(credential.subject_id)) || (credential.provider && credential.subject_id && adminIds.includes(`${credential.provider}:${credential.subject_id}`)) ); @@ -508,25 +514,11 @@ async function handleDeleteCredential(request: Request, env: StartupAPIEnv, cook } async function handleMeImage(request: Request, env: StartupAPIEnv, type: string, cookieManager: CookieManager): Promise { - const cookieHeader = request.headers.get('Cookie'); - if (!cookieHeader) return new Response('Unauthorized', { status: 401 }); - - const cookies = parseCookies(cookieHeader); - const sessionCookieEncrypted = cookies['session_id']; - - if (!sessionCookieEncrypted) { - return new Response('Unauthorized', { status: 401 }); - } - - const sessionCookie = await cookieManager.decrypt(sessionCookieEncrypted); - if (!sessionCookie || !sessionCookie.includes(':')) { - return new Response('Unauthorized', { status: 401 }); - } - - const [sessionId, doId] = sessionCookie.split(':'); + const user = await getUserFromSession(request, env, cookieManager); + if (!user) return new Response('Unauthorized', { status: 401 }); try { - const id = env.USER.idFromString(doId); + const id = env.USER.idFromString(user.id); const stub = env.USER.get(id); if (request.method === 'PUT') { @@ -549,7 +541,7 @@ async function handleMeImage(request: Request, env: StartupAPIEnv, type: string, return Response.json({ success: true }); } - return handleUserImage(request, env, doId, type, cookieManager); + return handleUserImage(request, env, user.id, type, cookieManager); } catch (e: any) { console.error('[handleMeImage] Error:', e.message, e.stack); return new Response('Error fetching image: ' + e.message, { status: 500 }); @@ -680,29 +672,12 @@ function parseCookies(cookieHeader: string): Record { } async function handleMyAccounts(request: Request, env: StartupAPIEnv, cookieManager: CookieManager): Promise { - const cookieHeader = request.headers.get('Cookie'); - if (!cookieHeader) return new Response('Unauthorized', { status: 401 }); - - const cookies = parseCookies(cookieHeader); - const sessionCookieEncrypted = cookies['session_id']; - - if (!sessionCookieEncrypted) { - return new Response('Unauthorized', { status: 401 }); - } - - const sessionCookie = await cookieManager.decrypt(sessionCookieEncrypted); - if (!sessionCookie || !sessionCookie.includes(':')) { - return new Response('Unauthorized', { status: 401 }); - } - - const [sessionId, doId] = sessionCookie.split(':'); + const user = await getUserFromSession(request, env, cookieManager); + if (!user) return new Response('Unauthorized', { status: 401 }); try { - const id = env.USER.idFromString(doId); + const id = env.USER.idFromString(user.id); const userStub = env.USER.get(id); - const data = await userStub.validateSession(sessionId); - - if (!data.valid) return Response.json(data, { status: 401 }); // Fetch memberships const memberships = await userStub.getMemberships(); @@ -731,22 +706,9 @@ async function handleMyAccounts(request: Request, env: StartupAPIEnv, cookieMana } async function handleSwitchAccount(request: Request, env: StartupAPIEnv, cookieManager: CookieManager): Promise { - const cookieHeader = request.headers.get('Cookie'); - if (!cookieHeader) return new Response('Unauthorized', { status: 401 }); - - const cookies = parseCookies(cookieHeader); - const sessionCookieEncrypted = cookies['session_id']; - - if (!sessionCookieEncrypted) { - return new Response('Unauthorized', { status: 401 }); - } - - const sessionCookie = await cookieManager.decrypt(sessionCookieEncrypted); - if (!sessionCookie || !sessionCookie.includes(':')) { - return new Response('Unauthorized', { status: 401 }); - } + const user = await getUserFromSession(request, env, cookieManager); + if (!user) return new Response('Unauthorized', { status: 401 }); - const [sessionId, doId] = sessionCookie.split(':'); const { account_id } = (await request.json()) as { account_id: string }; if (!account_id) { @@ -754,12 +716,8 @@ async function handleSwitchAccount(request: Request, env: StartupAPIEnv, cookieM } try { - const id = env.USER.idFromString(doId); + const id = env.USER.idFromString(user.id); const userStub = env.USER.get(id); - const data = await userStub.validateSession(sessionId); - - if (!data.valid) return Response.json(data, { status: 401 }); - return Response.json(await userStub.switchAccount(account_id)); } catch (e: any) { return new Response(e.message, { status: 400 }); @@ -773,29 +731,16 @@ async function handleSSR( usersPath: string, cookieManager: CookieManager, ): Promise { - const cookieHeader = request.headers.get('Cookie'); - const cookies = parseCookies(cookieHeader || ''); - const sessionCookieEncrypted = cookies['session_id']; - - if (!sessionCookieEncrypted) { - return Response.redirect(url.origin + '/', 302); - } - - const sessionCookie = await cookieManager.decrypt(sessionCookieEncrypted); - if (!sessionCookie || !sessionCookie.includes(':')) { + const user = await getUserFromSession(request, env, cookieManager); + if (!user) { return Response.redirect(url.origin + '/', 302); } - const [sessionId, doId] = sessionCookie.split(':'); + const { id: doId, sessionId, profile: initialProfile, credential } = user; try { const id = env.USER.idFromString(doId); const userStub = env.USER.get(id); - const data = await userStub.validateSession(sessionId); - - if (!data.valid) { - return Response.redirect(url.origin + '/', 302); - } // Get HTML from assets const assetUrl = new URL(url.toString()); @@ -821,17 +766,21 @@ async function handleSSR( let html = await assetResponse.text(); - const profile = { ...data.profile }; + const data: any = { + valid: true, + profile: { ...initialProfile }, + credential, + }; + const image = await userStub.getImage('avatar'); if (image) { const usersPathNormalized = usersPath.endsWith('/') ? usersPath : usersPath + '/'; - profile.picture = usersPathNormalized + 'me/avatar'; + data.profile.picture = usersPathNormalized + 'me/avatar'; } else { - profile.picture = null; + data.profile.picture = null; } - data.profile = profile; - data.is_admin = isAdmin({ id: doId, ...data.profile }, env); + data.is_admin = isAdmin({ id: doId, profile: data.profile, credential }, env); // Fetch memberships to find current account const memberships = await userStub.getMemberships(); @@ -864,14 +813,16 @@ async function handleSSR( providers: getActiveProviders(env).join(','), profile_json: JSON.stringify(data).replace(/"/g, '"'), credentials_json: JSON.stringify(credentials).replace(/"/g, '"'), - profile_name: profile.name || 'Anonymous', + profile_name: data.profile.name || 'Anonymous', profile_id: doId, - profile_email: profile.email || '', - profile_picture: profile.picture || '', - profile_picture_display: profile.picture ? 'display: block;' : 'display: none;', - profile_placeholder_display: profile.picture ? 'display: none;' : 'display: flex;', - profile_remove_btn_display: profile.picture ? 'display: flex;' : 'display: none;', - profile_provider_label: profile.provider ? `(from ${profile.provider.charAt(0).toUpperCase() + profile.provider.slice(1)})` : '', + profile_email: data.profile.email || '', + profile_picture: data.profile.picture || '', + profile_picture_display: data.profile.picture ? 'display: block;' : 'display: none;', + profile_placeholder_display: data.profile.picture ? 'display: none;' : 'display: flex;', + profile_remove_btn_display: data.profile.picture ? 'display: flex;' : 'display: none;', + profile_provider_label: data.profile.provider + ? `(from ${data.profile.provider.charAt(0).toUpperCase() + data.profile.provider.slice(1)})` + : '', nav_account_display: account && (account.role === 1 || data.is_admin) ? 'display: block;' : 'display: none;', credentials_list_html: renderCredentialsList(credentials, data.credential?.provider), link_credentials_html: renderLinkCredentialsList(getActiveProviders(env)), diff --git a/wrangler.test.jsonc b/wrangler.test.jsonc index 594601b..f6f739b 100644 --- a/wrangler.test.jsonc +++ b/wrangler.test.jsonc @@ -44,7 +44,7 @@ "ENVIRONMENT": "test", "SESSION_SECRET": "dev-secret", "ORIGIN_URL": "http://example.com", - "ADMIN_IDS": "admin", + "ADMIN_IDS": "admin,admin@example.com", "GOOGLE_CLIENT_ID": "google-id", "GOOGLE_CLIENT_SECRET": "google-secret", "TWITCH_CLIENT_ID": "twitch-id", From 979ae8be4506708875e9df451c1636ba6ff48f20 Mon Sep 17 00:00:00 2001 From: Sergey Chernyshev Date: Sun, 22 Feb 2026 13:56:57 -0500 Subject: [PATCH 2/6] fix: remove email-based admin verification for security and consistency --- src/SystemDO.ts | 10 ++++++++-- src/index.ts | 8 +------- wrangler.test.jsonc | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/SystemDO.ts b/src/SystemDO.ts index c97e521..9328a7e 100644 --- a/src/SystemDO.ts +++ b/src/SystemDO.ts @@ -44,8 +44,14 @@ export class SystemDO extends DurableObject { const adminIds = (this.env.ADMIN_IDS || '').split(',').map((id) => id.trim()); const isAdmin = adminIds.includes(u.id) || - (u.email && adminIds.includes(u.email)) || - (u.provider && u.id && adminIds.includes(`${u.provider}:${u.id}`)); + (this.env.ENVIRONMENT === 'test' && + adminIds.some((id) => { + try { + return u.id === this.env.USER.idFromName(id).toString(); + } catch (e) { + return false; + } + })); return { ...u, diff --git a/src/index.ts b/src/index.ts index bb61917..166371c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -352,9 +352,6 @@ function isAdmin(user: any, env: StartupAPIEnv): boolean { .filter(Boolean); const userId = user.id; - const profile = user.profile || user || {}; - const credential = user.credential || user || {}; - const email = profile.email || user.email; return ( adminIds.includes(userId) || @@ -365,10 +362,7 @@ function isAdmin(user: any, env: StartupAPIEnv): boolean { } catch (e) { return false; } - })) || - (email && adminIds.includes(email)) || - (credential.subject_id && adminIds.includes(credential.subject_id)) || - (credential.provider && credential.subject_id && adminIds.includes(`${credential.provider}:${credential.subject_id}`)) + })) ); } diff --git a/wrangler.test.jsonc b/wrangler.test.jsonc index f6f739b..594601b 100644 --- a/wrangler.test.jsonc +++ b/wrangler.test.jsonc @@ -44,7 +44,7 @@ "ENVIRONMENT": "test", "SESSION_SECRET": "dev-secret", "ORIGIN_URL": "http://example.com", - "ADMIN_IDS": "admin,admin@example.com", + "ADMIN_IDS": "admin", "GOOGLE_CLIENT_ID": "google-id", "GOOGLE_CLIENT_SECRET": "google-secret", "TWITCH_CLIENT_ID": "twitch-id", From 0d7932cabb3701ee5b35e066261593d3bb0fbb62 Mon Sep 17 00:00:00 2001 From: Sergey Chernyshev Date: Sun, 22 Feb 2026 14:06:25 -0500 Subject: [PATCH 3/6] fix: resolve CI failures and refactor admin logic --- .gitignore | 4 +++- package.json | 4 ++-- worker-configuration.d.ts | 46 +++++++++++++++++---------------------- wrangler.jsonc | 11 ++++++++++ 4 files changed, 36 insertions(+), 29 deletions(-) diff --git a/.gitignore b/.gitignore index c0fe47c..7d6e01a 100644 --- a/.gitignore +++ b/.gitignore @@ -165,7 +165,9 @@ dist .env* !.env.example .wrangler/ +wrangler.local.jsonc # Mac folder index -.DS_Store \ No newline at end of file +.DS_Store + diff --git a/package.json b/package.json index 35559ad..6a96bb1 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,8 @@ }, "scripts": { "deploy": "wrangler deploy", - "dev": "wrangler dev", - "start": "wrangler dev", + "dev": "wrangler dev -c wrangler.local.jsonc", + "start": "wrangler dev -c wrangler.local.jsonc", "preview": "wrangler dev --env preview", "test": "vitest run", "test:coverage": "vitest run --coverage --coverage.provider=istanbul", diff --git a/worker-configuration.d.ts b/worker-configuration.d.ts index 49b9680..3c48754 100644 --- a/worker-configuration.d.ts +++ b/worker-configuration.d.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -// Generated by Wrangler by running `wrangler types` (hash: 1b2f4131cc4427d006995ed2932f1531) +// Generated by Wrangler by running `wrangler types` (hash: 703a144f849ca290fb83bbbeca1c4ccf) // Runtime types generated with workerd@1.20260120.0 2025-09-27 global_fetch_strictly_public declare namespace Cloudflare { interface GlobalProps { @@ -9,36 +9,30 @@ declare namespace Cloudflare { interface PreviewEnv { IMAGE_STORAGE: R2Bucket; ASSETS: Fetcher; - SESSION_SECRET: string; - ADMIN_IDS: string; - ORIGIN_URL: string; - AUTH_ORIGIN: string; - TWITCH_CLIENT_ID: string; - TWITCH_CLIENT_SECRET: string; - GOOGLE_CLIENT_ID: string; - GOOGLE_CLIENT_SECRET: string; + GITHUB_PROJECT_ID: string; USER: DurableObjectNamespace; ACCOUNT: DurableObjectNamespace; SYSTEM: DurableObjectNamespace; CREDENTIAL: DurableObjectNamespace; } - interface Env { - SESSION_SECRET: string; - ADMIN_IDS: string; - ORIGIN_URL: string; - AUTH_ORIGIN: string; - TWITCH_CLIENT_ID: string; - TWITCH_CLIENT_SECRET: string; - GOOGLE_CLIENT_ID: string; - GOOGLE_CLIENT_SECRET: string; - IMAGE_STORAGE: R2Bucket; - ASSETS: Fetcher; - USER: DurableObjectNamespace; - ACCOUNT: DurableObjectNamespace; - SYSTEM: DurableObjectNamespace; - CREDENTIAL: DurableObjectNamespace; - } -} + interface Env { + GITHUB_PROJECT_ID: string; + IMAGE_STORAGE: R2Bucket; + ASSETS: Fetcher; + USER: DurableObjectNamespace; + ACCOUNT: DurableObjectNamespace; + SYSTEM: DurableObjectNamespace; + CREDENTIAL: DurableObjectNamespace; + ENVIRONMENT: string; + SESSION_SECRET: string; + ADMIN_IDS: string; + ORIGIN_URL: string; + AUTH_ORIGIN: string; + TWITCH_CLIENT_ID: string; + TWITCH_CLIENT_SECRET: string; + GOOGLE_CLIENT_ID: string; + GOOGLE_CLIENT_SECRET: string; + }} interface Env extends Cloudflare.Env {} // Begin runtime types diff --git a/wrangler.jsonc b/wrangler.jsonc index 69c1092..c1f8f4a 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -96,4 +96,15 @@ }, }, }, + "vars": { + "ENVIRONMENT": "", + "SESSION_SECRET": "", + "ADMIN_IDS": "", + "ORIGIN_URL": "", + "AUTH_ORIGIN": "", + "TWITCH_CLIENT_ID": "", + "TWITCH_CLIENT_SECRET": "", + "GOOGLE_CLIENT_ID": "", + "GOOGLE_CLIENT_SECRET": "", + }, } From 58bb49571319fad3dbede0f9e78aff6a8182417b Mon Sep 17 00:00:00 2001 From: Sergey Chernyshev Date: Sun, 22 Feb 2026 14:42:06 -0500 Subject: [PATCH 4/6] Created a bunch of environments to try and convince wrangler to create more realistic types --- package.json | 1 - worker-configuration.d.ts | 69 ++++++++++++++++++++++++++---------- wrangler.jsonc | 74 ++++++++++++++++++++++++++++++++------- 3 files changed, 112 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index 6a96bb1..ef4abef 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,6 @@ "scripts": { "deploy": "wrangler deploy", "dev": "wrangler dev -c wrangler.local.jsonc", - "start": "wrangler dev -c wrangler.local.jsonc", "preview": "wrangler dev --env preview", "test": "vitest run", "test:coverage": "vitest run --coverage --coverage.provider=istanbul", diff --git a/worker-configuration.d.ts b/worker-configuration.d.ts index 3c48754..477cfef 100644 --- a/worker-configuration.d.ts +++ b/worker-configuration.d.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -// Generated by Wrangler by running `wrangler types` (hash: 703a144f849ca290fb83bbbeca1c4ccf) +// Generated by Wrangler by running `wrangler types` (hash: a657e3f25efd30e04bbabe84a5440d70) // Runtime types generated with workerd@1.20260120.0 2025-09-27 global_fetch_strictly_public declare namespace Cloudflare { interface GlobalProps { @@ -9,30 +9,61 @@ declare namespace Cloudflare { interface PreviewEnv { IMAGE_STORAGE: R2Bucket; ASSETS: Fetcher; + ENVIRONMENT: "preview"; + SESSION_SECRET: ""; + ADMIN_IDS: ""; + ORIGIN_URL: "https://startup-api-demo-origin.sergeychernyshev.workers.dev/"; + TWITCH_CLIENT_ID: ""; + TWITCH_CLIENT_SECRET: ""; GITHUB_PROJECT_ID: string; USER: DurableObjectNamespace; ACCOUNT: DurableObjectNamespace; SYSTEM: DurableObjectNamespace; CREDENTIAL: DurableObjectNamespace; } - interface Env { - GITHUB_PROJECT_ID: string; - IMAGE_STORAGE: R2Bucket; - ASSETS: Fetcher; - USER: DurableObjectNamespace; - ACCOUNT: DurableObjectNamespace; - SYSTEM: DurableObjectNamespace; - CREDENTIAL: DurableObjectNamespace; - ENVIRONMENT: string; - SESSION_SECRET: string; - ADMIN_IDS: string; - ORIGIN_URL: string; - AUTH_ORIGIN: string; - TWITCH_CLIENT_ID: string; - TWITCH_CLIENT_SECRET: string; - GOOGLE_CLIENT_ID: string; - GOOGLE_CLIENT_SECRET: string; - }} + interface MinimalEnv { + IMAGE_STORAGE: R2Bucket; + ASSETS: Fetcher; + ENVIRONMENT: "minimal"; + SESSION_SECRET: ""; + ORIGIN_URL: ""; + GITHUB_PROJECT_ID: string; + USER: DurableObjectNamespace; + ACCOUNT: DurableObjectNamespace; + SYSTEM: DurableObjectNamespace; + CREDENTIAL: DurableObjectNamespace; + } + interface OptionalEnv { + ENVIRONMENT: ""; + SESSION_SECRET: ""; + ADMIN_IDS: ""; + ORIGIN_URL: ""; + AUTH_ORIGIN: ""; + TWITCH_CLIENT_ID: ""; + TWITCH_CLIENT_SECRET: ""; + GOOGLE_CLIENT_ID: ""; + GOOGLE_CLIENT_SECRET: ""; + GITHUB_PROJECT_ID: string; + } + interface Env { + GITHUB_PROJECT_ID: string; + IMAGE_STORAGE?: R2Bucket; + ASSETS?: Fetcher; + ENVIRONMENT?: "preview" | "minimal" | ""; + SESSION_SECRET?: ""; + ADMIN_IDS?: ""; + ORIGIN_URL?: "https://startup-api-demo-origin.sergeychernyshev.workers.dev/" | ""; + TWITCH_CLIENT_ID?: ""; + TWITCH_CLIENT_SECRET?: ""; + USER?: DurableObjectNamespace; + ACCOUNT?: DurableObjectNamespace; + SYSTEM?: DurableObjectNamespace; + CREDENTIAL?: DurableObjectNamespace; + AUTH_ORIGIN?: ""; + GOOGLE_CLIENT_ID?: ""; + GOOGLE_CLIENT_SECRET?: ""; + } +} interface Env extends Cloudflare.Env {} // Begin runtime types diff --git a/wrangler.jsonc b/wrangler.jsonc index c1f8f4a..8091418 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -61,8 +61,8 @@ }, ], "env": { + // preview environment used for preview site deployments in PRs and etc. "preview": { - // copied from main enviroment to make sure env.ASSETS is not optional "assets": { "directory": "./public", "run_worker_first": true, @@ -94,17 +94,67 @@ }, ], }, + "vars": { + "ENVIRONMENT": "preview", + "SESSION_SECRET": "", + "ADMIN_IDS": "", + "ORIGIN_URL": "https://startup-api-demo-origin.sergeychernyshev.workers.dev/", + "TWITCH_CLIENT_ID": "", + "TWITCH_CLIENT_SECRET": "", + }, + }, + // environment used to define the minimal number of variables + // so the rest are properly generated as optional in type definitions + "minimal": { + "assets": { + "directory": "./public", + "run_worker_first": true, + "binding": "ASSETS", + }, + "r2_buckets": [ + { + "binding": "IMAGE_STORAGE", + "bucket_name": "startup-api-images", + }, + ], + "durable_objects": { + "bindings": [ + { + "name": "USER", + "class_name": "UserDO", + }, + { + "name": "ACCOUNT", + "class_name": "AccountDO", + }, + { + "name": "SYSTEM", + "class_name": "SystemDO", + }, + { + "name": "CREDENTIAL", + "class_name": "CredentialDO", + }, + ], + }, + "vars": { + "ENVIRONMENT": "minimal", + "SESSION_SECRET": "", + "ORIGIN_URL": "", + }, + }, + "optional": { + "vars": { + "ENVIRONMENT": "", + "SESSION_SECRET": "", + "ADMIN_IDS": "", + "ORIGIN_URL": "", + "AUTH_ORIGIN": "", + "TWITCH_CLIENT_ID": "", + "TWITCH_CLIENT_SECRET": "", + "GOOGLE_CLIENT_ID": "", + "GOOGLE_CLIENT_SECRET": "", + }, }, - }, - "vars": { - "ENVIRONMENT": "", - "SESSION_SECRET": "", - "ADMIN_IDS": "", - "ORIGIN_URL": "", - "AUTH_ORIGIN": "", - "TWITCH_CLIENT_ID": "", - "TWITCH_CLIENT_SECRET": "", - "GOOGLE_CLIENT_ID": "", - "GOOGLE_CLIENT_SECRET": "", }, } From 65d3d7e12a70899147d96756a959552dd3de69c3 Mon Sep 17 00:00:00 2001 From: Sergey Chernyshev Date: Sun, 22 Feb 2026 14:43:43 -0500 Subject: [PATCH 5/6] Added required bindings to "optional" env --- worker-configuration.d.ts | 20 +++++++++++++------- wrangler.jsonc | 31 +++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/worker-configuration.d.ts b/worker-configuration.d.ts index 477cfef..bec96ac 100644 --- a/worker-configuration.d.ts +++ b/worker-configuration.d.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -// Generated by Wrangler by running `wrangler types` (hash: a657e3f25efd30e04bbabe84a5440d70) +// Generated by Wrangler by running `wrangler types` (hash: b67527d07bd06e5d88bcac89eae3c0e4) // Runtime types generated with workerd@1.20260120.0 2025-09-27 global_fetch_strictly_public declare namespace Cloudflare { interface GlobalProps { @@ -34,6 +34,8 @@ declare namespace Cloudflare { CREDENTIAL: DurableObjectNamespace; } interface OptionalEnv { + IMAGE_STORAGE: R2Bucket; + ASSETS: Fetcher; ENVIRONMENT: ""; SESSION_SECRET: ""; ADMIN_IDS: ""; @@ -44,21 +46,25 @@ declare namespace Cloudflare { GOOGLE_CLIENT_ID: ""; GOOGLE_CLIENT_SECRET: ""; GITHUB_PROJECT_ID: string; + USER: DurableObjectNamespace; + ACCOUNT: DurableObjectNamespace; + SYSTEM: DurableObjectNamespace; + CREDENTIAL: DurableObjectNamespace; } interface Env { GITHUB_PROJECT_ID: string; - IMAGE_STORAGE?: R2Bucket; - ASSETS?: Fetcher; + IMAGE_STORAGE: R2Bucket; + ASSETS: Fetcher; ENVIRONMENT?: "preview" | "minimal" | ""; SESSION_SECRET?: ""; ADMIN_IDS?: ""; ORIGIN_URL?: "https://startup-api-demo-origin.sergeychernyshev.workers.dev/" | ""; TWITCH_CLIENT_ID?: ""; TWITCH_CLIENT_SECRET?: ""; - USER?: DurableObjectNamespace; - ACCOUNT?: DurableObjectNamespace; - SYSTEM?: DurableObjectNamespace; - CREDENTIAL?: DurableObjectNamespace; + USER: DurableObjectNamespace; + ACCOUNT: DurableObjectNamespace; + SYSTEM: DurableObjectNamespace; + CREDENTIAL: DurableObjectNamespace; AUTH_ORIGIN?: ""; GOOGLE_CLIENT_ID?: ""; GOOGLE_CLIENT_SECRET?: ""; diff --git a/wrangler.jsonc b/wrangler.jsonc index 8091418..4a0e1e1 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -144,6 +144,37 @@ }, }, "optional": { + "assets": { + "directory": "./public", + "run_worker_first": true, + "binding": "ASSETS", + }, + "r2_buckets": [ + { + "binding": "IMAGE_STORAGE", + "bucket_name": "startup-api-images", + }, + ], + "durable_objects": { + "bindings": [ + { + "name": "USER", + "class_name": "UserDO", + }, + { + "name": "ACCOUNT", + "class_name": "AccountDO", + }, + { + "name": "SYSTEM", + "class_name": "SystemDO", + }, + { + "name": "CREDENTIAL", + "class_name": "CredentialDO", + }, + ], + }, "vars": { "ENVIRONMENT": "", "SESSION_SECRET": "", From 194232a264ad83ff363b14d2b1ab6ea331c86aba Mon Sep 17 00:00:00 2001 From: Sergey Chernyshev Date: Sun, 22 Feb 2026 14:56:43 -0500 Subject: [PATCH 6/6] Don't define system bindings --- src/StartupAPIEnv.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/StartupAPIEnv.ts b/src/StartupAPIEnv.ts index f5e2a70..2f4b666 100644 --- a/src/StartupAPIEnv.ts +++ b/src/StartupAPIEnv.ts @@ -9,6 +9,4 @@ export type StartupAPIEnv = { ADMIN_IDS: string; SESSION_SECRET: string; ENVIRONMENT?: string; - SYSTEM: DurableObjectNamespace; - IMAGE_STORAGE: R2Bucket; } & Env;