diff --git a/packages/backend/server/src/affine.env.ts b/packages/backend/server/src/affine.env.ts deleted file mode 100644 index 5605964bf5..0000000000 --- a/packages/backend/server/src/affine.env.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { homedir } from 'node:os'; -import { join } from 'node:path'; - -import { config } from 'dotenv'; - -import { SERVER_FLAVOR } from './config/default'; - -if (SERVER_FLAVOR === 'selfhosted') { - config({ path: join(homedir(), '.affine', '.env') }); -} else { - config(); -} diff --git a/packages/backend/server/src/affine.ts b/packages/backend/server/src/affine.ts deleted file mode 100644 index ceb37536da..0000000000 --- a/packages/backend/server/src/affine.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { getDefaultAFFiNEConfig } from './config/default'; - -globalThis.AFFiNE = getDefaultAFFiNEConfig(); diff --git a/packages/backend/server/src/app.controller.ts b/packages/backend/server/src/app.controller.ts index 43eb338179..87f3f981df 100644 --- a/packages/backend/server/src/app.controller.ts +++ b/packages/backend/server/src/app.controller.ts @@ -1,13 +1,16 @@ import { Controller, Get } from '@nestjs/common'; +import { Config } from './fundamentals/config'; + @Controller('/') export class AppController { + constructor(private readonly config: Config) {} + @Get() info() { - const version = AFFiNE.version; return { - compatibility: version, - message: `AFFiNE ${version} Server`, + compatibility: this.config.version, + message: `AFFiNE ${this.config.version} Server`, }; } } diff --git a/packages/backend/server/src/app.ts b/packages/backend/server/src/app.ts index ea47c41a32..8efc1b1f32 100644 --- a/packages/backend/server/src/app.ts +++ b/packages/backend/server/src/app.ts @@ -2,16 +2,16 @@ import { DynamicModule, Module, Type } from '@nestjs/common'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { AppController } from './app.controller'; -import { CacheInterceptor, CacheModule } from './cache'; -import { RedisModule } from './cache/redis'; -import { ConfigModule, SERVER_FLAVOR } from './config'; -import { EventModule } from './event'; -import { MetricsModule } from './metrics'; +import { CacheInterceptor, CacheModule } from './fundamentals/cache'; +import { ConfigModule } from './fundamentals/config'; +import { EventModule } from './fundamentals/event'; +import { MailModule } from './fundamentals/mailer'; +import { MetricsModule } from './fundamentals/metrics'; +import { PrismaModule } from './fundamentals/prisma'; +import { SessionModule } from './fundamentals/session'; +import { RateLimiterModule } from './fundamentals/throttler'; import { BusinessModules } from './modules'; import { AuthModule } from './modules/auth'; -import { PrismaModule } from './prisma'; -import { SessionModule } from './session'; -import { RateLimiterModule } from './throttler'; export const FunctionalityModules: Array = [ ConfigModule.forRoot(), @@ -22,13 +22,9 @@ export const FunctionalityModules: Array = [ SessionModule, RateLimiterModule, AuthModule, + MailModule, ]; -// better module registration logic -if (AFFiNE.redis.enabled) { - FunctionalityModules.push(RedisModule); -} - @Module({ providers: [ { @@ -37,6 +33,6 @@ if (AFFiNE.redis.enabled) { }, ], imports: [...FunctionalityModules, ...BusinessModules], - controllers: SERVER_FLAVOR === 'selfhosted' ? [] : [AppController], + controllers: [AppController], }) export class AppModule {} diff --git a/packages/backend/server/src/config/affine.env.ts b/packages/backend/server/src/config/affine.env.ts new file mode 100644 index 0000000000..be0b54f08e --- /dev/null +++ b/packages/backend/server/src/config/affine.env.ts @@ -0,0 +1,41 @@ +// Convenient way to map environment variables to config values. +AFFiNE.ENV_MAP = { + AFFINE_SERVER_PORT: ['port', 'int'], + AFFINE_SERVER_HOST: 'host', + AFFINE_SERVER_SUB_PATH: 'path', + AFFIHE_SERVER_HTTPS: ['https', 'boolean'], + AFFINE_ENV: 'affineEnv', + DATABASE_URL: 'db.url', + ENABLE_CAPTCHA: ['auth.captcha.enable', 'boolean'], + CAPTCHA_TURNSTILE_SECRET: ['auth.captcha.turnstile.secret', 'string'], + OAUTH_GOOGLE_ENABLED: ['auth.oauthProviders.google.enabled', 'boolean'], + OAUTH_GOOGLE_CLIENT_ID: 'auth.oauthProviders.google.clientId', + OAUTH_GOOGLE_CLIENT_SECRET: 'auth.oauthProviders.google.clientSecret', + OAUTH_GITHUB_ENABLED: ['auth.oauthProviders.github.enabled', 'boolean'], + OAUTH_GITHUB_CLIENT_ID: 'auth.oauthProviders.github.clientId', + OAUTH_GITHUB_CLIENT_SECRET: 'auth.oauthProviders.github.clientSecret', + OAUTH_EMAIL_LOGIN: 'auth.email.login', + OAUTH_EMAIL_SENDER: 'auth.email.sender', + OAUTH_EMAIL_SERVER: 'auth.email.server', + OAUTH_EMAIL_PORT: ['auth.email.port', 'int'], + OAUTH_EMAIL_PASSWORD: 'auth.email.password', + THROTTLE_TTL: ['rateLimiter.ttl', 'int'], + THROTTLE_LIMIT: ['rateLimiter.limit', 'int'], + REDIS_SERVER_ENABLED: ['redis.enabled', 'boolean'], + REDIS_SERVER_HOST: 'redis.host', + REDIS_SERVER_PORT: ['redis.port', 'int'], + REDIS_SERVER_USER: 'redis.username', + REDIS_SERVER_PASSWORD: 'redis.password', + REDIS_SERVER_DATABASE: ['redis.database', 'int'], + DOC_MERGE_INTERVAL: ['doc.manager.updatePollInterval', 'int'], + DOC_MERGE_USE_JWST_CODEC: [ + 'doc.manager.experimentalMergeWithJwstCodec', + 'boolean', + ], + ENABLE_LOCAL_EMAIL: ['auth.localEmail', 'boolean'], + STRIPE_API_KEY: 'payment.stripe.keys.APIKey', + STRIPE_WEBHOOK_KEY: 'payment.stripe.keys.webhookKey', + FEATURES_EARLY_ACCESS_PREVIEW: ['featureFlags.earlyAccessPreview', 'boolean'], +}; + +export default AFFiNE; diff --git a/packages/backend/server/src/affine.config.ts b/packages/backend/server/src/config/affine.ts similarity index 87% rename from packages/backend/server/src/affine.config.ts rename to packages/backend/server/src/config/affine.ts index 869b20467d..7ae037507f 100644 --- a/packages/backend/server/src/affine.config.ts +++ b/packages/backend/server/src/config/affine.ts @@ -1,11 +1,14 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ // Custom configurations - const env = process.env; const node = AFFiNE.node; -// TODO: may be separate config overring in `affine.[env].config`? +// TODO(@forehalo): detail explained +AFFiNE.host = 'localhost'; +AFFiNE.port = 3010; + if (node.prod) { + AFFiNE.host = '0.0.0.0'; // Storage if (env.R2_OBJECT_STORAGE_ACCOUNT_ID) { AFFiNE.storage.providers.r2 = { @@ -29,3 +32,5 @@ if (node.prod) { // Metrics AFFiNE.metrics.enabled = true; } + +export default AFFiNE; diff --git a/packages/backend/server/src/config/env.ts b/packages/backend/server/src/config/env.ts deleted file mode 100644 index 8d5b950fff..0000000000 --- a/packages/backend/server/src/config/env.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { set } from 'lodash-es'; - -import { type AFFiNEConfig, parseEnvValue } from './def'; - -export function applyEnvToConfig(rawConfig: AFFiNEConfig) { - for (const env in rawConfig.ENV_MAP) { - const config = rawConfig.ENV_MAP[env]; - const [path, value] = - typeof config === 'string' - ? [config, process.env[env]] - : [config[0], parseEnvValue(process.env[env], config[1])]; - - if (value !== undefined) { - set(rawConfig, path, value); - } - } -} diff --git a/packages/backend/server/src/constants.ts b/packages/backend/server/src/constants.ts deleted file mode 100644 index 5c0677a3ab..0000000000 --- a/packages/backend/server/src/constants.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const OPERATION_NAME = 'x-operation-name'; - -export const REQUEST_ID = 'x-request-id'; diff --git a/packages/backend/server/src/data/app.ts b/packages/backend/server/src/data/app.ts index 5e00fd214a..84c3027a3d 100644 --- a/packages/backend/server/src/data/app.ts +++ b/packages/backend/server/src/data/app.ts @@ -4,7 +4,7 @@ import { Logger, Module } from '@nestjs/common'; import { CommandFactory } from 'nest-commander'; import { AppModule as BusinessAppModule } from '../app'; -import { ConfigModule } from '../config'; +import { ConfigModule } from '../fundamentals/config'; import { CreateCommand, NameQuestion } from './commands/create'; import { RevertCommand, RunCommand } from './commands/run'; @@ -16,9 +16,6 @@ import { RevertCommand, RunCommand } from './commands/run'; enableUpdateAutoMerging: false, }, }, - metrics: { - enabled: false, - }, }), BusinessAppModule, ], diff --git a/packages/backend/server/src/data/commands/run.ts b/packages/backend/server/src/data/commands/run.ts index b8123cd8d9..136b7df0d7 100644 --- a/packages/backend/server/src/data/commands/run.ts +++ b/packages/backend/server/src/data/commands/run.ts @@ -4,15 +4,14 @@ import { fileURLToPath } from 'node:url'; import { Logger } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; +import { PrismaClient } from '@prisma/client'; import { Command, CommandRunner } from 'nest-commander'; -import { PrismaService } from '../../prisma'; - interface Migration { file: string; name: string; - up: (db: PrismaService, injector: ModuleRef) => Promise; - down: (db: PrismaService, injector: ModuleRef) => Promise; + up: (db: PrismaClient, injector: ModuleRef) => Promise; + down: (db: PrismaClient, injector: ModuleRef) => Promise; } export async function collectMigrations(): Promise { @@ -48,7 +47,7 @@ export async function collectMigrations(): Promise { export class RunCommand extends CommandRunner { logger = new Logger(RunCommand.name); constructor( - private readonly db: PrismaService, + private readonly db: PrismaClient, private readonly injector: ModuleRef ) { super(); @@ -139,7 +138,7 @@ export class RevertCommand extends CommandRunner { logger = new Logger(RevertCommand.name); constructor( - private readonly db: PrismaService, + private readonly db: PrismaClient, private readonly injector: ModuleRef ) { super(); diff --git a/packages/backend/server/src/data/migrations/1698398506533-guid.ts b/packages/backend/server/src/data/migrations/1698398506533-guid.ts index e7e0262472..4a6393e286 100644 --- a/packages/backend/server/src/data/migrations/1698398506533-guid.ts +++ b/packages/backend/server/src/data/migrations/1698398506533-guid.ts @@ -1,7 +1,7 @@ import { PrismaClient } from '@prisma/client'; import { applyUpdate, Doc, encodeStateAsUpdate } from 'yjs'; -import { DocID } from '../../utils/doc'; +import { DocID } from '../../modules/utils/doc'; export class Guid1698398506533 { // do the migration diff --git a/packages/backend/server/src/cache/index.ts b/packages/backend/server/src/fundamentals/cache/index.ts similarity index 89% rename from packages/backend/server/src/cache/index.ts rename to packages/backend/server/src/fundamentals/cache/index.ts index 06c73367f2..2137f84cba 100644 --- a/packages/backend/server/src/cache/index.ts +++ b/packages/backend/server/src/fundamentals/cache/index.ts @@ -4,7 +4,7 @@ import { Redis } from 'ioredis'; import { SessionCache, ThrottlerCache } from './instances'; import { LocalCache } from './providers/cache'; import { RedisCache } from './providers/redis'; -import { CacheRedis, SessionRedis, ThrottlerRedis } from './redis'; +import { CacheRedis, RedisModule, SessionRedis, ThrottlerRedis } from './redis'; function makeCacheProvider(CacheToken: Type, RedisToken: Type): Provider { return { @@ -25,6 +25,7 @@ const ThrottlerCacheProvider = makeCacheProvider( @Global() @Module({ + imports: AFFiNE.redis.enabled ? [RedisModule] : [], providers: [CacheProvider, SessionCacheProvider, ThrottlerCacheProvider], exports: [CacheProvider, SessionCacheProvider, ThrottlerCacheProvider], }) diff --git a/packages/backend/server/src/cache/instances.ts b/packages/backend/server/src/fundamentals/cache/instances.ts similarity index 100% rename from packages/backend/server/src/cache/instances.ts rename to packages/backend/server/src/fundamentals/cache/instances.ts diff --git a/packages/backend/server/src/cache/interceptor.ts b/packages/backend/server/src/fundamentals/cache/interceptor.ts similarity index 100% rename from packages/backend/server/src/cache/interceptor.ts rename to packages/backend/server/src/fundamentals/cache/interceptor.ts diff --git a/packages/backend/server/src/cache/providers/cache.ts b/packages/backend/server/src/fundamentals/cache/providers/cache.ts similarity index 100% rename from packages/backend/server/src/cache/providers/cache.ts rename to packages/backend/server/src/fundamentals/cache/providers/cache.ts diff --git a/packages/backend/server/src/cache/providers/redis.ts b/packages/backend/server/src/fundamentals/cache/providers/redis.ts similarity index 100% rename from packages/backend/server/src/cache/providers/redis.ts rename to packages/backend/server/src/fundamentals/cache/providers/redis.ts diff --git a/packages/backend/server/src/cache/redis/index.ts b/packages/backend/server/src/fundamentals/cache/redis/index.ts similarity index 100% rename from packages/backend/server/src/cache/redis/index.ts rename to packages/backend/server/src/fundamentals/cache/redis/index.ts diff --git a/packages/backend/server/src/config/def.ts b/packages/backend/server/src/fundamentals/config/def.ts similarity index 90% rename from packages/backend/server/src/config/def.ts rename to packages/backend/server/src/fundamentals/config/def.ts index ceb871ee1f..ce8e80533e 100644 --- a/packages/backend/server/src/config/def.ts +++ b/packages/backend/server/src/fundamentals/config/def.ts @@ -1,6 +1,7 @@ import type { ApolloDriverConfig } from '@nestjs/apollo'; import type { LeafPaths } from '../utils/types'; +import { EnvConfigType } from './env'; import type { AFFiNEStorageConfig } from './storage'; declare global { @@ -18,8 +19,6 @@ export enum ExternalAccount { } export type ServerFlavor = 'allinone' | 'graphql' | 'sync' | 'selfhosted'; - -type EnvConfigType = 'string' | 'int' | 'float' | 'boolean'; type ConfigPaths = LeafPaths< Omit< AFFiNEConfig, @@ -31,40 +30,11 @@ type ConfigPaths = LeafPaths< | 'dev' | 'test' | 'deploy' + | 'node' >, '', '....' >; -/** - * parse number value from environment variables - */ -function int(value: string) { - const n = parseInt(value); - return Number.isNaN(n) ? undefined : n; -} - -function float(value: string) { - const n = parseFloat(value); - return Number.isNaN(n) ? undefined : n; -} - -function boolean(value: string) { - return value === '1' || value.toLowerCase() === 'true'; -} - -export function parseEnvValue(value: string | undefined, type?: EnvConfigType) { - if (value === undefined) { - return; - } - - return type === 'int' - ? int(value) - : type === 'float' - ? float(value) - : type === 'boolean' - ? boolean(value) - : value; -} /** * All Configurations that would control AFFiNE server behaviors @@ -80,6 +50,10 @@ export interface AFFiNEConfig { * System version */ readonly version: string; + /** + * Server flavor + */ + readonly flavor: ServerFlavor; /** * Deployment environment */ @@ -370,3 +344,5 @@ export interface AFFiNEConfig { } & import('stripe').Stripe.StripeConfig; }; } + +export * from './storage'; diff --git a/packages/backend/server/src/config/default.ts b/packages/backend/server/src/fundamentals/config/default.ts similarity index 63% rename from packages/backend/server/src/config/default.ts rename to packages/backend/server/src/fundamentals/config/default.ts index 94c8e72793..e16d3e464d 100644 --- a/packages/backend/server/src/config/default.ts +++ b/packages/backend/server/src/fundamentals/config/default.ts @@ -1,19 +1,15 @@ -/// +/// import { createPrivateKey, createPublicKey } from 'node:crypto'; import parse from 'parse-duration'; -import pkg from '../../package.json' assert { type: 'json' }; +import pkg from '../../../package.json' assert { type: 'json' }; import type { AFFiNEConfig, ServerFlavor } from './def'; -import { applyEnvToConfig } from './env'; import { getDefaultAFFiNEStorageConfig } from './storage'; -export const SERVER_FLAVOR = (process.env.SERVER_FLAVOR ?? - 'allinone') as ServerFlavor; - // Don't use this in production -export const examplePrivateKey = `-----BEGIN EC PRIVATE KEY----- +const examplePrivateKey = `-----BEGIN EC PRIVATE KEY----- MHcCAQEEIEtyAJLIULkphVhqXqxk4Nr8Ggty3XLwUJWBxzAWCWTMoAoGCCqGSM49 AwEHoUQDQgAEF3U/0wIeJ3jRKXeFKqQyBKlr9F7xaAUScRrAuSP33rajm3cdfihI 3JvMxVNsS2lE8PSGQrvDrJZaDo0L+Lq9Gg== @@ -50,50 +46,12 @@ const jwtKeyPair = (function () { export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => { let isHttps: boolean | null = null; + const flavor = (process.env.SERVER_FLAVOR ?? 'allinone') as ServerFlavor; const defaultConfig = { serverId: 'affine-nestjs-server', version: pkg.version, - ENV_MAP: { - AFFINE_SERVER_PORT: ['port', 'int'], - AFFINE_SERVER_HOST: 'host', - AFFINE_SERVER_SUB_PATH: 'path', - AFFIHE_SERVER_HTTPS: 'https', - AFFINE_ENV: 'affineEnv', - DATABASE_URL: 'db.url', - ENABLE_CAPTCHA: ['auth.captcha.enable', 'boolean'], - CAPTCHA_TURNSTILE_SECRET: ['auth.captcha.turnstile.secret', 'string'], - OAUTH_GOOGLE_ENABLED: ['auth.oauthProviders.google.enabled', 'boolean'], - OAUTH_GOOGLE_CLIENT_ID: 'auth.oauthProviders.google.clientId', - OAUTH_GOOGLE_CLIENT_SECRET: 'auth.oauthProviders.google.clientSecret', - OAUTH_GITHUB_ENABLED: ['auth.oauthProviders.github.enabled', 'boolean'], - OAUTH_GITHUB_CLIENT_ID: 'auth.oauthProviders.github.clientId', - OAUTH_GITHUB_CLIENT_SECRET: 'auth.oauthProviders.github.clientSecret', - OAUTH_EMAIL_LOGIN: 'auth.email.login', - OAUTH_EMAIL_SENDER: 'auth.email.sender', - OAUTH_EMAIL_SERVER: 'auth.email.server', - OAUTH_EMAIL_PORT: ['auth.email.port', 'int'], - OAUTH_EMAIL_PASSWORD: 'auth.email.password', - THROTTLE_TTL: ['rateLimiter.ttl', 'int'], - THROTTLE_LIMIT: ['rateLimiter.limit', 'int'], - REDIS_SERVER_ENABLED: ['redis.enabled', 'boolean'], - REDIS_SERVER_HOST: 'redis.host', - REDIS_SERVER_PORT: ['redis.port', 'int'], - REDIS_SERVER_USER: 'redis.username', - REDIS_SERVER_PASSWORD: 'redis.password', - REDIS_SERVER_DATABASE: ['redis.database', 'int'], - DOC_MERGE_INTERVAL: ['doc.manager.updatePollInterval', 'int'], - DOC_MERGE_USE_JWST_CODEC: [ - 'doc.manager.experimentalMergeWithJwstCodec', - 'boolean', - ], - ENABLE_LOCAL_EMAIL: ['auth.localEmail', 'boolean'], - STRIPE_API_KEY: 'payment.stripe.keys.APIKey', - STRIPE_WEBHOOK_KEY: 'payment.stripe.keys.webhookKey', - FEATURES_EARLY_ACCESS_PREVIEW: [ - 'featureFlags.earlyAccessPreview', - 'boolean', - ], - } satisfies AFFiNEConfig['ENV_MAP'], + flavor, + ENV_MAP: {}, affineEnv: 'dev', get affine() { const env = this.affineEnv; @@ -194,7 +152,7 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => { }, doc: { manager: { - enableUpdateAutoMerging: SERVER_FLAVOR !== 'sync', + enableUpdateAutoMerging: flavor !== 'sync', updatePollInterval: 3000, experimentalMergeWithJwstCodec: false, }, @@ -216,7 +174,5 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => { }, } satisfies AFFiNEConfig; - applyEnvToConfig(defaultConfig); - return defaultConfig; }; diff --git a/packages/backend/server/src/fundamentals/config/env.ts b/packages/backend/server/src/fundamentals/config/env.ts new file mode 100644 index 0000000000..50554bc462 --- /dev/null +++ b/packages/backend/server/src/fundamentals/config/env.ts @@ -0,0 +1,50 @@ +import { set } from 'lodash-es'; + +import type { AFFiNEConfig } from './def'; + +export type EnvConfigType = 'string' | 'int' | 'float' | 'boolean'; +/** + * parse number value from environment variables + */ +function int(value: string) { + const n = parseInt(value); + return Number.isNaN(n) ? undefined : n; +} + +function float(value: string) { + const n = parseFloat(value); + return Number.isNaN(n) ? undefined : n; +} + +function boolean(value: string) { + return value === '1' || value.toLowerCase() === 'true'; +} + +const envParsers: Record unknown> = { + int, + float, + boolean, + string: value => value, +}; + +export function parseEnvValue(value: string | undefined, type: EnvConfigType) { + if (value === undefined) { + return; + } + + return envParsers[type](value); +} + +export function applyEnvToConfig(rawConfig: AFFiNEConfig) { + for (const env in rawConfig.ENV_MAP) { + const config = rawConfig.ENV_MAP[env]; + const [path, value] = + typeof config === 'string' + ? [config, parseEnvValue(process.env[env], 'string')] + : [config[0], parseEnvValue(process.env[env], config[1] ?? 'string')]; + + if (value !== undefined) { + set(rawConfig, path, value); + } + } +} diff --git a/packages/backend/server/src/fundamentals/config/index.ts b/packages/backend/server/src/fundamentals/config/index.ts new file mode 100644 index 0000000000..34fadc0421 --- /dev/null +++ b/packages/backend/server/src/fundamentals/config/index.ts @@ -0,0 +1,6 @@ +// eslint-disable-next-line simple-import-sort/imports + +export * from './def'; +export * from './default'; +export { applyEnvToConfig, parseEnvValue } from './env'; +export * from './module'; diff --git a/packages/backend/server/src/config/index.ts b/packages/backend/server/src/fundamentals/config/module.ts similarity index 68% rename from packages/backend/server/src/config/index.ts rename to packages/backend/server/src/fundamentals/config/module.ts index 433221764e..8af40f23e9 100644 --- a/packages/backend/server/src/config/index.ts +++ b/packages/backend/server/src/fundamentals/config/module.ts @@ -1,24 +1,12 @@ -// eslint-disable-next-line simple-import-sort/imports -import type { DynamicModule, FactoryProvider } from '@nestjs/common'; +import { DynamicModule, FactoryProvider } from '@nestjs/common'; import { merge } from 'lodash-es'; -import type { DeepPartial } from '../utils/types'; -import type { AFFiNEConfig } from './def'; - -type ConstructorOf = { - new (): T; -}; - -function ApplyType(): ConstructorOf { - // @ts-expect-error used to fake the type of config - return class Inner implements T { - constructor() {} - }; -} +import { ApplyType, DeepPartial } from '../utils/types'; +import { AFFiNEConfig } from './def'; /** - * usage: - * ``` + * @example + * * import { Config } from '@affine/server' * * class TestConfig { @@ -27,7 +15,6 @@ function ApplyType(): ConstructorOf { * return this.config.env * } * } - * ``` */ export class Config extends ApplyType() {} @@ -69,7 +56,3 @@ export class ConfigModule { }; }; } - -export type { AFFiNEConfig } from './def'; -export { SERVER_FLAVOR } from './default'; -export * from './storage'; diff --git a/packages/backend/server/src/config/storage/index.ts b/packages/backend/server/src/fundamentals/config/storage/index.ts similarity index 100% rename from packages/backend/server/src/config/storage/index.ts rename to packages/backend/server/src/fundamentals/config/storage/index.ts diff --git a/packages/backend/server/src/event/events.ts b/packages/backend/server/src/fundamentals/event/events.ts similarity index 100% rename from packages/backend/server/src/event/events.ts rename to packages/backend/server/src/fundamentals/event/events.ts diff --git a/packages/backend/server/src/event/index.ts b/packages/backend/server/src/fundamentals/event/index.ts similarity index 100% rename from packages/backend/server/src/event/index.ts rename to packages/backend/server/src/fundamentals/event/index.ts diff --git a/packages/backend/server/src/event/types.ts b/packages/backend/server/src/fundamentals/event/types.ts similarity index 100% rename from packages/backend/server/src/event/types.ts rename to packages/backend/server/src/fundamentals/event/types.ts diff --git a/packages/backend/server/src/graphql.module.ts b/packages/backend/server/src/fundamentals/graphql/index.ts similarity index 72% rename from packages/backend/server/src/graphql.module.ts rename to packages/backend/server/src/fundamentals/graphql/index.ts index 71610e2235..5f1b055638 100644 --- a/packages/backend/server/src/graphql.module.ts +++ b/packages/backend/server/src/fundamentals/graphql/index.ts @@ -6,8 +6,8 @@ import { Request, Response } from 'express'; import { join } from 'path'; import { fileURLToPath } from 'url'; -import { Config } from './config'; -import { GQLLoggerPlugin } from './graphql/logger-plugin'; +import { Config } from '../config'; +import { GQLLoggerPlugin } from './logger-plugin'; @Global() @Module({ @@ -21,12 +21,12 @@ import { GQLLoggerPlugin } from './graphql/logger-plugin'; csrfPrevention: { requestHeaders: ['content-type'], }, - autoSchemaFile: config.node.test - ? join( - fileURLToPath(import.meta.url), - '../../node_modules/.cache/schema.gql' - ) - : join(fileURLToPath(import.meta.url), '..', 'schema.gql'), + autoSchemaFile: join( + fileURLToPath(import.meta.url), + config.node.test + ? '../../../../node_modules/.cache/schema.gql' + : '../../../schema.gql' + ), context: ({ req, res }: { req: Request; res: Response }) => ({ req, res, diff --git a/packages/backend/server/src/graphql/logger-plugin.ts b/packages/backend/server/src/fundamentals/graphql/logger-plugin.ts similarity index 79% rename from packages/backend/server/src/graphql/logger-plugin.ts rename to packages/backend/server/src/fundamentals/graphql/logger-plugin.ts index 9028ce722a..628c6ce42a 100644 --- a/packages/backend/server/src/graphql/logger-plugin.ts +++ b/packages/backend/server/src/fundamentals/graphql/logger-plugin.ts @@ -8,17 +8,22 @@ import { Logger } from '@nestjs/common'; import { Response } from 'express'; import { metrics } from '../metrics/metrics'; -import { ReqContext } from '../types'; + +export interface RequestContext { + req: Express.Request & { + res: Express.Response; + }; +} @Plugin() export class GQLLoggerPlugin implements ApolloServerPlugin { protected logger = new Logger(GQLLoggerPlugin.name); requestDidStart( - reqContext: GraphQLRequestContext - ): Promise>> { - const res = reqContext.contextValue.req.res as Response; - const operation = reqContext.request.operationName; + ctx: GraphQLRequestContext + ): Promise>> { + const res = ctx.contextValue.req.res as Response; + const operation = ctx.request.operationName; metrics.gql.counter('query_counter').add(1, { operation }); const start = Date.now(); diff --git a/packages/backend/server/src/fundamentals/index.ts b/packages/backend/server/src/fundamentals/index.ts new file mode 100644 index 0000000000..35b18decc1 --- /dev/null +++ b/packages/backend/server/src/fundamentals/index.ts @@ -0,0 +1,20 @@ +export { Cache, CacheInterceptor, MakeCache, PreventCache } from './cache'; +export { + applyEnvToConfig, + Config, + getDefaultAFFiNEStorageConfig, +} from './config'; +export { EventEmitter, type EventPayload, OnEvent } from './event'; +export { MailService } from './mailer'; +export { CallCounter, CallTimer, metrics } from './metrics'; +export { PrismaService } from './prisma'; +export { SessionService } from './session'; +export * from './storage'; +export { AuthThrottlerGuard, CloudThrottlerGuard, Throttle } from './throttler'; +export { + getRequestFromHost, + getRequestResponseFromContext, + getRequestResponseFromHost, +} from './utils/request'; +export type * from './utils/types'; +export { RedisIoAdapter } from './websocket'; diff --git a/packages/backend/server/src/fundamentals/mailer/index.ts b/packages/backend/server/src/fundamentals/mailer/index.ts new file mode 100644 index 0000000000..e990f6c489 --- /dev/null +++ b/packages/backend/server/src/fundamentals/mailer/index.ts @@ -0,0 +1,12 @@ +import { Global, Module } from '@nestjs/common'; + +import { MailService } from './mail.service'; +import { MAILER } from './mailer'; + +@Global() +@Module({ + providers: [MAILER, MailService], + exports: [MailService], +}) +export class MailModule {} +export { MailService }; diff --git a/packages/backend/server/src/modules/auth/mailer/mail.service.ts b/packages/backend/server/src/fundamentals/mailer/mail.service.ts similarity index 99% rename from packages/backend/server/src/modules/auth/mailer/mail.service.ts rename to packages/backend/server/src/fundamentals/mailer/mail.service.ts index 702d2fae3e..66241e90c7 100644 --- a/packages/backend/server/src/modules/auth/mailer/mail.service.ts +++ b/packages/backend/server/src/fundamentals/mailer/mail.service.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; -import { Config } from '../../../config'; +import { Config } from '../config'; import { MAILER_SERVICE, type MailerService, diff --git a/packages/backend/server/src/modules/auth/mailer/mailer.ts b/packages/backend/server/src/fundamentals/mailer/mailer.ts similarity index 95% rename from packages/backend/server/src/modules/auth/mailer/mailer.ts rename to packages/backend/server/src/fundamentals/mailer/mailer.ts index d1c8f65c3a..ba02cc8887 100644 --- a/packages/backend/server/src/modules/auth/mailer/mailer.ts +++ b/packages/backend/server/src/fundamentals/mailer/mailer.ts @@ -2,7 +2,7 @@ import { FactoryProvider } from '@nestjs/common'; import { createTransport, Transporter } from 'nodemailer'; import SMTPTransport from 'nodemailer/lib/smtp-transport'; -import { Config } from '../../../config'; +import { Config } from '../config'; export const MAILER_SERVICE = Symbol('MAILER_SERVICE'); diff --git a/packages/backend/server/src/modules/auth/mailer/template.ts b/packages/backend/server/src/fundamentals/mailer/template.ts similarity index 100% rename from packages/backend/server/src/modules/auth/mailer/template.ts rename to packages/backend/server/src/fundamentals/mailer/template.ts diff --git a/packages/backend/server/src/metrics/index.ts b/packages/backend/server/src/fundamentals/metrics/index.ts similarity index 90% rename from packages/backend/server/src/metrics/index.ts rename to packages/backend/server/src/fundamentals/metrics/index.ts index 9ca470b582..dc2b481daa 100644 --- a/packages/backend/server/src/metrics/index.ts +++ b/packages/backend/server/src/fundamentals/metrics/index.ts @@ -1,8 +1,7 @@ import { Global, Module, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; import { NodeSDK } from '@opentelemetry/sdk-node'; -import { Config } from '../config'; -import { parseEnvValue } from '../config/def'; +import { Config, parseEnvValue } from '../config'; import { createSDK, registerCustomMetrics } from './opentelemetry'; @Global() diff --git a/packages/backend/server/src/metrics/metrics.ts b/packages/backend/server/src/fundamentals/metrics/metrics.ts similarity index 100% rename from packages/backend/server/src/metrics/metrics.ts rename to packages/backend/server/src/fundamentals/metrics/metrics.ts diff --git a/packages/backend/server/src/metrics/opentelemetry.ts b/packages/backend/server/src/fundamentals/metrics/opentelemetry.ts similarity index 100% rename from packages/backend/server/src/metrics/opentelemetry.ts rename to packages/backend/server/src/fundamentals/metrics/opentelemetry.ts diff --git a/packages/backend/server/src/metrics/prisma.ts b/packages/backend/server/src/fundamentals/metrics/prisma.ts similarity index 100% rename from packages/backend/server/src/metrics/prisma.ts rename to packages/backend/server/src/fundamentals/metrics/prisma.ts diff --git a/packages/backend/server/src/metrics/utils.ts b/packages/backend/server/src/fundamentals/metrics/utils.ts similarity index 100% rename from packages/backend/server/src/metrics/utils.ts rename to packages/backend/server/src/fundamentals/metrics/utils.ts diff --git a/packages/backend/server/src/prisma/index.ts b/packages/backend/server/src/fundamentals/prisma/index.ts similarity index 100% rename from packages/backend/server/src/prisma/index.ts rename to packages/backend/server/src/fundamentals/prisma/index.ts diff --git a/packages/backend/server/src/prisma/service.ts b/packages/backend/server/src/fundamentals/prisma/service.ts similarity index 100% rename from packages/backend/server/src/prisma/service.ts rename to packages/backend/server/src/fundamentals/prisma/service.ts diff --git a/packages/backend/server/src/session.ts b/packages/backend/server/src/fundamentals/session/index.ts similarity index 95% rename from packages/backend/server/src/session.ts rename to packages/backend/server/src/fundamentals/session/index.ts index 878bf0e821..a185452abb 100644 --- a/packages/backend/server/src/session.ts +++ b/packages/backend/server/src/fundamentals/session/index.ts @@ -1,6 +1,6 @@ import { Global, Injectable, Module } from '@nestjs/common'; -import { SessionCache } from './cache/instances'; +import { SessionCache } from '../cache'; @Injectable() export class SessionService { diff --git a/packages/backend/server/src/modules/storage/__tests__/fs.spec.ts b/packages/backend/server/src/fundamentals/storage/__tests__/fs.spec.ts similarity index 100% rename from packages/backend/server/src/modules/storage/__tests__/fs.spec.ts rename to packages/backend/server/src/fundamentals/storage/__tests__/fs.spec.ts diff --git a/packages/backend/server/src/storage/index.ts b/packages/backend/server/src/fundamentals/storage/index.ts similarity index 69% rename from packages/backend/server/src/storage/index.ts rename to packages/backend/server/src/fundamentals/storage/index.ts index 8d2fc87b18..d2ecdf9284 100644 --- a/packages/backend/server/src/storage/index.ts +++ b/packages/backend/server/src/fundamentals/storage/index.ts @@ -9,10 +9,10 @@ try { const require = createRequire(import.meta.url); storageModule = process.arch === 'arm64' - ? require('../../storage.arm64.node') + ? require('../.././storage.arm64.node') : process.arch === 'arm' - ? require('../../storage.armv7.node') - : require('../../storage.node'); + ? require('../../../storage.armv7.node') + : require('../../../storage.node'); } export { storageModule as OctoBaseStorageModule }; @@ -32,3 +32,14 @@ export const mintChallengeResponse = async (resource: string, bits: number) => { if (!resource) return null; return storageModule.mintChallengeResponse(resource, bits); }; + +export type { + BlobInputType, + BlobOutputType, + GetObjectMetadata, + ListObjectsMetadata, + PutObjectMetadata, + StorageProvider, +} from './providers'; +export { createStorageProvider } from './providers'; +export { toBuffer } from './providers/utils'; diff --git a/packages/backend/server/src/modules/storage/providers/fs.ts b/packages/backend/server/src/fundamentals/storage/providers/fs.ts similarity index 99% rename from packages/backend/server/src/modules/storage/providers/fs.ts rename to packages/backend/server/src/fundamentals/storage/providers/fs.ts index 3ad735d2e4..46dbe0cdea 100644 --- a/packages/backend/server/src/modules/storage/providers/fs.ts +++ b/packages/backend/server/src/fundamentals/storage/providers/fs.ts @@ -15,7 +15,7 @@ import { join, parse, resolve } from 'node:path'; import { Logger } from '@nestjs/common'; import { Readable } from 'stream'; -import { FsStorageConfig } from '../../../config/storage'; +import { FsStorageConfig } from '../../config/storage'; import { BlobInputType, GetObjectMetadata, diff --git a/packages/backend/server/src/modules/storage/providers/index.ts b/packages/backend/server/src/fundamentals/storage/providers/index.ts similarity index 93% rename from packages/backend/server/src/modules/storage/providers/index.ts rename to packages/backend/server/src/fundamentals/storage/providers/index.ts index bfcc51cda2..62c6d81d01 100644 --- a/packages/backend/server/src/modules/storage/providers/index.ts +++ b/packages/backend/server/src/fundamentals/storage/providers/index.ts @@ -1,4 +1,4 @@ -import { AFFiNEStorageConfig, Storages } from '../../../config/storage'; +import { AFFiNEStorageConfig, Storages } from '../../config/storage'; import { FsStorageProvider } from './fs'; import type { StorageProvider } from './provider'; import { R2StorageProvider } from './r2'; diff --git a/packages/backend/server/src/modules/storage/providers/provider.ts b/packages/backend/server/src/fundamentals/storage/providers/provider.ts similarity index 94% rename from packages/backend/server/src/modules/storage/providers/provider.ts rename to packages/backend/server/src/fundamentals/storage/providers/provider.ts index 6e83189184..0c9114b657 100644 --- a/packages/backend/server/src/modules/storage/providers/provider.ts +++ b/packages/backend/server/src/fundamentals/storage/providers/provider.ts @@ -1,6 +1,6 @@ import type { Readable } from 'node:stream'; -import { StorageProviderType } from '../../../config'; +import { StorageProviderType } from '../../config'; export interface GetObjectMetadata { /** diff --git a/packages/backend/server/src/modules/storage/providers/r2.ts b/packages/backend/server/src/fundamentals/storage/providers/r2.ts similarity index 89% rename from packages/backend/server/src/modules/storage/providers/r2.ts rename to packages/backend/server/src/fundamentals/storage/providers/r2.ts index 9d9f832bec..6e2b9cd5b0 100644 --- a/packages/backend/server/src/modules/storage/providers/r2.ts +++ b/packages/backend/server/src/fundamentals/storage/providers/r2.ts @@ -1,6 +1,6 @@ import { Logger } from '@nestjs/common'; -import { R2StorageConfig } from '../../../config/storage'; +import { R2StorageConfig } from '../../config/storage'; import { S3StorageProvider } from './s3'; export class R2StorageProvider extends S3StorageProvider { diff --git a/packages/backend/server/src/modules/storage/providers/s3.ts b/packages/backend/server/src/fundamentals/storage/providers/s3.ts similarity index 98% rename from packages/backend/server/src/modules/storage/providers/s3.ts rename to packages/backend/server/src/fundamentals/storage/providers/s3.ts index f1f811998d..09d8d83cd3 100644 --- a/packages/backend/server/src/modules/storage/providers/s3.ts +++ b/packages/backend/server/src/fundamentals/storage/providers/s3.ts @@ -11,7 +11,7 @@ import { } from '@aws-sdk/client-s3'; import { Logger } from '@nestjs/common'; -import { S3StorageConfig } from '../../../config/storage'; +import { S3StorageConfig } from '../../config/storage'; import { BlobInputType, GetObjectMetadata, diff --git a/packages/backend/server/src/modules/storage/providers/utils.ts b/packages/backend/server/src/fundamentals/storage/providers/utils.ts similarity index 100% rename from packages/backend/server/src/modules/storage/providers/utils.ts rename to packages/backend/server/src/fundamentals/storage/providers/utils.ts diff --git a/packages/backend/server/src/throttler.ts b/packages/backend/server/src/fundamentals/throttler/index.ts similarity index 93% rename from packages/backend/server/src/throttler.ts rename to packages/backend/server/src/fundamentals/throttler/index.ts index a94fe9355b..a7c4ee4ee6 100644 --- a/packages/backend/server/src/throttler.ts +++ b/packages/backend/server/src/fundamentals/throttler/index.ts @@ -9,9 +9,9 @@ import { } from '@nestjs/throttler'; import { ThrottlerStorageRedisService } from 'nestjs-throttler-storage-redis'; -import { ThrottlerCache } from './cache'; -import { Config } from './config'; -import { getRequestResponseFromContext } from './utils/nestjs'; +import { ThrottlerCache } from '../cache'; +import { Config } from '../config'; +import { getRequestResponseFromContext } from '../utils/request'; @Injectable() class CustomOptionsFactory implements ThrottlerOptionsFactory { diff --git a/packages/backend/server/src/utils/nestjs.ts b/packages/backend/server/src/fundamentals/utils/request.ts similarity index 100% rename from packages/backend/server/src/utils/nestjs.ts rename to packages/backend/server/src/fundamentals/utils/request.ts diff --git a/packages/backend/server/src/utils/types.ts b/packages/backend/server/src/fundamentals/utils/types.ts similarity index 71% rename from packages/backend/server/src/utils/types.ts rename to packages/backend/server/src/fundamentals/utils/types.ts index 10079af2b5..09c96b9d77 100644 --- a/packages/backend/server/src/utils/types.ts +++ b/packages/backend/server/src/fundamentals/utils/types.ts @@ -1,3 +1,16 @@ +import { Readable } from 'node:stream'; + +export type ConstructorOf = { + new (): T; +}; + +export function ApplyType(): ConstructorOf { + // @ts-expect-error used to fake the type of config + return class Inner implements T { + constructor() {} + }; +} + export type DeepPartial = T extends Array ? DeepPartial[] : T extends ReadonlyArray @@ -40,3 +53,10 @@ export type LeafPaths< : never; }[keyof T] : never; + +export interface FileUpload { + filename: string; + mimetype: string; + encoding: string; + createReadStream: () => Readable; +} diff --git a/packages/backend/server/src/fundamentals/websocket/index.ts b/packages/backend/server/src/fundamentals/websocket/index.ts new file mode 100644 index 0000000000..f772e0ea2c --- /dev/null +++ b/packages/backend/server/src/fundamentals/websocket/index.ts @@ -0,0 +1 @@ +export { RedisIoAdapter } from './redis-adapter'; diff --git a/packages/backend/server/src/modules/sync/redis-adapter.ts b/packages/backend/server/src/fundamentals/websocket/redis-adapter.ts similarity index 100% rename from packages/backend/server/src/modules/sync/redis-adapter.ts rename to packages/backend/server/src/fundamentals/websocket/redis-adapter.ts diff --git a/packages/backend/server/src/index.ts b/packages/backend/server/src/index.ts index 6333e1798d..60a8ce3fb8 100644 --- a/packages/backend/server/src/index.ts +++ b/packages/backend/server/src/index.ts @@ -9,21 +9,16 @@ import cookieParser from 'cookie-parser'; import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; import { AppModule } from './app'; -import { Config } from './config'; +import { RedisIoAdapter } from './fundamentals'; +import { CacheRedis } from './fundamentals/cache/redis'; import { ExceptionLogger } from './middleware/exception-logger'; import { serverTimingAndCache } from './middleware/timing'; -import { RedisIoAdapter } from './modules/sync/redis-adapter'; -import { CacheRedis } from './cache/redis'; -const { NODE_ENV, AFFINE_ENV } = process.env; const app = await NestFactory.create(AppModule, { cors: true, rawBody: true, bodyParser: true, - logger: - NODE_ENV !== 'production' || AFFINE_ENV !== 'production' - ? ['verbose'] - : ['log'], + logger: AFFiNE.affine.stable ? ['log'] : ['verbose'], }); app.use(serverTimingAndCache); @@ -39,18 +34,16 @@ app.use( app.useGlobalFilters(new ExceptionLogger()); app.use(cookieParser()); -const config = app.get(Config); - -const host = config.node.prod ? '0.0.0.0' : 'localhost'; -const port = config.port ?? 3010; - -if (!config.node.test && config.redis.enabled) { +if (AFFiNE.redis.enabled) { const redis = app.get(CacheRedis, { strict: false }); const redisIoAdapter = new RedisIoAdapter(app); await redisIoAdapter.connectToRedis(redis); app.useWebSocketAdapter(redisIoAdapter); } -await app.listen(port, host); +await app.listen(AFFiNE.port, AFFiNE.host); -console.log(`Listening on http://${host}:${port}`); +console.log( + `AFFiNE Server has been started on http://${AFFiNE.host}:${AFFiNE.port}.` +); +console.log(`And the public server should be recognized as ${AFFiNE.baseUrl}`); diff --git a/packages/backend/server/src/middleware/exception-logger.ts b/packages/backend/server/src/middleware/exception-logger.ts index 4e4c97d3c8..8630c118d9 100644 --- a/packages/backend/server/src/middleware/exception-logger.ts +++ b/packages/backend/server/src/middleware/exception-logger.ts @@ -9,9 +9,10 @@ import { import { GqlContextType } from '@nestjs/graphql'; import { Request, Response } from 'express'; -import { REQUEST_ID } from '../constants'; const TrivialExceptions = [NotFoundException]; +export const REQUEST_ID_HEADER = 'x-request-id'; + @Catch() export class ExceptionLogger implements ExceptionFilter { private readonly logger = new Logger('ExceptionLogger'); @@ -21,7 +22,7 @@ export class ExceptionLogger implements ExceptionFilter { const ctx = host.switchToHttp(); const request = ctx.getRequest(); - const requestId = request?.header(REQUEST_ID); + const requestId = request?.header(REQUEST_ID_HEADER); const shouldVerboseLog = !TrivialExceptions.some( e => exception instanceof e diff --git a/packages/backend/server/src/modules/auth/guard.ts b/packages/backend/server/src/modules/auth/guard.ts index 7e311159d2..eab1577677 100644 --- a/packages/backend/server/src/modules/auth/guard.ts +++ b/packages/backend/server/src/modules/auth/guard.ts @@ -10,8 +10,10 @@ import { Reflector } from '@nestjs/core'; import type { NextAuthOptions } from 'next-auth'; import { AuthHandler } from 'next-auth/core'; -import { PrismaService } from '../../prisma'; -import { getRequestResponseFromContext } from '../../utils/nestjs'; +import { + getRequestResponseFromContext, + PrismaService, +} from '../../fundamentals'; import { NextAuthOptionsProvide } from './next-auth-options'; import { AuthService } from './service'; diff --git a/packages/backend/server/src/modules/auth/index.ts b/packages/backend/server/src/modules/auth/index.ts index 95dc5405c7..b5b72ab31e 100644 --- a/packages/backend/server/src/modules/auth/index.ts +++ b/packages/backend/server/src/modules/auth/index.ts @@ -1,7 +1,5 @@ import { Global, Module } from '@nestjs/common'; -import { SessionModule } from '../../session'; -import { MAILER, MailService } from './mailer'; import { NextAuthController } from './next-auth.controller'; import { NextAuthOptionsProvider } from './next-auth-options'; import { AuthResolver } from './resolver'; @@ -9,18 +7,12 @@ import { AuthService } from './service'; @Global() @Module({ - imports: [SessionModule], - providers: [ - AuthService, - AuthResolver, - NextAuthOptionsProvider, - MAILER, - MailService, - ], - exports: [AuthService, NextAuthOptionsProvider, MailService], + providers: [AuthService, AuthResolver, NextAuthOptionsProvider], + exports: [AuthService, NextAuthOptionsProvider], controllers: [NextAuthController], }) export class AuthModule {} export * from './guard'; export { TokenType } from './resolver'; +export { AuthService }; diff --git a/packages/backend/server/src/modules/auth/mailer/index.ts b/packages/backend/server/src/modules/auth/mailer/index.ts deleted file mode 100644 index 9c53bf57ca..0000000000 --- a/packages/backend/server/src/modules/auth/mailer/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { MailService } from './mail.service'; -export { MAILER } from './mailer'; diff --git a/packages/backend/server/src/modules/auth/next-auth-options.ts b/packages/backend/server/src/modules/auth/next-auth-options.ts index 841f244249..261ad7d8ec 100644 --- a/packages/backend/server/src/modules/auth/next-auth-options.ts +++ b/packages/backend/server/src/modules/auth/next-auth-options.ts @@ -8,12 +8,14 @@ import Email from 'next-auth/providers/email'; import Github from 'next-auth/providers/github'; import Google from 'next-auth/providers/google'; -import { Config } from '../../config'; -import { PrismaService } from '../../prisma'; -import { SessionService } from '../../session'; +import { + Config, + MailService, + PrismaService, + SessionService, +} from '../../fundamentals'; import { FeatureType } from '../features'; import { Quota_FreePlanV1 } from '../quota'; -import { MailService } from './mailer'; import { decode, encode, diff --git a/packages/backend/server/src/modules/auth/next-auth.controller.ts b/packages/backend/server/src/modules/auth/next-auth.controller.ts index fee533ecea..a58d89c117 100644 --- a/packages/backend/server/src/modules/auth/next-auth.controller.ts +++ b/packages/backend/server/src/modules/auth/next-auth.controller.ts @@ -22,11 +22,14 @@ import { nanoid } from 'nanoid'; import type { AuthAction, CookieOption, NextAuthOptions } from 'next-auth'; import { AuthHandler } from 'next-auth/core'; -import { Config } from '../../config'; -import { metrics } from '../../metrics'; -import { PrismaService } from '../../prisma/service'; -import { SessionService } from '../../session'; -import { AuthThrottlerGuard, Throttle } from '../../throttler'; +import { + AuthThrottlerGuard, + Config, + metrics, + PrismaService, + SessionService, + Throttle, +} from '../../fundamentals'; import { NextAuthOptionsProvide } from './next-auth-options'; import { AuthService } from './service'; diff --git a/packages/backend/server/src/modules/auth/resolver.ts b/packages/backend/server/src/modules/auth/resolver.ts index aac7b4e673..8ab9ef089d 100644 --- a/packages/backend/server/src/modules/auth/resolver.ts +++ b/packages/backend/server/src/modules/auth/resolver.ts @@ -16,9 +16,12 @@ import { import type { Request } from 'express'; import { nanoid } from 'nanoid'; -import { Config } from '../../config'; -import { SessionService } from '../../session'; -import { CloudThrottlerGuard, Throttle } from '../../throttler'; +import { + CloudThrottlerGuard, + Config, + SessionService, + Throttle, +} from '../../fundamentals'; import { UserType } from '../users'; import { Auth, CurrentUser } from './guard'; import { AuthService } from './service'; diff --git a/packages/backend/server/src/modules/auth/service.ts b/packages/backend/server/src/modules/auth/service.ts index 59239cb799..306ff4e090 100644 --- a/packages/backend/server/src/modules/auth/service.ts +++ b/packages/backend/server/src/modules/auth/service.ts @@ -11,11 +11,13 @@ import { Algorithm, sign, verify as jwtVerify } from '@node-rs/jsonwebtoken'; import type { User } from '@prisma/client'; import { nanoid } from 'nanoid'; -import { Config } from '../../config'; -import { PrismaService } from '../../prisma'; -import { verifyChallengeResponse } from '../../storage'; +import { + Config, + MailService, + PrismaService, + verifyChallengeResponse, +} from '../../fundamentals'; import { Quota_FreePlanV1 } from '../quota'; -import { MailService } from './mailer'; export type UserClaim = Pick< User, @@ -192,6 +194,7 @@ export class AuthService { name, email, password: hashedPassword, + // TODO(@forehalo): handle in event system features: { create: { reason: 'created by api sign up', diff --git a/packages/backend/server/src/modules/auth/utils/jwt.ts b/packages/backend/server/src/modules/auth/utils/jwt.ts index 82b9bd42c9..929a26f1ad 100644 --- a/packages/backend/server/src/modules/auth/utils/jwt.ts +++ b/packages/backend/server/src/modules/auth/utils/jwt.ts @@ -4,8 +4,7 @@ import { BadRequestException } from '@nestjs/common'; import { Algorithm, sign, verify as jwtVerify } from '@node-rs/jsonwebtoken'; import { JWT } from 'next-auth/jwt'; -import { Config } from '../../../config'; -import { PrismaService } from '../../../prisma'; +import { Config, PrismaService } from '../../../fundamentals'; import { getUtcTimestamp, UserClaim } from '../service'; export const jwtEncode = async ( diff --git a/packages/backend/server/src/modules/auth/utils/send-mail.ts b/packages/backend/server/src/modules/auth/utils/send-mail.ts index c2cf89b578..9cc349bd53 100644 --- a/packages/backend/server/src/modules/auth/utils/send-mail.ts +++ b/packages/backend/server/src/modules/auth/utils/send-mail.ts @@ -2,9 +2,7 @@ import { Logger } from '@nestjs/common'; import { nanoid } from 'nanoid'; import type { SendVerificationRequestParams } from 'next-auth/providers/email'; -import { Config } from '../../../config'; -import { SessionService } from '../../../session'; -import { MailService } from '../mailer'; +import { Config, MailService, SessionService } from '../../../fundamentals'; export async function sendVerificationRequest( config: Config, diff --git a/packages/backend/server/src/modules/config.ts b/packages/backend/server/src/modules/config.ts index 7ebd5b5a4c..7286e496d8 100644 --- a/packages/backend/server/src/modules/config.ts +++ b/packages/backend/server/src/modules/config.ts @@ -1,8 +1,6 @@ import { Module } from '@nestjs/common'; import { Field, ObjectType, Query } from '@nestjs/graphql'; -import { SERVER_FLAVOR } from '../config'; - @ObjectType() export class ServerConfigType { @Field({ description: 'server version' }) @@ -22,7 +20,7 @@ export class ServerConfigResolver { serverConfig(): ServerConfigType { return { version: AFFiNE.version, - flavor: SERVER_FLAVOR, + flavor: AFFiNE.flavor, baseUrl: AFFiNE.baseUrl, }; } diff --git a/packages/backend/server/src/modules/doc/history.ts b/packages/backend/server/src/modules/doc/history.ts index ab6a5dcc7d..303b6fd56e 100644 --- a/packages/backend/server/src/modules/doc/history.ts +++ b/packages/backend/server/src/modules/doc/history.ts @@ -3,10 +3,13 @@ import { isDeepStrictEqual } from 'node:util'; import { Injectable, Logger } from '@nestjs/common'; import { Cron, CronExpression } from '@nestjs/schedule'; -import { Config } from '../../config'; -import { type EventPayload, OnEvent } from '../../event'; -import { metrics } from '../../metrics'; -import { PrismaService } from '../../prisma'; +import { + Config, + type EventPayload, + metrics, + OnEvent, + PrismaService, +} from '../../fundamentals'; import { QuotaService } from '../quota'; import { Permission } from '../workspaces/types'; import { isEmptyBuffer } from './manager'; diff --git a/packages/backend/server/src/modules/doc/manager.ts b/packages/backend/server/src/modules/doc/manager.ts index 4ac2424f92..9484fc50cc 100644 --- a/packages/backend/server/src/modules/doc/manager.ts +++ b/packages/backend/server/src/modules/doc/manager.ts @@ -16,12 +16,16 @@ import { transact, } from 'yjs'; -import { Cache } from '../../cache'; -import { Config } from '../../config'; -import { EventEmitter, type EventPayload, OnEvent } from '../../event'; -import { metrics } from '../../metrics/metrics'; -import { PrismaService } from '../../prisma'; -import { mergeUpdatesInApplyWay as jwstMergeUpdates } from '../../storage'; +import { + Cache, + Config, + EventEmitter, + type EventPayload, + mergeUpdatesInApplyWay as jwstMergeUpdates, + metrics, + OnEvent, + PrismaService, +} from '../../fundamentals'; function compare(yBinary: Buffer, jwstBinary: Buffer, strict = false): boolean { if (yBinary.equals(jwstBinary)) { diff --git a/packages/backend/server/src/modules/features/feature.ts b/packages/backend/server/src/modules/features/feature.ts index 3945790b66..99c78a4eba 100644 --- a/packages/backend/server/src/modules/features/feature.ts +++ b/packages/backend/server/src/modules/features/feature.ts @@ -1,4 +1,5 @@ -import { PrismaService } from '../../prisma'; +import { PrismaClient } from '@prisma/client'; + import { Feature, FeatureSchema, FeatureType } from './types'; class FeatureConfig { @@ -66,7 +67,7 @@ export type FeatureConfigType = InstanceType< const FeatureCache = new Map>(); -export async function getFeature(prisma: PrismaService, featureId: number) { +export async function getFeature(prisma: PrismaClient, featureId: number) { const cachedQuota = FeatureCache.get(featureId); if (cachedQuota) { diff --git a/packages/backend/server/src/modules/features/index.ts b/packages/backend/server/src/modules/features/index.ts index ee8d61a283..d29a7dbfe7 100644 --- a/packages/backend/server/src/modules/features/index.ts +++ b/packages/backend/server/src/modules/features/index.ts @@ -1,6 +1,5 @@ import { Module } from '@nestjs/common'; -import { PrismaService } from '../../prisma'; import { FeatureManagementService } from './management'; import { FeatureService } from './service'; @@ -18,4 +17,4 @@ export class FeatureModule {} export { type CommonFeature, commonFeatureSchema } from './types'; export { FeatureKind, Features, FeatureType } from './types'; -export { FeatureManagementService, FeatureService, PrismaService }; +export { FeatureManagementService, FeatureService }; diff --git a/packages/backend/server/src/modules/features/management.ts b/packages/backend/server/src/modules/features/management.ts index 57766fc519..75f11ba1e3 100644 --- a/packages/backend/server/src/modules/features/management.ts +++ b/packages/backend/server/src/modules/features/management.ts @@ -1,7 +1,6 @@ import { Injectable, Logger } from '@nestjs/common'; -import { Config } from '../../config'; -import { PrismaService } from '../../prisma'; +import { Config, PrismaService } from '../../fundamentals'; import { FeatureService } from './service'; import { FeatureType } from './types'; diff --git a/packages/backend/server/src/modules/features/service.ts b/packages/backend/server/src/modules/features/service.ts index b7bf53c023..23b442492e 100644 --- a/packages/backend/server/src/modules/features/service.ts +++ b/packages/backend/server/src/modules/features/service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { PrismaService } from '../../prisma'; +import { PrismaService } from '../../fundamentals'; import { UserType } from '../users/types'; import { WorkspaceType } from '../workspaces/types'; import { FeatureConfigType, getFeature } from './feature'; diff --git a/packages/backend/server/src/modules/index.ts b/packages/backend/server/src/modules/index.ts index 0522f3e2dd..fca3f4e9d6 100644 --- a/packages/backend/server/src/modules/index.ts +++ b/packages/backend/server/src/modules/index.ts @@ -4,8 +4,7 @@ import { DynamicModule, Type } from '@nestjs/common'; import { ScheduleModule } from '@nestjs/schedule'; import { ServeStaticModule } from '@nestjs/serve-static'; -import { SERVER_FLAVOR } from '../config'; -import { GqlModule } from '../graphql.module'; +import { GqlModule } from '../fundamentals/graphql'; import { ServerConfigModule } from './config'; import { DocModule } from './doc'; import { PaymentModule } from './payment'; @@ -18,7 +17,7 @@ import { WorkspaceModule } from './workspaces'; const BusinessModules: (Type | DynamicModule)[] = []; -switch (SERVER_FLAVOR) { +switch (AFFiNE.flavor) { case 'sync': BusinessModules.push(SyncModule, DocModule); break; @@ -68,4 +67,4 @@ switch (SERVER_FLAVOR) { break; } -export { BusinessModules, SERVER_FLAVOR }; +export { BusinessModules }; diff --git a/packages/backend/server/src/modules/payment/resolver.ts b/packages/backend/server/src/modules/payment/resolver.ts index 282d63acaa..be95d24d10 100644 --- a/packages/backend/server/src/modules/payment/resolver.ts +++ b/packages/backend/server/src/modules/payment/resolver.ts @@ -16,8 +16,7 @@ import type { User, UserInvoice, UserSubscription } from '@prisma/client'; import { GraphQLError } from 'graphql'; import { groupBy } from 'lodash-es'; -import { Config } from '../../config'; -import { PrismaService } from '../../prisma'; +import { Config, PrismaService } from '../../fundamentals'; import { Auth, CurrentUser, Public } from '../auth'; import { UserType } from '../users'; import { diff --git a/packages/backend/server/src/modules/payment/service.ts b/packages/backend/server/src/modules/payment/service.ts index e858e12529..137c3585bd 100644 --- a/packages/backend/server/src/modules/payment/service.ts +++ b/packages/backend/server/src/modules/payment/service.ts @@ -9,8 +9,7 @@ import type { } from '@prisma/client'; import Stripe from 'stripe'; -import { Config } from '../../config'; -import { PrismaService } from '../../prisma'; +import { Config, PrismaService } from '../../fundamentals'; import { FeatureManagementService } from '../features'; import { QuotaService, QuotaType } from '../quota'; import { ScheduleManager } from './schedule'; diff --git a/packages/backend/server/src/modules/payment/stripe.ts b/packages/backend/server/src/modules/payment/stripe.ts index 4538471121..4f17d8ded3 100644 --- a/packages/backend/server/src/modules/payment/stripe.ts +++ b/packages/backend/server/src/modules/payment/stripe.ts @@ -2,7 +2,7 @@ import { FactoryProvider } from '@nestjs/common'; import { omit } from 'lodash-es'; import Stripe from 'stripe'; -import { Config } from '../../config'; +import { Config } from '../../fundamentals'; export const StripeProvider: FactoryProvider = { provide: Stripe, diff --git a/packages/backend/server/src/modules/payment/webhook.ts b/packages/backend/server/src/modules/payment/webhook.ts index 13f692132d..fa428e87e0 100644 --- a/packages/backend/server/src/modules/payment/webhook.ts +++ b/packages/backend/server/src/modules/payment/webhook.ts @@ -10,7 +10,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter'; import type { Request } from 'express'; import Stripe from 'stripe'; -import { Config } from '../../config'; +import { Config } from '../../fundamentals'; @Controller('/api/stripe') export class StripeWebhook { diff --git a/packages/backend/server/src/modules/quota/quota.ts b/packages/backend/server/src/modules/quota/quota.ts index caf7c570eb..990eb30479 100644 --- a/packages/backend/server/src/modules/quota/quota.ts +++ b/packages/backend/server/src/modules/quota/quota.ts @@ -1,4 +1,4 @@ -import { PrismaService } from '../../prisma'; +import { PrismaService } from '../../fundamentals'; import { formatDate, formatSize, Quota, QuotaSchema } from './types'; const QuotaCache = new Map(); diff --git a/packages/backend/server/src/modules/quota/service.ts b/packages/backend/server/src/modules/quota/service.ts index cfafbbc3f0..5f05a59e57 100644 --- a/packages/backend/server/src/modules/quota/service.ts +++ b/packages/backend/server/src/modules/quota/service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { PrismaService } from '../../prisma'; +import { PrismaService } from '../../fundamentals'; import { FeatureKind } from '../features'; import { QuotaConfig } from './quota'; import { QuotaType } from './types'; diff --git a/packages/backend/server/src/modules/storage/wrappers/avatar.ts b/packages/backend/server/src/modules/storage/wrappers/avatar.ts index 6622631bd8..c8598b65bf 100644 --- a/packages/backend/server/src/modules/storage/wrappers/avatar.ts +++ b/packages/backend/server/src/modules/storage/wrappers/avatar.ts @@ -1,18 +1,17 @@ import { Injectable } from '@nestjs/common'; -import { AFFiNEStorageConfig, Config } from '../../../config'; -import { type EventPayload, OnEvent } from '../../../event'; -import { +import type { BlobInputType, - createStorageProvider, + EventPayload, PutObjectMetadata, StorageProvider, -} from '../providers'; +} from '../../../fundamentals'; +import { Config, createStorageProvider, OnEvent } from '../../../fundamentals'; @Injectable() export class AvatarStorage { public readonly provider: StorageProvider; - private readonly storageConfig: AFFiNEStorageConfig['storages']['avatar']; + private readonly storageConfig: Config['storage']['storages']['avatar']; constructor(private readonly config: Config) { this.provider = createStorageProvider(this.config.storage, 'avatar'); diff --git a/packages/backend/server/src/modules/storage/wrappers/blob.ts b/packages/backend/server/src/modules/storage/wrappers/blob.ts index 0eca2d4375..80b2b9bc70 100644 --- a/packages/backend/server/src/modules/storage/wrappers/blob.ts +++ b/packages/backend/server/src/modules/storage/wrappers/blob.ts @@ -3,15 +3,19 @@ import { Readable } from 'node:stream'; import type { Storage } from '@affine/storage'; import { Injectable, OnModuleInit } from '@nestjs/common'; -import { Config } from '../../../config'; -import { EventEmitter, type EventPayload, OnEvent } from '../../../event'; -import { OctoBaseStorageModule } from '../../../storage'; -import { +import type { BlobInputType, - createStorageProvider, + EventPayload, StorageProvider, -} from '../providers'; -import { toBuffer } from '../providers/utils'; +} from '../../../fundamentals'; +import { + Config, + createStorageProvider, + EventEmitter, + OctoBaseStorageModule, + OnEvent, + toBuffer, +} from '../../../fundamentals'; @Injectable() export class WorkspaceBlobStorage implements OnModuleInit { diff --git a/packages/backend/server/src/modules/sync/events/events.gateway.ts b/packages/backend/server/src/modules/sync/events/events.gateway.ts index abc1a946d0..5813bf03f8 100644 --- a/packages/backend/server/src/modules/sync/events/events.gateway.ts +++ b/packages/backend/server/src/modules/sync/events/events.gateway.ts @@ -11,12 +11,11 @@ import { import { Server, Socket } from 'socket.io'; import { encodeStateAsUpdate, encodeStateVector } from 'yjs'; -import { metrics } from '../../../metrics'; -import { CallTimer } from '../../../metrics/utils'; -import { DocID } from '../../../utils/doc'; +import { CallTimer, metrics } from '../../../fundamentals'; import { Auth, CurrentUser } from '../../auth'; import { DocManager } from '../../doc'; import { UserType } from '../../users'; +import { DocID } from '../../utils/doc'; import { PermissionService } from '../../workspaces/permission'; import { Permission } from '../../workspaces/types'; import { diff --git a/packages/backend/server/src/modules/sync/utils.ts b/packages/backend/server/src/modules/sync/utils.ts deleted file mode 100644 index ca13e99719..0000000000 --- a/packages/backend/server/src/modules/sync/utils.ts +++ /dev/null @@ -1,11 +0,0 @@ -export function assertExists( - val: T | null | undefined, - message: string | Error = 'val does not exist' -): asserts val is T { - if (val === null || val === undefined) { - if (message instanceof Error) { - throw message; - } - throw new Error(message); - } -} diff --git a/packages/backend/server/src/modules/users/management.ts b/packages/backend/server/src/modules/users/management.ts index a740466b98..08e3bd4def 100644 --- a/packages/backend/server/src/modules/users/management.ts +++ b/packages/backend/server/src/modules/users/management.ts @@ -5,7 +5,7 @@ import { } from '@nestjs/common'; import { Args, Context, Int, Mutation, Query, Resolver } from '@nestjs/graphql'; -import { CloudThrottlerGuard, Throttle } from '../../throttler'; +import { CloudThrottlerGuard, Throttle } from '../../fundamentals'; import { Auth, CurrentUser } from '../auth/guard'; import { AuthService } from '../auth/service'; import { FeatureManagementService } from '../features'; diff --git a/packages/backend/server/src/modules/users/resolver.ts b/packages/backend/server/src/modules/users/resolver.ts index 94cbc2c81d..12842820ae 100644 --- a/packages/backend/server/src/modules/users/resolver.ts +++ b/packages/backend/server/src/modules/users/resolver.ts @@ -11,10 +11,13 @@ import type { User } from '@prisma/client'; import { GraphQLError } from 'graphql'; import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs'; -import { EventEmitter } from '../../event'; -import { PrismaService } from '../../prisma/service'; -import { CloudThrottlerGuard, Throttle } from '../../throttler'; -import type { FileUpload } from '../../types'; +import { + CloudThrottlerGuard, + EventEmitter, + type FileUpload, + PrismaService, + Throttle, +} from '../../fundamentals'; import { Auth, CurrentUser, Public, Publicable } from '../auth/guard'; import { FeatureManagementService } from '../features'; import { QuotaService } from '../quota'; diff --git a/packages/backend/server/src/modules/users/users.ts b/packages/backend/server/src/modules/users/users.ts index 6215ea4433..84a889fd1c 100644 --- a/packages/backend/server/src/modules/users/users.ts +++ b/packages/backend/server/src/modules/users/users.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { PrismaService } from '../../prisma'; +import { PrismaService } from '../../fundamentals'; @Injectable() export class UsersService { diff --git a/packages/backend/server/src/utils/__tests__/doc.spec.ts b/packages/backend/server/src/modules/utils/__tests__/doc.spec.ts similarity index 100% rename from packages/backend/server/src/utils/__tests__/doc.spec.ts rename to packages/backend/server/src/modules/utils/__tests__/doc.spec.ts diff --git a/packages/backend/server/src/utils/doc.ts b/packages/backend/server/src/modules/utils/doc.ts similarity index 100% rename from packages/backend/server/src/utils/doc.ts rename to packages/backend/server/src/modules/utils/doc.ts diff --git a/packages/backend/server/src/modules/workspaces/controller.ts b/packages/backend/server/src/modules/workspaces/controller.ts index e38ed68071..e6528fb7ff 100644 --- a/packages/backend/server/src/modules/workspaces/controller.ts +++ b/packages/backend/server/src/modules/workspaces/controller.ts @@ -9,13 +9,12 @@ import { } from '@nestjs/common'; import type { Response } from 'express'; -import { CallTimer } from '../../metrics'; -import { PrismaService } from '../../prisma'; -import { DocID } from '../../utils/doc'; +import { CallTimer, PrismaService } from '../../fundamentals'; import { Auth, CurrentUser, Publicable } from '../auth'; import { DocHistoryManager, DocManager } from '../doc'; import { WorkspaceBlobStorage } from '../storage'; import { UserType } from '../users'; +import { DocID } from '../utils/doc'; import { PermissionService, PublicPageMode } from './permission'; import { Permission } from './types'; diff --git a/packages/backend/server/src/modules/workspaces/management.ts b/packages/backend/server/src/modules/workspaces/management.ts index b5a74f94ac..635c606499 100644 --- a/packages/backend/server/src/modules/workspaces/management.ts +++ b/packages/backend/server/src/modules/workspaces/management.ts @@ -9,7 +9,7 @@ import { Resolver, } from '@nestjs/graphql'; -import { CloudThrottlerGuard, Throttle } from '../../throttler'; +import { CloudThrottlerGuard, Throttle } from '../../fundamentals'; import { Auth, CurrentUser } from '../auth'; import { FeatureManagementService, FeatureType } from '../features'; import { UserType } from '../users'; diff --git a/packages/backend/server/src/modules/workspaces/permission.ts b/packages/backend/server/src/modules/workspaces/permission.ts index 1248075b1c..2f955131ae 100644 --- a/packages/backend/server/src/modules/workspaces/permission.ts +++ b/packages/backend/server/src/modules/workspaces/permission.ts @@ -1,7 +1,7 @@ import { ForbiddenException, Injectable } from '@nestjs/common'; import { Prisma } from '@prisma/client'; -import { PrismaService } from '../../prisma'; +import { PrismaService } from '../../fundamentals'; import { Permission } from './types'; export enum PublicPageMode { diff --git a/packages/backend/server/src/modules/workspaces/resolvers/blob.ts b/packages/backend/server/src/modules/workspaces/resolvers/blob.ts index ae19169dcb..28d3951752 100644 --- a/packages/backend/server/src/modules/workspaces/resolvers/blob.ts +++ b/packages/backend/server/src/modules/workspaces/resolvers/blob.ts @@ -12,9 +12,12 @@ import { import { GraphQLError } from 'graphql'; import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs'; -import { MakeCache, PreventCache } from '../../../cache'; -import { CloudThrottlerGuard } from '../../../throttler'; -import type { FileUpload } from '../../../types'; +import { + CloudThrottlerGuard, + type FileUpload, + MakeCache, + PreventCache, +} from '../../../fundamentals'; import { Auth, CurrentUser } from '../../auth'; import { FeatureManagementService, FeatureType } from '../../features'; import { QuotaManagementService } from '../../quota'; diff --git a/packages/backend/server/src/modules/workspaces/resolvers/history.ts b/packages/backend/server/src/modules/workspaces/resolvers/history.ts index e7940f9df1..3b8475f3a4 100644 --- a/packages/backend/server/src/modules/workspaces/resolvers/history.ts +++ b/packages/backend/server/src/modules/workspaces/resolvers/history.ts @@ -12,11 +12,11 @@ import { } from '@nestjs/graphql'; import type { SnapshotHistory } from '@prisma/client'; -import { CloudThrottlerGuard } from '../../../throttler'; -import { DocID } from '../../../utils/doc'; +import { CloudThrottlerGuard } from '../../../fundamentals'; import { Auth, CurrentUser } from '../../auth'; -import { DocHistoryManager } from '../../doc/history'; +import { DocHistoryManager } from '../../doc'; import { UserType } from '../../users'; +import { DocID } from '../../utils/doc'; import { PermissionService } from '../permission'; import { Permission, WorkspaceType } from '../types'; diff --git a/packages/backend/server/src/modules/workspaces/resolvers/page.ts b/packages/backend/server/src/modules/workspaces/resolvers/page.ts index c37bc4049c..26454323af 100644 --- a/packages/backend/server/src/modules/workspaces/resolvers/page.ts +++ b/packages/backend/server/src/modules/workspaces/resolvers/page.ts @@ -11,11 +11,10 @@ import { } from '@nestjs/graphql'; import type { WorkspacePage as PrismaWorkspacePage } from '@prisma/client'; -import { PrismaService } from '../../../prisma'; -import { CloudThrottlerGuard } from '../../../throttler'; -import { DocID } from '../../../utils/doc'; +import { CloudThrottlerGuard, PrismaService } from '../../../fundamentals'; import { Auth, CurrentUser } from '../../auth'; import { UserType } from '../../users'; +import { DocID } from '../../utils/doc'; import { PermissionService, PublicPageMode } from '../permission'; import { Permission, WorkspaceType } from '../types'; diff --git a/packages/backend/server/src/modules/workspaces/resolvers/workspace.ts b/packages/backend/server/src/modules/workspaces/resolvers/workspace.ts index 328744e628..ddc7b5dd85 100644 --- a/packages/backend/server/src/modules/workspaces/resolvers/workspace.ts +++ b/packages/backend/server/src/modules/workspaces/resolvers/workspace.ts @@ -20,12 +20,15 @@ import { GraphQLError } from 'graphql'; import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs'; import { applyUpdate, Doc } from 'yjs'; -import { EventEmitter } from '../../../event'; -import { PrismaService } from '../../../prisma'; -import { CloudThrottlerGuard, Throttle } from '../../../throttler'; -import type { FileUpload } from '../../../types'; +import { + CloudThrottlerGuard, + EventEmitter, + type FileUpload, + MailService, + PrismaService, + Throttle, +} from '../../../fundamentals'; import { Auth, CurrentUser, Public } from '../../auth'; -import { MailService } from '../../auth/mailer'; import { AuthService } from '../../auth/service'; import { FeatureManagementService, FeatureType } from '../../features'; import { QuotaManagementService } from '../../quota'; diff --git a/packages/backend/server/src/prelude.ts b/packages/backend/server/src/prelude.ts index d70e339c2c..22a6315fed 100644 --- a/packages/backend/server/src/prelude.ts +++ b/packages/backend/server/src/prelude.ts @@ -1,8 +1,43 @@ import 'reflect-metadata'; -import './affine.env'; -import './affine'; -import './affine.config'; -if (process.env.NODE_ENV === 'development') { - console.log('AFFiNE Config:', globalThis.AFFiNE); +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { config } from 'dotenv'; + +import { + applyEnvToConfig, + getDefaultAFFiNEConfig, +} from './fundamentals/config'; + +async function load() { + // Initializing AFFiNE config + // + // 1. load dotenv file to `process.env` + // load `.env` under pwd + config(); + // load `.env` under user config folder + config({ + path: join(fileURLToPath(import.meta.url), '../config/.env'), + }); + + // 2. generate AFFiNE default config and assign to `globalThis.AFFiNE` + globalThis.AFFiNE = getDefaultAFFiNEConfig(); + + // TODO(@forehalo): + // Modules may contribute to ENV_MAP, figure out a good way to involve them instead of hardcoding in `./config/affine.env` + // 3. load env => config map to `globalThis.AFFiNE.ENV_MAP + await import('./config/affine.env'); + + // 4. apply `process.env` map overriding to `globalThis.AFFiNE` + applyEnvToConfig(globalThis.AFFiNE); + + // 5. load `./config/affine` to patch custom configs + await import('./config/affine'); + + if (process.env.NODE_ENV === 'development') { + console.log('AFFiNE Config:', JSON.stringify(globalThis.AFFiNE, null, 2)); + } } + +await load(); diff --git a/packages/backend/server/src/types.ts b/packages/backend/server/src/types.ts deleted file mode 100644 index 249d2b722b..0000000000 --- a/packages/backend/server/src/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Readable } from 'node:stream'; - -export interface FileUpload { - filename: string; - mimetype: string; - encoding: string; - createReadStream: () => Readable; -} - -export interface ReqContext { - req: Express.Request & { - res: Express.Response; - }; -} diff --git a/packages/backend/server/tests/app.e2e.ts b/packages/backend/server/tests/app.e2e.ts index 600737252e..acd38d68e5 100644 --- a/packages/backend/server/tests/app.e2e.ts +++ b/packages/backend/server/tests/app.e2e.ts @@ -4,14 +4,13 @@ import { randomUUID } from 'node:crypto'; import { Transformer } from '@napi-rs/image'; import type { INestApplication } from '@nestjs/common'; import { hashSync } from '@node-rs/argon2'; -import { type User } from '@prisma/client'; +import { PrismaClient, type User } from '@prisma/client'; import ava, { type TestFn } from 'ava'; import type { Express } from 'express'; import request from 'supertest'; import { AppModule } from '../src/app'; import { FeatureManagementService } from '../src/modules/features'; -import { PrismaService } from '../src/prisma/service'; import { createTestingApp } from './utils'; const gql = '/graphql'; @@ -52,7 +51,7 @@ test.beforeEach(async t => { imports: [AppModule], tapModule(builder) { builder - .overrideProvider(PrismaService) + .overrideProvider(PrismaClient) .useClass(FakePrisma) .overrideProvider(FeatureManagementService) .useValue({ canEarlyAccess: () => true }); diff --git a/packages/backend/server/tests/auth.e2e.ts b/packages/backend/server/tests/auth.e2e.ts index 440c189ca6..faa2de65e5 100644 --- a/packages/backend/server/tests/auth.e2e.ts +++ b/packages/backend/server/tests/auth.e2e.ts @@ -5,7 +5,7 @@ import { import type { INestApplication } from '@nestjs/common'; import ava, { type TestFn } from 'ava'; -import { MailService } from '../src/modules/auth/mailer'; +import { MailService } from '../src/fundamentals/mailer'; import { AuthService } from '../src/modules/auth/service'; import { changeEmail, diff --git a/packages/backend/server/tests/auth.spec.ts b/packages/backend/server/tests/auth.spec.ts index 906182ea2a..20d02ed66d 100644 --- a/packages/backend/server/tests/auth.spec.ts +++ b/packages/backend/server/tests/auth.spec.ts @@ -2,10 +2,13 @@ import { TestingModule } from '@nestjs/testing'; import test from 'ava'; -import { ConfigModule } from '../src/config'; +import { ConfigModule } from '../src/fundamentals/config'; +import { + mintChallengeResponse, + verifyChallengeResponse, +} from '../src/fundamentals/storage'; import { AuthResolver } from '../src/modules/auth/resolver'; import { AuthService } from '../src/modules/auth/service'; -import { mintChallengeResponse, verifyChallengeResponse } from '../src/storage'; import { createTestingModule } from './utils'; let authService: AuthService; diff --git a/packages/backend/server/tests/cache.spec.ts b/packages/backend/server/tests/cache.spec.ts index 6ffc2c2f00..ac18126ad9 100644 --- a/packages/backend/server/tests/cache.spec.ts +++ b/packages/backend/server/tests/cache.spec.ts @@ -1,8 +1,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import test from 'ava'; -import { Cache, CacheModule } from '../src/cache'; -import { ConfigModule } from '../src/config'; +import { Cache, CacheModule } from '../src/fundamentals/cache'; +import { ConfigModule } from '../src/fundamentals/config'; let cache: Cache; let module: TestingModule; diff --git a/packages/backend/server/tests/config.spec.ts b/packages/backend/server/tests/config.spec.ts index ad5c19138d..0081d8c8fa 100644 --- a/packages/backend/server/tests/config.spec.ts +++ b/packages/backend/server/tests/config.spec.ts @@ -1,7 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import test from 'ava'; -import { Config, ConfigModule } from '../src/config'; +import { Config, ConfigModule } from '../src/fundamentals/config'; let config: Config; let module: TestingModule; diff --git a/packages/backend/server/tests/doc.spec.ts b/packages/backend/server/tests/doc.spec.ts index d772514c24..08b39223ea 100644 --- a/packages/backend/server/tests/doc.spec.ts +++ b/packages/backend/server/tests/doc.spec.ts @@ -1,6 +1,7 @@ import { mock } from 'node:test'; import { TestingModule } from '@nestjs/testing'; +import { PrismaClient } from '@prisma/client'; import test from 'ava'; import * as Sinon from 'sinon'; import { @@ -10,11 +11,10 @@ import { encodeStateAsUpdate, } from 'yjs'; -import { Config } from '../src/config'; +import { Config } from '../src/fundamentals/config'; import { DocManager, DocModule } from '../src/modules/doc'; import { QuotaModule } from '../src/modules/quota'; import { StorageModule } from '../src/modules/storage'; -import { PrismaService } from '../src/prisma'; import { createTestingModule, initTestingDB } from './utils'; const createModule = () => { @@ -33,7 +33,7 @@ test.beforeEach(async () => { }); m = await createModule(); await m.init(); - await initTestingDB(m.get(PrismaService)); + await initTestingDB(m.get(PrismaClient)); }); test.afterEach.always(async () => { @@ -96,7 +96,7 @@ test('should poll when intervel due', async t => { }); test('should merge update when intervel due', async t => { - const db = m.get(PrismaService); + const db = m.get(PrismaClient); const manager = m.get(DocManager); const doc = new YDoc(); @@ -161,7 +161,7 @@ test('should merge update when intervel due', async t => { }); test('should have sequential update number', async t => { - const db = m.get(PrismaService); + const db = m.get(PrismaClient); const manager = m.get(DocManager); const doc = new YDoc(); const text = doc.getText('content'); @@ -278,7 +278,7 @@ test('should throw if meet max retry times', async t => { }); test('should not update snapshot if state is outdated', async t => { - const db = m.get(PrismaService); + const db = m.get(PrismaClient); const manager = m.get(DocManager); await db.snapshot.create({ diff --git a/packages/backend/server/tests/exception-logger.e2e.ts b/packages/backend/server/tests/exception-logger.e2e.ts deleted file mode 100644 index fccd7117b0..0000000000 --- a/packages/backend/server/tests/exception-logger.e2e.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Controller, Get, type INestApplication } from '@nestjs/common'; -import { Test } from '@nestjs/testing'; -import test from 'ava'; -import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; -import request from 'supertest'; - -import { AppModule } from '../src/app'; -import { ExceptionLogger } from '../src/middleware/exception-logger'; -import { FeatureManagementService } from '../src/modules/features'; -import { PrismaService } from '../src/prisma'; - -const gql = '/graphql'; -const rest = '/rest'; - -let app: INestApplication; - -class FakePrisma { - get workspace() { - return { - async findUnique() { - throw new Error('exception from graphql'); - }, - }; - } -} - -@Controller('rest') -export class MockController { - @Get() - test(): string { - throw new Error('exception from rest api'); - } -} - -test.beforeEach(async () => { - const module = await Test.createTestingModule({ - imports: [AppModule], - controllers: [MockController], - }) - .overrideProvider(PrismaService) - .useClass(FakePrisma) - .overrideProvider(FeatureManagementService) - .useValue({}) - .compile(); - app = module.createNestApplication({ - cors: true, - bodyParser: true, - }); - app.useGlobalFilters(new ExceptionLogger()); - app.use( - graphqlUploadExpress({ - maxFileSize: 10 * 1024 * 1024, - maxFiles: 5, - }) - ); - await app.init(); -}); - -test.afterEach.always(async () => { - await app.close(); -}); - -test('should get response from graphql', async t => { - const id = 'workspace'; - - const response = await request(app.getHttpServer()) - .post(gql) - .send({ - name: 'getPublicWorkspace', - query: ` - query getPublicWorkspace($id: String!) { - publicWorkspace(id: $id) { - id - } - } - `, - variables: { id }, - }); - - t.is(response.status, 200); - t.is(response.body.errors[0].message, 'exception from graphql'); -}); - -test('should get response from rest api', async t => { - const response = await request(app.getHttpServer()).get(rest); - - t.is(response.status, 500); - t.is(response.body.error, 'exception from rest api'); -}); diff --git a/packages/backend/server/tests/feature.spec.ts b/packages/backend/server/tests/feature.spec.ts index 20012f7806..267097615e 100644 --- a/packages/backend/server/tests/feature.spec.ts +++ b/packages/backend/server/tests/feature.spec.ts @@ -1,9 +1,10 @@ /// import { INestApplication, Injectable } from '@nestjs/common'; +import { PrismaClient } from '@prisma/client'; import ava, { type TestFn } from 'ava'; -import { ConfigModule } from '../src/config'; +import { ConfigModule } from '../src/fundamentals/config'; import { AuthService } from '../src/modules/auth/service'; import { FeatureManagementService, @@ -14,12 +15,11 @@ import { import { UserType } from '../src/modules/users/types'; import { WorkspaceResolver } from '../src/modules/workspaces/resolvers'; import { Permission } from '../src/modules/workspaces/types'; -import { PrismaService } from '../src/prisma'; import { createTestingApp } from './utils'; @Injectable() class WorkspaceResolverMock { - constructor(private readonly prisma: PrismaService) {} + constructor(private readonly prisma: PrismaClient) {} async createWorkspace(user: UserType, _init: null) { const workspace = await this.prisma.workspace.create({ diff --git a/packages/backend/server/tests/history.spec.ts b/packages/backend/server/tests/history.spec.ts index 7d416bd08d..da87c4578b 100644 --- a/packages/backend/server/tests/history.spec.ts +++ b/packages/backend/server/tests/history.spec.ts @@ -1,18 +1,18 @@ import { TestingModule } from '@nestjs/testing'; import type { Snapshot } from '@prisma/client'; +import { PrismaClient } from '@prisma/client'; import test from 'ava'; import * as Sinon from 'sinon'; -import { type EventPayload } from '../src/event'; +import { type EventPayload } from '../src/fundamentals/event'; import { DocHistoryManager } from '../src/modules/doc'; import { QuotaModule } from '../src/modules/quota'; import { StorageModule } from '../src/modules/storage'; -import { PrismaService } from '../src/prisma'; import { createTestingModule } from './utils'; let m: TestingModule; let manager: DocHistoryManager; -let db: PrismaService; +let db: PrismaClient; // cleanup database before each test test.beforeEach(async () => { @@ -25,7 +25,7 @@ test.beforeEach(async () => { Sinon.stub(manager, 'getExpiredDateFromNow').resolves( new Date(Date.now() + 1000) ); - db = m.get(PrismaService); + db = m.get(PrismaClient); }); test.afterEach.always(async () => { diff --git a/packages/backend/server/tests/mailer.e2e.ts b/packages/backend/server/tests/mailer.e2e.ts index 0e948dfcc9..21039981b3 100644 --- a/packages/backend/server/tests/mailer.e2e.ts +++ b/packages/backend/server/tests/mailer.e2e.ts @@ -10,7 +10,7 @@ import { TestingModule } from '@nestjs/testing'; import { PrismaClient } from '@prisma/client'; import ava, { type TestFn } from 'ava'; -import { ConfigModule } from '../src/config'; +import { ConfigModule } from '../src/fundamentals/config'; import { AuthService } from '../src/modules/auth/service'; import { createTestingModule } from './utils'; diff --git a/packages/backend/server/tests/mailer.spec.ts b/packages/backend/server/tests/mailer.spec.ts index 7f45664ec6..b01bcfb931 100644 --- a/packages/backend/server/tests/mailer.spec.ts +++ b/packages/backend/server/tests/mailer.spec.ts @@ -3,13 +3,13 @@ import { randomUUID } from 'node:crypto'; import type { INestApplication } from '@nestjs/common'; import { hashSync } from '@node-rs/argon2'; import { type User } from '@prisma/client'; +import { PrismaClient } from '@prisma/client'; import ava, { type TestFn } from 'ava'; import { AppModule } from '../src/app'; -import { MailService } from '../src/modules/auth/mailer'; +import { MailService } from '../src/fundamentals/mailer'; import { FeatureKind, FeatureManagementService } from '../src/modules/features'; import { Quotas } from '../src/modules/quota'; -import { PrismaService } from '../src/prisma'; import { createTestingApp, createWorkspace, @@ -131,7 +131,7 @@ test.beforeEach(async t => { imports: [AppModule], tapModule: module => { module - .overrideProvider(PrismaService) + .overrideProvider(PrismaClient) .useValue(FakePrisma) .overrideProvider(FeatureManagementService) .useValue({ diff --git a/packages/backend/server/tests/quota.spec.ts b/packages/backend/server/tests/quota.spec.ts index 75c7662a04..99d7fe5d29 100644 --- a/packages/backend/server/tests/quota.spec.ts +++ b/packages/backend/server/tests/quota.spec.ts @@ -3,7 +3,7 @@ import { TestingModule } from '@nestjs/testing'; import ava, { type TestFn } from 'ava'; -import { AuthService } from '../src/modules/auth/service'; +import { AuthService } from '../src/modules/auth'; import { QuotaManagementService, QuotaModule, diff --git a/packages/backend/server/tests/session.spec.ts b/packages/backend/server/tests/session.spec.ts index 8814011d39..6c5852a82d 100644 --- a/packages/backend/server/tests/session.spec.ts +++ b/packages/backend/server/tests/session.spec.ts @@ -3,9 +3,9 @@ import { TestingModule } from '@nestjs/testing'; import ava, { type TestFn } from 'ava'; -import { CacheModule } from '../src/cache'; -import { ConfigModule } from '../src/config'; -import { SessionModule, SessionService } from '../src/session'; +import { CacheModule } from '../src/fundamentals/cache'; +import { ConfigModule } from '../src/fundamentals/config'; +import { SessionModule, SessionService } from '../src/fundamentals/session'; import { createTestingModule } from './utils'; const test = ava as TestFn<{ diff --git a/packages/backend/server/tests/utils/utils.ts b/packages/backend/server/tests/utils/utils.ts index 334a78941e..fcfd25c3b6 100644 --- a/packages/backend/server/tests/utils/utils.ts +++ b/packages/backend/server/tests/utils/utils.ts @@ -6,7 +6,7 @@ import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; import { AppModule, FunctionalityModules } from '../../src/app'; import { UserFeaturesInit1698652531198 } from '../../src/data/migrations/1698652531198-user-features-init'; -import { GqlModule } from '../../src/graphql.module'; +import { GqlModule } from '../../src/fundamentals/graphql'; async function flushDB(client: PrismaClient) { const result: { tablename: string }[] = diff --git a/packages/backend/server/tests/workspace-invite.e2e.ts b/packages/backend/server/tests/workspace-invite.e2e.ts index 8ecc747290..60e43175b0 100644 --- a/packages/backend/server/tests/workspace-invite.e2e.ts +++ b/packages/backend/server/tests/workspace-invite.e2e.ts @@ -7,9 +7,8 @@ import { PrismaClient } from '@prisma/client'; import ava, { type TestFn } from 'ava'; import { AppModule } from '../src/app'; -import { MailService } from '../src/modules/auth/mailer'; +import { MailService } from '../src/fundamentals/mailer'; import { AuthService } from '../src/modules/auth/service'; -import { PrismaService } from '../src/prisma'; import { acceptInviteById, createTestingApp, @@ -33,7 +32,7 @@ test.beforeEach(async t => { imports: [AppModule], }); t.context.app = app; - t.context.client = app.get(PrismaService); + t.context.client = app.get(PrismaClient); t.context.auth = app.get(AuthService); t.context.mail = app.get(MailService); }); diff --git a/packages/backend/server/tests/workspace.e2e.ts b/packages/backend/server/tests/workspace.e2e.ts index 8483a92141..4ea4e592eb 100644 --- a/packages/backend/server/tests/workspace.e2e.ts +++ b/packages/backend/server/tests/workspace.e2e.ts @@ -4,7 +4,6 @@ import ava, { type TestFn } from 'ava'; import request from 'supertest'; import { AppModule } from '../src/app'; -import { PrismaService } from '../src/prisma'; import { acceptInviteById, createTestingApp, @@ -29,7 +28,7 @@ test.beforeEach(async t => { imports: [AppModule], }); - t.context.client = app.get(PrismaService); + t.context.client = app.get(PrismaClient); t.context.app = app; });