diff --git a/packages/backend/server/src/app.module.ts b/packages/backend/server/src/app.module.ts index ac80a578013a..11be39973570 100644 --- a/packages/backend/server/src/app.module.ts +++ b/packages/backend/server/src/app.module.ts @@ -1,10 +1,13 @@ -import { join } from 'node:path'; +import { join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; import { DynamicModule, ForwardReference, Logger, + MiddlewareConsumer, Module, + NestModule, } from '@nestjs/common'; import { ScheduleModule } from '@nestjs/schedule'; import { ServeStaticModule } from '@nestjs/serve-static'; @@ -16,7 +19,7 @@ import { ADD_ENABLED_FEATURES, ServerConfigModule } from './core/config'; import { DocModule } from './core/doc'; import { FeatureModule } from './core/features'; import { QuotaModule } from './core/quota'; -import { CustomSetupModule } from './core/setup'; +import { CustomSetupModule, SetupMiddleware } from './core/setup'; import { StorageModule } from './core/storage'; import { SyncModule } from './core/sync'; import { UserModule } from './core/user'; @@ -135,16 +138,26 @@ export class AppModuleBuilder { } compile() { + const configure = (consumer: MiddlewareConsumer) => { + if (this.config.isSelfhosted) { + consumer.apply(SetupMiddleware).forRoutes('*'); + } + }; + @Module({ imports: this.modules, controllers: this.config.isSelfhosted ? [] : [AppController], }) - class AppModule {} + class AppModule implements NestModule { + configure = configure; + } return AppModule; } } +const pwd = resolve(fileURLToPath(import.meta.url), '../../'); + function buildAppModule() { AFFiNE = mergeConfigOverride(AFFiNE); const factor = new AppModuleBuilder(AFFiNE); @@ -179,12 +192,12 @@ function buildAppModule() { config => config.isSelfhosted, CustomSetupModule, ServeStaticModule.forRoot({ - rootPath: join('/app', 'static'), - exclude: ['/admin*'], + rootPath: join(pwd, 'static', 'admin'), + renderPath: /^\/admin\/?/, }), ServeStaticModule.forRoot({ - rootPath: join('/app', 'static', 'admin'), - serveRoot: '/admin', + rootPath: join(pwd, 'static'), + renderPath: '*', }) ); diff --git a/packages/backend/server/src/core/setup/index.ts b/packages/backend/server/src/core/setup/index.ts index 56fe7cfd790a..aba4796d01f6 100644 --- a/packages/backend/server/src/core/setup/index.ts +++ b/packages/backend/server/src/core/setup/index.ts @@ -1,9 +1,42 @@ -import { Module } from '@nestjs/common'; +import { Injectable, Module, NestMiddleware } from '@nestjs/common'; +import { PrismaClient } from '@prisma/client'; +import type { Request, Response } from 'express'; import { AuthModule } from '../auth'; import { UserModule } from '../user'; import { CustomSetupController } from './controller'; +@Injectable() +export class SetupMiddleware implements NestMiddleware { + private initialized: boolean | null = null; + constructor(private readonly db: PrismaClient) {} + + use(req: Request, res: Response, next: (error?: Error | any) => void) { + // never throw + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.allow().then(allowed => { + if (allowed) { + next(); + } else if (!req.path.startsWith('/admin/setup')) { + res.redirect('/admin/setup'); + } + }); + } + + async allow() { + try { + if (this.initialized === null) { + this.initialized = (await this.db.user.count()) > 0; + } + } catch (e) { + // avoid block the whole app + return true; + } + + return this.initialized; + } +} + @Module({ imports: [AuthModule, UserModule], controllers: [CustomSetupController],