Files
docmost/apps/server/src/integrations/environment/environment.service.ts
T

405 lines
11 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import ms, { StringValue } from 'ms';
@Injectable()
export class EnvironmentService {
constructor(private configService: ConfigService) {}
getNodeEnv(): string {
return this.configService.get<string>('NODE_ENV', 'development');
}
isDevelopment(): boolean {
return this.getNodeEnv() === 'development';
}
getAppUrl(): string {
const rawUrl =
this.configService.get<string>('APP_URL') ||
`http://localhost:${this.getPort()}`;
const { origin } = new URL(rawUrl);
return origin;
}
isHttps(): boolean {
const appUrl = this.configService.get<string>('APP_URL');
try {
const url = new URL(appUrl);
return url.protocol === 'https:';
} catch (error) {
return false;
}
}
getSubdomainHost(): string {
return this.configService.get<string>('SUBDOMAIN_HOST');
}
getPort(): number {
return parseInt(this.configService.get<string>('PORT', '3000'));
}
getAppSecret(): string {
return this.configService.get<string>('APP_SECRET');
}
getDatabaseURL(): string {
return this.configService.get<string>('DATABASE_URL');
}
getDatabaseMaxPool(): number {
return parseInt(this.configService.get<string>('DATABASE_MAX_POOL', '10'));
}
getRedisUrl(): string {
return this.configService.get<string>(
'REDIS_URL',
'redis://localhost:6379',
);
}
getJwtTokenExpiresIn(): string {
return this.configService.get<string>('JWT_TOKEN_EXPIRES_IN', '90d');
}
getCookieExpiresIn(): Date {
const expiresInStr = this.getJwtTokenExpiresIn();
let msUntilExpiry: number;
try {
msUntilExpiry = ms(expiresInStr as StringValue);
} catch (err) {
msUntilExpiry = ms('90d');
}
return new Date(Date.now() + msUntilExpiry);
}
getGotenbergUrl(): string | undefined {
return this.configService.get<string>('GOTENBERG_URL');
}
getStorageDriver(): string {
return this.configService.get<string>('STORAGE_DRIVER', 'local');
}
getFileUploadSizeLimit(): string {
return this.configService.get<string>('FILE_UPLOAD_SIZE_LIMIT', '50mb');
}
getFileImportSizeLimit(): string {
return this.configService.get<string>('FILE_IMPORT_SIZE_LIMIT', '200mb');
}
getAwsS3AccessKeyId(): string {
return this.configService.get<string>('AWS_S3_ACCESS_KEY_ID');
}
getAwsS3SecretAccessKey(): string {
return this.configService.get<string>('AWS_S3_SECRET_ACCESS_KEY');
}
getAwsS3Region(): string {
return this.configService.get<string>('AWS_S3_REGION');
}
getAwsS3Bucket(): string {
return this.configService.get<string>('AWS_S3_BUCKET');
}
getAwsS3Endpoint(): string {
return this.configService.get<string>('AWS_S3_ENDPOINT');
}
getAwsS3ForcePathStyle(): boolean {
return this.configService.get<boolean>('AWS_S3_FORCE_PATH_STYLE');
}
getAwsS3Url(): string {
return this.configService.get<string>('AWS_S3_URL');
}
getMailDriver(): string {
return this.configService.get<string>('MAIL_DRIVER', 'log');
}
getMailFromAddress(): string {
return this.configService.get<string>('MAIL_FROM_ADDRESS');
}
getMailFromName(): string {
return this.configService.get<string>('MAIL_FROM_NAME', 'Docmost');
}
getSmtpHost(): string {
return this.configService.get<string>('SMTP_HOST');
}
getSmtpPort(): number {
return parseInt(this.configService.get<string>('SMTP_PORT'));
}
getSmtpSecure(): boolean {
const secure = this.configService
.get<string>('SMTP_SECURE', 'false')
.toLowerCase();
return secure === 'true';
}
getSmtpIgnoreTLS(): boolean {
const ignoretls = this.configService
.get<string>('SMTP_IGNORETLS', 'false')
.toLowerCase();
return ignoretls === 'true';
}
getSmtpUsername(): string {
return this.configService.get<string>('SMTP_USERNAME');
}
getSmtpPassword(): string {
return this.configService.get<string>('SMTP_PASSWORD');
}
getPostmarkToken(): string {
return this.configService.get<string>('POSTMARK_TOKEN');
}
getDrawioUrl(): string {
return this.configService.get<string>('DRAWIO_URL');
}
isCloud(): boolean {
const cloudConfig = this.configService
.get<string>('CLOUD', 'false')
.toLowerCase();
return cloudConfig === 'true';
}
isSelfHosted(): boolean {
return !this.isCloud();
}
getStripePublishableKey(): string {
return this.configService.get<string>('STRIPE_PUBLISHABLE_KEY');
}
getStripeSecretKey(): string {
return this.configService.get<string>('STRIPE_SECRET_KEY');
}
getStripeWebhookSecret(): string {
return this.configService.get<string>('STRIPE_WEBHOOK_SECRET');
}
getBillingTrialDays(): number {
return parseInt(this.configService.get<string>('BILLING_TRIAL_DAYS', '14'));
}
getCollabUrl(): string {
return this.configService.get<string>('COLLAB_URL');
}
isCollabDisableRedis(): boolean {
const isStandalone = this.configService
.get<string>('COLLAB_DISABLE_REDIS', 'false')
.toLowerCase();
return isStandalone === 'true';
}
isDisableTelemetry(): boolean {
const disable = this.configService
.get<string>('DISABLE_TELEMETRY', 'false')
.toLowerCase();
return disable === 'true';
}
getPostHogHost(): string {
return this.configService.get<string>('POSTHOG_HOST');
}
getPostHogKey(): string {
return this.configService.get<string>('POSTHOG_KEY');
}
getSearchDriver(): string {
return this.configService
.get<string>('SEARCH_DRIVER', 'database')
.toLowerCase();
}
getTypesenseUrl(): string {
return this.configService
.get<string>('TYPESENSE_URL', 'http://localhost:8108')
.toLowerCase();
}
getTypesenseApiKey(): string {
return this.configService.get<string>('TYPESENSE_API_KEY');
}
getTypesenseLocale(): string {
return this.configService
.get<string>('TYPESENSE_LOCALE', 'en')
.toLowerCase();
}
getAiDriver(): string {
return this.configService.get<string>('AI_DRIVER');
}
getAiEmbeddingModel(): string {
return this.configService.get<string>('AI_EMBEDDING_MODEL');
}
getAiCompletionModel(): string {
return this.configService.get<string>('AI_COMPLETION_MODEL');
}
getAiChatModel(): string {
return (
this.configService.get<string>('AI_CHAT_MODEL') ||
this.configService.get<string>('AI_COMPLETION_MODEL')
);
}
getAiEmbeddingDimension(): number {
return parseInt(
this.configService.get<string>('AI_EMBEDDING_DIMENSION'),
10,
);
}
getAiEmbeddingSupportsMrl(): boolean | undefined {
const val = this.configService.get<string>('AI_EMBEDDING_SUPPORTS_MRL');
if (val === undefined || val === null || val === '') return undefined;
return val === 'true';
}
getOpenAiApiKey(): string {
return this.configService.get<string>('OPENAI_API_KEY');
}
getOpenAiApiUrl(): string {
return this.configService.get<string>('OPENAI_API_URL');
}
getGeminiApiKey(): string {
return this.configService.get<string>('GEMINI_API_KEY');
}
getOllamaApiUrl(): string {
return this.configService.get<string>(
'OLLAMA_API_URL',
'http://localhost:11434',
);
}
getEventStoreDriver(): string {
return this.configService
.get<string>('EVENT_STORE_DRIVER', 'postgres')
.toLowerCase();
}
getClickHouseUrl(): string {
return this.configService.get<string>('CLICKHOUSE_URL');
}
getBaseQueryCacheEnabled(): boolean {
const enabled = this.configService
.get<string>('BASE_QUERY_CACHE_ENABLED', 'false')
.toLowerCase();
return enabled === 'true';
}
getBaseQueryCacheMinRows(): number {
return parseInt(
this.configService.get<string>('BASE_QUERY_CACHE_MIN_ROWS', '25000'),
10,
);
}
getBaseQueryCacheMaxCollections(): number {
// Default is intentionally low (50) because a single-node self-host with
// ~100 MB per collection can pin ~5 GB RSS at the cap. SaaS/larger
// deployments can raise via env.
return parseInt(
this.configService.get<string>('BASE_QUERY_CACHE_MAX_COLLECTIONS', '50'),
10,
);
}
getBaseQueryCacheWarmTopN(): number {
return parseInt(
this.configService.get<string>('BASE_QUERY_CACHE_WARM_TOP_N', '50'),
10,
);
}
getBaseQueryCacheDebug(): boolean {
return (
this.configService
.get<string>('BASE_QUERY_CACHE_DEBUG', 'false')
.toLowerCase() === 'true'
);
}
getBaseQueryCacheTrace(): boolean {
return (
this.configService
.get<string>('BASE_QUERY_CACHE_TRACE', 'false')
.toLowerCase() === 'true'
);
}
getBaseQueryCacheMemoryLimit(): string {
// Per-DuckDB-instance memory ceiling. DuckDB accepts human-readable sizes:
// '256MB', '1GB', etc. Default 512MB is sized for bases up to ~300K rows
// with moderate schemas without spilling. DuckDB automatically spills
// to `temp_directory` when this is exceeded, so over-allocating is
// cheap — the risk is under-sizing.
return this.configService.get<string>(
'BASE_QUERY_CACHE_MEMORY_LIMIT',
'512MB',
);
}
getBaseQueryCacheTempDirectory(): string {
// Directory DuckDB uses to spill pages when an instance exceeds its
// memory_limit. Defaults to the system temp dir plus a namespace so
// different processes don't collide. Setting this explicitly is what
// enables spill-to-disk on `:memory:` instances — without it, DuckDB
// OOMs at memory_limit instead of paging.
const defaultPath = `${require('node:os').tmpdir()}/docmost-duckdb-cache`;
return this.configService.get<string>(
'BASE_QUERY_CACHE_TEMP_DIR',
defaultPath,
);
}
getBaseQueryCacheThreads(): number {
// Per-DuckDB-instance thread budget. Defaults to 2 so multiple concurrent
// instances don't fight for every core on a shared host.
return parseInt(
this.configService.get<string>('BASE_QUERY_CACHE_THREADS', '2'),
10,
);
}
getBaseQueryCacheReaderPoolSize(): number {
// Number of reader connections held open against the shared DuckDB
// instance. Reads are dispatched via `withReader()` which checks out a
// connection, runs the query, returns it. Bigger pool = more concurrent
// reads without serialization, at the cost of per-connection overhead
// (each connection carries its own catalog snapshot + prepared-statement
// cache ~= 300 KB).
//
// Default 4 matches libuv's default thread-pool size. Raise to 8+ if
// you see p99 list latency correlate with concurrent request volume.
return parseInt(
this.configService.get<string>('BASE_QUERY_CACHE_READER_POOL_SIZE', '4'),
10,
);
}
}