diff --git a/packages/backend/server/src/config/affine.env.ts b/packages/backend/server/src/config/affine.env.ts index 60bb5fa768..14fdb185b4 100644 --- a/packages/backend/server/src/config/affine.env.ts +++ b/packages/backend/server/src/config/affine.env.ts @@ -12,6 +12,7 @@ AFFiNE.ENV_MAP = { MAILER_PASSWORD: 'mailer.auth.pass', MAILER_SENDER: 'mailer.from.address', MAILER_SECURE: ['mailer.secure', 'boolean'], + DATABASE_URL: 'database.datasourceUrl', OAUTH_GOOGLE_CLIENT_ID: 'plugins.oauth.providers.google.clientId', OAUTH_GOOGLE_CLIENT_SECRET: 'plugins.oauth.providers.google.clientSecret', OAUTH_GITHUB_CLIENT_ID: 'plugins.oauth.providers.github.clientId', diff --git a/packages/backend/server/src/core/config/index.ts b/packages/backend/server/src/core/config/index.ts index 15dc42ae39..85df6d38a8 100644 --- a/packages/backend/server/src/core/config/index.ts +++ b/packages/backend/server/src/core/config/index.ts @@ -2,10 +2,18 @@ import './config'; import { Module } from '@nestjs/common'; -import { ServerConfigResolver, ServerRuntimeConfigResolver } from './resolver'; +import { + ServerConfigResolver, + ServerRuntimeConfigResolver, + ServerServiceConfigResolver, +} from './resolver'; @Module({ - providers: [ServerConfigResolver, ServerRuntimeConfigResolver], + providers: [ + ServerConfigResolver, + ServerRuntimeConfigResolver, + ServerServiceConfigResolver, + ], }) export class ServerConfigModule {} export { ADD_ENABLED_FEATURES, ServerConfigType } from './resolver'; diff --git a/packages/backend/server/src/core/config/resolver.ts b/packages/backend/server/src/core/config/resolver.ts index bffcc7cd09..7a1a64c217 100644 --- a/packages/backend/server/src/core/config/resolver.ts +++ b/packages/backend/server/src/core/config/resolver.ts @@ -175,11 +175,42 @@ export class ServerConfigResolver { } } +@ObjectType() +class ServerServiceConfig { + @Field() + name!: string; + + @Field(() => GraphQLJSONObject) + config!: any; +} + +interface ServerServeConfig { + https: boolean; + host: string; + port: number; + externalUrl: string; +} + +interface ServerMailerConfig { + host?: string | null; + port?: number | null; + secure?: boolean | null; + service?: string | null; + sender?: string | null; +} + +interface ServerDatabaseConfig { + host: string; + port: number; + user?: string | null; + database: string; +} + +@Admin() @Resolver(() => ServerRuntimeConfigType) export class ServerRuntimeConfigResolver { constructor(private readonly config: Config) {} - @Admin() @Query(() => [ServerRuntimeConfigType], { description: 'get all server runtime configurable settings', }) @@ -187,7 +218,6 @@ export class ServerRuntimeConfigResolver { return this.config.runtime.list(); } - @Admin() @Mutation(() => ServerRuntimeConfigType, { description: 'update server runtime configurable setting', }) @@ -198,7 +228,6 @@ export class ServerRuntimeConfigResolver { return await this.config.runtime.set(id as any, value); } - @Admin() @Mutation(() => [ServerRuntimeConfigType], { description: 'update multiple server runtime configurable settings', }) @@ -213,3 +242,57 @@ export class ServerRuntimeConfigResolver { return results; } } + +@Admin() +@Resolver(() => ServerServiceConfig) +export class ServerServiceConfigResolver { + constructor(private readonly config: Config) {} + + @Query(() => [ServerServiceConfig]) + serverServiceConfigs() { + return [ + { + name: 'server', + config: this.serve(), + }, + { + name: 'mailer', + config: this.mail(), + }, + { + name: 'database', + config: this.database(), + }, + ]; + } + + serve(): ServerServeConfig { + return this.config.server; + } + + mail(): ServerMailerConfig { + const sender = + typeof this.config.mailer.from === 'string' + ? this.config.mailer.from + : this.config.mailer.from?.address; + + return { + host: this.config.mailer.host, + port: this.config.mailer.port, + secure: this.config.mailer.secure, + service: this.config.mailer.service, + sender, + }; + } + + database(): ServerDatabaseConfig { + const url = new URL(this.config.database.datasourceUrl); + + return { + host: url.hostname, + port: Number(url.port), + user: url.username, + database: url.pathname.slice(1) ?? url.username, + }; + } +} diff --git a/packages/backend/server/src/fundamentals/prisma/config.ts b/packages/backend/server/src/fundamentals/prisma/config.ts new file mode 100644 index 0000000000..17106e5ab4 --- /dev/null +++ b/packages/backend/server/src/fundamentals/prisma/config.ts @@ -0,0 +1,17 @@ +import type { Prisma } from '@prisma/client'; + +import { defineStartupConfig, ModuleConfig } from '../config'; + +interface PrismaStartupConfiguration extends Prisma.PrismaClientOptions { + datasourceUrl: string; +} + +declare module '../config' { + interface AppConfig { + database: ModuleConfig; + } +} + +defineStartupConfig('database', { + datasourceUrl: '', +}); diff --git a/packages/backend/server/src/fundamentals/prisma/index.ts b/packages/backend/server/src/fundamentals/prisma/index.ts index 91f4406575..cbaf0b98e8 100644 --- a/packages/backend/server/src/fundamentals/prisma/index.ts +++ b/packages/backend/server/src/fundamentals/prisma/index.ts @@ -1,18 +1,22 @@ +import './config'; + import { Global, Module, Provider } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; +import { Config } from '../config'; import { PrismaService } from './service'; // only `PrismaClient` can be injected const clientProvider: Provider = { provide: PrismaClient, - useFactory: () => { + useFactory: (config: Config) => { if (PrismaService.INSTANCE) { return PrismaService.INSTANCE; } - return new PrismaService(); + return new PrismaService(config.database); }, + inject: [Config], }; @Global() diff --git a/packages/backend/server/src/fundamentals/prisma/service.ts b/packages/backend/server/src/fundamentals/prisma/service.ts index 3f326e50d4..a4e53e20fc 100644 --- a/packages/backend/server/src/fundamentals/prisma/service.ts +++ b/packages/backend/server/src/fundamentals/prisma/service.ts @@ -1,6 +1,6 @@ import type { OnModuleDestroy, OnModuleInit } from '@nestjs/common'; import { Injectable } from '@nestjs/common'; -import { PrismaClient } from '@prisma/client'; +import { Prisma, PrismaClient } from '@prisma/client'; @Injectable() export class PrismaService @@ -9,8 +9,8 @@ export class PrismaService { static INSTANCE: PrismaService | null = null; - constructor() { - super(); + constructor(opts: Prisma.PrismaClientOptions) { + super(opts); PrismaService.INSTANCE = this; } diff --git a/packages/backend/server/src/schema.gql b/packages/backend/server/src/schema.gql index 3673cc5798..c9fe668ec5 100644 --- a/packages/backend/server/src/schema.gql +++ b/packages/backend/server/src/schema.gql @@ -541,6 +541,7 @@ type Query { """get all server runtime configurable settings""" serverRuntimeConfig: [ServerRuntimeConfigType!]! + serverServiceConfigs: [ServerServiceConfig!]! """Get user by email""" user(email: String!): UserOrLimitedUser @@ -663,6 +664,11 @@ type ServerRuntimeConfigType { value: JSON! } +type ServerServiceConfig { + config: JSONObject! + name: String! +} + type SubscriptionAlreadyExistsDataType { plan: String! } diff --git a/packages/frontend/graphql/src/schema.ts b/packages/frontend/graphql/src/schema.ts index e3e2b300e1..d13a5c9ce1 100644 --- a/packages/frontend/graphql/src/schema.ts +++ b/packages/frontend/graphql/src/schema.ts @@ -798,6 +798,7 @@ export interface Query { serverConfig: ServerConfigType; /** get all server runtime configurable settings */ serverRuntimeConfig: Array; + serverServiceConfigs: Array; /** Get user by email */ user: Maybe; /** Get user by id */ @@ -952,6 +953,12 @@ export interface ServerRuntimeConfigType { value: Scalars['JSON']['output']; } +export interface ServerServiceConfig { + __typename?: 'ServerServiceConfig'; + config: Scalars['JSONObject']['output']; + name: Scalars['String']['output']; +} + export interface SubscriptionAlreadyExistsDataType { __typename?: 'SubscriptionAlreadyExistsDataType'; plan: Scalars['String']['output'];