2023-12-14 11:55:40 -05:00
|
|
|
import { Inject, Injectable } from '@nestjs/common';
|
2023-10-24 17:05:42 +02:00
|
|
|
import { DateTime } from 'luxon';
|
2024-03-20 21:20:38 +01:00
|
|
|
import { StorageCore, StorageFolder } from 'src/cores/storage.core';
|
|
|
|
|
import { SystemConfigCore } from 'src/cores/system-config.core';
|
2024-03-20 19:32:04 +01:00
|
|
|
import { Version, isDev, mimeTypes, serverVersion } from 'src/domain/domain.constant';
|
2023-07-15 20:24:46 -05:00
|
|
|
import {
|
2023-09-08 22:51:46 -04:00
|
|
|
ServerConfigDto,
|
2023-08-18 00:55:26 -04:00
|
|
|
ServerFeaturesDto,
|
2023-07-15 20:24:46 -05:00
|
|
|
ServerInfoResponseDto,
|
|
|
|
|
ServerMediaTypesResponseDto,
|
|
|
|
|
ServerPingResponse,
|
|
|
|
|
ServerStatsResponseDto,
|
|
|
|
|
UsageByUserDto,
|
2024-03-20 23:53:07 +01:00
|
|
|
} from 'src/dtos/server-info.dto';
|
2024-03-20 16:02:51 -05:00
|
|
|
import { SystemMetadataKey } from 'src/entities/system-metadata.entity';
|
2024-03-20 19:32:04 +01:00
|
|
|
import { ImmichLogger } from 'src/infra/logger';
|
2024-03-20 21:42:58 +01:00
|
|
|
import { ClientEvent, ICommunicationRepository } from 'src/interfaces/communication.repository';
|
|
|
|
|
import { IServerInfoRepository } from 'src/interfaces/server-info.repository';
|
|
|
|
|
import { IStorageRepository } from 'src/interfaces/storage.repository';
|
|
|
|
|
import { ISystemConfigRepository } from 'src/interfaces/system-config.repository';
|
|
|
|
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.repository';
|
|
|
|
|
import { IUserRepository, UserStatsQueryResponse } from 'src/interfaces/user.repository';
|
2024-03-20 15:04:03 -05:00
|
|
|
import { asHumanReadable } from 'src/utils';
|
2023-03-21 22:49:19 -04:00
|
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
|
export class ServerInfoService {
|
2023-12-14 11:55:40 -05:00
|
|
|
private logger = new ImmichLogger(ServerInfoService.name);
|
2023-08-18 00:55:26 -04:00
|
|
|
private configCore: SystemConfigCore;
|
2023-10-24 17:05:42 +02:00
|
|
|
private releaseVersion = serverVersion;
|
|
|
|
|
private releaseVersionCheckedAt: DateTime | null = null;
|
2023-05-24 22:05:31 -04:00
|
|
|
|
2023-03-21 22:49:19 -04:00
|
|
|
constructor(
|
2023-10-24 17:05:42 +02:00
|
|
|
@Inject(ICommunicationRepository) private communicationRepository: ICommunicationRepository,
|
2023-08-18 00:55:26 -04:00
|
|
|
@Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
|
2023-03-21 22:49:19 -04:00
|
|
|
@Inject(IUserRepository) private userRepository: IUserRepository,
|
2023-10-24 17:05:42 +02:00
|
|
|
@Inject(IServerInfoRepository) private repository: IServerInfoRepository,
|
2023-03-21 22:49:19 -04:00
|
|
|
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
|
2024-01-03 23:28:32 -06:00
|
|
|
@Inject(ISystemMetadataRepository) private readonly systemMetadataRepository: ISystemMetadataRepository,
|
2023-08-18 00:55:26 -04:00
|
|
|
) {
|
2023-10-09 02:51:03 +02:00
|
|
|
this.configCore = SystemConfigCore.create(configRepository);
|
2023-12-13 12:23:51 -05:00
|
|
|
this.communicationRepository.on('connect', (userId) => this.handleConnect(userId));
|
2023-08-18 00:55:26 -04:00
|
|
|
}
|
2023-03-21 22:49:19 -04:00
|
|
|
|
2024-01-08 16:22:26 +01:00
|
|
|
async init(): Promise<void> {
|
|
|
|
|
await this.handleVersionCheck();
|
|
|
|
|
|
|
|
|
|
const featureFlags = await this.getFeatures();
|
|
|
|
|
if (featureFlags.configFile) {
|
|
|
|
|
await this.setAdminOnboarding();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-21 22:49:19 -04:00
|
|
|
async getInfo(): Promise<ServerInfoResponseDto> {
|
2023-10-14 13:12:59 -04:00
|
|
|
const libraryBase = StorageCore.getBaseFolder(StorageFolder.LIBRARY);
|
2023-05-24 22:05:31 -04:00
|
|
|
const diskInfo = await this.storageRepository.checkDiskUsage(libraryBase);
|
2023-03-21 22:49:19 -04: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;
|
2024-02-02 04:18:00 +01:00
|
|
|
serverInfo.diskUsagePercentage = Number.parseFloat(usagePercentage);
|
2023-03-21 22:49:19 -04:00
|
|
|
return serverInfo;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ping(): ServerPingResponse {
|
2023-08-18 00:55:26 -04:00
|
|
|
return { res: 'pong' };
|
2023-03-21 22:49:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getVersion() {
|
|
|
|
|
return serverVersion;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 00:15:03 -04:00
|
|
|
getFeatures(): Promise<ServerFeaturesDto> {
|
|
|
|
|
return this.configCore.getFeatures();
|
2023-08-18 00:55:26 -04:00
|
|
|
}
|
|
|
|
|
|
2023-10-25 15:13:05 -07:00
|
|
|
async getTheme() {
|
|
|
|
|
const { theme } = await this.configCore.getConfig();
|
2023-10-26 19:32:33 -07:00
|
|
|
return theme;
|
2023-10-25 15:13:05 -07:00
|
|
|
}
|
|
|
|
|
|
2023-09-08 22:51:46 -04:00
|
|
|
async getConfig(): Promise<ServerConfigDto> {
|
|
|
|
|
const config = await this.configCore.getConfig();
|
2023-10-11 04:37:13 +02:00
|
|
|
const isInitialized = await this.userRepository.hasAdmin();
|
2024-01-03 23:28:32 -06:00
|
|
|
const onboarding = await this.systemMetadataRepository.get(SystemMetadataKey.ADMIN_ONBOARDING);
|
2023-10-11 04:37:13 +02:00
|
|
|
|
2023-09-08 22:51:46 -04:00
|
|
|
return {
|
2024-01-04 00:00:17 -05:00
|
|
|
loginPageMessage: config.server.loginPageMessage,
|
2023-10-06 07:01:14 +00:00
|
|
|
trashDays: config.trash.days,
|
2024-03-06 00:45:40 -05:00
|
|
|
userDeleteDelay: config.user.deleteDelay,
|
2023-09-08 22:51:46 -04:00
|
|
|
oauthButtonText: config.oauth.buttonText,
|
2023-10-11 04:37:13 +02:00
|
|
|
isInitialized,
|
2024-01-03 23:28:32 -06:00
|
|
|
isOnboarded: onboarding?.isOnboarded || false,
|
2024-01-03 21:54:48 -05:00
|
|
|
externalDomain: config.server.externalDomain,
|
2023-09-08 22:51:46 -04:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-03 23:28:32 -06:00
|
|
|
setAdminOnboarding(): Promise<void> {
|
|
|
|
|
return this.systemMetadataRepository.set(SystemMetadataKey.ADMIN_ONBOARDING, { isOnboarded: true });
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-03 21:33:15 -04:00
|
|
|
async getStatistics(): Promise<ServerStatsResponseDto> {
|
2023-03-21 22:49:19 -04: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;
|
2023-11-11 20:03:32 -05:00
|
|
|
usage.userName = user.userName;
|
2023-03-21 22:49:19 -04:00
|
|
|
usage.photos = user.photos;
|
|
|
|
|
usage.videos = user.videos;
|
|
|
|
|
usage.usage = user.usage;
|
2024-01-12 18:43:36 -06:00
|
|
|
usage.quotaSizeInBytes = user.quotaSizeInBytes;
|
2023-03-21 22:49:19 -04:00
|
|
|
|
|
|
|
|
serverStats.photos += usage.photos;
|
|
|
|
|
serverStats.videos += usage.videos;
|
|
|
|
|
serverStats.usage += usage.usage;
|
|
|
|
|
serverStats.usageByUser.push(usage);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return serverStats;
|
|
|
|
|
}
|
2023-07-15 20:24:46 -05:00
|
|
|
|
|
|
|
|
getSupportedMediaTypes(): ServerMediaTypesResponseDto {
|
|
|
|
|
return {
|
2023-08-15 12:05:32 -04:00
|
|
|
video: Object.keys(mimeTypes.video),
|
|
|
|
|
image: Object.keys(mimeTypes.image),
|
|
|
|
|
sidecar: Object.keys(mimeTypes.sidecar),
|
2023-07-15 20:24:46 -05:00
|
|
|
};
|
|
|
|
|
}
|
2023-10-24 17:05:42 +02:00
|
|
|
|
|
|
|
|
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)
|
2024-03-04 15:42:22 +01:00
|
|
|
if (this.releaseVersionCheckedAt && DateTime.now().diff(this.releaseVersionCheckedAt).as('minutes') < 60) {
|
2023-10-24 17:05:42 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const githubRelease = await this.repository.getGitHubRelease();
|
2023-12-21 11:06:26 -05:00
|
|
|
const githubVersion = Version.fromString(githubRelease.tag_name);
|
2023-10-24 17:05:42 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-05 23:23:06 +01:00
|
|
|
private handleConnect(userId: string) {
|
2023-12-13 12:23:51 -05:00
|
|
|
this.communicationRepository.send(ClientEvent.SERVER_VERSION, userId, serverVersion);
|
2023-10-24 17:05:42 +02:00
|
|
|
this.newReleaseNotification(userId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private newReleaseNotification(userId?: string) {
|
2023-12-13 12:23:51 -05:00
|
|
|
const event = ClientEvent.NEW_RELEASE;
|
2023-10-24 17:05:42 +02:00
|
|
|
const payload = {
|
|
|
|
|
isAvailable: this.releaseVersion.isNewerThan(serverVersion),
|
|
|
|
|
checkedAt: this.releaseVersionCheckedAt,
|
|
|
|
|
serverVersion,
|
|
|
|
|
releaseVersion: this.releaseVersion,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
userId
|
|
|
|
|
? this.communicationRepository.send(event, userId, payload)
|
|
|
|
|
: this.communicationRepository.broadcast(event, payload);
|
|
|
|
|
}
|
2023-03-21 22:49:19 -04:00
|
|
|
}
|