refactor: server-info (#2038)

This commit is contained in:
Jason Rasmussen 2023-03-21 22:49:19 -04:00 committed by GitHub
parent e10bbfa933
commit b9bc621e2a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 632 additions and 420 deletions

View file

@ -1,19 +0,0 @@
import { ApiProperty } from '@nestjs/swagger';
export class ServerInfoResponseDto {
diskSize!: string;
diskUse!: string;
diskAvailable!: string;
@ApiProperty({ type: 'integer', format: 'int64' })
diskSizeRaw!: number;
@ApiProperty({ type: 'integer', format: 'int64' })
diskUseRaw!: number;
@ApiProperty({ type: 'integer', format: 'int64' })
diskAvailableRaw!: number;
@ApiProperty({ type: 'number', format: 'float' })
diskUsagePercentage!: number;
}

View file

@ -1,10 +0,0 @@
import { ApiResponseProperty } from '@nestjs/swagger';
export class ServerPingResponse {
constructor(res: string) {
this.res = res;
}
@ApiResponseProperty({ type: String, example: 'pong' })
res!: string;
}

View file

@ -1,27 +0,0 @@
import { ApiProperty } from '@nestjs/swagger';
import { UsageByUserDto } from './usage-by-user-response.dto';
export class ServerStatsResponseDto {
@ApiProperty({ type: 'integer' })
photos = 0;
@ApiProperty({ type: 'integer' })
videos = 0;
@ApiProperty({ type: 'integer', format: 'int64' })
usage = 0;
@ApiProperty({
isArray: true,
type: UsageByUserDto,
title: 'Array of usage for each user',
example: [
{
photos: 1,
videos: 1,
diskUsageRaw: 1,
},
],
})
usageByUser: UsageByUserDto[] = [];
}

View file

@ -1,11 +0,0 @@
import { ApiProperty } from '@nestjs/swagger';
import { IServerVersion } from 'apps/immich/src/constants/server_version.constant';
export class ServerVersionReponseDto implements IServerVersion {
@ApiProperty({ type: 'integer' })
major!: number;
@ApiProperty({ type: 'integer' })
minor!: number;
@ApiProperty({ type: 'integer' })
patch!: number;
}

View file

@ -1,16 +0,0 @@
import { ApiProperty } from '@nestjs/swagger';
export class UsageByUserDto {
@ApiProperty({ type: 'string' })
userId!: string;
@ApiProperty({ type: 'string' })
userFirstName!: string;
@ApiProperty({ type: 'string' })
userLastName!: string;
@ApiProperty({ type: 'integer' })
photos!: number;
@ApiProperty({ type: 'integer' })
videos!: number;
@ApiProperty({ type: 'integer', format: 'int64' })
usage!: number;
}

View file

@ -1,36 +0,0 @@
import { Controller, Get } from '@nestjs/common';
import { ServerInfoService } from './server-info.service';
import { serverVersion } from '../../constants/server_version.constant';
import { ApiTags } from '@nestjs/swagger';
import { ServerPingResponse } from './response-dto/server-ping-response.dto';
import { ServerVersionReponseDto } from './response-dto/server-version-response.dto';
import { ServerInfoResponseDto } from './response-dto/server-info-response.dto';
import { ServerStatsResponseDto } from './response-dto/server-stats-response.dto';
import { Authenticated } from '../../decorators/authenticated.decorator';
@ApiTags('Server Info')
@Controller('server-info')
export class ServerInfoController {
constructor(private readonly serverInfoService: ServerInfoService) {}
@Get()
async getServerInfo(): Promise<ServerInfoResponseDto> {
return await this.serverInfoService.getServerInfo();
}
@Get('/ping')
async pingServer(): Promise<ServerPingResponse> {
return new ServerPingResponse('pong');
}
@Get('/version')
async getServerVersion(): Promise<ServerVersionReponseDto> {
return serverVersion;
}
@Authenticated({ admin: true })
@Get('/stats')
async getStats(): Promise<ServerStatsResponseDto> {
return await this.serverInfoService.getStats();
}
}

View file

@ -1,12 +0,0 @@
import { Module } from '@nestjs/common';
import { ServerInfoService } from './server-info.service';
import { ServerInfoController } from './server-info.controller';
import { UserEntity } from '@app/infra';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [TypeOrmModule.forFeature([UserEntity])],
controllers: [ServerInfoController],
providers: [ServerInfoService],
})
export class ServerInfoModule {}

