1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-10 13:56:47 +01:00
immich/server/src/services/server-info.service.ts

193 lines
6.8 KiB
TypeScript
Raw Normal View History

import { Inject, Injectable } from '@nestjs/common';
import { DateTime } from 'luxon';
2024-03-21 04:15:09 +01:00
import { isDev, serverVersion } from 'src/constants';
import { StorageCore, StorageFolder } from 'src/cores/storage.core';
import { SystemConfigCore } from 'src/cores/system-config.core';
import { OnServerEvent } from 'src/decorators';
import {
ServerConfigDto,
ServerFeaturesDto,
ServerInfoResponseDto,
ServerMediaTypesResponseDto,
ServerPingResponse,
ServerStatsResponseDto,
UsageByUserDto,
} from 'src/dtos/server-info.dto';
2024-03-20 22:02:51 +01:00
import { SystemMetadataKey } from 'src/entities/system-metadata.entity';
import { ClientEvent, IEventRepository, ServerEvent, ServerEventMap } from 'src/interfaces/event.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { IServerInfoRepository } from 'src/interfaces/server-info.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { IUserRepository, UserStatsQueryResponse } from 'src/interfaces/user.interface';
2024-03-21 04:15:09 +01:00
import { asHumanReadable } from 'src/utils/bytes';
import { mimeTypes } from 'src/utils/mime-types';
import { Version } from 'src/utils/version';
2023-03-22 03:49:19 +01:00
@Injectable()
export class ServerInfoService {
private configCore: SystemConfigCore;
private releaseVersion = serverVersion;
private releaseVersionCheckedAt: DateTime | null = null;
2023-03-22 03:49:19 +01:00
constructor(
@Inject(IEventRepository) private eventRepository: IEventRepository,
@Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
2023-03-22 03:49:19 +01:00
@Inject(IUserRepository) private userRepository: IUserRepository,
@Inject(IServerInfoRepository) private repository: IServerInfoRepository,
2023-03-22 03:49:19 +01:00
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
@Inject(ISystemMetadataRepository) private systemMetadataRepository: ISystemMetadataRepository,
@Inject(ILoggerRepository) private logger: ILoggerRepository,
) {
this.logger.setContext(ServerInfoService.name);
this.configCore = SystemConfigCore.create(configRepository, this.logger);
}
2023-03-22 03:49:19 +01:00
onConnect() {}
async init(): Promise<void> {
await this.handleVersionCheck();
const featureFlags = await this.getFeatures();
if (featureFlags.configFile) {
await this.systemMetadataRepository.set(SystemMetadataKey.ADMIN_ONBOARDING, {
isOnboarded: true,
});
}
}
2023-03-22 03:49:19 +01:00
async getInfo(): Promise<ServerInfoResponseDto> {
const libraryBase = StorageCore.getBaseFolder(StorageFolder.LIBRARY);
const diskInfo = await this.storageRepository.checkDiskUsage(libraryBase);
2023-03-22 03:49:19 +01:00
const usagePercentage = (((diskInfo.total - diskInfo.free) / diskInfo.total) * 100).toFixed(2);
const serverInfo = new ServerInfoResponseDto();
serverInfo.diskAvailable = asHumanReadable(diskInfo.available);
serverInfo.diskSize = asHumanReadable(diskInfo.total);
serverInfo.diskUse = asHumanReadable(diskInfo.total - diskInfo.free);
serverInfo.diskAvailableRaw = diskInfo.available;
serverInfo.diskSizeRaw = diskInfo.total;
serverInfo.diskUseRaw = diskInfo.total - diskInfo.free;
serverInfo.diskUsagePercentage = Number.parseFloat(usagePercentage);
2023-03-22 03:49:19 +01:00
return serverInfo;
}
ping(): ServerPingResponse {
return { res: 'pong' };
2023-03-22 03:49:19 +01:00
}
getVersion() {
return serverVersion;
}
getFeatures(): Promise<ServerFeaturesDto> {
return this.configCore.getFeatures();
}
async getTheme() {
const { theme } = await this.configCore.getConfig();
return theme;
}
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,
feat(server): trash asset (#4015) * refactor(server): delete assets endpoint * fix: formatting * chore: cleanup * chore: open api * chore(mobile): replace DeleteAssetDTO with BulkIdsDTOs * feat: trash an asset * chore(server): formatting * chore: open api * chore: wording * chore: open-api * feat(server): add withDeleted to getAssets queries * WIP: mobile-recycle-bin * feat(server): recycle-bin to system config * feat(web): use recycle-bin system config * chore(server): domain assetcore removed * chore(server): rename recycle-bin to trash * chore(web): rename recycle-bin to trash * chore(server): always send soft deleted assets for getAllByUserId * chore(web): formatting * feat(server): permanent delete assets older than trashed period * feat(web): trash empty placeholder image * feat(server): empty trash * feat(web): empty trash * WIP: mobile-recycle-bin * refactor(server): empty / restore trash to separate endpoint * test(server): handle failures * test(server): fix e2e server-info test * test(server): deletion test refactor * feat(mobile): use map settings from server-config to enable / disable map * feat(mobile): trash asset * fix(server): operations on assets in trash * feat(web): show trash statistics * fix(web): handle trash enabled * fix(mobile): restore updates from trash * fix(server): ignore trashed assets for person * fix(server): add / remove search index when trashed / restored * chore(web): format * fix(server): asset service test * fix(server): include trashed assts for duplicates from uploads * feat(mobile): no dialog for trash, always dialog for permanent delete * refactor(mobile): use isar where instead of dart filter * refactor(mobile): asset provide - handle deletes in single db txn * chore(mobile): review changes * feat(web): confirmation before empty trash * server: review changes * fix(server): handle library changes * fix: filter external assets from getting trashed / deleted * fix(server): empty-bin * feat: broadcast config update events through ws * change order of trash button on mobile * styling * fix(mobile): do not show trashed toast for local only assets --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-06 09:01:14 +02:00
trashDays: config.trash.days,
userDeleteDelay: config.user.deleteDelay,
oauthButtonText: config.oauth.buttonText,
isInitialized,
isOnboarded: onboarding?.isOnboarded || false,
externalDomain: config.server.externalDomain,
};
}
async getStatistics(): Promise<ServerStatsResponseDto> {
2023-03-22 03:49:19 +01:00
const userStats: UserStatsQueryResponse[] = await this.userRepository.getUserStats();
const serverStats = new ServerStatsResponseDto();
for (const user of userStats) {
const usage = new UsageByUserDto();
usage.userId = user.userId;
usage.userName = user.userName;
2023-03-22 03:49:19 +01:00
usage.photos = user.photos;
usage.videos = user.videos;
usage.usage = user.usage;
usage.quotaSizeInBytes = user.quotaSizeInBytes;
2023-03-22 03:49:19 +01:00
serverStats.photos += usage.photos;
serverStats.videos += usage.videos;
serverStats.usage += usage.usage;
serverStats.usageByUser.push(usage);
}
return serverStats;
}
getSupportedMediaTypes(): ServerMediaTypesResponseDto {
return {
2023-08-15 18:05:32 +02:00
video: Object.keys(mimeTypes.video),
image: Object.keys(mimeTypes.image),
sidecar: Object.keys(mimeTypes.sidecar),
};
}
async handleVersionCheck(): Promise<boolean> {
try {
if (isDev) {
return true;
}
const { newVersionCheck } = await this.configCore.getConfig();
if (!newVersionCheck.enabled) {
return true;
}
// check once per hour (max)
if (this.releaseVersionCheckedAt && DateTime.now().diff(this.releaseVersionCheckedAt).as('minutes') < 60) {
return true;
}
const githubRelease = await this.repository.getGitHubRelease();
const githubVersion = Version.fromString(githubRelease.tag_name);
const publishedAt = new Date(githubRelease.published_at);
this.releaseVersion = githubVersion;
this.releaseVersionCheckedAt = DateTime.now();
if (githubVersion.isNewerThan(serverVersion)) {
this.logger.log(`Found ${githubVersion.toString()}, released at ${publishedAt.toLocaleString()}`);
this.newReleaseNotification();
}
} catch (error: Error | any) {
this.logger.warn(`Unable to run version check: ${error}`, error?.stack);
}
return true;
}
@OnServerEvent(ServerEvent.WEBSOCKET_CONNECT)
onWebsocketConnection({ userId }: ServerEventMap[ServerEvent.WEBSOCKET_CONNECT]) {
this.eventRepository.clientSend(ClientEvent.SERVER_VERSION, userId, serverVersion);
this.newReleaseNotification(userId);
}
private newReleaseNotification(userId?: string) {
const event = ClientEvent.NEW_RELEASE;
const payload = {
isAvailable: this.releaseVersion.isNewerThan(serverVersion),
checkedAt: this.releaseVersionCheckedAt,
serverVersion,
releaseVersion: this.releaseVersion,
};
userId
? this.eventRepository.clientSend(event, userId, payload)
: this.eventRepository.clientBroadcast(event, payload);
}
2023-03-22 03:49:19 +01:00
}