Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 31 additions & 37 deletions frontend/db/index.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
import { neon } from '@neondatabase/serverless';
import * as dotenv from 'dotenv';
import { drizzle as drizzleNeon } from 'drizzle-orm/neon-http';
import { drizzle as drizzlePg } from 'drizzle-orm/node-postgres';
import type { PgDatabase, PgQueryResultHKT } from 'drizzle-orm/pg-core';
import { Pool } from 'pg';

import { readServerEnv } from '@/lib/env/server-env';

import * as schema from './schema';

dotenv.config();

type AppDatabase = PgDatabase<PgQueryResultHKT, typeof schema>;

const APP_ENV = process.env.APP_ENV?.trim().toLowerCase();
const APP_ENV = readServerEnv('APP_ENV')?.toLowerCase();

const DATABASE_URL = readServerEnv('DATABASE_URL');
const DATABASE_URL_LOCAL = readServerEnv('DATABASE_URL_LOCAL');
const IS_LOCAL_ENV = APP_ENV === 'local';

const STRICT_LOCAL_DB_GUARD = readServerEnv('SHOP_STRICT_LOCAL_DB') === '1';
const REQUIRED_LOCAL_DB_URL = readServerEnv('SHOP_REQUIRED_DATABASE_URL_LOCAL');

if (process.env.NODE_ENV !== 'test') {
console.log('[db] runtime env check', {
APP_ENV: process.env.APP_ENV ?? '<undefined>',
has_DATABASE_URL: Boolean(process.env.DATABASE_URL?.trim()),
has_DATABASE_URL_LOCAL: Boolean(process.env.DATABASE_URL_LOCAL?.trim()),
NETLIFY: process.env.NETLIFY ?? '<undefined>',
CONTEXT: process.env.CONTEXT ?? '<undefined>',
APP_ENV: APP_ENV ?? '<undefined>',
has_DATABASE_URL: Boolean(DATABASE_URL),
has_DATABASE_URL_LOCAL: Boolean(DATABASE_URL_LOCAL),
NETLIFY: readServerEnv('NETLIFY') ?? '<undefined>',
CONTEXT: readServerEnv('CONTEXT') ?? '<undefined>',
NODE_ENV: process.env.NODE_ENV ?? '<undefined>',
});
}

const IS_LOCAL_ENV = APP_ENV === 'local';

const STRICT_LOCAL_DB_GUARD = process.env.SHOP_STRICT_LOCAL_DB === '1';
const REQUIRED_LOCAL_DB_URL = process.env.SHOP_REQUIRED_DATABASE_URL_LOCAL;