View file

@ -1,81 +0,0 @@
import { APP_UPLOAD_LOCATION } from '@app/common/constants';
import { Injectable } from '@nestjs/common';
import { ServerInfoResponseDto } from './response-dto/server-info-response.dto';
import diskusage from 'diskusage';
import { ServerStatsResponseDto } from './response-dto/server-stats-response.dto';
import { UsageByUserDto } from './response-dto/usage-by-user-response.dto';
import { UserEntity } from '@app/infra';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { asHumanReadable } from '../../utils/human-readable.util';
@Injectable()
export class ServerInfoService {
constructor(
@InjectRepository(UserEntity)
private userRepository: Repository<UserEntity>,
) {}
async getServerInfo(): Promise<ServerInfoResponseDto> {
const diskInfo = await diskusage.check(APP_UPLOAD_LOCATION);
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 = parseFloat(usagePercentage);
return serverInfo;
}
async getStats(): Promise<ServerStatsResponseDto> {
type UserStatsQueryResponse = {
userId: string;
userFirstName: string;
userLastName: string;
photos: string;
videos: string;
usage: string;
};
const userStatsQueryResponse: UserStatsQueryResponse[] = await this.userRepository
.createQueryBuilder('users')
.select('users.id', 'userId')
.addSelect('users.firstName', 'userFirstName')
.addSelect('users.lastName', 'userLastName')
.addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'IMAGE' AND assets.isVisible)`, 'photos')
.addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'VIDEO' AND assets.isVisible)`, 'videos')
.addSelect('COALESCE(SUM(exif.fileSizeInByte), 0)', 'usage')
.leftJoin('users.assets', 'assets')
.leftJoin('assets.exifInfo', 'exif')
.groupBy('users.id')
.orderBy('users.createdAt', 'ASC')
.getRawMany();
const usageByUser = userStatsQueryResponse.map((userStats) => {
const usage = new UsageByUserDto();
usage.userId = userStats.userId;
usage.userFirstName = userStats.userFirstName;
usage.userLastName = userStats.userLastName;
usage.photos = Number(userStats.photos);
usage.videos = Number(userStats.videos);
usage.usage = Number(userStats.usage);
return usage;
});
const serverStats = new ServerStatsResponseDto();
usageByUser.forEach((user) => {
serverStats.photos += user.photos;
serverStats.videos += user.videos;
serverStats.usage += user.usage;
});
serverStats.usageByUser = usageByUser;
return serverStats;
}
}

View file

@ -2,7 +2,6 @@ import { immichAppConfig } from '@app/common/config';
import { Module, OnModuleInit } from '@nestjs/common';
import { AssetModule } from './api-v1/asset/asset.module';
import { ConfigModule } from '@nestjs/config';
import { ServerInfoModule } from './api-v1/server-info/server-info.module';
import { AlbumModule } from './api-v1/album/album.module';
import { AppController } from './app.controller';
import { ScheduleModule } from '@nestjs/schedule';
@ -17,6 +16,7 @@ import {
JobController,
OAuthController,
SearchController,
ServerInfoController,
ShareController,
SystemConfigController,
UserController,
@ -34,8 +34,6 @@ import { AuthGuard } from './middlewares/auth.guard';
AssetModule,
ServerInfoModule,
AlbumModule,
ScheduleModule.forRoot(),
@ -52,6 +50,7 @@ import { AuthGuard } from './middlewares/auth.guard';
JobController,
OAuthController,
SearchController,
ServerInfoController,
ShareController,
SystemConfigController,
UserController,

View file

@ -1,4 +1,4 @@
import { APP_UPLOAD_LOCATION } from '@app/common/constants';
import { APP_UPLOAD_LOCATION } from '@app/domain/domain.constant';
import { BadRequestException, Logger, UnauthorizedException } from '@nestjs/common';
import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface';
import { createHash, randomUUID } from 'crypto';

View file

@ -1,4 +1,4 @@
import { APP_UPLOAD_LOCATION } from '@app/common/constants';
import { APP_UPLOAD_LOCATION } from '@app/domain/domain.constant';
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface';
import { Request } from 'express';

View file

@ -1,17 +0,0 @@
import pkg from 'package.json';
const [major, minor, patch] = pkg.version.split('.');
export interface IServerVersion {
major: number;
minor: number;
patch: number;
}
export const serverVersion: IServerVersion = {
major: Number(major),
minor: Number(minor),
patch: Number(patch),
};
export const SERVER_VERSION = `${serverVersion.major}.${serverVersion.minor}.${serverVersion.patch}`;

View file

@ -4,6 +4,7 @@ export * from './device-info.controller';
export * from './job.controller';
export * from './oauth.controller';
export * from './search.controller';
export * from './server-info.controller';
export * from './share.controller';
export * from './system-config.controller';
export * from './user.controller';

View file

@ -0,0 +1,37 @@
import {
ServerInfoResponseDto,
ServerInfoService,
ServerPingResponse,
ServerStatsResponseDto,
ServerVersionReponseDto,
} from '@app/domain';
import { Controller, Get } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Authenticated } from '../decorators/authenticated.decorator';
@ApiTags('Server Info')
@Controller('server-info')
export class ServerInfoController {
constructor(private readonly service: ServerInfoService) {}
@Get()
getServerInfo(): Promise<ServerInfoResponseDto> {
return this.service.getInfo();
}
@Get('/ping')
pingServer(): ServerPingResponse {
return this.service.ping();
}
@Get('/version')
getServerVersion(): ServerVersionReponseDto {
return this.service.getVersion();
}
@Authenticated({ admin: true })
@Get('/stats')
getStats(): Promise<ServerStatsResponseDto> {
return this.service.getStats();
}
}

View file

@ -6,12 +6,11 @@ import cookieParser from 'cookie-parser';
import { writeFileSync } from 'fs';
import path from 'path';
import { AppModule } from './app.module';
import { SERVER_VERSION } from './constants/server_version.constant';
import { RedisIoAdapter } from './middlewares/redis-io.adapter.middleware';
import { json } from 'body-parser';
import { patchOpenAPI } from './utils/patch-open-api.util';
import { getLogLevels, MACHINE_LEARNING_ENABLED } from '@app/common';
import { IMMICH_ACCESS_COOKIE, SearchService } from '@app/domain';
import { SERVER_VERSION, IMMICH_ACCESS_COOKIE, SearchService } from '@app/domain';
const logger = new Logger('ImmichServer');

View file

@ -2,7 +2,7 @@ import { AssetEntity } from '@app/infra';
import { BadRequestException, Injectable, InternalServerErrorException, Logger, StreamableFile } from '@nestjs/common';
import archiver from 'archiver';
import { extname } from 'path';
import { asHumanReadable, HumanReadableSize } from '../../utils/human-readable.util';
import { asHumanReadable, HumanReadableSize } from '@app/domain';
export interface DownloadArchive {
stream: StreamableFile;

View file

@ -1,24 +0,0 @@
const KiB = Math.pow(1024, 1);
const MiB = Math.pow(1024, 2);
const GiB = Math.pow(1024, 3);
const TiB = Math.pow(1024, 4);
const PiB = Math.pow(1024, 5);
export const HumanReadableSize = { KiB, MiB, GiB, TiB, PiB };
export function asHumanReadable(bytes: number, precision = 1): string {
const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB'];
let magnitude = 0;
let remainder = bytes;
while (remainder >= 1024) {
if (magnitude + 1 < units.length) {
magnitude++;
remainder /= 1024;
} else {
break;
}
}
return `${remainder.toFixed(magnitude == 0 ? 0 : precision)} ${units[magnitude]}`;
}

View file

@ -1,6 +1,6 @@
import { Logger } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { SERVER_VERSION } from 'apps/immich/src/constants/server_version.constant';
import { SERVER_VERSION } from '@app/domain';
import { getLogLevels } from '@app/common';
import { RedisIoAdapter } from '../../immich/src/middlewares/redis-io.adapter.middleware';
import { MicroservicesModule } from './microservices.module';

View file

@ -1,6 +1,5 @@
import { APP_UPLOAD_LOCATION } from '@app/common/constants';
import { AssetEntity, AssetType } from '@app/infra';
import {
APP_UPLOAD_LOCATION,
IAssetJob,
IAssetRepository,
IBaseJob,
@ -10,6 +9,7 @@ import {
SystemConfigService,
WithoutProperty,
} from '@app/domain';
import { AssetEntity, AssetType } from '@app/infra';
import { Process, Processor } from '@nestjs/bull';
import { Inject, Logger } from '@nestjs/common';
import { Job } from 'bull';