mirror of
https://github.com/immich-app/immich
synced 2025-11-14 17:36:12 +00:00
feat(web): onboarding (#6066)
* feat(web): onboarding * feat: openapi * feat: modulization * feat: page advancing * Animation * Add storage templaete settings * sql * more style * Theme * information and styling * hide/show table * Styling * Update user property * fix test * fix test: * fix e2e * test * Update web/src/lib/components/onboarding-page/onboarding-hello.svelte Co-authored-by: bo0tzz <git@bo0tzz.me> * naming * use System Metadata * better return type * onboarding using server metadata * revert previous changes in user entity * sql * test web * fix test server * server/web test * more test * consolidate color theme change logic * consolidate save button to storage template * merge main * fix web --------- Co-authored-by: bo0tzz <git@bo0tzz.me>
This commit is contained in:
parent
f8d64be13c
commit
18f59f78e3
35 changed files with 698 additions and 111 deletions
|
|
@ -86,6 +86,7 @@ export class ServerConfigDto {
|
|||
@ApiProperty({ type: 'integer' })
|
||||
trashDays!: number;
|
||||
isInitialized!: boolean;
|
||||
isOnboarded!: boolean;
|
||||
externalDomain!: string;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { SystemMetadataKey } from '@app/infra/entities';
|
||||
import {
|
||||
newCommunicationRepositoryMock,
|
||||
newServerInfoRepositoryMock,
|
||||
newStorageRepositoryMock,
|
||||
newSystemConfigRepositoryMock,
|
||||
newSystemMetadataRepositoryMock,
|
||||
newUserRepositoryMock,
|
||||
} from '@test';
|
||||
import { serverVersion } from '../domain.constant';
|
||||
|
|
@ -11,6 +13,7 @@ import {
|
|||
IServerInfoRepository,
|
||||
IStorageRepository,
|
||||
ISystemConfigRepository,
|
||||
ISystemMetadataRepository,
|
||||
IUserRepository,
|
||||
} from '../repositories';
|
||||
import { ServerInfoService } from './server-info.service';
|
||||
|
|
@ -22,6 +25,7 @@ describe(ServerInfoService.name, () => {
|
|||
let serverInfoMock: jest.Mocked<IServerInfoRepository>;
|
||||
let storageMock: jest.Mocked<IStorageRepository>;
|
||||
let userMock: jest.Mocked<IUserRepository>;
|
||||
let systemMetadataMock: jest.Mocked<ISystemMetadataRepository>;
|
||||
|
||||
beforeEach(() => {
|
||||
configMock = newSystemConfigRepositoryMock();
|
||||
|
|
@ -29,8 +33,16 @@ describe(ServerInfoService.name, () => {
|
|||
serverInfoMock = newServerInfoRepositoryMock();
|
||||
storageMock = newStorageRepositoryMock();
|
||||
userMock = newUserRepositoryMock();
|
||||
systemMetadataMock = newSystemMetadataRepositoryMock();
|
||||
|
||||
sut = new ServerInfoService(communicationMock, configMock, userMock, serverInfoMock, storageMock);
|
||||
sut = new ServerInfoService(
|
||||
communicationMock,
|
||||
configMock,
|
||||
userMock,
|
||||
serverInfoMock,
|
||||
storageMock,
|
||||
systemMetadataMock,
|
||||
);
|
||||
});
|
||||
|
||||
it('should work', () => {
|
||||
|
|
@ -184,12 +196,21 @@ describe(ServerInfoService.name, () => {
|
|||
loginPageMessage: '',
|
||||
oauthButtonText: 'Login with OAuth',
|
||||
trashDays: 30,
|
||||
isInitialized: undefined,
|
||||
isOnboarded: false,
|
||||
externalDomain: '',
|
||||
});
|
||||
expect(configMock.load).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
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([
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { SystemMetadataKey } from '@app/infra/entities';
|
||||
import { ImmichLogger } from '@app/infra/logger';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DateTime } from 'luxon';
|
||||
|
|
@ -9,6 +10,7 @@ import {
|
|||
IServerInfoRepository,
|
||||
IStorageRepository,
|
||||
ISystemConfigRepository,
|
||||
ISystemMetadataRepository,
|
||||
IUserRepository,
|
||||
UserStatsQueryResponse,
|
||||
} from '../repositories';
|
||||
|
|
@ -37,6 +39,7 @@ export class ServerInfoService {
|
|||
@Inject(IUserRepository) private userRepository: IUserRepository,
|
||||
@Inject(IServerInfoRepository) private repository: IServerInfoRepository,
|
||||
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
|
||||
@Inject(ISystemMetadataRepository) private readonly systemMetadataRepository: ISystemMetadataRepository,
|
||||
) {
|
||||
this.configCore = SystemConfigCore.create(configRepository);
|
||||
this.communicationRepository.on('connect', (userId) => this.handleConnect(userId));
|
||||
|
|
@ -79,16 +82,22 @@ export class ServerInfoService {
|
|||
async getConfig(): Promise<ServerConfigDto> {
|
||||
const config = await this.configCore.getConfig();
|
||||
const isInitialized = await this.userRepository.hasAdmin();
|
||||
const onboarding = await this.systemMetadataRepository.get(SystemMetadataKey.ADMIN_ONBOARDING);
|
||||
|
||||
return {
|
||||
loginPageMessage: config.server.loginPageMessage,
|
||||
trashDays: config.trash.days,
|
||||
oauthButtonText: config.oauth.buttonText,
|
||||
isInitialized,
|
||||
isOnboarded: onboarding?.isOnboarded || false,
|
||||
externalDomain: config.server.externalDomain,
|
||||
};
|
||||
}
|
||||
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
ServerThemeDto,
|
||||
ServerVersionResponseDto,
|
||||
} from '@app/domain';
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { Controller, Get, HttpCode, HttpStatus, Post } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { AdminRoute, Authenticated, PublicRoute } from '../app.guard';
|
||||
import { UseValidation } from '../app.utils';
|
||||
|
|
@ -67,4 +67,11 @@ export class ServerInfoController {
|
|||
getSupportedMediaTypes(): ServerMediaTypesResponseDto {
|
||||
return this.service.getSupportedMediaTypes();
|
||||
}
|
||||
|
||||
@AdminRoute()
|
||||
@Post('admin-onboarding')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
setAdminOnboarding(): Promise<void> {
|
||||
return this.service.setAdminOnboarding();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,10 @@ export class SystemMetadataEntity {
|
|||
|
||||
export enum SystemMetadataKey {
|
||||
REVERSE_GEOCODING_STATE = 'reverse-geocoding-state',
|
||||
ADMIN_ONBOARDING = 'admin-onboarding',
|
||||
}
|
||||
|
||||
export interface SystemMetadata extends Record<SystemMetadataKey, { [key: string]: unknown }> {
|
||||
[SystemMetadataKey.REVERSE_GEOCODING_STATE]: { lastUpdate?: string; lastImportFileName?: string };
|
||||
[SystemMetadataKey.ADMIN_ONBOARDING]: { isOnboarded: boolean };
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue