mirror of
https://github.com/immich-app/immich
synced 2026-08-15 13:03:57 +00:00
refactor: use respondWithCookie
refactor: merge start/end into one route refactor: rename MaintenanceRepository to AppRepository chore: use new ApiTag/Endpoint refactor: apply other requested changes
This commit is contained in:
parent
15c0d5f27a
commit
1fc98990ab
22 changed files with 1041 additions and 10008 deletions
|
|
@ -34,41 +34,41 @@ describe('/admin/maintenance', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('POST /end', async () => {
|
||||
it('should not work out of maintenance mode', async () => {
|
||||
const { status, body } = await request(app)
|
||||
.post('/admin/maintenance/end')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||
.send();
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest('Not in maintenance mode'));
|
||||
});
|
||||
});
|
||||
|
||||
// => enter maintenance mode
|
||||
|
||||
describe.sequential('POST /start', () => {
|
||||
describe.sequential('POST /', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(app).post('/admin/maintenance/start').send();
|
||||
const { status, body } = await request(app).post('/admin/maintenance').send({
|
||||
maintenanceMode: false
|
||||
});
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorDto.unauthorized);
|
||||
});
|
||||
|
||||
it('should only work for admins', async () => {
|
||||
const { status, body } = await request(app)
|
||||
.post('/admin/maintenance/start')
|
||||
.post('/admin/maintenance')
|
||||
.set('Authorization', `Bearer ${nonAdmin.accessToken}`)
|
||||
.send();
|
||||
.send({ maintenanceMode: false });
|
||||
expect(status).toBe(403);
|
||||
expect(body).toEqual(errorDto.forbidden);
|
||||
});
|
||||
|
||||
it('should be a no-op if try to exit maintenance mode', async () => {
|
||||
const { status } = await request(app)
|
||||
.post('/admin/maintenance')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||
.send({ maintenanceMode: false });
|
||||
expect(status).toBe(201);
|
||||
});
|
||||
|
||||
it('should enter maintenance mode', async () => {
|
||||
const { status, headers } = await request(app)
|
||||
.post('/admin/maintenance/start')
|
||||
.post('/admin/maintenance')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||
.send();
|
||||
|
||||
.send({
|
||||
maintenaceMode: true
|
||||
});
|
||||
expect(status).toBe(201);
|
||||
|
||||
cookie = headers['set-cookie'][0].split(';')[0];
|
||||
|
|
@ -134,21 +134,25 @@ describe('/admin/maintenance', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('POST /start', async () => {
|
||||
it('should not work in maintenance mode', async () => {
|
||||
const { status, body } = await request(app).post('/admin/maintenance/start').set('cookie', cookie!).send();
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest('Already in maintenance mode'));
|
||||
describe('POST /', async () => {
|
||||
it('should be a no-op if try to enter maintenance mode', async () => {
|
||||
const { status } = await request(app)
|
||||
.post('/admin/maintenance')
|
||||
.set('cookie', cookie!)
|
||||
.send({ maintenanceMode: true });
|
||||
expect(status).toBe(201);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// => exit maintenance mode
|
||||
|
||||
describe.sequential('POST /end', () => {
|
||||
describe.sequential('POST /', () => {
|
||||
it('should exit maintenance mode', async () => {
|
||||
console.info('using', cookie!);
|
||||
const { status } = await request(app).post('/admin/maintenance/end').set('cookie', cookie!).send();
|
||||
const { status } = await request(app).post('/admin/maintenance').set('cookie', cookie!).send({
|
||||
maintenanceMode: false
|
||||
});
|
||||
|
||||
expect(status).toBe(201);
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -17,11 +17,11 @@ import { GlobalExceptionFilter } from 'src/middleware/global-exception.filter';
|
|||
import { LoggingInterceptor } from 'src/middleware/logging.interceptor';
|
||||
import { MaintenanceAuthGuard } from 'src/middleware/maintenance-auth.guard';
|
||||
import { repositories } from 'src/repositories';
|
||||
import { AppRepository } from 'src/repositories/app.repository';
|
||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||
import { EventRepository } from 'src/repositories/event.repository';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
import { MaintenanceWorkerRepository } from 'src/repositories/maintenance-worker.repository';
|
||||
import { MaintenanceRepository } from 'src/repositories/maintenance.repository';
|
||||
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
|
||||
import { teardownTelemetry, TelemetryRepository } from 'src/repositories/telemetry.repository';
|
||||
import { WebsocketRepository } from 'src/repositories/websocket.repository';
|
||||
|
|
@ -104,7 +104,7 @@ export class ApiModule extends BaseModule {}
|
|||
ConfigRepository,
|
||||
LoggingRepository,
|
||||
SystemMetadataRepository,
|
||||
MaintenanceRepository,
|
||||
AppRepository,
|
||||
MaintenanceWorkerRepository,
|
||||
MaintenanceWorkerService,
|
||||
...commonMiddleware,
|
||||
|
|
|
|||
|
|
@ -150,6 +150,7 @@ export const endpointTags: Record<ApiTag, string> = {
|
|||
'Queues and background jobs are used for processing tasks asynchronously. Queues can be paused and resumed as needed.',
|
||||
[ApiTag.Libraries]:
|
||||
'An external library is made up of input file paths or expressions that are scanned for asset files. Discovered files are automatically imported. Assets much be unique within a library, but can be duplicated across libraries. Each user has a default upload library, and can have one or more external libraries.',
|
||||
[ApiTag.Maintenance]: 'Maintenance mode allows you to put Immich in a read-only state to perform various operations.',
|
||||
[ApiTag.Map]:
|
||||
'Map endpoints include supplemental functionality related to geolocation, such as reverse geocoding and retrieving map markers for assets with geolocation data.',
|
||||
[ApiTag.Memories]:
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
import { BadRequestException, Body, Controller, Get, Post, Req, Res } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Body, Controller, Get, Post, Req, Res } from '@nestjs/common';
|
||||
import { Request, Response } from 'express';
|
||||
import { MaintenanceAuthDto, MaintenanceLoginDto } from 'src/dtos/maintenance.dto';
|
||||
import { MaintenanceAuthDto, MaintenanceLoginDto, SetMaintenanceModeDto } from 'src/dtos/maintenance.dto';
|
||||
import { ServerConfigDto } from 'src/dtos/server.dto';
|
||||
import { ImmichCookie } from 'src/enum';
|
||||
import { MaintenanceRoute } from 'src/middleware/maintenance-auth.guard';
|
||||
import { MaintenanceWorkerService } from 'src/services/maintenance-worker.service';
|
||||
import { respondWithCookie } from 'src/utils/response';
|
||||
|
||||
@ApiTags('Maintenance (admin)')
|
||||
@Controller()
|
||||
export class MaintenanceWorkerController {
|
||||
constructor(private service: MaintenanceWorkerService) {}
|
||||
|
|
@ -21,23 +20,21 @@ export class MaintenanceWorkerController {
|
|||
async maintenanceLogin(
|
||||
@Req() request: Request,
|
||||
@Body() dto: MaintenanceLoginDto,
|
||||
@Res({ passthrough: true }) response: Response,
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
): Promise<MaintenanceAuthDto> {
|
||||
const token = dto.token ?? request.cookies[ImmichCookie.MaintenanceToken];
|
||||
const auth = await this.service.login(token);
|
||||
response.cookie(ImmichCookie.MaintenanceToken, token);
|
||||
return auth;
|
||||
return respondWithCookie(res, auth, {
|
||||
isSecure: true,
|
||||
values: [{ key: ImmichCookie.MaintenanceToken, value: token }],
|
||||
});
|
||||
}
|
||||
|
||||
@Post('admin/maintenance/start')
|
||||
@Post('admin/maintenance')
|
||||
@MaintenanceRoute()
|
||||
startMaintenance(): void {
|
||||
throw new BadRequestException('Already in maintenance mode');
|
||||
}
|
||||
|
||||
@Post('admin/maintenance/end')
|
||||
@MaintenanceRoute()
|
||||
async endMaintenance(): Promise<void> {
|
||||
await this.service.endMaintenance();
|
||||
async setMaintenanceMode(@Body() dto: SetMaintenanceModeDto): Promise<void> {
|
||||
if (!dto.maintenanceMode) {
|
||||
await this.service.endMaintenance();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,47 @@
|
|||
import { BadRequestException, Body, Controller, Post, Res } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Response } from 'express';
|
||||
import { Endpoint, HistoryBuilder } from 'src/decorators';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { MaintenanceAuthDto, MaintenanceLoginDto } from 'src/dtos/maintenance.dto';
|
||||
import { ImmichCookie, Permission } from 'src/enum';
|
||||
import { MaintenanceAuthDto, MaintenanceLoginDto, SetMaintenanceModeDto } from 'src/dtos/maintenance.dto';
|
||||
import { ApiTag, ImmichCookie, Permission } from 'src/enum';
|
||||
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
||||
import { MaintenanceService } from 'src/services/maintenance.service';
|
||||
import { respondWithCookie } from 'src/utils/response';
|
||||
|
||||
@ApiTags('Maintenance (admin)')
|
||||
@ApiTags(ApiTag.Maintenance)
|
||||
@Controller('admin/maintenance')
|
||||
export class MaintenanceController {
|
||||
constructor(private service: MaintenanceService) {}
|
||||
|
||||
@Post('login')
|
||||
@Endpoint({
|
||||
summary: 'Log into maintenance mode',
|
||||
description: 'Login with maintenance token or cookie to receive current information and perform further actions.',
|
||||
history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'),
|
||||
})
|
||||
maintenanceLogin(@Body() _dto: MaintenanceLoginDto): MaintenanceAuthDto {
|
||||
throw new BadRequestException('Not in maintenance mode');
|
||||
}
|
||||
|
||||
@Post('start')
|
||||
@Post()
|
||||
@Endpoint({
|
||||
summary: 'Set maintenance mode',
|
||||
description: 'Put Immich into or take it out of maintenance mode',
|
||||
history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'),
|
||||
})
|
||||
@Authenticated({ permission: Permission.Maintenance, admin: true })
|
||||
async startMaintenance(@Auth() auth: AuthDto, @Res({ passthrough: true }) response: Response): Promise<void> {
|
||||
const { jwt } = await this.service.startMaintenance(auth.user.name);
|
||||
response.cookie(ImmichCookie.MaintenanceToken, jwt);
|
||||
}
|
||||
|
||||
@Post('end')
|
||||
@Authenticated({ permission: Permission.Maintenance, admin: true })
|
||||
endMaintenance(): void {
|
||||
throw new BadRequestException('Not in maintenance mode');
|
||||
async setMaintenanceMode(
|
||||
@Auth() auth: AuthDto,
|
||||
@Body() dto: SetMaintenanceModeDto,
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
): Promise<void> {
|
||||
if (dto.maintenanceMode) {
|
||||
const { jwt } = await this.service.startMaintenance(auth.user.name);
|
||||
return respondWithCookie(res, undefined, {
|
||||
isSecure: true,
|
||||
values: [{ key: ImmichCookie.MaintenanceToken, value: jwt }],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
import { ValidateString } from 'src/validation';
|
||||
import { ValidateBoolean, ValidateString } from 'src/validation';
|
||||
|
||||
export class SetMaintenanceModeDto {
|
||||
@ValidateBoolean()
|
||||
maintenanceMode!: boolean;
|
||||
}
|
||||
|
||||
export class MaintenanceLoginDto {
|
||||
@ValidateString({ optional: true })
|
||||
|
|
|
|||
|
|
@ -796,6 +796,7 @@ export enum ApiTag {
|
|||
Faces = 'Faces',
|
||||
Jobs = 'Jobs',
|
||||
Libraries = 'Libraries',
|
||||
Maintenance = 'Maintenance (admin)',
|
||||
Map = 'Map',
|
||||
Memories = 'Memories',
|
||||
Notifications = 'Notifications',
|
||||
|
|
|
|||
|
|
@ -18,17 +18,13 @@ class Workers {
|
|||
/**
|
||||
* Currently running workers
|
||||
*/
|
||||
workers: Partial<Record<ImmichWorker, Worker | ChildProcess>>;
|
||||
workers: Partial<Record<ImmichWorker, { kill: () => void }>> = {};
|
||||
|
||||
/**
|
||||
* Fail-safe in case anything dies during restart
|
||||
*/
|
||||
restarting = false;
|
||||
|
||||
constructor() {
|
||||
this.workers = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot all enabled workers
|
||||
*/
|
||||
|
|
@ -80,17 +76,27 @@ class Workers {
|
|||
const basePath = dirname(__filename);
|
||||
const workerFile = join(basePath, 'workers', `${name}.js`);
|
||||
|
||||
const worker: Worker | ChildProcess =
|
||||
name === ImmichWorker.Api
|
||||
? fork(workerFile, [], {
|
||||
execArgv: process.execArgv.map((arg) => (arg.startsWith('--inspect') ? '--inspect=0.0.0.0:9231' : arg)),
|
||||
})
|
||||
: new Worker(workerFile);
|
||||
let anyWorker: Worker | ChildProcess;
|
||||
let kill: () => void;
|
||||
|
||||
worker.on('error', (error) => this.onError(name, error));
|
||||
worker.on('exit', (exitCode) => this.onExit(name, exitCode));
|
||||
if (name === ImmichWorker.Api) {
|
||||
const worker = fork(workerFile, [], {
|
||||
execArgv: process.execArgv.map((arg) => (arg.startsWith('--inspect') ? '--inspect=0.0.0.0:9231' : arg)),
|
||||
});
|
||||
|
||||
this.workers[name] = worker;
|
||||
kill = () => worker.kill();
|
||||
anyWorker = worker;
|
||||
} else {
|
||||
const worker = new Worker(workerFile);
|
||||
|
||||
kill = () => worker.terminate();
|
||||
anyWorker = worker;
|
||||
}
|
||||
|
||||
anyWorker.on('error', (error) => this.onError(name, error));
|
||||
anyWorker.on('exit', (exitCode) => this.onExit(name, exitCode));
|
||||
|
||||
this.workers[name] = { kill };
|
||||
}
|
||||
|
||||
onError(name: ImmichWorker, error: Error) {
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ export class MaintenanceAuthGuard implements CanActivate {
|
|||
}
|
||||
|
||||
const request = context.switchToHttp().getRequest<MaintenanceAuthRequest>();
|
||||
request['auth'] = await this.service.authenticate(request.headers);
|
||||
request.auth = await this.service.authenticate(request.headers);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,9 @@ import { Injectable } from '@nestjs/common';
|
|||
import { ExitCode } from 'src/enum';
|
||||
|
||||
@Injectable()
|
||||
export class MaintenanceRepository {
|
||||
export class AppRepository {
|
||||
private closeFn?: () => Promise<void>;
|
||||
|
||||
constructor() {}
|
||||
|
||||
exitApp() {
|
||||
/* eslint-disable unicorn/no-process-exit */
|
||||
void this.closeFn?.().finally(() => process.exit(ExitCode.AppRestart));
|
||||
|
|
@ -3,6 +3,7 @@ import { ActivityRepository } from 'src/repositories/activity.repository';
|
|||
import { AlbumUserRepository } from 'src/repositories/album-user.repository';
|
||||
import { AlbumRepository } from 'src/repositories/album.repository';
|
||||
import { ApiKeyRepository } from 'src/repositories/api-key.repository';
|
||||
import { AppRepository } from 'src/repositories/app.repository';
|
||||
import { AssetJobRepository } from 'src/repositories/asset-job.repository';
|
||||
import { AssetRepository } from 'src/repositories/asset.repository';
|
||||
import { AuditRepository } from 'src/repositories/audit.repository';
|
||||
|
|
@ -18,7 +19,6 @@ import { JobRepository } from 'src/repositories/job.repository';
|
|||
import { LibraryRepository } from 'src/repositories/library.repository';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
import { MachineLearningRepository } from 'src/repositories/machine-learning.repository';
|
||||
import { MaintenanceRepository } from 'src/repositories/maintenance.repository';
|
||||
import { MapRepository } from 'src/repositories/map.repository';
|
||||
import { MediaRepository } from 'src/repositories/media.repository';
|
||||
import { MemoryRepository } from 'src/repositories/memory.repository';
|
||||
|
|
@ -55,6 +55,7 @@ export const repositories = [
|
|||
AlbumUserRepository,
|
||||
AuditRepository,
|
||||
ApiKeyRepository,
|
||||
AppRepository,
|
||||
AssetRepository,
|
||||
AssetJobRepository,
|
||||
ConfigRepository,
|
||||
|
|
@ -69,7 +70,6 @@ export const repositories = [
|
|||
LibraryRepository,
|
||||
LoggingRepository,
|
||||
MachineLearningRepository,
|
||||
MaintenanceRepository,
|
||||
MapRepository,
|
||||
MediaRepository,
|
||||
MemoryRepository,
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ import {
|
|||
WebSocketServer,
|
||||
} from '@nestjs/websockets';
|
||||
import { Server, Socket } from 'socket.io';
|
||||
import { AppRepository } from 'src/repositories/app.repository';
|
||||
import { AppRestartEvent } from 'src/repositories/event.repository';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
import { MaintenanceRepository } from 'src/repositories/maintenance.repository';
|
||||
|
||||
export const serverEvents = ['AppRestart'] as const;
|
||||
export type ServerEvents = (typeof serverEvents)[number];
|
||||
|
|
@ -30,7 +30,7 @@ export class MaintenanceWorkerRepository implements OnGatewayConnection, OnGatew
|
|||
|
||||
constructor(
|
||||
private logger: LoggingRepository,
|
||||
private maintenanceRepository: MaintenanceRepository,
|
||||
private maintenanceRepository: AppRepository,
|
||||
) {
|
||||
this.logger.setContext(MaintenanceWorkerRepository.name);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { ActivityRepository } from 'src/repositories/activity.repository';
|
|||
import { AlbumUserRepository } from 'src/repositories/album-user.repository';
|
||||
import { AlbumRepository } from 'src/repositories/album.repository';
|
||||
import { ApiKeyRepository } from 'src/repositories/api-key.repository';
|
||||
import { AppRepository } from 'src/repositories/app.repository';
|
||||
import { AssetJobRepository } from 'src/repositories/asset-job.repository';
|
||||
import { AssetRepository } from 'src/repositories/asset.repository';
|
||||
import { AuditRepository } from 'src/repositories/audit.repository';
|
||||
|
|
@ -25,7 +26,6 @@ import { JobRepository } from 'src/repositories/job.repository';
|
|||
import { LibraryRepository } from 'src/repositories/library.repository';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
import { MachineLearningRepository } from 'src/repositories/machine-learning.repository';
|
||||
import { MaintenanceRepository } from 'src/repositories/maintenance.repository';
|
||||
import { MapRepository } from 'src/repositories/map.repository';
|
||||
import { MediaRepository } from 'src/repositories/media.repository';
|
||||
import { MemoryRepository } from 'src/repositories/memory.repository';
|
||||
|
|
@ -65,6 +65,7 @@ export const BASE_SERVICE_DEPENDENCIES = [
|
|||
AlbumRepository,
|
||||
AlbumUserRepository,
|
||||
ApiKeyRepository,
|
||||
AppRepository,
|
||||
AssetRepository,
|
||||
AssetJobRepository,
|
||||
AuditRepository,
|
||||
|
|
@ -79,7 +80,6 @@ export const BASE_SERVICE_DEPENDENCIES = [
|
|||
JobRepository,
|
||||
LibraryRepository,
|
||||
MachineLearningRepository,
|
||||
MaintenanceRepository,
|
||||
MapRepository,
|
||||
MediaRepository,
|
||||
MemoryRepository,
|
||||
|
|
@ -120,6 +120,7 @@ export class BaseService {
|
|||
protected albumRepository: AlbumRepository,
|
||||
protected albumUserRepository: AlbumUserRepository,
|
||||
protected apiKeyRepository: ApiKeyRepository,
|
||||
protected appRepository: AppRepository,
|
||||
protected assetRepository: AssetRepository,
|
||||
protected assetJobRepository: AssetJobRepository,
|
||||
protected auditRepository: AuditRepository,
|
||||
|
|
@ -134,7 +135,6 @@ export class BaseService {
|
|||
protected jobRepository: JobRepository,
|
||||
protected libraryRepository: LibraryRepository,
|
||||
protected machineLearningRepository: MachineLearningRepository,
|
||||
protected maintenanceRepository: MaintenanceRepository,
|
||||
protected mapRepository: MapRepository,
|
||||
protected mediaRepository: MediaRepository,
|
||||
protected memoryRepository: MemoryRepository,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { OnEvent } from 'src/decorators';
|
||||
import { MaintenanceAuthDto } from 'src/dtos/maintenance.dto';
|
||||
import { SystemMetadataKey } from 'src/enum';
|
||||
|
|
@ -19,11 +19,6 @@ export class MaintenanceService extends BaseService {
|
|||
}
|
||||
|
||||
async startMaintenance(username: string): Promise<{ jwt: string }> {
|
||||
const { isMaintenanceMode } = await this.getMaintenanceMode();
|
||||
if (isMaintenanceMode) {
|
||||
throw new BadRequestException('Already in maintenance mode');
|
||||
}
|
||||
|
||||
const secret = generateMaintenanceSecret();
|
||||
await this.systemMetadataRepository.set(SystemMetadataKey.MaintenanceMode, { isMaintenanceMode: true, secret });
|
||||
await this.eventRepository.emit('AppRestart', { isMaintenanceMode: true });
|
||||
|
|
@ -37,7 +32,7 @@ export class MaintenanceService extends BaseService {
|
|||
|
||||
@OnEvent({ name: 'AppRestart', server: true })
|
||||
onRestart(): void {
|
||||
this.maintenanceRepository.exitApp();
|
||||
this.appRepository.exitApp();
|
||||
}
|
||||
|
||||
async createLoginUrl(auth: MaintenanceAuthDto, secret?: string): Promise<string> {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { NestFactory } from '@nestjs/core';
|
|||
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 { AppRepository } from 'src/repositories/app.repository';
|
||||
import { ApiService } from 'src/services/api.service';
|
||||
import { isStartUpError } from 'src/utils/misc';
|
||||
|
||||
|
|
@ -12,7 +12,7 @@ async function bootstrap() {
|
|||
configureTelemetry();
|
||||
|
||||
const app = await NestFactory.create<NestExpressApplication>(ApiModule, { bufferLogs: true });
|
||||
app.get(MaintenanceRepository).setCloseFn(() => app.close());
|
||||
app.get(AppRepository).setCloseFn(() => app.close());
|
||||
|
||||
void configureExpress(app, {
|
||||
ssr: ApiService,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { NestFactory } from '@nestjs/core';
|
|||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
import { configureExpress, configureTelemetry } from 'src/app.common';
|
||||
import { MaintenanceModule } from 'src/app.module';
|
||||
import { MaintenanceRepository } from 'src/repositories/maintenance.repository';
|
||||
import { AppRepository } from 'src/repositories/app.repository';
|
||||
import { MaintenanceWorkerService } from 'src/services/maintenance-worker.service';
|
||||
import { isStartUpError } from 'src/utils/misc';
|
||||
|
||||
|
|
@ -11,7 +11,7 @@ async function bootstrap() {
|
|||
configureTelemetry();
|
||||
|
||||
const app = await NestFactory.create<NestExpressApplication>(MaintenanceModule, { bufferLogs: true });
|
||||
app.get(MaintenanceRepository).setCloseFn(() => app.close());
|
||||
app.get(AppRepository).setCloseFn(() => app.close());
|
||||
void configureExpress(app, {
|
||||
permitSwaggerWrite: false,
|
||||
ssr: MaintenanceWorkerService,
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ import { isMainThread } from 'node:worker_threads';
|
|||
import { MicroservicesModule } from 'src/app.module';
|
||||
import { serverVersion } from 'src/constants';
|
||||
import { WebSocketAdapter } from 'src/middleware/websocket.adapter';
|
||||
import { AppRepository } from 'src/repositories/app.repository';
|
||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
import { MaintenanceRepository } from 'src/repositories/maintenance.repository';
|
||||
import { bootstrapTelemetry } from 'src/repositories/telemetry.repository';
|
||||
import { isStartUpError } from 'src/utils/misc';
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ export async function bootstrap() {
|
|||
const app = await NestFactory.create(MicroservicesModule, { bufferLogs: true });
|
||||
const logger = await app.resolve(LoggingRepository);
|
||||
const configRepository = app.get(ConfigRepository);
|
||||
app.get(MaintenanceRepository).setCloseFn(() => app.close());
|
||||
app.get(AppRepository).setCloseFn(() => app.close());
|
||||
|
||||
const { environment, host } = configRepository.getEnv();
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { ActivityRepository } from 'src/repositories/activity.repository';
|
|||
import { AlbumUserRepository } from 'src/repositories/album-user.repository';
|
||||
import { AlbumRepository } from 'src/repositories/album.repository';
|
||||
import { ApiKeyRepository } from 'src/repositories/api-key.repository';
|
||||
import { AppRepository } from 'src/repositories/app.repository';
|
||||
import { AssetJobRepository } from 'src/repositories/asset-job.repository';
|
||||
import { AssetRepository } from 'src/repositories/asset.repository';
|
||||
import { AuditRepository } from 'src/repositories/audit.repository';
|
||||
|
|
@ -34,7 +35,6 @@ import { JobRepository } from 'src/repositories/job.repository';
|
|||
import { LibraryRepository } from 'src/repositories/library.repository';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
import { MachineLearningRepository } from 'src/repositories/machine-learning.repository';
|
||||
import { MaintenanceRepository } from 'src/repositories/maintenance.repository';
|
||||
import { MapRepository } from 'src/repositories/map.repository';
|
||||
import { MediaRepository } from 'src/repositories/media.repository';
|
||||
import { MemoryRepository } from 'src/repositories/memory.repository';
|
||||
|
|
@ -211,6 +211,7 @@ export type ServiceOverrides = {
|
|||
album: AlbumRepository;
|
||||
albumUser: AlbumUserRepository;
|
||||
apiKey: ApiKeyRepository;
|
||||
app: AppRepository;
|
||||
audit: AuditRepository;
|
||||
asset: AssetRepository;
|
||||
assetJob: AssetJobRepository;
|
||||
|
|
@ -226,7 +227,6 @@ export type ServiceOverrides = {
|
|||
library: LibraryRepository;
|
||||
logger: LoggingRepository;
|
||||
machineLearning: MachineLearningRepository;
|
||||
maintenance: MaintenanceRepository;
|
||||
map: MapRepository;
|
||||
media: MediaRepository;
|
||||
memory: MemoryRepository;
|
||||
|
|
@ -286,6 +286,7 @@ export const getMocks = () => {
|
|||
albumUser: automock(AlbumUserRepository),
|
||||
asset: newAssetRepositoryMock(),
|
||||
assetJob: automock(AssetJobRepository),
|
||||
app: automock(AppRepository, { strict: false }),
|
||||
config: newConfigRepositoryMock(),
|
||||
database: newDatabaseRepositoryMock(),
|
||||
downloadRepository: automock(DownloadRepository, { strict: false }),
|
||||
|
|
@ -297,7 +298,6 @@ export const getMocks = () => {
|
|||
apiKey: automock(ApiKeyRepository),
|
||||
library: automock(LibraryRepository, { strict: false }),
|
||||
machineLearning: automock(MachineLearningRepository, { args: [loggerMock], strict: false }),
|
||||
maintenance: automock(MaintenanceRepository, { strict: false }),
|
||||
map: automock(MapRepository, { args: [undefined, undefined, { setContext: () => {} }] }),
|
||||
media: newMediaRepositoryMock(),
|
||||
memory: automock(MemoryRepository),
|
||||
|
|
@ -348,6 +348,7 @@ export const newTestService = <T extends BaseService>(
|
|||
overrides.album || (mocks.album as As<AlbumRepository>),
|
||||
overrides.albumUser || (mocks.albumUser as As<AlbumUserRepository>),
|
||||
overrides.apiKey || (mocks.apiKey as As<ApiKeyRepository>),
|
||||
overrides.app || (mocks.app as As<AppRepository>),
|
||||
overrides.asset || (mocks.asset as As<AssetRepository>),
|
||||
overrides.assetJob || (mocks.assetJob as As<AssetJobRepository>),
|
||||
overrides.audit || (mocks.audit as As<AuditRepository>),
|
||||
|
|
@ -362,7 +363,6 @@ export const newTestService = <T extends BaseService>(
|
|||
overrides.job || (mocks.job as As<JobRepository>),
|
||||
overrides.library || (mocks.library as As<LibraryRepository>),
|
||||
overrides.machineLearning || (mocks.machineLearning as As<MachineLearningRepository>),
|
||||
overrides.maintenance || (mocks.maintenance as As<MaintenanceRepository>),
|
||||
overrides.map || (mocks.map as As<MapRepository>),
|
||||
overrides.media || (mocks.media as As<MediaRepository>),
|
||||
overrides.memory || (mocks.memory as As<MemoryRepository>),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { startMaintenance } from '@immich/sdk';
|
||||
import { setMaintenanceMode } from '@immich/sdk';
|
||||
import { Button } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
|
@ -13,7 +13,11 @@
|
|||
|
||||
async function start() {
|
||||
try {
|
||||
await startMaintenance();
|
||||
await setMaintenanceMode({
|
||||
setMaintenanceModeDto: {
|
||||
maintenanceMode: true,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, $t('admin.maintenance_start_error'));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import FormatMessage from '$lib/elements/FormatMessage.svelte';
|
||||
import { maintenanceAuth } from '$lib/stores/maintenance.store';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { endMaintenance } from '@immich/sdk';
|
||||
import { setMaintenanceMode } from '@immich/sdk';
|
||||
import { Button, Heading, Link } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
|
|
@ -16,7 +16,11 @@
|
|||
|
||||
async function end() {
|
||||
try {
|
||||
await endMaintenance();
|
||||
await setMaintenanceMode({
|
||||
setMaintenanceModeDto: {
|
||||
maintenanceMode: false,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, $t('maintenance_end_error'));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue