mirror of
https://github.com/immich-app/immich.git
synced 2025-12-23 09:15:05 +03:00
@@ -21,6 +21,7 @@ import { SessionController } from 'src/controllers/session.controller';
|
||||
import { SharedLinkController } from 'src/controllers/shared-link.controller';
|
||||
import { SyncController } from 'src/controllers/sync.controller';
|
||||
import { SystemConfigController } from 'src/controllers/system-config.controller';
|
||||
import { SystemMetadataController } from 'src/controllers/system-metadata.controller';
|
||||
import { TagController } from 'src/controllers/tag.controller';
|
||||
import { TimelineController } from 'src/controllers/timeline.controller';
|
||||
import { TrashController } from 'src/controllers/trash.controller';
|
||||
@@ -51,6 +52,7 @@ export const controllers = [
|
||||
SharedLinkController,
|
||||
SyncController,
|
||||
SystemConfigController,
|
||||
SystemMetadataController,
|
||||
TagController,
|
||||
TimelineController,
|
||||
TrashController,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Controller, Get, HttpCode, HttpStatus, Post } from '@nestjs/common';
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import {
|
||||
ServerConfigDto,
|
||||
@@ -65,11 +65,4 @@ export class ServerInfoController {
|
||||
getSupportedMediaTypes(): ServerMediaTypesResponseDto {
|
||||
return this.service.getSupportedMediaTypes();
|
||||
}
|
||||
|
||||
@AdminRoute()
|
||||
@Post('admin-onboarding')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
setAdminOnboarding(): Promise<void> {
|
||||
return this.service.setAdminOnboarding();
|
||||
}
|
||||
}
|
||||
|
||||
28
server/src/controllers/system-metadata.controller.ts
Normal file
28
server/src/controllers/system-metadata.controller.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Body, Controller, Get, HttpCode, HttpStatus, Post } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { AdminOnboardingUpdateDto, ReverseGeocodingStateResponseDto } from 'src/dtos/system-metadata.dto';
|
||||
import { Authenticated } from 'src/middleware/auth.guard';
|
||||
import { SystemMetadataService } from 'src/services/system-metadata.service';
|
||||
|
||||
@ApiTags('System Metadata')
|
||||
@Controller('system-metadata')
|
||||
@Authenticated({ admin: true })
|
||||
export class SystemMetadataController {
|
||||
constructor(private service: SystemMetadataService) {}
|
||||
|
||||
@Get('admin-onboarding')
|
||||
getAdminOnboarding(): Promise<AdminOnboardingUpdateDto> {
|
||||
return this.service.getAdminOnboarding();
|
||||
}
|
||||
|
||||
@Post('admin-onboarding')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
updateAdminOnboarding(@Body() dto: AdminOnboardingUpdateDto): Promise<void> {
|
||||
return this.service.updateAdminOnboarding(dto);
|
||||
}
|
||||
|
||||
@Get('reverse-geocoding-state')
|
||||
getReverseGeocodingState(): Promise<ReverseGeocodingStateResponseDto> {
|
||||
return this.service.getReverseGeocodingState();
|
||||
}
|
||||
}
|
||||
15
server/src/dtos/system-metadata.dto.ts
Normal file
15
server/src/dtos/system-metadata.dto.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { IsBoolean } from 'class-validator';
|
||||
|
||||
export class AdminOnboardingUpdateDto {
|
||||
@IsBoolean()
|
||||
isOnboarded!: boolean;
|
||||
}
|
||||
|
||||
export class AdminOnboardingResponseDto {
|
||||
isOnboarded!: boolean;
|
||||
}
|
||||
|
||||
export class ReverseGeocodingStateResponseDto {
|
||||
lastUpdate!: string | null;
|
||||
lastImportFileName!: string | null;
|
||||
}
|
||||
@@ -36,8 +36,8 @@ export class MetadataRepository implements IMetadataRepository {
|
||||
this.logger.log('Initializing metadata repository');
|
||||
const geodataDate = await readFile(geodataDatePath, 'utf8');
|
||||
|
||||
// TODO move to metadata service init
|
||||
const geocodingMetadata = await this.systemMetadataRepository.get(SystemMetadataKey.REVERSE_GEOCODING_STATE);
|
||||
|
||||
if (geocodingMetadata?.lastUpdate === geodataDate) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import { StorageTemplateService } from 'src/services/storage-template.service';
|
||||
import { StorageService } from 'src/services/storage.service';
|
||||
import { SyncService } from 'src/services/sync.service';
|
||||
import { SystemConfigService } from 'src/services/system-config.service';
|
||||
import { SystemMetadataService } from 'src/services/system-metadata.service';
|
||||
import { TagService } from 'src/services/tag.service';
|
||||
import { TimelineService } from 'src/services/timeline.service';
|
||||
import { TrashService } from 'src/services/trash.service';
|
||||
@@ -58,6 +59,7 @@ export const services = [
|
||||
StorageTemplateService,
|
||||
SyncService,
|
||||
SystemConfigService,
|
||||
SystemMetadataService,
|
||||
TagService,
|
||||
TimelineService,
|
||||
TrashService,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { serverVersion } from 'src/constants';
|
||||
import { SystemMetadataKey } from 'src/entities/system-metadata.entity';
|
||||
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
import { IServerInfoRepository } from 'src/interfaces/server-info.interface';
|
||||
@@ -207,13 +206,6 @@ describe(ServerInfoService.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('setAdminOnboarding', () => {
|
||||
it('should set admin onboarding to true', async () => {
|
||||
await sut.setAdminOnboarding();
|
||||
expect(systemMetadataMock.set).toHaveBeenCalledWith(SystemMetadataKey.ADMIN_ONBOARDING, { isOnboarded: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStats', () => {
|
||||
it('should total up usage by user', async () => {
|
||||
userMock.getUserStats.mockResolvedValue([
|
||||
|
||||
@@ -51,7 +51,9 @@ export class ServerInfoService {
|
||||
|
||||
const featureFlags = await this.getFeatures();
|
||||
if (featureFlags.configFile) {
|
||||
await this.setAdminOnboarding();
|
||||
await this.systemMetadataRepository.set(SystemMetadataKey.ADMIN_ONBOARDING, {
|
||||
isOnboarded: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,10 +107,6 @@ export class ServerInfoService {
|
||||
};
|
||||
}
|
||||
|
||||
setAdminOnboarding(): Promise<void> {
|
||||
return this.systemMetadataRepository.set(SystemMetadataKey.ADMIN_ONBOARDING, { isOnboarded: true });
|
||||
}
|
||||
|
||||
async getStatistics(): Promise<ServerStatsResponseDto> {
|
||||
const userStats: UserStatsQueryResponse[] = await this.userRepository.getUserStats();
|
||||
const serverStats = new ServerStatsResponseDto();
|
||||
|
||||
31
server/src/services/system-metadata.service.spec.ts
Normal file
31
server/src/services/system-metadata.service.spec.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { SystemMetadataKey } from 'src/entities/system-metadata.entity';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { SystemMetadataService } from 'src/services/system-metadata.service';
|
||||
import { newSystemMetadataRepositoryMock } from 'test/repositories/system-metadata.repository.mock';
|
||||
import { Mocked } from 'vitest';
|
||||
|
||||
describe(SystemMetadataService.name, () => {
|
||||
let sut: SystemMetadataService;
|
||||
let metadataMock: Mocked<ISystemMetadataRepository>;
|
||||
|
||||
beforeEach(() => {
|
||||
metadataMock = newSystemMetadataRepositoryMock();
|
||||
sut = new SystemMetadataService(metadataMock);
|
||||
});
|
||||
|
||||
it('should work', () => {
|
||||
expect(sut).toBeDefined();
|
||||
});
|
||||
|
||||
describe('updateAdminOnboarding', () => {
|
||||
it('should update isOnboarded to true', async () => {
|
||||
await expect(sut.updateAdminOnboarding({ isOnboarded: true })).resolves.toBeUndefined();
|
||||
expect(metadataMock.set).toHaveBeenCalledWith(SystemMetadataKey.ADMIN_ONBOARDING, { isOnboarded: true });
|
||||
});
|
||||
|
||||
it('should update isOnboarded to false', async () => {
|
||||
await expect(sut.updateAdminOnboarding({ isOnboarded: false })).resolves.toBeUndefined();
|
||||
expect(metadataMock.set).toHaveBeenCalledWith(SystemMetadataKey.ADMIN_ONBOARDING, { isOnboarded: false });
|
||||
});
|
||||
});
|
||||
});
|
||||
29
server/src/services/system-metadata.service.ts
Normal file
29
server/src/services/system-metadata.service.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import {
|
||||
AdminOnboardingResponseDto,
|
||||
AdminOnboardingUpdateDto,
|
||||
ReverseGeocodingStateResponseDto,
|
||||
} from 'src/dtos/system-metadata.dto';
|
||||
import { SystemMetadataKey } from 'src/entities/system-metadata.entity';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
|
||||
@Injectable()
|
||||
export class SystemMetadataService {
|
||||
constructor(@Inject(ISystemMetadataRepository) private repository: ISystemMetadataRepository) {}
|
||||
|
||||
async getAdminOnboarding(): Promise<AdminOnboardingResponseDto> {
|
||||
const value = await this.repository.get(SystemMetadataKey.ADMIN_ONBOARDING);
|
||||
return { isOnboarded: false, ...value };
|
||||
}
|
||||
|
||||
async updateAdminOnboarding(dto: AdminOnboardingUpdateDto): Promise<void> {
|
||||
await this.repository.set(SystemMetadataKey.ADMIN_ONBOARDING, {
|
||||
isOnboarded: dto.isOnboarded,
|
||||
});
|
||||
}
|
||||
|
||||
async getReverseGeocodingState(): Promise<ReverseGeocodingStateResponseDto> {
|
||||
const value = await this.repository.get(SystemMetadataKey.REVERSE_GEOCODING_STATE);
|
||||
return { lastUpdate: null, lastImportFileName: null, ...value };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user