diff --git a/.envrc.example b/.envrc.example index ca9d912..1565987 100644 --- a/.envrc.example +++ b/.envrc.example @@ -1,7 +1,11 @@ use asdf +# db export SUPABASE_URL= export SUPABASE_ANON_KEY= -export LOCAL_SUPABASE_URL= -export LOCAL_SUPABASE_ANON_KEY= +# server +export ALCHEMY_API_KEY= + +# client +export NEXT_PUBLIC_SERVER_URL= diff --git a/.gitignore b/.gitignore index 8e3f529..add9cf2 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ lcov.info .envrc **/*.bak repopack-output.txt +*.tsbuildinfo diff --git a/.lefthook.yml b/.lefthook.yml index d9b1bb6..dce40f4 100644 --- a/.lefthook.yml +++ b/.lefthook.yml @@ -8,6 +8,11 @@ pre-commit: stage_fixed: true lint: run: bun biome lint --config-path=.biome.jsonc --no-errors-on-unmatched - typecheck: + typecheck-client: glob: "*.{cjs,js,jsx,mjs,ts,tsx}" - run: bun tsc-files --noEmit + root: "apps/client" + run: bun tsc-files --noEmit {staged_files} + typecheck-server: + glob: "*.{cjs,js,jsx,mjs,ts,tsx}" + root: "apps/server" + run: bun tsc-files --noEmit {staged_files} diff --git a/apps/client/src/lib/account-kit.ts b/apps/client/src/lib/account-kit.ts index 43d3338..8fb0bd8 100644 --- a/apps/client/src/lib/account-kit.ts +++ b/apps/client/src/lib/account-kit.ts @@ -1,8 +1,7 @@ import { alchemy, sepolia } from '@account-kit/infra' import { type AlchemyAccountsUIConfig, cookieStorage, createConfig } from '@account-kit/react' -import { url } from 'client/l/trpc' +import config from 'client/l/config' -console.log(url) const uiConfig: AlchemyAccountsUIConfig = { illustrationStyle: 'filled', auth: { @@ -18,6 +17,6 @@ export const alchemyConfig = createConfig({ storage: cookieStorage, transport: alchemy({ // proxying to backend server to hide API key - rpcUrl: `${url}/web3-rpc-proxy`, + rpcUrl: `${config.serverUrl}/${config.alchemyProxyEndpoint}`, }), }, uiConfig) diff --git a/apps/client/src/lib/config.ts b/apps/client/src/lib/config.ts new file mode 100644 index 0000000..5e16ec9 --- /dev/null +++ b/apps/client/src/lib/config.ts @@ -0,0 +1,16 @@ +import { isEnvVarDefined, sharedConfig, type SharedConfigI } from 'config' + +interface ClientConfigI { + serverUrl: string +} + +// @ts-expect-error 4111 +const serverUrl = process.env.NEXT_PUBLIC_SERVER_URL ?? '' +isEnvVarDefined(serverUrl, 'NEXT_PUBLIC_SERVER_URL') + +const clientConfig: SharedConfigI & ClientConfigI = { + ...sharedConfig, + serverUrl, +} + +export default clientConfig diff --git a/apps/client/src/lib/trpc.ts b/apps/client/src/lib/trpc.ts index 803084d..5d22cb9 100644 --- a/apps/client/src/lib/trpc.ts +++ b/apps/client/src/lib/trpc.ts @@ -1,11 +1,7 @@ import { createTRPCProxyClient, httpBatchLink, type HTTPHeaders } from '@trpc/client' +import config from 'client/l/config' import type { AppRouter } from 'server/trpc/trpc.router' -// TODO: make this configurable and/or use env vars -const PORT = 3001 -const PROD_URL = 'https://rideau.fly.dev' -export const url = process.env.NODE_ENV === 'production' ? PROD_URL : `http://localhost:${PORT}` - let headers: HTTPHeaders export const setHeaders = (newHeaders: HTTPHeaders) => { @@ -16,7 +12,7 @@ export const trpc = createTRPCProxyClient({ links: [ httpBatchLink({ headers: () => headers, - url: `${url}/trpc`, + url: `${config.serverUrl}/trpc`, }), ], }) diff --git a/apps/server/Dockerfile b/apps/server/Dockerfile index a986525..1e8caa3 100644 --- a/apps/server/Dockerfile +++ b/apps/server/Dockerfile @@ -14,6 +14,7 @@ RUN yarn install --ignore-scripts --frozen-lockfile --production=false FROM install AS prerelease COPY tsconfig.json . COPY apps/server apps/server +COPY config config RUN yarn workspace server build &&\ find -type d -name node_modules -exec rm -fr {} + &&\ yarn install --frozen-lockfile --production=true @@ -27,4 +28,4 @@ USER node # TODO: make this configurable/overridable EXPOSE 3001 ENTRYPOINT ["dumb-init", "--"] -CMD ["node", "apps/server/dist"] +CMD ["node", "apps/server/dist/apps/server/src/index"] diff --git a/apps/server/config.ts b/apps/server/config.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/server/fly.toml b/apps/server/fly.toml index fc15ad3..2bc28be 100644 --- a/apps/server/fly.toml +++ b/apps/server/fly.toml @@ -4,7 +4,7 @@ # app = 'rideau' -primary_region = 'ams' +primary_region = 'fra' [build] diff --git a/apps/server/nest-cli.json b/apps/server/nest-cli.json index d77c397..0321723 100644 --- a/apps/server/nest-cli.json +++ b/apps/server/nest-cli.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", - "entryFile": "index", + "entryFile": "apps/server/src/index", "compilerOptions": { "deleteOutDir": true } diff --git a/apps/server/package.json b/apps/server/package.json index 9a74b7e..4851d35 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -33,7 +33,7 @@ "@nestjs/testing": "^10.0.0", "@types/express": "^5.0.0", "@types/jest": "^29.5.2", - "@types/node": "^20.3.1", + "@types/node": "^22.7.5", "@types/supertest": "^6.0.0", "eslint": "^8.42.0", "jest": "^29.5.0", diff --git a/apps/server/src/web3-rpc-proxy/web3-rpc-proxy.controller.spec.ts b/apps/server/src/alchemy-proxy/alchemy-proxy.controller.spec.ts similarity index 100% rename from apps/server/src/web3-rpc-proxy/web3-rpc-proxy.controller.spec.ts rename to apps/server/src/alchemy-proxy/alchemy-proxy.controller.spec.ts diff --git a/apps/server/src/web3-rpc-proxy/web3-rpc-proxy.controller.ts b/apps/server/src/alchemy-proxy/alchemy-proxy.controller.ts similarity index 80% rename from apps/server/src/web3-rpc-proxy/web3-rpc-proxy.controller.ts rename to apps/server/src/alchemy-proxy/alchemy-proxy.controller.ts index b182990..e22b85d 100644 --- a/apps/server/src/web3-rpc-proxy/web3-rpc-proxy.controller.ts +++ b/apps/server/src/alchemy-proxy/alchemy-proxy.controller.ts @@ -1,13 +1,13 @@ import { All, Controller, Logger, Req, Res } from '@nestjs/common' import type { Request, Response } from 'express' +import config from 'server/l/config' -const ENDPOINT = 'web3-rpc-proxy' -const endpointRgx = new RegExp(`^/${ENDPOINT}/`) +const endpointRgx = new RegExp(`^/${config.alchemyProxyEndpoint}/`) const API_URL = 'https://api.g.alchemy.com' -@Controller(ENDPOINT) -export class Web3RpcProxyController { - private readonly logger = new Logger(Web3RpcProxyController.name) +@Controller(config.alchemyProxyEndpoint) +export class AlchemyProxyController { + private readonly logger = new Logger(AlchemyProxyController.name) @All('*') async proxy(@Req() req: Request, @Res() res: Response) { @@ -31,7 +31,7 @@ export class Web3RpcProxyController { method, headers: { 'Content-Type': 'application/json', - Authorization: `Bearer ${process.env['ALCHEMY_API_KEY']}`, + Authorization: `Bearer ${config.alchemyApiKey}`, }, body: JSON.stringify(body), }, diff --git a/apps/server/src/app.module.ts b/apps/server/src/app.module.ts index ea8873b..a74ae1a 100644 --- a/apps/server/src/app.module.ts +++ b/apps/server/src/app.module.ts @@ -2,11 +2,11 @@ import { Module } from '@nestjs/common' import { AppController } from 'server/app.controller' import { AppService } from 'server/app.service' import { TrpcModule } from 'server/trpc/trpc.module' -import { Web3RpcProxyController } from './web3-rpc-proxy/web3-rpc-proxy.controller' +import { AlchemyProxyController } from './alchemy-proxy/alchemy-proxy.controller' @Module({ imports: [TrpcModule], - controllers: [AppController, Web3RpcProxyController], + controllers: [AppController, AlchemyProxyController], providers: [AppService], }) export class AppModule {} diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index c1f30e1..791d607 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -1,10 +1,8 @@ import { NestFactory } from '@nestjs/core' import { AppModule } from 'server/app.module' +import config from 'server/l/config' import { TrpcRouter } from 'server/trpc/trpc.router' -// TODO: make port configurable -const PORT = 3001 - async function bootstrap() { const app = await NestFactory.create(AppModule) app.enableCors() @@ -12,7 +10,7 @@ async function bootstrap() { const trpc = app.get(TrpcRouter) trpc.applyMiddleware(app) - await app.listen(PORT) + await app.listen(config.port) } bootstrap() diff --git a/apps/server/src/lib/config.ts b/apps/server/src/lib/config.ts new file mode 100644 index 0000000..66c5343 --- /dev/null +++ b/apps/server/src/lib/config.ts @@ -0,0 +1,19 @@ +import { getEnvVar, sharedConfig, type SharedConfigI } from 'config' + +interface ServerConfigI { + alchemyApiKey: string + port: number + supabase: { anonKey: string; url: string } +} + +const serverConfig: ServerConfigI & SharedConfigI = { + ...sharedConfig, + alchemyApiKey: getEnvVar('ALCHEMY_API_KEY'), + port: 3001, + supabase: { + anonKey: getEnvVar('SUPABASE_ANON_KEY'), + url: getEnvVar('SUPABASE_URL'), + }, +} + +export default serverConfig diff --git a/apps/server/src/supabase/supabase.service.ts b/apps/server/src/supabase/supabase.service.ts index 15e72e4..fdd7867 100644 --- a/apps/server/src/supabase/supabase.service.ts +++ b/apps/server/src/supabase/supabase.service.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common' import { createClient } from '@supabase/supabase-js' +import config from 'server/l/config' @Injectable() export class SupabaseService { - // biome-ignore lint/style/noNonNullAssertion: FIXME - supabase = createClient(process.env['SUPABASE_URL']!, process.env['SUPABASE_ANON_KEY']!) + supabase = createClient(config.supabase.url, config.supabase.anonKey) } diff --git a/bun.lockb b/bun.lockb index b53a275..acbbbf9 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/config/index.ts b/config/index.ts new file mode 100644 index 0000000..cb415f1 --- /dev/null +++ b/config/index.ts @@ -0,0 +1,3 @@ +export { default as sharedConfig } from 'config/shared' +export type { SharedConfigI } from 'config/types' +export { getEnvVar, isEnvVarDefined } from 'config/utils' diff --git a/config/shared.ts b/config/shared.ts new file mode 100644 index 0000000..c964d64 --- /dev/null +++ b/config/shared.ts @@ -0,0 +1,8 @@ +import type { SharedConfigI } from 'config/types' + +const config: SharedConfigI = { + appName: 'rideau', + alchemyProxyEndpoint: 'alchemy', +} + +export default config diff --git a/config/types.ts b/config/types.ts new file mode 100644 index 0000000..02ac9f0 --- /dev/null +++ b/config/types.ts @@ -0,0 +1,9 @@ +export enum Env { + DEVELOPMENT = 'DEVELOPMENT', + PRODUCTION = 'PRODUCTION', +} + +export interface SharedConfigI { + appName: string + alchemyProxyEndpoint: string +} diff --git a/config/utils.ts b/config/utils.ts new file mode 100644 index 0000000..157db6d --- /dev/null +++ b/config/utils.ts @@ -0,0 +1,15 @@ +export function isEnvVarDefined(name: string, value: unknown) { + if (value === '') throw new Error(`Missing environment variable ${name}`) +} + +/** + * DO NOT USE TO CHECK ENVIRONMENT VARIABLES USED CLIENT SIDE IN NEXTJS + * NextJS inline client-side environment variables to make them accessible by the browser + * But dynamic access to environment variables is not inlined + * {@link https://nextjs.org/docs/basic-features/environment-variables#loading-environment-variables} + */ +export function getEnvVar(name: T) { + const value = process.env[name] ?? '' + isEnvVarDefined(name, value) + return value +} diff --git a/tsconfig.json b/tsconfig.json index 6cf712f..db06277 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "noEmit": true, - "baseUrl": "apps", + "baseUrl": ".", "allowJs": true, "checkJs": true, "declaration": true, @@ -20,14 +20,14 @@ "noUnusedLocals": true, "noUnusedParameters": true, "paths": { - "client/*": ["client/src/*"], - "client/c/*": ["client/src/components/*"], - "client/h/*": ["client/src/hooks/*"], - "client/l/*": ["client/src/lib/*"], - "client/p/*": ["client/src/providers/*"], - "client/p": ["client/src/providers"], - "server/*": ["server/src/*"], - "server/l/*": ["server/src/lib/*"] + "client/*": ["apps/client/src/*"], + "client/c/*": ["apps/client/src/components/*"], + "client/h/*": ["apps/client/src/hooks/*"], + "client/l/*": ["apps/client/src/lib/*"], + "client/p/*": ["apps/client/src/providers/*"], + "client/p": ["apps/client/src/providers"], + "server/*": ["apps/server/src/*"], + "server/l/*": ["apps/server/src/lib/*"] }, "removeComments": true, "skipDefaultLibCheck": true, diff --git a/yarn.lock b/yarn.lock index 6ac4064..f0572c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,6 +1,6 @@ # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. # yarn lockfile v1 -# bun ./bun.lockb --hash: FBCD7AF1034998C3-81b4157dbd3990ad-191C33EDCCA73559-4e08cd2f2985cf67 +# bun ./bun.lockb --hash: A7975FC6190E722F-cfa64adf9c1f0f32-47EE7DF9252DAE90-f880c665a4692a26 "@aa-sdk/core@^4.0.0-beta.9": @@ -3862,13 +3862,20 @@ dependencies: undici-types "~5.26.4" -"@types/node@*", "@types/node@^20", "@types/node@^20.3.1": +"@types/node@*", "@types/node@^20": version "20.16.10" resolved "https://registry.npmjs.org/@types/node/-/node-20.16.10.tgz" integrity sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA== dependencies: undici-types "~6.19.2" +"@types/node@^22.7.5": + version "22.7.5" + resolved "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz" + integrity sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ== + dependencies: + undici-types "~6.19.2" + "@types/node-forge@^1.3.0": version "1.3.11" resolved "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz" @@ -10173,7 +10180,7 @@ serve-static@1.16.2, serve-static@^1.13.1: "@nestjs/testing" "^10.0.0" "@types/express" "^5.0.0" "@types/jest" "^29.5.2" - "@types/node" "^20.3.1" + "@types/node" "^22.7.5" "@types/supertest" "^6.0.0" eslint "^8.42.0" jest "^29.5.0"