From 55ddf9f9a88d7e42b3d44b4a3439d2bd2d9005bb Mon Sep 17 00:00:00 2001 From: Justin322322 Date: Tue, 12 Aug 2025 12:29:40 +0800 Subject: [PATCH 1/5] web: fix broken homepage image; use existing asset and add fallback (closes #543) --- apps/web/src/components/landing/hero.tsx | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/apps/web/src/components/landing/hero.tsx b/apps/web/src/components/landing/hero.tsx index 46d99b0cc..7657ed15b 100644 --- a/apps/web/src/components/landing/hero.tsx +++ b/apps/web/src/components/landing/hero.tsx @@ -9,17 +9,23 @@ import { ArrowRight } from "lucide-react"; import Image from "next/image"; import { Handlebars } from "./handlebars"; import Link from "next/link"; +import { useState } from "react"; export function Hero() { + const [bgError, setBgError] = useState(false); return (
- landing-page.bg + {!bgError && ( + Landing page background setBgError(true)} + priority + /> + )} Date: Tue, 12 Aug 2025 12:38:46 +0800 Subject: [PATCH 2/5] web: make optional API env vars optional to fix CI build - Make FREESOUND_CLIENT_ID, FREESOUND_API_KEY, CLOUDFLARE_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET_NAME, MODAL_TRANSCRIPTION_URL optional in env.ts - Add isFreesoundConfigured() utility function - Update sounds search API to check configuration before using Freesound vars - Fixes CI build errors where these optional features caused required env validation failures --- apps/web/src/app/api/sounds/search/route.ts | 18 ++++++++++++++++++ apps/web/src/env.ts | 14 +++++++------- apps/web/src/lib/transcription-utils.ts | 9 +++++++++ 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/apps/web/src/app/api/sounds/search/route.ts b/apps/web/src/app/api/sounds/search/route.ts index c89bc76c6..632107169 100644 --- a/apps/web/src/app/api/sounds/search/route.ts +++ b/apps/web/src/app/api/sounds/search/route.ts @@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server"; import { z } from "zod"; import { env } from "@/env"; import { baseRateLimit } from "@/lib/rate-limit"; +import { isFreesoundConfigured } from "@/lib/transcription-utils"; const searchParamsSchema = z.object({ q: z.string().max(500, "Query too long").optional(), @@ -97,6 +98,23 @@ export async function GET(request: NextRequest) { return NextResponse.json({ error: "Too many requests" }, { status: 429 }); } + // Check Freesound configuration + const freesoundCheck = isFreesoundConfigured(); + if (!freesoundCheck.configured) { + console.error( + "Missing environment variables:", + JSON.stringify(freesoundCheck.missingVars) + ); + + return NextResponse.json( + { + error: "Sound search not configured", + message: `Sound search requires environment variables: ${freesoundCheck.missingVars.join(", ")}. Check README for setup instructions.`, + }, + { status: 503 } + ); + } + const { searchParams } = new URL(request.url); const validationResult = searchParamsSchema.safeParse({ diff --git a/apps/web/src/env.ts b/apps/web/src/env.ts index 71614300e..79c76d611 100644 --- a/apps/web/src/env.ts +++ b/apps/web/src/env.ts @@ -15,15 +15,15 @@ export const env = createEnv({ .default("development"), UPSTASH_REDIS_REST_URL: z.string().url(), UPSTASH_REDIS_REST_TOKEN: z.string(), - FREESOUND_CLIENT_ID: z.string(), - FREESOUND_API_KEY: z.string(), + FREESOUND_CLIENT_ID: z.string().optional(), + FREESOUND_API_KEY: z.string().optional(), // R2 / Cloudflare - CLOUDFLARE_ACCOUNT_ID: z.string(), - R2_ACCESS_KEY_ID: z.string(), - R2_SECRET_ACCESS_KEY: z.string(), - R2_BUCKET_NAME: z.string(), + CLOUDFLARE_ACCOUNT_ID: z.string().optional(), + R2_ACCESS_KEY_ID: z.string().optional(), + R2_SECRET_ACCESS_KEY: z.string().optional(), + R2_BUCKET_NAME: z.string().optional(), // Modal transcription - MODAL_TRANSCRIPTION_URL: z.string(), + MODAL_TRANSCRIPTION_URL: z.string().optional(), }, client: {}, runtimeEnv: { diff --git a/apps/web/src/lib/transcription-utils.ts b/apps/web/src/lib/transcription-utils.ts index 6388a3b6d..398b380b7 100644 --- a/apps/web/src/lib/transcription-utils.ts +++ b/apps/web/src/lib/transcription-utils.ts @@ -11,3 +11,12 @@ export function isTranscriptionConfigured() { return { configured: missingVars.length === 0, missingVars }; } + +export function isFreesoundConfigured() { + const missingVars = []; + + if (!env.FREESOUND_CLIENT_ID) missingVars.push("FREESOUND_CLIENT_ID"); + if (!env.FREESOUND_API_KEY) missingVars.push("FREESOUND_API_KEY"); + + return { configured: missingVars.length === 0, missingVars }; +} From 5951adb01ad679bc619dc5762f9fa4623b4bf6ce Mon Sep 17 00:00:00 2001 From: Justin322322 Date: Tue, 12 Aug 2025 12:44:09 +0800 Subject: [PATCH 3/5] web: fix TypeScript errors and improve optional feature handling This commit addresses TypeScript compilation errors that occurred after making optional API environment variables truly optional in the previous commit. ## Changes Made ### API Route Updates - env.R2_ACCESS_KEY_ID! and env.R2_SECRET_ACCESS_KEY! in AwsClient constructor - env.R2_BUCKET_NAME! and env.CLOUDFLARE_ACCOUNT_ID! in URL construction - Safe because isTranscriptionConfigured() check ensures these exist before usage - transcribe/route.ts: Added non-null assertion for Modal transcription URL - env.MODAL_TRANSCRIPTION_URL! in fetch() call - Safe because isTranscriptionConfigured() check validates this beforehand - sounds/search/route.ts: Added non-null assertion for Freesound API key - env.FREESOUND_API_KEY! in URLSearchParams construction - Safe because new isFreesoundConfigured() check validates this beforehand ### Configuration Validation - transcription-utils.ts: Added isFreesoundConfigured() utility function - Checks for FREESOUND_CLIENT_ID and FREESOUND_API_KEY availability - Returns {configured: boolean, missingVars: string[]} for consistent error handling - Mirrors existing isTranscriptionConfigured() pattern - sounds/search/route.ts: Added Freesound configuration check - Early return with 503 status if Freesound credentials missing - Provides clear error message listing required environment variables - Prevents runtime errors when optional sound search feature is not configured ## Why These Changes Are Safe 1. Non-null assertions are protected: All usage of env.VAR! is preceded by configuration checks that verify the variables exist 2. Graceful degradation: Missing optional features return proper HTTP 503 responses instead of crashing 3. Consistent error handling: All optional features follow the same pattern of checking configuration before usage 4. Build-time validation: Environment variables are optional at build time but validated at runtime when features are accessed ## Testing - Local build passes: bun run build completes successfully - TypeScript compilation: No type errors - Lint checks: All pass - Optional features degrade gracefully when env vars missing This ensures the CI/CD pipeline passes while maintaining proper runtime validation for optional features like sound search and auto-transcription. --- apps/web/src/app/api/get-upload-url/route.ts | 6 +++--- apps/web/src/app/api/sounds/search/route.ts | 2 +- apps/web/src/app/api/transcribe/route.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/web/src/app/api/get-upload-url/route.ts b/apps/web/src/app/api/get-upload-url/route.ts index dc5b7328f..9fd75b9a8 100644 --- a/apps/web/src/app/api/get-upload-url/route.ts +++ b/apps/web/src/app/api/get-upload-url/route.ts @@ -70,8 +70,8 @@ export async function POST(request: NextRequest) { // Initialize R2 client const client = new AwsClient({ - accessKeyId: env.R2_ACCESS_KEY_ID, - secretAccessKey: env.R2_SECRET_ACCESS_KEY, + accessKeyId: env.R2_ACCESS_KEY_ID!, + secretAccessKey: env.R2_SECRET_ACCESS_KEY!, }); // Generate unique filename with timestamp @@ -80,7 +80,7 @@ export async function POST(request: NextRequest) { // Create presigned URL const url = new URL( - `https://${env.R2_BUCKET_NAME}.${env.CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com/${fileName}` + `https://${env.R2_BUCKET_NAME!}.${env.CLOUDFLARE_ACCOUNT_ID!}.r2.cloudflarestorage.com/${fileName}` ); url.searchParams.set("X-Amz-Expires", "3600"); // 1 hour expiry diff --git a/apps/web/src/app/api/sounds/search/route.ts b/apps/web/src/app/api/sounds/search/route.ts index 632107169..d00d5755b 100644 --- a/apps/web/src/app/api/sounds/search/route.ts +++ b/apps/web/src/app/api/sounds/search/route.ts @@ -168,7 +168,7 @@ export async function GET(request: NextRequest) { const params = new URLSearchParams({ query: query || "", - token: env.FREESOUND_API_KEY, + token: env.FREESOUND_API_KEY!, page: page.toString(), page_size: pageSize.toString(), sort: sortParam, diff --git a/apps/web/src/app/api/transcribe/route.ts b/apps/web/src/app/api/transcribe/route.ts index 9a497f65e..fdb91863b 100644 --- a/apps/web/src/app/api/transcribe/route.ts +++ b/apps/web/src/app/api/transcribe/route.ts @@ -112,7 +112,7 @@ export async function POST(request: NextRequest) { } // Call Modal transcription service - const response = await fetch(env.MODAL_TRANSCRIPTION_URL, { + const response = await fetch(env.MODAL_TRANSCRIPTION_URL!, { method: "POST", headers: { "Content-Type": "application/json", From 03480604cda4796eed448d419095247093e8912b Mon Sep 17 00:00:00 2001 From: Justin322322 Date: Tue, 12 Aug 2025 13:01:45 +0800 Subject: [PATCH 4/5] web: improve security and logging in sounds search API - Remove sensitive environment variable names from client error responses - Simplify error message to 'Sound search is not configured' for production - Keep detailed logging server-side for operational visibility - Conditionally include missingVars in response only for non-production environments - Prevents information disclosure while maintaining developer debugging capability Security improvement: Production clients no longer see internal configuration details --- apps/web/src/app/api/sounds/search/route.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/web/src/app/api/sounds/search/route.ts b/apps/web/src/app/api/sounds/search/route.ts index d00d5755b..8767eed96 100644 --- a/apps/web/src/app/api/sounds/search/route.ts +++ b/apps/web/src/app/api/sounds/search/route.ts @@ -101,15 +101,19 @@ export async function GET(request: NextRequest) { // Check Freesound configuration const freesoundCheck = isFreesoundConfigured(); if (!freesoundCheck.configured) { + // Log missing variables server-side only console.error( - "Missing environment variables:", - JSON.stringify(freesoundCheck.missingVars) + "Sound search not configured - missing environment variables:", + freesoundCheck.missingVars ); return NextResponse.json( { error: "Sound search not configured", - message: `Sound search requires environment variables: ${freesoundCheck.missingVars.join(", ")}. Check README for setup instructions.`, + message: "Sound search is not configured", + ...(env.NODE_ENV !== "production" && { + missingVars: freesoundCheck.missingVars, + }), }, { status: 503 } ); From fb5de2f16d280e1a8945c86ae855ca4c6554031f Mon Sep 17 00:00:00 2001 From: Justin322322 Date: Tue, 12 Aug 2025 13:04:18 +0800 Subject: [PATCH 5/5] web: fix Next.js Image dimensions in Hero component - Replace invalid float height (1903.5) with proper fill prop - Add relative positioning to container for proper image anchoring - Use fill + sizes='100vw' for optimized full-bleed background - Remove redundant size-full class (covered by fill) - Maintains responsive behavior and all existing styling Fixes invalid HTML attributes and improves image loading performance --- apps/web/src/components/landing/hero.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/web/src/components/landing/hero.tsx b/apps/web/src/components/landing/hero.tsx index 7657ed15b..0e327fd7e 100644 --- a/apps/web/src/components/landing/hero.tsx +++ b/apps/web/src/components/landing/hero.tsx @@ -14,13 +14,13 @@ import { useState } from "react"; export function Hero() { const [bgError, setBgError] = useState(false); return ( -
+
{!bgError && ( Landing page background setBgError(true)} priority