if (STRICT_LOCAL_DB_GUARD) {
if (!IS_LOCAL_ENV) {
Expand All @@ -36,21 +39,21 @@ if (STRICT_LOCAL_DB_GUARD) {
);
}

if (!process.env.DATABASE_URL_LOCAL?.trim()) {
if (!DATABASE_URL_LOCAL) {
throw new Error(
'[db] SHOP_STRICT_LOCAL_DB=1 requires DATABASE_URL_LOCAL to be set'
);
}

if (process.env.DATABASE_URL?.trim()) {
if (DATABASE_URL) {
throw new Error(
'[db] SHOP_STRICT_LOCAL_DB=1 forbids DATABASE_URL during shop-local tests'
);
}

if (
REQUIRED_LOCAL_DB_URL &&
process.env.DATABASE_URL_LOCAL !== REQUIRED_LOCAL_DB_URL
DATABASE_URL_LOCAL !== REQUIRED_LOCAL_DB_URL
) {
throw new Error(
'[db] SHOP_STRICT_LOCAL_DB=1 requires DATABASE_URL_LOCAL to match SHOP_REQUIRED_DATABASE_URL_LOCAL exactly'
Expand All @@ -60,38 +63,29 @@ if (STRICT_LOCAL_DB_GUARD) {

let db: AppDatabase;

if (IS_LOCAL_ENV) {
const url = process.env.DATABASE_URL_LOCAL?.trim();
if (DATABASE_URL) {
const sql = neon(DATABASE_URL);
db = drizzleNeon(sql, { schema });

if (!url) {
if (process.env.NODE_ENV !== 'test') {
console.log('[db] using production database (neon http)');
}
} else if (IS_LOCAL_ENV) {
if (!DATABASE_URL_LOCAL) {
throw new Error('[db] APP_ENV=local requires DATABASE_URL_LOCAL to be set');
}

const pool = new Pool({
connectionString: url,
});

const pool = new Pool({ connectionString: DATABASE_URL_LOCAL });
db = drizzlePg(pool, { schema });

if (process.env.NODE_ENV !== 'test') {
console.log('[db] using local PostgreSQL (pg)');
}
} else {
const url = process.env.DATABASE_URL?.trim();

if (!url) {
throw new Error(
`[db] APP_ENV=${APP_ENV} requires DATABASE_URL to be set`
);
}

const sql = neon(url);

db = drizzleNeon(sql, { schema });

if (process.env.NODE_ENV !== 'test') {
console.log('[db] using production database (neon http)');
}
throw new Error(
`[db] no usable database configuration found (APP_ENV=${APP_ENV ?? 'undefined'})`
);
}


export { db };
56 changes: 35 additions & 21 deletions frontend/lib/env/auth.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
import * as dotenv from 'dotenv';
dotenv.config();
import { readServerEnv } from './server-env';

type AppEnv = 'local' | 'develop' | 'production';

const validAppEnvs = ['local', 'develop', 'production'];
const rawAppEnv = process.env.APP_ENV;
const validAppEnvs: AppEnv[] = ['local', 'develop', 'production'];

if (!rawAppEnv) {
throw new Error('APP_ENV is not defined');
}
const rawAppEnv = readServerEnv('APP_ENV')?.toLowerCase();
const context = readServerEnv('CONTEXT')?.toLowerCase();

const inferredAppEnv: AppEnv | undefined =
context === 'production'
? 'production'
: context
? 'develop'
: undefined;

const resolvedAppEnv = (rawAppEnv ?? inferredAppEnv) as AppEnv | undefined;

if (!validAppEnvs.includes(rawAppEnv as AppEnv)) {
if (!resolvedAppEnv || !validAppEnvs.includes(resolvedAppEnv)) {
throw new Error(
`Invalid APP_ENV: ${rawAppEnv}. Must be one of: ${validAppEnvs.join(', ')}`
`Invalid APP_ENV: ${rawAppEnv ?? '<undefined>'}. Must be one of: ${validAppEnvs.join(', ')}`
);
}

const APP_ENV = rawAppEnv as AppEnv;
const APP_ENV: AppEnv = resolvedAppEnv;

function requireEnv(name: string): string {
const value = process.env[name];
const value = readServerEnv(name);
if (!value) {
throw new Error(`Missing env var: ${name}`);
}
Expand All @@ -29,16 +35,24 @@ function requireEnv(name: string): string {
export const authEnv = {
appEnv: APP_ENV,

google: {
clientId: requireEnv('GOOGLE_CLIENT_ID'),
clientSecret: requireEnv('GOOGLE_CLIENT_SECRET'),
redirectUri:
APP_ENV === 'local'
? requireEnv('GOOGLE_CLIENT_REDIRECT_URI_LOCAL')
: APP_ENV === 'develop'
? requireEnv('GOOGLE_CLIENT_REDIRECT_URI_DEVELOP')
: requireEnv('GOOGLE_CLIENT_REDIRECT_URI_PROD'),
},
google:
APP_ENV === 'local'
? {
clientId: requireEnv('GOOGLE_CLIENT_ID_LOCAL'),
clientSecret: requireEnv('GOOGLE_CLIENT_SECRET_LOCAL'),
redirectUri: requireEnv('GOOGLE_CLIENT_REDIRECT_URI_LOCAL'),
}
: APP_ENV === 'develop'
? {
clientId: requireEnv('GOOGLE_CLIENT_ID_DEVELOP'),
clientSecret: requireEnv('GOOGLE_CLIENT_SECRET_DEVELOP'),
redirectUri: requireEnv('GOOGLE_CLIENT_REDIRECT_URI_DEVELOP'),
}
: {
clientId: requireEnv('GOOGLE_CLIENT_ID_PROD'),
clientSecret: requireEnv('GOOGLE_CLIENT_SECRET_PROD'),
redirectUri: requireEnv('GOOGLE_CLIENT_REDIRECT_URI_PROD'),
},

github:
APP_ENV === 'local'
Expand Down
17 changes: 17 additions & 0 deletions frontend/lib/env/server-env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import 'server-only';

type NetlifyEnv = {
get?: (key: string) => string | undefined;
};

function readFromNetlifyEnv(key: string): string | undefined {
const maybeNetlify = (globalThis as { Netlify?: { env?: NetlifyEnv } }).Netlify;
const value = maybeNetlify?.env?.get?.(key);
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
}

export function readServerEnv(key: string): string | undefined {
const fromProcess = process.env[key]?.trim();
if (fromProcess) return fromProcess;
return readFromNetlifyEnv(key);
}
8 changes: 4 additions & 4 deletions frontend/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import { routing } from './i18n/routing';

const AUTH_COOKIE_NAME = 'auth_session';

const AUTH_SECRET = process.env.AUTH_SECRET;
if (!AUTH_SECRET) {
throw new Error('AUTH_SECRET is not defined');
}
// const AUTH_SECRET = process.env.AUTH_SECRET;
// if (!AUTH_SECRET) {
// throw new Error('AUTH_SECRET is not defined');
// }

function decodeAuthToken(token: string): AuthTokenPayload | null {
const parts = token.split('.');
Expand Down
7 changes: 0 additions & 7 deletions frontend/scripts/generate-env.sh

This file was deleted.

2 changes: 1 addition & 1 deletion netlify.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[build]
base = "frontend"
command = "bash scripts/generate-env.sh && npm ci --include=optional && npm run build"
command = "npm ci --include=optional && npm run build"
publish = ".next"

[build.environment]
Expand Down
Loading