From 73aee3af784aced7e222ce3c5e204b88fece3617 Mon Sep 17 00:00:00 2001 From: santosral Date: Tue, 7 Apr 2026 17:00:32 +0800 Subject: [PATCH] feat: add feature flag for AI Chat --- .env.example | 3 +++ .../(main)/(protected)/+layout.server.ts | 4 ++++ src/routes/(main)/(protected)/+layout.svelte | 8 ++++++-- src/routes/(main)/hooks.server.ts | 20 ++++++++++++++++--- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/.env.example b/.env.example index 46085685..f75a4461 100644 --- a/.env.example +++ b/.env.example @@ -31,3 +31,6 @@ OPENAI_BASE_URL=xxx GOOGLE_CLIENT_ID=xxx GOOGLE_CLIENT_SECRET=xxx GOOGLE_HOSTED_DOMAIN= + +# The feature flags. +FEATURE_AI_CHAT=true diff --git a/src/routes/(main)/(protected)/+layout.server.ts b/src/routes/(main)/(protected)/+layout.server.ts index 650dd707..6d4daaf4 100644 --- a/src/routes/(main)/(protected)/+layout.server.ts +++ b/src/routes/(main)/(protected)/+layout.server.ts @@ -1,5 +1,6 @@ import { error, redirect } from '@sveltejs/kit'; +import { env } from '$env/dynamic/private'; import { db, type UserFindUniqueArgs, type UserGetPayload } from '$lib/server/db'; import type { LayoutServerLoad } from './$types'; @@ -39,5 +40,8 @@ export const load: LayoutServerLoad = async (event) => { username: user.name, csrfToken: event.locals.session.csrfToken(), onboarded, + featureFlags: { + aiChat: env.FEATURE_AI_CHAT === 'true', + }, }; }; diff --git a/src/routes/(main)/(protected)/+layout.svelte b/src/routes/(main)/(protected)/+layout.svelte index b7e60e21..b070e658 100644 --- a/src/routes/(main)/(protected)/+layout.svelte +++ b/src/routes/(main)/(protected)/+layout.svelte @@ -198,7 +198,9 @@ /> {/if} - + {#if page.data.featureFlags.aiChat} + + {/if} {/if} @@ -327,6 +329,8 @@ {/if} - +{#if page.data.featureFlags.aiChat} + +{/if} diff --git a/src/routes/(main)/hooks.server.ts b/src/routes/(main)/hooks.server.ts index 9228c00c..76003ce0 100644 --- a/src/routes/(main)/hooks.server.ts +++ b/src/routes/(main)/hooks.server.ts @@ -1,6 +1,7 @@ -import { type Handle, redirect } from '@sveltejs/kit'; +import { type Handle, json, redirect } from '@sveltejs/kit'; import { sequence } from '@sveltejs/kit/hooks'; +import { env } from '$env/dynamic/private'; import { HOME_PATH, nanoid } from '$lib/helpers/index.js'; import { learnerAuth } from '$lib/server/auth/index.js'; import { logger } from '$lib/server/logger.js'; @@ -9,13 +10,25 @@ import { logger } from '$lib/server/logger.js'; * A handle that adds a request ID to the response headers and attaches a scoped logger to the * event. Downstream handles are expected to use the scoped logger for logging. */ -const requestLoggingHandle: Handle = async ({ event, resolve }) => { +const requestLoggingHandle: Handle = ({ event, resolve }) => { const requestId = nanoid(); event.setHeaders({ 'X-Request-Id': requestId }); event.locals.logger = logger.child({ requestId }); - return await resolve(event); + return resolve(event); +}; + +/** + * A handle that gates API routes behind feature flags. + * Returns 403 for disabled features before any further processing. + */ +const featureFlagHandle: Handle = ({ event, resolve }) => { + if (event.url.pathname.startsWith('/api/messages') && env.FEATURE_AI_CHAT !== 'true') { + return json(null, { status: 403 }); + } + + return resolve(event); }; /** @@ -52,6 +65,7 @@ const routeProtectionHandle: Handle = async ({ event, resolve }) => { export const handle: Handle = sequence( requestLoggingHandle, + featureFlagHandle, learnerAuth.handle, routeProtectionHandle, );