feat: add SSR to maintenance API

This commit is contained in:
izzy 2025-11-07 15:36:51 +00:00
parent 598b1f87a1
commit 998675f48c
No known key found for this signature in database
GPG key ID: 5059F398521BB0F6
5 changed files with 44 additions and 13 deletions

View file

@ -10,6 +10,7 @@ import { ConfigRepository } from 'src/repositories/config.repository';
import { LoggingRepository } from 'src/repositories/logging.repository';
import { bootstrapTelemetry } from 'src/repositories/telemetry.repository';
import { ApiService } from 'src/services/api.service';
import { MaintenanceWorkerService } from 'src/services/maintenance-worker.service';
import { useSwagger } from 'src/utils/misc';
export function configureTelemetry() {
@ -23,7 +24,7 @@ export async function configureExpress(
app: NestExpressApplication,
{
permitSwaggerWrite = true,
ssr = true,
ssr,
}: {
/**
* Whether to allow swagger module to write to the specs.json
@ -32,12 +33,10 @@ export async function configureExpress(
*/
permitSwaggerWrite?: boolean;
/**
* Whether to enable server-side rendering
* @requires ApiService
* @default true
* Service to use for server-side rendering
*/
ssr?: boolean;
} = {},
ssr: typeof ApiService | typeof MaintenanceWorkerService;
},
) {
const configRepository = app.get(ConfigRepository);
const { environment, host, port, resourcePaths, network } = configRepository.getEnv();
@ -78,10 +77,7 @@ export async function configureExpress(
);
}
if (ssr) {
app.use(app.get(ApiService).ssr(excludePaths));
}
app.use(app.get(ssr).ssr(excludePaths));
app.use(compression());
const server = await (host ? app.listen(port, host) : app.listen(port));

View file

@ -12,7 +12,7 @@ import { SharedLinkService } from 'src/services/shared-link.service';
import { VersionService } from 'src/services/version.service';
import { OpenGraphTags } from 'src/utils/misc';
const render = (index: string, meta: OpenGraphTags) => {
export const render = (index: string, meta: OpenGraphTags) => {
const [title, description, imageUrl] = [meta.title, meta.description, meta.imageUrl].map((item) =>
item ? sanitizeHtml(item, { allowedTags: [] }) : '',
);

View file

@ -1,6 +1,8 @@
import { BadRequestException, Injectable, UnauthorizedException } from '@nestjs/common';
import { parse } from 'cookie';
import { NextFunction, Request, Response } from 'express';
import { jwtVerify } from 'jose';
import { readFileSync } from 'node:fs';
import { IncomingHttpHeaders } from 'node:http';
import { MaintenanceAuthDto } from 'src/dtos/maintenance.dto';
import { ImmichCookie, SystemMetadataKey } from 'src/enum';
@ -109,4 +111,34 @@ export class MaintenanceWorkerService {
await this.systemMetadataRepository.set(SystemMetadataKey.MaintenanceMode, state);
this.maintenanceWorkerRepository.restartApp(state);
}
ssr(excludePaths: string[]) {
const { resourcePaths } = this.configRepository.getEnv();
let index = '';
try {
index = readFileSync(resourcePaths.web.indexHtml).toString();
} catch {
this.logger.warn(`Unable to open ${resourcePaths.web.indexHtml}, skipping SSR.`);
}
return async (request: Request, res: Response, next: NextFunction) => {
if (
request.url.startsWith('/api') ||
request.method.toLowerCase() !== 'get' ||
excludePaths.some((item) => request.url.startsWith(item))
) {
return next();
}
const shareKey = request.url.match(/^\/share\/(.+)$/);
const shareSlug = request.url.match(/^\/s\/(.+)$/);
res
.status(shareKey || shareSlug ? 500 : 200)
.type('text/html')
.header('Cache-Control', 'no-store')
.send(index);
};
}
}

View file

@ -3,6 +3,7 @@ import { NestExpressApplication } from '@nestjs/platform-express';
import { configureExpress, configureTelemetry } from 'src/app.common';
import { ApiModule } from 'src/app.module';
import { MaintenanceRepository } from 'src/repositories/maintenance.repository';
import { ApiService } from 'src/services/api.service';
import { isStartUpError } from 'src/utils/misc';
async function bootstrap() {
@ -13,7 +14,9 @@ async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(ApiModule, { bufferLogs: true });
app.get(MaintenanceRepository).setCloseFn(() => app.close());
void configureExpress(app);
void configureExpress(app, {
ssr: ApiService,
});
}
bootstrap().catch((error) => {

View file

@ -14,7 +14,7 @@ async function bootstrap() {
app.get(MaintenanceRepository).setCloseFn(() => app.close());
void configureExpress(app, {
permitSwaggerWrite: false,
ssr: false,
ssr: MaintenanceWorkerService,
});
void app.get(MaintenanceWorkerService).logSecret();