mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
fix(server): redirect to setup page if not initialized (#7875)
This commit is contained in:
@@ -8,15 +8,19 @@ import {
|
||||
ServerRuntimeConfigResolver,
|
||||
ServerServiceConfigResolver,
|
||||
} from './resolver';
|
||||
import { ServerService } from './service';
|
||||
|
||||
@Module({
|
||||
providers: [
|
||||
ServerService,
|
||||
ServerConfigResolver,
|
||||
ServerFeatureConfigResolver,
|
||||
ServerRuntimeConfigResolver,
|
||||
ServerServiceConfigResolver,
|
||||
],
|
||||
exports: [ServerService],
|
||||
})
|
||||
export class ServerConfigModule {}
|
||||
export { ServerService };
|
||||
export { ADD_ENABLED_FEATURES } from './server-feature';
|
||||
export { ServerFeature } from './types';
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
ResolveField,
|
||||
Resolver,
|
||||
} from '@nestjs/graphql';
|
||||
import { PrismaClient, RuntimeConfig, RuntimeConfigType } from '@prisma/client';
|
||||
import { RuntimeConfig, RuntimeConfigType } from '@prisma/client';
|
||||
import { GraphQLJSON, GraphQLJSONObject } from 'graphql-scalars';
|
||||
|
||||
import { Config, URLHelper } from '../../fundamentals';
|
||||
@@ -19,6 +19,7 @@ import { FeatureType } from '../features';
|
||||
import { AvailableUserFeatureConfig } from '../features/resolver';
|
||||
import { ServerFlags } from './config';
|
||||
import { ENABLED_FEATURES } from './server-feature';
|
||||
import { ServerService } from './service';
|
||||
import { ServerConfigType } from './types';
|
||||
|
||||
@ObjectType()
|
||||
@@ -76,7 +77,7 @@ export class ServerConfigResolver {
|
||||
constructor(
|
||||
private readonly config: Config,
|
||||
private readonly url: URLHelper,
|
||||
private readonly db: PrismaClient
|
||||
private readonly server: ServerService
|
||||
) {}
|
||||
|
||||
@Public()
|
||||
@@ -131,7 +132,7 @@ export class ServerConfigResolver {
|
||||
description: 'whether server has been initialized',
|
||||
})
|
||||
async initialized() {
|
||||
return (await this.db.user.count()) > 0;
|
||||
return this.server.initialized();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
17
packages/backend/server/src/core/config/service.ts
Normal file
17
packages/backend/server/src/core/config/service.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class ServerService {
|
||||
private _initialized: boolean | null = null;
|
||||
constructor(private readonly db: PrismaClient) {}
|
||||
|
||||
async initialized() {
|
||||
if (!this._initialized) {
|
||||
const userCount = await this.db.user.count();
|
||||
this._initialized = userCount > 0;
|
||||
}
|
||||
|
||||
return this._initialized;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Body, Controller, Post, Req, Res } from '@nestjs/common';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import type { Request, Response } from 'express';
|
||||
|
||||
import {
|
||||
@@ -10,6 +9,7 @@ import {
|
||||
PasswordRequired,
|
||||
} from '../../fundamentals';
|
||||
import { AuthService, Public } from '../auth';
|
||||
import { ServerService } from '../config';
|
||||
import { UserService } from '../user/service';
|
||||
|
||||
interface CreateUserInput {
|
||||
@@ -20,11 +20,11 @@ interface CreateUserInput {
|
||||
@Controller('/api/setup')
|
||||
export class CustomSetupController {
|
||||
constructor(
|
||||
private readonly db: PrismaClient,
|
||||
private readonly user: UserService,
|
||||
private readonly auth: AuthService,
|
||||
private readonly event: EventEmitter,
|
||||
private readonly mutex: MutexService
|
||||
private readonly mutex: MutexService,
|
||||
private readonly server: ServerService
|
||||
) {}
|
||||
|
||||
@Public()
|
||||
@@ -44,7 +44,7 @@ export class CustomSetupController {
|
||||
throw new InternalServerError();
|
||||
}
|
||||
|
||||
if ((await this.db.user.count()) > 0) {
|
||||
if (await this.server.initialized()) {
|
||||
throw new ActionForbidden('First user already created');
|
||||
}
|
||||
|
||||
99
packages/backend/server/src/core/selfhost/index.ts
Normal file
99
packages/backend/server/src/core/selfhost/index.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { join } from 'node:path';
|
||||
|
||||
import {
|
||||
Injectable,
|
||||
Module,
|
||||
NestMiddleware,
|
||||
OnModuleInit,
|
||||
} from '@nestjs/common';
|
||||
import { HttpAdapterHost } from '@nestjs/core';
|
||||
import type { Application, Request, Response } from 'express';
|
||||
import { static as serveStatic } from 'express';
|
||||
|
||||
import { Config } from '../../fundamentals';
|
||||
import { AuthModule } from '../auth';
|
||||
import { ServerConfigModule, ServerService } from '../config';
|
||||
import { UserModule } from '../user';
|
||||
import { CustomSetupController } from './controller';
|
||||
|
||||
@Injectable()
|
||||
export class SetupMiddleware implements NestMiddleware {
|
||||
constructor(private readonly server: ServerService) {}
|
||||
|
||||
use = (req: Request, res: Response, next: (error?: Error | any) => void) => {
|
||||
// never throw
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.server
|
||||
.initialized()
|
||||
.then(initialized => {
|
||||
// Redirect to setup page if not initialized
|
||||
if (!initialized && req.path !== '/admin/setup') {
|
||||
res.redirect('/admin/setup');
|
||||
return;
|
||||
}
|
||||
|
||||
// redirect to admin page if initialized
|
||||
if (initialized && req.path === '/admin/setup') {
|
||||
res.redirect('/admin');
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
})
|
||||
.catch(() => {
|
||||
next();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@Module({
|
||||
imports: [AuthModule, UserModule, ServerConfigModule],
|
||||
providers: [SetupMiddleware],
|
||||
controllers: [CustomSetupController],
|
||||
})
|
||||
export class SelfhostModule implements OnModuleInit {
|
||||
constructor(
|
||||
private readonly config: Config,
|
||||
private readonly adapterHost: HttpAdapterHost,
|
||||
private readonly check: SetupMiddleware
|
||||
) {}
|
||||
|
||||
onModuleInit() {
|
||||
const staticPath = join(this.config.projectRoot, 'static');
|
||||
const app = this.adapterHost.httpAdapter.getInstance<Application>();
|
||||
const basePath = this.config.server.path;
|
||||
|
||||
app.get(basePath + '/admin/index.html', (_req, res) => {
|
||||
res.redirect(basePath + '/admin');
|
||||
});
|
||||
app.use(
|
||||
basePath + '/admin',
|
||||
serveStatic(join(staticPath, 'admin'), {
|
||||
redirect: false,
|
||||
index: false,
|
||||
})
|
||||
);
|
||||
|
||||
app.get(
|
||||
[basePath + '/admin', basePath + '/admin/*'],
|
||||
this.check.use,
|
||||
(_req, res) => {
|
||||
res.sendFile(join(staticPath, 'admin', 'index.html'));
|
||||
}
|
||||
);
|
||||
|
||||
app.get(basePath + '/index.html', (_req, res) => {
|
||||
res.redirect(basePath);
|
||||
});
|
||||
app.use(
|
||||
basePath,
|
||||
serveStatic(staticPath, {
|
||||
redirect: false,
|
||||
index: false,
|
||||
})
|
||||
);
|
||||
app.get('*', this.check.use, (_req, res) => {
|
||||
res.sendFile(join(staticPath, 'index.html'));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { AuthModule } from '../auth';
|
||||
import { UserModule } from '../user';
|
||||
import { CustomSetupController } from './controller';
|
||||
|
||||
@Module({
|
||||
imports: [AuthModule, UserModule],
|
||||
controllers: [CustomSetupController],
|
||||
})
|
||||
export class CustomSetupModule {}
|
||||
Reference in New Issue
Block a user