2025-07-13 16:42:15 +00:00
|
|
|
import dotenv from "dotenv";
|
2025-11-04 09:53:31 +00:00
|
|
|
import type { Secrets } from "./secrets.js";
|
2025-07-13 16:42:15 +00:00
|
|
|
|
2025-07-03 19:42:49 +00:00
|
|
|
export interface AppConfig {
|
2025-07-14 14:59:17 +00:00
|
|
|
NODE_ENV: "development" | "production" | "test" | "staging";
|
2025-07-03 19:42:49 +00:00
|
|
|
PORT: number;
|
|
|
|
|
SUPABASE_URL: string;
|
|
|
|
|
SUPABASE_SERVICE_ROLE_KEY: string;
|
2025-07-29 19:24:12 +00:00
|
|
|
SUPABASE_CONNECTION_STRING: string;
|
2025-11-03 08:47:10 +00:00
|
|
|
SUPABASE_CA_CERT: string;
|
|
|
|
|
STRIPE_SECRET_KEY: string;
|
|
|
|
|
STRIPE_WEBHOOK_SECRET: string;
|
2026-03-16 07:32:31 +00:00
|
|
|
STRIPE_SOLO_PRICE_ID: string;
|
2026-03-16 07:44:33 +00:00
|
|
|
STRIPE_TEAM_PRICE_ID: string;
|
2026-03-16 07:32:31 +00:00
|
|
|
STRIPE_FOUNDER_PRICE_ID: string;
|
2025-07-03 19:42:49 +00:00
|
|
|
EMAIL_USER: string;
|
2025-10-05 08:45:39 +00:00
|
|
|
EMAIL_CLIENT_ID: string;
|
|
|
|
|
EMAIL_CLIENT_SECRET: string;
|
|
|
|
|
EMAIL_REFRESH_TOKEN: string;
|
2026-05-01 08:11:08 +00:00
|
|
|
API_BASE_URL: string;
|
2025-07-03 19:42:49 +00:00
|
|
|
XTABLO_URL: string;
|
2025-07-29 19:24:12 +00:00
|
|
|
R2_ACCOUNT_ID: string;
|
|
|
|
|
R2_ACCESS_KEY_ID: string;
|
|
|
|
|
R2_SECRET_ACCESS_KEY: string;
|
2025-07-03 19:42:49 +00:00
|
|
|
LOG_LEVEL: "debug" | "info" | "warn" | "error";
|
2025-10-28 17:04:16 +00:00
|
|
|
TASKS_SECRET: string;
|
2026-04-24 13:31:34 +00:00
|
|
|
ADMIN_TOKEN_SIGNING_SECRET: string;
|
|
|
|
|
ADMIN_TOKEN_AUDIENCE: string;
|
|
|
|
|
ADMIN_APP_URL: string;
|
2026-04-30 16:37:29 +00:00
|
|
|
CLIENT_AUTH_JWT_SECRET: string;
|
|
|
|
|
CLIENT_AUTH_COOKIE_NAME: string;
|
|
|
|
|
CLIENT_AUTH_COOKIE_DOMAIN: string;
|
|
|
|
|
CLIENT_MAGIC_LINK_TTL_MINUTES: number;
|
|
|
|
|
CLIENT_SESSION_TTL_DAYS: number;
|
|
|
|
|
CLIENTS_URL: string;
|
2025-11-13 08:24:23 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test user
|
|
|
|
|
*/
|
|
|
|
|
TEST_USER_DATA: {
|
|
|
|
|
id: string;
|
|
|
|
|
email: string;
|
|
|
|
|
user_metadata: Record<string, unknown>;
|
|
|
|
|
app_metadata: Record<string, unknown>;
|
|
|
|
|
aud: string;
|
|
|
|
|
created_at: string;
|
|
|
|
|
};
|
2025-07-03 19:42:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function validateEnvVar(name: string, value: string | undefined): string {
|
|
|
|
|
if (!value) {
|
|
|
|
|
throw new Error(`Missing required environment variable: ${name}`);
|
|
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-10 07:52:47 +00:00
|
|
|
export function createConfig(secrets?: Secrets): AppConfig {
|
2025-07-03 19:42:49 +00:00
|
|
|
const NODE_ENV = (process.env.NODE_ENV || "development") as
|
|
|
|
|
| "development"
|
|
|
|
|
| "production"
|
2025-11-10 07:52:47 +00:00
|
|
|
| "staging"
|
|
|
|
|
| "test";
|
2025-07-03 19:42:49 +00:00
|
|
|
|
2025-09-23 20:22:33 +00:00
|
|
|
dotenv.config({ path: `.env.${NODE_ENV}` });
|
|
|
|
|
|
2025-11-10 07:52:47 +00:00
|
|
|
// In test mode, use environment variables directly instead of secrets
|
|
|
|
|
const isTestMode = NODE_ENV === "test";
|
|
|
|
|
|
2025-11-25 07:48:26 +00:00
|
|
|
const isStagingMode = NODE_ENV === "staging";
|
2025-11-25 07:53:38 +00:00
|
|
|
const getStripeSecretKey = (isStagingMode: boolean) =>
|
|
|
|
|
isStagingMode ? secrets!.stripeSecretKeyStaging : secrets!.stripeSecretKey;
|
|
|
|
|
const getStripeWebhookSecret = (isStagingMode: boolean) =>
|
|
|
|
|
isStagingMode ? secrets!.stripeWebhookSecretStaging : secrets!.stripeWebhookSecret;
|
2026-03-16 07:32:41 +00:00
|
|
|
const getStripeSecretKeyFromEnv = () => process.env.STRIPE_SECRET_KEY;
|
|
|
|
|
const getStripeWebhookSecretFromEnv = () => process.env.STRIPE_WEBHOOK_SECRET;
|
2025-11-25 07:48:26 +00:00
|
|
|
|
2025-07-03 19:42:49 +00:00
|
|
|
// Base configuration
|
|
|
|
|
const baseConfig: AppConfig = {
|
|
|
|
|
NODE_ENV,
|
|
|
|
|
PORT: parseInt(process.env.PORT || "8080", 10),
|
|
|
|
|
SUPABASE_URL: validateEnvVar("SUPABASE_URL", process.env.SUPABASE_URL),
|
2025-11-10 07:52:47 +00:00
|
|
|
SUPABASE_SERVICE_ROLE_KEY: isTestMode
|
|
|
|
|
? validateEnvVar("SUPABASE_SERVICE_ROLE_KEY", process.env.SUPABASE_SERVICE_ROLE_KEY)
|
|
|
|
|
: secrets!.supabaseServiceRoleKey,
|
|
|
|
|
SUPABASE_CONNECTION_STRING: isTestMode
|
|
|
|
|
? validateEnvVar("SUPABASE_CONNECTION_STRING", process.env.SUPABASE_CONNECTION_STRING)
|
|
|
|
|
: secrets!.supabaseConnectionString,
|
|
|
|
|
SUPABASE_CA_CERT: isTestMode
|
|
|
|
|
? validateEnvVar("SUPABASE_CA_CERT", process.env.SUPABASE_CA_CERT)
|
|
|
|
|
: secrets!.supabaseCaCert,
|
|
|
|
|
STRIPE_SECRET_KEY: isTestMode
|
|
|
|
|
? validateEnvVar("STRIPE_SECRET_KEY", process.env.STRIPE_SECRET_KEY)
|
2026-03-16 07:32:41 +00:00
|
|
|
: getStripeSecretKeyFromEnv() || getStripeSecretKey(isStagingMode),
|
2025-11-10 07:52:47 +00:00
|
|
|
STRIPE_WEBHOOK_SECRET: isTestMode
|
|
|
|
|
? validateEnvVar("STRIPE_WEBHOOK_SECRET", process.env.STRIPE_WEBHOOK_SECRET)
|
2026-03-16 07:32:41 +00:00
|
|
|
: getStripeWebhookSecretFromEnv() || getStripeWebhookSecret(isStagingMode),
|
2026-03-16 07:41:02 +00:00
|
|
|
STRIPE_SOLO_PRICE_ID: validateEnvVar("STRIPE_SOLO_PRICE_ID", process.env.STRIPE_SOLO_PRICE_ID),
|
2026-04-24 13:31:34 +00:00
|
|
|
STRIPE_TEAM_PRICE_ID: validateEnvVar("STRIPE_TEAM_PRICE_ID", process.env.STRIPE_TEAM_PRICE_ID),
|
2026-03-16 07:32:41 +00:00
|
|
|
STRIPE_FOUNDER_PRICE_ID: validateEnvVar(
|
|
|
|
|
"STRIPE_FOUNDER_PRICE_ID",
|
|
|
|
|
process.env.STRIPE_FOUNDER_PRICE_ID
|
|
|
|
|
),
|
2025-07-03 19:42:49 +00:00
|
|
|
EMAIL_USER: validateEnvVar("EMAIL_USER", process.env.EMAIL_USER),
|
2025-10-24 06:39:16 +00:00
|
|
|
EMAIL_CLIENT_ID: validateEnvVar("EMAIL_CLIENT_ID", process.env.EMAIL_CLIENT_ID),
|
2025-11-10 07:52:47 +00:00
|
|
|
EMAIL_CLIENT_SECRET: isTestMode
|
|
|
|
|
? validateEnvVar("EMAIL_CLIENT_SECRET", process.env.EMAIL_CLIENT_SECRET)
|
|
|
|
|
: secrets!.emailClientSecret,
|
|
|
|
|
EMAIL_REFRESH_TOKEN: isTestMode
|
|
|
|
|
? validateEnvVar("EMAIL_REFRESH_TOKEN", process.env.EMAIL_REFRESH_TOKEN)
|
|
|
|
|
: secrets!.emailRefreshToken,
|
2026-05-01 08:11:08 +00:00
|
|
|
API_BASE_URL: process.env.API_BASE_URL || `http://localhost:${process.env.PORT || "8080"}/api/v1`,
|
2025-07-13 07:54:46 +00:00
|
|
|
XTABLO_URL: process.env.XTABLO_URL || "https://app.xtablo.com",
|
2025-07-29 19:24:12 +00:00
|
|
|
R2_ACCOUNT_ID: validateEnvVar("R2_ACCOUNT_ID", process.env.R2_ACCOUNT_ID),
|
2025-11-10 07:52:47 +00:00
|
|
|
R2_ACCESS_KEY_ID: isTestMode
|
|
|
|
|
? validateEnvVar("R2_ACCESS_KEY_ID", process.env.R2_ACCESS_KEY_ID)
|
|
|
|
|
: secrets!.r2AccessKeyId,
|
|
|
|
|
R2_SECRET_ACCESS_KEY: isTestMode
|
|
|
|
|
? validateEnvVar("R2_SECRET_ACCESS_KEY", process.env.R2_SECRET_ACCESS_KEY)
|
|
|
|
|
: secrets!.r2SecretAccessKey,
|
2025-10-28 17:04:16 +00:00
|
|
|
TASKS_SECRET: process.env.TASKS_SECRET || "",
|
2026-04-24 13:31:34 +00:00
|
|
|
ADMIN_TOKEN_SIGNING_SECRET: isTestMode
|
|
|
|
|
? validateEnvVar("ADMIN_TOKEN_SIGNING_SECRET", process.env.ADMIN_TOKEN_SIGNING_SECRET)
|
|
|
|
|
: secrets!.adminTokenSigningSecret,
|
|
|
|
|
ADMIN_TOKEN_AUDIENCE: process.env.ADMIN_TOKEN_AUDIENCE || "xtablo-admin",
|
|
|
|
|
ADMIN_APP_URL: process.env.ADMIN_APP_URL || "http://localhost:5176",
|
2026-04-30 16:37:29 +00:00
|
|
|
CLIENT_AUTH_JWT_SECRET:
|
|
|
|
|
process.env.CLIENT_AUTH_JWT_SECRET ||
|
|
|
|
|
process.env.ADMIN_TOKEN_SIGNING_SECRET ||
|
|
|
|
|
"client-auth-local-secret",
|
|
|
|
|
CLIENT_AUTH_COOKIE_NAME: process.env.CLIENT_AUTH_COOKIE_NAME || "xtablo_client_session",
|
|
|
|
|
CLIENT_AUTH_COOKIE_DOMAIN: process.env.CLIENT_AUTH_COOKIE_DOMAIN || "clients.xtablo.com",
|
|
|
|
|
CLIENT_MAGIC_LINK_TTL_MINUTES: parseInt(process.env.CLIENT_MAGIC_LINK_TTL_MINUTES || "30", 10),
|
|
|
|
|
CLIENT_SESSION_TTL_DAYS: parseInt(process.env.CLIENT_SESSION_TTL_DAYS || "7", 10),
|
|
|
|
|
CLIENTS_URL: process.env.CLIENTS_URL || "https://clients.xtablo.com",
|
2025-07-03 19:42:49 +00:00
|
|
|
LOG_LEVEL: "info",
|
2025-11-13 08:24:23 +00:00
|
|
|
TEST_USER_DATA: {
|
|
|
|
|
id: "test",
|
|
|
|
|
email: "test@test.com",
|
|
|
|
|
user_metadata: {},
|
|
|
|
|
app_metadata: {},
|
|
|
|
|
aud: "test",
|
|
|
|
|
created_at: new Date("2025-01-01").toISOString(),
|
|
|
|
|
},
|
2025-07-03 19:42:49 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Environment-specific configurations
|
|
|
|
|
if (NODE_ENV === "development") {
|
|
|
|
|
baseConfig.LOG_LEVEL = "debug";
|
|
|
|
|
} else if (NODE_ENV === "production") {
|
|
|
|
|
baseConfig.LOG_LEVEL = "info";
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-04 09:53:31 +00:00
|
|
|
console.log("✓ Configuration loaded successfully");
|
2025-07-13 07:54:46 +00:00
|
|
|
|
2025-07-03 19:42:49 +00:00
|
|
|
return baseConfig;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper functions for common environment checks
|
2025-11-04 09:53:31 +00:00
|
|
|
// export const isDevelopment = () => config.NODE_ENV === "development";
|
|
|
|
|
// export const isProduction = () => config.NODE_ENV === "production";
|