From 196670bf4b93cb0946dc548898c16f771a70918d Mon Sep 17 00:00:00 2001 From: Mohammad Kermani Date: Fri, 21 Mar 2025 20:13:22 +0000 Subject: [PATCH] refactor: move all configs to a validated configs module --- app/(auth)/adapter.ts | 12 ++++++------ app/(auth)/auth.config.ts | 13 ++++++++----- app/(auth)/auth.ts | 18 +++++------------- app/(auth)/service.ts | 7 +------ app/(chat)/adapter.ts | 8 ++++---- app/config/index.tsx | 8 +++++--- app/context/index.tsx | 14 ++++++-------- config/BooleanString.ts | 13 +++++++++++++ config/NonEmptyString.ts | 10 ++++++++++ config/PatternCoreEndpoint.ts | 15 +++++++++++++++ config/config-client-only.ts | 16 ++++++++++++++++ config/config.ts | 25 +++++++++++++++++++++++++ config/index.ts | 1 + lib/ai/pattern-model.ts | 5 ----- 14 files changed, 115 insertions(+), 50 deletions(-) create mode 100644 config/BooleanString.ts create mode 100644 config/NonEmptyString.ts create mode 100644 config/PatternCoreEndpoint.ts create mode 100644 config/config-client-only.ts create mode 100644 config/config.ts create mode 100644 config/index.ts diff --git a/app/(auth)/adapter.ts b/app/(auth)/adapter.ts index 9b39e01..85e9d40 100644 --- a/app/(auth)/adapter.ts +++ b/app/(auth)/adapter.ts @@ -1,18 +1,18 @@ -import { Ok, Err, Result } from 'ts-results-es'; +import { Ok, Err, type Result } from 'ts-results-es'; +import config from '@/config'; import { extractErrorMessageOrDefault } from '@/lib/utils'; -import { +import type { ApiCreateProjectResponse, ApiCreateWorkspaceResponse, ApiListAllProjectsResponse, ApiListAllWorkspacesResponse, } from './types'; -const patternCoreEndpoint = process.env.PATTERN_CORE_ENDPOINT; -if (!patternCoreEndpoint) { - throw new Error('PATTERN_CORE_ENDPOINT is not set'); -} +const { + patternCoreEndpoint: { value: patternCoreEndpoint }, +} = config; /** * Get all workspaces diff --git a/app/(auth)/auth.config.ts b/app/(auth)/auth.config.ts index 695c1c8..1f060ac 100644 --- a/app/(auth)/auth.config.ts +++ b/app/(auth)/auth.config.ts @@ -1,6 +1,8 @@ -import { type SIWESession } from '@reown/appkit-siwe'; +import type { SIWESession } from '@reown/appkit-siwe'; import type { NextAuthConfig } from 'next-auth'; +import config from '@/config'; + import { fetchSessionPrerequisites } from './service'; declare module 'next-auth' { @@ -22,10 +24,11 @@ declare module 'next-auth/jwt' { } } -export const nextAuthSecret = process.env.NEXTAUTH_SECRET; -if (!nextAuthSecret) { - throw new Error('NEXTAUTH_SECRET is not set'); -} +const { + nextAuth: { + secret: { value: nextAuthSecret }, + }, +} = config; export const authConfig = { secret: nextAuthSecret, diff --git a/app/(auth)/auth.ts b/app/(auth)/auth.ts index 7e5d089..699dd3c 100644 --- a/app/(auth)/auth.ts +++ b/app/(auth)/auth.ts @@ -6,22 +6,14 @@ import NextAuth from 'next-auth'; import 'next-auth/jwt'; import credentialsProvider from 'next-auth/providers/credentials'; +import config from '@/config'; + import { authConfig } from './auth.config'; -/** - * TODO: Move all configs into a validated configs module to avoid duplication - * - * https://github.com/pattern-tech/pattern-app/issues/3 - */ -export const nextAuthSecret = process.env.NEXTAUTH_SECRET; -if (!nextAuthSecret) { - throw new Error('NEXTAUTH_SECRET is not set'); -} +const { + patternCoreEndpoint: { value: patternCoreEndpoint }, +} = config; -const patternCoreEndpoint = process.env.PATTERN_CORE_ENDPOINT; -if (!patternCoreEndpoint) { - throw new Error('PATTERN_CORE_ENDPOINT is not set'); -} const siweVerificationApi = `${patternCoreEndpoint}/auth/verify`; const providers = [ diff --git a/app/(auth)/service.ts b/app/(auth)/service.ts index 58ddbf8..cbc006c 100644 --- a/app/(auth)/service.ts +++ b/app/(auth)/service.ts @@ -1,4 +1,4 @@ -import { Err, Ok, Result } from 'ts-results-es'; +import { Err, Ok, type Result } from 'ts-results-es'; import { createProjectInWorkspace, @@ -7,11 +7,6 @@ import { getAllWorkspaces, } from './adapter'; -const patternCoreEndpoint = process.env.PATTERN_CORE_ENDPOINT; -if (!patternCoreEndpoint) { - throw new Error('PATTERN_CORE_ENDPOINT is not set'); -} - /** * Checks if the default workspace and project exist, if not, creates them * @param accessToken diff --git a/app/(chat)/adapter.ts b/app/(chat)/adapter.ts index da5b1ba..f03a530 100644 --- a/app/(chat)/adapter.ts +++ b/app/(chat)/adapter.ts @@ -9,12 +9,12 @@ import type { ApiGetAllConversationsResponse, ApiRenameConversationResponse, } from '@/app/(chat)/types'; +import config from '@/config'; import { extractErrorMessageOrDefault } from '@/lib/utils'; -const patternCoreEndpoint = process.env.PATTERN_CORE_ENDPOINT; -if (!patternCoreEndpoint) { - throw new Error('PATTERN_CORE_ENDPOINT is not set'); -} +const { + patternCoreEndpoint: { value: patternCoreEndpoint }, +} = config; /** * Get a conversation diff --git a/app/config/index.tsx b/app/config/index.tsx index 89d626c..c01a021 100644 --- a/app/config/index.tsx +++ b/app/config/index.tsx @@ -16,9 +16,11 @@ import { import { getCsrfToken, getSession, signIn, signOut } from 'next-auth/react'; import { getAddress } from 'viem'; -export const walletConnectProjectId = - process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID; -if (!walletConnectProjectId) throw new Error('Project ID is not defined'); +import config from '@/config/config-client-only'; + +export const { + walletConnectProjectId: { value: walletConnectProjectId }, +} = config; export const metadata = { name: 'Pattern', diff --git a/app/context/index.tsx b/app/context/index.tsx index 3ba250a..cec5248 100644 --- a/app/context/index.tsx +++ b/app/context/index.tsx @@ -5,17 +5,15 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import React, { type ReactNode } from 'react'; import { type State, WagmiProvider } from 'wagmi'; -import { - chains, - metadata, - walletConnectProjectId, - siweConfig, - wagmiAdapter, -} from '../config'; +import config from '@/config/config-client-only'; + +import { chains, metadata, siweConfig, wagmiAdapter } from '../config'; const queryClient = new QueryClient(); -if (!walletConnectProjectId) throw new Error('Project ID is not defined'); +const { + walletConnectProjectId: { value: walletConnectProjectId }, +} = config; createAppKit({ adapters: [wagmiAdapter], diff --git a/config/BooleanString.ts b/config/BooleanString.ts new file mode 100644 index 0000000..a3f5a7d --- /dev/null +++ b/config/BooleanString.ts @@ -0,0 +1,13 @@ +export class BooleanString { + private constructor(public value: boolean) {} + + static parse(value: string | undefined): BooleanString { + if (value === 'true') { + return new BooleanString(true); + } + if (value === 'false') { + return new BooleanString(false); + } + throw new Error(`Cannot parse "${value}" as BooleanString`); + } +} diff --git a/config/NonEmptyString.ts b/config/NonEmptyString.ts new file mode 100644 index 0000000..7db2323 --- /dev/null +++ b/config/NonEmptyString.ts @@ -0,0 +1,10 @@ +export class NonEmptyString { + private constructor(public value: string) {} + + static parse(value: string | undefined): NonEmptyString { + if (value) { + return new NonEmptyString(value); + } + throw new Error(`Cannot parse "${value}" as NonEmptyString`); + } +} diff --git a/config/PatternCoreEndpoint.ts b/config/PatternCoreEndpoint.ts new file mode 100644 index 0000000..72b8626 --- /dev/null +++ b/config/PatternCoreEndpoint.ts @@ -0,0 +1,15 @@ +export class PatternCoreEndpoint { + private constructor(public value: string) {} + + static parse(value: string | undefined): PatternCoreEndpoint { + if (!value) { + throw new Error(`Cannot parse "${value}" as PatternCoreEndpoint`); + } + try { + new URL(value); + return new PatternCoreEndpoint(value); + } catch (error) { + throw new Error(`Cannot parse "${value}" as PatternCoreEndpoint`); + } + } +} diff --git a/config/config-client-only.ts b/config/config-client-only.ts new file mode 100644 index 0000000..95f2257 --- /dev/null +++ b/config/config-client-only.ts @@ -0,0 +1,16 @@ +import { NonEmptyString } from './NonEmptyString'; + +interface PatternUiConfigClientOnly { + walletConnectProjectId: NonEmptyString; +} + +const NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID = + process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID; + +const config: PatternUiConfigClientOnly = { + walletConnectProjectId: NonEmptyString.parse( + NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID, + ), +}; + +export default config; diff --git a/config/config.ts b/config/config.ts new file mode 100644 index 0000000..53b30d2 --- /dev/null +++ b/config/config.ts @@ -0,0 +1,25 @@ +import 'server-only'; + +import { BooleanString } from './BooleanString'; +import { NonEmptyString } from './NonEmptyString'; +import { PatternCoreEndpoint } from './PatternCoreEndpoint'; + +interface PatternUiConfig { + nextAuth: { + secret: NonEmptyString; + trustHost: BooleanString; + }; + patternCoreEndpoint: PatternCoreEndpoint; +} + +const { NEXTAUTH_SECRET, PATTERN_CORE_ENDPOINT, AUTH_TRUST_HOST } = process.env; + +const config: PatternUiConfig = { + nextAuth: { + secret: NonEmptyString.parse(NEXTAUTH_SECRET), + trustHost: BooleanString.parse(AUTH_TRUST_HOST), + }, + patternCoreEndpoint: PatternCoreEndpoint.parse(PATTERN_CORE_ENDPOINT), +}; + +export default config; diff --git a/config/index.ts b/config/index.ts new file mode 100644 index 0000000..91af05d --- /dev/null +++ b/config/index.ts @@ -0,0 +1 @@ +export { default } from './config'; diff --git a/lib/ai/pattern-model.ts b/lib/ai/pattern-model.ts index aaa3531..7ce066c 100644 --- a/lib/ai/pattern-model.ts +++ b/lib/ai/pattern-model.ts @@ -13,11 +13,6 @@ import type { import { extractErrorMessageOrDefault } from '../utils'; -export const patternCoreEndpoint = process.env.PATTERN_CORE_ENDPOINT; -if (!patternCoreEndpoint) { - throw new Error('PATTERN_CORE_ENDPOINT is not set'); -} - const textDecoder = new TextDecoder(); export class PatternModel implements LanguageModelV1 {