Skip to content

Commit

Permalink
fix(server): redirect to setup page if not initialized (#7875)
Browse files Browse the repository at this point in the history
  • Loading branch information
forehalo authored Aug 14, 2024
1 parent 89537e6 commit 57449c1
Show file tree
Hide file tree
Showing 20 changed files with 328 additions and 84 deletions.
4 changes: 2 additions & 2 deletions .github/helm/affine/charts/graphql/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -204,12 +204,12 @@ spec:
protocol: TCP
livenessProbe:
httpGet:
path: /
path: /info
port: http
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
readinessProbe:
httpGet:
path: /
path: /info
port: http
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
resources:
Expand Down
1 change: 1 addition & 0 deletions packages/backend/server/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.env
static/
2 changes: 1 addition & 1 deletion packages/backend/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
"@nestjs/platform-express": "^10.3.7",
"@nestjs/platform-socket.io": "^10.3.7",
"@nestjs/schedule": "^4.0.1",
"@nestjs/serve-static": "^4.0.2",
"@nestjs/throttler": "5.2.0",
"@nestjs/websockets": "^10.3.7",
"@node-rs/argon2": "^1.8.0",
Expand Down Expand Up @@ -138,6 +137,7 @@
],
"watchMode": {
"ignoreChanges": [
"static/**",
"**/*.gen.*"
]
},
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/server/src/app.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Controller, Get } from '@nestjs/common';
import { Public } from './core/auth';
import { Config, SkipThrottle } from './fundamentals';

@Controller('/')
@Controller('/info')
export class AppController {
constructor(private readonly config: Config) {}

Expand Down
22 changes: 4 additions & 18 deletions packages/backend/server/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { join } from 'node:path';

import {
DynamicModule,
ForwardReference,
Logger,
Module,
} from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
import { ServeStaticModule } from '@nestjs/serve-static';
import { get } from 'lodash-es';

import { AppController } from './app.controller';
Expand All @@ -16,7 +13,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 { SelfhostModule } from './core/selfhost';
import { StorageModule } from './core/storage';
import { SyncModule } from './core/sync';
import { UserModule } from './core/user';
Expand Down Expand Up @@ -137,15 +134,15 @@ export class AppModuleBuilder {
compile() {
@Module({
imports: this.modules,
controllers: this.config.isSelfhosted ? [] : [AppController],
controllers: [AppController],
})
class AppModule {}

return AppModule;
}
}

function buildAppModule() {
export function buildAppModule() {
AFFiNE = mergeConfigOverride(AFFiNE);
const factor = new AppModuleBuilder(AFFiNE);

Expand Down Expand Up @@ -175,18 +172,7 @@ function buildAppModule() {
)

// self hosted server only
.useIf(
config => config.isSelfhosted,
CustomSetupModule,
ServeStaticModule.forRoot({
rootPath: join('/app', 'static'),
exclude: ['/admin*'],
}),
ServeStaticModule.forRoot({
rootPath: join('/app', 'static', 'admin'),
serveRoot: '/admin',
})
);
.useIf(config => config.isSelfhosted, SelfhostModule);

// plugin modules
ENABLED_PLUGINS.forEach(name => {
Expand Down
4 changes: 4 additions & 0 deletions packages/backend/server/src/core/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
7 changes: 4 additions & 3 deletions packages/backend/server/src/core/config/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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();
}
}

Expand Down
17 changes: 17 additions & 0 deletions packages/backend/server/src/core/config/service.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -10,6 +9,7 @@ import {
PasswordRequired,
} from '../../fundamentals';
import { AuthService, Public } from '../auth';
import { ServerService } from '../config';
import { UserService } from '../user/service';

interface CreateUserInput {
Expand All @@ -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()
Expand All @@ -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');
}

Expand Down
99 changes: 99 additions & 0 deletions packages/backend/server/src/core/selfhost/index.ts
Original file line number Diff line number Diff line change
@@ -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'));
});
}
}
11 changes: 0 additions & 11 deletions packages/backend/server/src/core/setup/index.ts

This file was deleted.

1 change: 1 addition & 0 deletions packages/backend/server/src/fundamentals/config/def.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface PreDefinedAFFiNEConfig {
ENV_MAP: Record<string, ConfigPaths | [ConfigPaths, EnvConfigType?]>;
serverId: string;
serverName: string;
readonly projectRoot: string;
readonly AFFINE_ENV: AFFINE_ENV;
readonly NODE_ENV: NODE_ENV;
readonly version: string;
Expand Down
4 changes: 4 additions & 0 deletions packages/backend/server/src/fundamentals/config/default.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

import pkg from '../../../package.json' assert { type: 'json' };
import {
AFFINE_ENV,
Expand Down Expand Up @@ -62,6 +65,7 @@ function getPredefinedAFFiNEConfig(): PreDefinedAFFiNEConfig {
affine,
node,
deploy: !node.dev && !node.test,
projectRoot: resolve(fileURLToPath(import.meta.url), '../../../../'),
};
}

Expand Down
1 change: 1 addition & 0 deletions packages/backend/server/tests/config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ test.afterEach.always(async () => {

test('should be able to get config', t => {
t.true(typeof config.server.host === 'string');
t.is(config.projectRoot, process.cwd());
t.is(config.NODE_ENV, 'test');
});

Expand Down
Loading

0 comments on commit 57449c1

Please sign in to comment.