import { UserModel } from './models/user.ts'; import { Config, OptionalApp } from './types.ts'; export class AppConfig { private static config: Config; private static getDefaultConfig(): Config { return { auth: { baseUrl: 'http://localhost:8000', allowSignups: false, enableEmailVerification: false, enableForeverSignup: true, allowedCookieDomains: [], skipCookieDomainSecurity: false, }, files: { rootPath: 'data-files', }, core: { enabledApps: ['news', 'notes', 'photos', 'expenses'], }, visuals: { title: '', description: '', helpEmail: 'help@bewcloud.com', }, }; } /** This allows for backwards-compatibility with the old config format, which was in the .env file. */ private static async getLegacyConfigFromEnv(): Promise { const defaultConfig = this.getDefaultConfig(); if (typeof Deno === 'undefined') { return defaultConfig; } await import('std/dotenv/load.ts'); const baseUrl = Deno.env.get('BASE_URL') ?? defaultConfig.auth.baseUrl; const allowSignups = Deno.env.get('CONFIG_ALLOW_SIGNUPS') === 'true'; const enabledApps = (Deno.env.get('CONFIG_ENABLED_APPS') ?? '').split(',') as OptionalApp[]; const filesRootPath = Deno.env.get('CONFIG_FILES_ROOT_PATH') ?? defaultConfig.files.rootPath; const enableEmailVerification = (Deno.env.get('CONFIG_ENABLE_EMAILS') ?? 'false') === 'true'; const enableForeverSignup = (Deno.env.get('CONFIG_ENABLE_FOREVER_SIGNUP') ?? 'true') === 'true'; const allowedCookieDomains = (Deno.env.get('CONFIG_ALLOWED_COOKIE_DOMAINS') || '').split(',').filter( Boolean, ) as string[]; const skipCookieDomainSecurity = Deno.env.get('CONFIG_SKIP_COOKIE_DOMAIN_SECURITY') === 'true'; const title = Deno.env.get('CUSTOM_TITLE') ?? defaultConfig.visuals.title; const description = Deno.env.get('CUSTOM_DESCRIPTION') ?? defaultConfig.visuals.description; const helpEmail = Deno.env.get('HELP_EMAIL') ?? defaultConfig.visuals.helpEmail; return { ...defaultConfig, auth: { ...defaultConfig.auth, baseUrl, allowSignups, enableEmailVerification, enableForeverSignup, allowedCookieDomains, skipCookieDomainSecurity, }, files: { ...defaultConfig.files, rootPath: filesRootPath, }, core: { ...defaultConfig.core, enabledApps, }, visuals: { ...defaultConfig.visuals, title, description, helpEmail, }, }; } private static async loadConfig(): Promise { if (this.config) { return; } let initialConfig = this.getDefaultConfig(); if ( typeof Deno.env.get('BASE_URL') === 'string' || typeof Deno.env.get('CONFIG_ALLOW_SIGNUPS') === 'string' || typeof Deno.env.get('CONFIG_ENABLED_APPS') === 'string' ) { console.warn( '\nDEPRECATION WARNING: .env file has config variables. This will be used but is deprecated. Please use the bewcloud.config.ts file instead.', ); initialConfig = await this.getLegacyConfigFromEnv(); } const config: Config = { ...initialConfig, }; try { const configFromFile: Config = (await import(`${Deno.cwd()}/bewcloud.config.ts`)).default; this.config = { ...config, auth: { ...config.auth, ...configFromFile.auth, }, files: { ...config.files, ...configFromFile.files, }, core: { ...config.core, ...configFromFile.core, }, visuals: { ...config.visuals, ...configFromFile.visuals, }, }; console.info('\nConfig loaded from bewcloud.config.ts', JSON.stringify(this.config, null, 2), '\n'); return; } catch (error) { console.error('Error loading config from bewcloud.config.ts. Using default and legacy config instead.', error); } this.config = config; } static async getConfig(): Promise { await this.loadConfig(); return this.config; } static async isSignupAllowed(): Promise { await this.loadConfig(); const areSignupsAllowed = this.config.auth.allowSignups; const areThereAdmins = await UserModel.isThereAnAdmin(); if (areSignupsAllowed || !areThereAdmins) { return true; } return false; } static async isAppEnabled(app: OptionalApp): Promise { await this.loadConfig(); const enabledApps = this.config.core.enabledApps; return enabledApps.includes(app); } static async isCookieDomainAllowed(domain: string): Promise { await this.loadConfig(); const allowedDomains = this.config.auth.allowedCookieDomains; if (allowedDomains.length === 0) { return true; } return allowedDomains.includes(domain); } static async isCookieDomainSecurityDisabled(): Promise { await this.loadConfig(); return this.config.auth.skipCookieDomainSecurity; } static async isEmailVerificationEnabled(): Promise { await this.loadConfig(); return this.config.auth.enableEmailVerification; } static async isForeverSignupEnabled(): Promise { await this.loadConfig(); return this.config.auth.enableForeverSignup; } static async getFilesRootPath(): Promise { await this.loadConfig(); const filesRootPath = `${Deno.cwd()}/${this.config.files.rootPath}`; return filesRootPath; } }