refactor(server): cleanup selfhost module (#9038)

This commit is contained in:
forehalo
2024-12-06 09:23:22 +00:00
parent b1c8fca25e
commit 1aff9de981
3 changed files with 164 additions and 157 deletions

View File

@@ -1,166 +1,15 @@
import { join } from 'node:path';
import { Module } from '@nestjs/common';
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 isMobile from 'is-mobile';
import { Config } from '../../fundamentals';
import { AuthModule } from '../auth';
import { ServerConfigModule, ServerService } from '../config';
import { ServerConfigModule } 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
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();
});
};
}
import { SetupMiddleware } from './setup';
import { StaticFilesResolver } from './static';
@Module({
imports: [AuthModule, UserModule, ServerConfigModule],
providers: [SetupMiddleware],
providers: [SetupMiddleware, StaticFilesResolver],
controllers: [CustomSetupController],
})
export class SelfhostModule implements OnModuleInit {
constructor(
private readonly config: Config,
private readonly adapterHost: HttpAdapterHost,
private readonly check: SetupMiddleware
) {}
onModuleInit() {
// in command line mode
if (!this.adapterHost.httpAdapter) {
return;
}
const app = this.adapterHost.httpAdapter.getInstance<Application>();
// for example, '/affine' in host [//host.com/affine]
const basePath = this.config.server.path;
const staticPath = join(this.config.projectRoot, 'static');
// web => {
// affine: 'static/index.html',
// selfhost: 'static/selfhost.html'
// }
// admin => {
// affine: 'static/admin/index.html',
// selfhost: 'static/admin/selfhost.html'
// }
// mobile => {
// affine: 'static/mobile/index.html',
// selfhost: 'static/mobile/selfhost.html'
// }
// NOTE(@forehalo):
// the order following routes should be respected,
// otherwise the app won't work properly.
// START REGION: /admin
// do not allow '/index.html' url, redirect to '/'
app.get(basePath + '/admin/index.html', (_req, res) => {
return res.redirect(basePath + '/admin');
});
// serve all static files
app.use(
basePath,
serveStatic(join(staticPath, 'admin'), {
redirect: false,
index: false,
fallthrough: true,
})
);
// fallback all unknown routes
app.get(
[basePath + '/admin', basePath + '/admin/*'],
this.check.use,
(_req, res) => {
res.sendFile(
join(
staticPath,
'admin',
this.config.isSelfhosted ? 'selfhost.html' : 'index.html'
)
);
}
);
// END REGION
// START REGION: /mobile
// serve all static files
app.use(
basePath,
serveStatic(join(staticPath, 'mobile'), {
redirect: false,
index: false,
fallthrough: true,
})
);
// END REGION
// START REGION: /
// do not allow '/index.html' url, redirect to '/'
app.get(basePath + '/index.html', (_req, res) => {
return res.redirect(basePath);
});
// serve all static files
app.use(
basePath,
serveStatic(staticPath, {
redirect: false,
index: false,
fallthrough: true,
})
);
// fallback all unknown routes
app.get([basePath, basePath + '/*'], this.check.use, (req, res) => {
const mobile =
this.config.AFFINE_ENV === 'dev' &&
isMobile({
ua: req.headers['user-agent'] ?? undefined,
});
return res.sendFile(
join(
staticPath,
mobile ? 'mobile' : '',
this.config.isSelfhosted ? 'selfhost.html' : 'index.html'
)
);
});
// END REGION
}
}
export class SelfhostModule {}

View File

@@ -0,0 +1,33 @@
import { Injectable, NestMiddleware } from '@nestjs/common';
import type { Request, Response } from 'express';
import { ServerService } from '../config';
@Injectable()
export class SetupMiddleware implements NestMiddleware {
constructor(private readonly server: ServerService) {}
use = (req: Request, res: Response, next: (error?: Error | any) => void) => {
// never throw
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();
});
};
}

View File

@@ -0,0 +1,125 @@
import { join } from 'node:path';
import { Injectable, OnModuleInit } from '@nestjs/common';
import { HttpAdapterHost } from '@nestjs/core';
import type { Application } from 'express';
import { static as serveStatic } from 'express';
import isMobile from 'is-mobile';
import { Config } from '../../fundamentals';
import { SetupMiddleware } from './setup';
@Injectable()
export class StaticFilesResolver implements OnModuleInit {
constructor(
private readonly config: Config,
private readonly adapterHost: HttpAdapterHost,
private readonly check: SetupMiddleware
) {}
onModuleInit() {
// in command line mode
if (!this.adapterHost.httpAdapter) {
return;
}
const app = this.adapterHost.httpAdapter.getInstance<Application>();
// for example, '/affine' in host [//host.com/affine]
const basePath = this.config.server.path;
const staticPath = join(this.config.projectRoot, 'static');
// web => {
// affine: 'static/index.html',
// selfhost: 'static/selfhost.html'
// }
// admin => {
// affine: 'static/admin/index.html',
// selfhost: 'static/admin/selfhost.html'
// }
// mobile => {
// affine: 'static/mobile/index.html',
// selfhost: 'static/mobile/selfhost.html'
// }
// NOTE(@forehalo):
// the order following routes should be respected,
// otherwise the app won't work properly.
// START REGION: /admin
// do not allow '/index.html' url, redirect to '/'
app.get(basePath + '/admin/index.html', (_req, res) => {
return res.redirect(basePath + '/admin');
});
// serve all static files
app.use(
basePath,
serveStatic(join(staticPath, 'admin'), {
redirect: false,
index: false,
fallthrough: true,
})
);
// fallback all unknown routes
app.get(
[basePath + '/admin', basePath + '/admin/*'],
this.check.use,
(_req, res) => {
res.sendFile(
join(
staticPath,
'admin',
this.config.isSelfhosted ? 'selfhost.html' : 'index.html'
)
);
}
);
// END REGION
// START REGION: /mobile
// serve all static files
app.use(
basePath,
serveStatic(join(staticPath, 'mobile'), {
redirect: false,
index: false,
fallthrough: true,
})
);
// END REGION
// START REGION: /
// do not allow '/index.html' url, redirect to '/'
app.get(basePath + '/index.html', (_req, res) => {
return res.redirect(basePath);
});
// serve all static files
app.use(
basePath,
serveStatic(staticPath, {
redirect: false,
index: false,
fallthrough: true,
})
);
// fallback all unknown routes
app.get([basePath, basePath + '/*'], this.check.use, (req, res) => {
const mobile =
this.config.AFFINE_ENV === 'dev' &&
isMobile({
ua: req.headers['user-agent'] ?? undefined,
});
return res.sendFile(
join(
staticPath,
mobile ? 'mobile' : '',
this.config.isSelfhosted ? 'selfhost.html' : 'index.html'
)
);
});
// END REGION
}
}