chore: organize config, validation, decorators (#8118)
* refactor: validation * refactor: utilities * refactor: config
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
|
||||
import { AuthDto } from 'src/domain/auth/auth.dto';
|
||||
import { setDifference, setIsEqual, setUnion } from 'src/domain/domain.util';
|
||||
import { IAccessRepository } from 'src/domain/repositories/access.repository';
|
||||
import { SharedLinkEntity } from 'src/infra/entities/shared-link.entity';
|
||||
import { setDifference, setIsEqual, setUnion } from 'src/utils';
|
||||
|
||||
export enum Permission {
|
||||
ACTIVITY_CREATE = 'activity.create',
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsEnum, IsNotEmpty, IsString, ValidateIf } from 'class-validator';
|
||||
import { Optional, ValidateUUID } from 'src/domain/domain.util';
|
||||
import { UserDto, mapSimpleUser } from 'src/domain/user/response-dto/user-response.dto';
|
||||
import { ActivityEntity } from 'src/infra/entities/activity.entity';
|
||||
import { Optional, ValidateUUID } from 'src/validation';
|
||||
|
||||
export enum ReactionType {
|
||||
COMMENT = 'comment',
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { AssetResponseDto, mapAsset } from 'src/domain/asset/response-dto/asset-response.dto';
|
||||
import { AuthDto } from 'src/domain/auth/auth.dto';
|
||||
import { Optional } from 'src/domain/domain.util';
|
||||
import { UserResponseDto, mapUser } from 'src/domain/user/response-dto/user-response.dto';
|
||||
import { AlbumEntity, AssetOrder } from 'src/infra/entities/album.entity';
|
||||
import { Optional } from 'src/validation';
|
||||
|
||||
export class AlbumResponseDto {
|
||||
id!: string;
|
||||
|
||||
@@ -14,7 +14,6 @@ import { AlbumInfoDto } from 'src/domain/album/dto/album.dto';
|
||||
import { GetAlbumsDto } from 'src/domain/album/dto/get-albums.dto';
|
||||
import { BulkIdErrorReason, BulkIdResponseDto, BulkIdsDto } from 'src/domain/asset/response-dto/asset-ids-response.dto';
|
||||
import { AuthDto } from 'src/domain/auth/auth.dto';
|
||||
import { setUnion } from 'src/domain/domain.util';
|
||||
import { IAccessRepository } from 'src/domain/repositories/access.repository';
|
||||
import { AlbumAssetCount, AlbumInfoOptions, IAlbumRepository } from 'src/domain/repositories/album.repository';
|
||||
import { IAssetRepository } from 'src/domain/repositories/asset.repository';
|
||||
@@ -22,6 +21,7 @@ import { IUserRepository } from 'src/domain/repositories/user.repository';
|
||||
import { AlbumEntity } from 'src/infra/entities/album.entity';
|
||||
import { AssetEntity } from 'src/infra/entities/asset.entity';
|
||||
import { UserEntity } from 'src/infra/entities/user.entity';
|
||||
import { setUnion } from 'src/utils';
|
||||
|
||||
@Injectable()
|
||||
export class AlbumService {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ArrayNotEmpty } from 'class-validator';
|
||||
import { ValidateUUID } from 'src/domain/domain.util';
|
||||
import { ValidateUUID } from 'src/validation';
|
||||
|
||||
export class AddUsersDto {
|
||||
@ValidateUUID({ each: true })
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsString } from 'class-validator';
|
||||
import { Optional, ValidateUUID } from 'src/domain/domain.util';
|
||||
import { Optional, ValidateUUID } from 'src/validation';
|
||||
|
||||
export class CreateAlbumDto {
|
||||
@IsString()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsEnum, IsString } from 'class-validator';
|
||||
import { Optional, ValidateBoolean, ValidateUUID } from 'src/domain/domain.util';
|
||||
import { AssetOrder } from 'src/infra/entities/album.entity';
|
||||
import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation';
|
||||
|
||||
export class UpdateAlbumDto {
|
||||
@Optional()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ValidateBoolean } from 'src/domain/domain.util';
|
||||
import { ValidateBoolean } from 'src/validation';
|
||||
|
||||
export class AlbumInfoDto {
|
||||
@ValidateBoolean({ optional: true })
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ValidateBoolean, ValidateUUID } from 'src/domain/domain.util';
|
||||
import { ValidateBoolean, ValidateUUID } from 'src/validation';
|
||||
|
||||
export class GetAlbumsDto {
|
||||
@ValidateBoolean({ optional: true })
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
import { Optional } from 'src/domain/domain.util';
|
||||
import { Optional } from 'src/validation';
|
||||
export class APIKeyCreateDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
|
||||
@@ -21,7 +21,6 @@ import { MapMarkerResponseDto } from 'src/domain/asset/response-dto/map-marker-r
|
||||
import { TimeBucketResponseDto } from 'src/domain/asset/response-dto/time-bucket-response.dto';
|
||||
import { AuthDto } from 'src/domain/auth/auth.dto';
|
||||
import { mimeTypes } from 'src/domain/domain.constant';
|
||||
import { usePagination } from 'src/domain/domain.util';
|
||||
import { JOBS_ASSET_PAGINATION_SIZE, JobName } from 'src/domain/job/job.constants';
|
||||
import { IAssetDeletionJob, ISidecarWriteJob } from 'src/domain/job/job.interface';
|
||||
import { IAccessRepository } from 'src/domain/repositories/access.repository';
|
||||
@@ -38,6 +37,7 @@ import { SystemConfigCore } from 'src/domain/system-config/system-config.core';
|
||||
import { AssetEntity } from 'src/infra/entities/asset.entity';
|
||||
import { LibraryType } from 'src/infra/entities/library.entity';
|
||||
import { ImmichLogger } from 'src/infra/logger';
|
||||
import { usePagination } from 'src/utils';
|
||||
|
||||
export enum UploadFieldName {
|
||||
ASSET_DATA = 'assetData',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsEnum } from 'class-validator';
|
||||
import { ValidateUUID } from 'src/domain/domain.util';
|
||||
import { ValidateUUID } from 'src/validation';
|
||||
|
||||
export class AssetIdsDto {
|
||||
@ValidateUUID({ each: true })
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ValidateUUID } from 'src/domain/domain.util';
|
||||
import { ValidateUUID } from 'src/validation';
|
||||
|
||||
export class UpdateStackParentDto {
|
||||
@ValidateUUID()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { ValidateBoolean } from 'src/domain/domain.util';
|
||||
import { AssetStats } from 'src/domain/repositories/asset.repository';
|
||||
import { AssetType } from 'src/infra/entities/asset.entity';
|
||||
import { ValidateBoolean } from 'src/validation';
|
||||
|
||||
export class AssetStatsDto {
|
||||
@ValidateBoolean({ optional: true })
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
ValidateIf,
|
||||
} from 'class-validator';
|
||||
import { BulkIdsDto } from 'src/domain/asset/response-dto/asset-ids-response.dto';
|
||||
import { Optional, ValidateBoolean, ValidateUUID } from 'src/domain/domain.util';
|
||||
import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation';
|
||||
|
||||
export class DeviceIdDto {
|
||||
@IsNotEmpty()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ValidateBoolean, ValidateDate } from 'src/domain/domain.util';
|
||||
import { ValidateBoolean, ValidateDate } from 'src/validation';
|
||||
|
||||
export class MapMarkerDto {
|
||||
@ValidateBoolean({ optional: true })
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
|
||||
import { Optional, ValidateBoolean, ValidateUUID } from 'src/domain/domain.util';
|
||||
import { TimeBucketSize } from 'src/domain/repositories/asset.repository';
|
||||
import { AssetOrder } from 'src/infra/entities/album.entity';
|
||||
import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation';
|
||||
|
||||
export class TimeBucketDto {
|
||||
@IsNotEmpty()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ValidateUUID } from 'src/domain/domain.util';
|
||||
import { ValidateUUID } from 'src/validation';
|
||||
|
||||
/** @deprecated Use `BulkIdResponseDto` instead */
|
||||
export enum AssetIdErrorReason {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsArray, IsEnum, IsString, IsUUID, ValidateNested } from 'class-validator';
|
||||
import { Optional, ValidateDate, ValidateUUID } from 'src/domain/domain.util';
|
||||
import { EntityType } from 'src/infra/entities/audit.entity';
|
||||
import { AssetPathType, PathType, PersonPathType, UserPathType } from 'src/infra/entities/move.entity';
|
||||
import { Optional, ValidateDate, ValidateUUID } from 'src/validation';
|
||||
|
||||
const PathEnum = Object.values({ ...AssetPathType, ...PersonPathType, ...UserPathType });
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
} from 'src/domain/audit/audit.dto';
|
||||
import { AuthDto } from 'src/domain/auth/auth.dto';
|
||||
import { AUDIT_LOG_MAX_DURATION } from 'src/domain/domain.constant';
|
||||
import { usePagination } from 'src/domain/domain.util';
|
||||
import { JOBS_ASSET_PAGINATION_SIZE } from 'src/domain/job/job.constants';
|
||||
import { IAccessRepository } from 'src/domain/repositories/access.repository';
|
||||
import { IAssetRepository } from 'src/domain/repositories/asset.repository';
|
||||
@@ -26,6 +25,7 @@ import { StorageCore, StorageFolder } from 'src/domain/storage/storage.core';
|
||||
import { DatabaseAction } from 'src/infra/entities/audit.entity';
|
||||
import { AssetPathType, PersonPathType, UserPathType } from 'src/infra/entities/move.entity';
|
||||
import { ImmichLogger } from 'src/infra/logger';
|
||||
import { usePagination } from 'src/utils';
|
||||
|
||||
@Injectable()
|
||||
export class AuditService {
|
||||
|
||||
@@ -34,7 +34,6 @@ import {
|
||||
mapLoginResponse,
|
||||
mapUserToken,
|
||||
} from 'src/domain/auth/auth.dto';
|
||||
import { HumanReadableSize } from 'src/domain/domain.util';
|
||||
import { IAccessRepository } from 'src/domain/repositories/access.repository';
|
||||
import { IKeyRepository } from 'src/domain/repositories/api-key.repository';
|
||||
import { ICryptoRepository } from 'src/domain/repositories/crypto.repository';
|
||||
@@ -49,6 +48,7 @@ import { UserCore } from 'src/domain/user/user.core';
|
||||
import { SystemConfig } from 'src/infra/entities/system-config.entity';
|
||||
import { UserEntity } from 'src/infra/entities/user.entity';
|
||||
import { ImmichLogger } from 'src/infra/logger';
|
||||
import { HumanReadableSize } from 'src/utils';
|
||||
|
||||
export interface LoginDetails {
|
||||
isSecure: boolean;
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
// TODO: remove nestjs references from domain
|
||||
import { ConfigModuleOptions } from '@nestjs/config';
|
||||
import Joi from 'joi';
|
||||
import { LogLevel } from 'src/infra/entities/system-config.entity';
|
||||
|
||||
const WHEN_DB_URL_SET = Joi.when('DB_URL', {
|
||||
is: Joi.exist(),
|
||||
then: Joi.string().optional(),
|
||||
otherwise: Joi.string().required(),
|
||||
});
|
||||
|
||||
export const immichAppConfig: ConfigModuleOptions = {
|
||||
envFilePath: '.env',
|
||||
isGlobal: true,
|
||||
validationSchema: Joi.object({
|
||||
NODE_ENV: Joi.string().optional().valid('development', 'production', 'staging').default('development'),
|
||||
LOG_LEVEL: Joi.string()
|
||||
.optional()
|
||||
.valid(...Object.values(LogLevel)),
|
||||
|
||||
DB_USERNAME: WHEN_DB_URL_SET,
|
||||
DB_PASSWORD: WHEN_DB_URL_SET,
|
||||
DB_DATABASE_NAME: WHEN_DB_URL_SET,
|
||||
DB_URL: Joi.string().optional(),
|
||||
DB_VECTOR_EXTENSION: Joi.string().optional().valid('pgvector', 'pgvecto.rs').default('pgvecto.rs'),
|
||||
|
||||
MACHINE_LEARNING_PORT: Joi.number().optional(),
|
||||
MICROSERVICES_PORT: Joi.number().optional(),
|
||||
IMMICH_METRICS_PORT: Joi.number().optional(),
|
||||
|
||||
IMMICH_METRICS: Joi.boolean().optional().default(false),
|
||||
IMMICH_HOST_METRICS: Joi.boolean().optional().default(false),
|
||||
IMMICH_API_METRICS: Joi.boolean().optional().default(false),
|
||||
IMMICH_IO_METRICS: Joi.boolean().optional().default(false),
|
||||
}),
|
||||
};
|
||||
@@ -1,280 +0,0 @@
|
||||
import { BadRequestException, applyDecorators } from '@nestjs/common';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Transform } from 'class-transformer';
|
||||
import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsDate,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
ValidateIf,
|
||||
ValidationOptions,
|
||||
isDateString,
|
||||
} from 'class-validator';
|
||||
import { CronJob } from 'cron';
|
||||
import _ from 'lodash';
|
||||
import { basename, extname } from 'node:path';
|
||||
import sanitize from 'sanitize-filename';
|
||||
import { ImmichLogger } from 'src/infra/logger';
|
||||
|
||||
export enum CacheControl {
|
||||
PRIVATE_WITH_CACHE = 'private_with_cache',
|
||||
PRIVATE_WITHOUT_CACHE = 'private_without_cache',
|
||||
NONE = 'none',
|
||||
}
|
||||
|
||||
export class ImmichFileResponse {
|
||||
public readonly path!: string;
|
||||
public readonly contentType!: string;
|
||||
public readonly cacheControl!: CacheControl;
|
||||
|
||||
constructor(response: ImmichFileResponse) {
|
||||
Object.assign(this, response);
|
||||
}
|
||||
}
|
||||
|
||||
export interface OpenGraphTags {
|
||||
title: string;
|
||||
description: string;
|
||||
imageUrl?: string;
|
||||
}
|
||||
|
||||
export const isConnectionAborted = (error: Error | any) => error.code === 'ECONNABORTED';
|
||||
|
||||
type UUIDOptions = { optional?: boolean; each?: boolean };
|
||||
export const ValidateUUID = (options?: UUIDOptions) => {
|
||||
const { optional, each } = { optional: false, each: false, ...options };
|
||||
return applyDecorators(
|
||||
IsUUID('4', { each }),
|
||||
ApiProperty({ format: 'uuid' }),
|
||||
optional ? Optional() : IsNotEmpty(),
|
||||
each ? IsArray() : IsString(),
|
||||
);
|
||||
};
|
||||
|
||||
type DateOptions = { optional?: boolean; nullable?: boolean; format?: 'date' | 'date-time' };
|
||||
export const ValidateDate = (options?: DateOptions) => {
|
||||
const { optional, nullable, format } = { optional: false, nullable: false, format: 'date-time', ...options };
|
||||
|
||||
const decorators = [
|
||||
ApiProperty({ format }),
|
||||
IsDate(),
|
||||
optional ? Optional({ nullable: true }) : IsNotEmpty(),
|
||||
Transform(({ key, value }) => {
|
||||
if (value === null || value === undefined) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (!isDateString(value)) {
|
||||
throw new BadRequestException(`${key} must be a date string`);
|
||||
}
|
||||
|
||||
return new Date(value as string);
|
||||
}),
|
||||
];
|
||||
|
||||
if (optional) {
|
||||
decorators.push(Optional({ nullable }));
|
||||
}
|
||||
|
||||
return applyDecorators(...decorators);
|
||||
};
|
||||
|
||||
type BooleanOptions = { optional?: boolean };
|
||||
export const ValidateBoolean = (options?: BooleanOptions) => {
|
||||
const { optional } = { optional: false, ...options };
|
||||
const decorators = [
|
||||
// ApiProperty(),
|
||||
IsBoolean(),
|
||||
Transform(({ value }) => {
|
||||
if (value == 'true') {
|
||||
return true;
|
||||
} else if (value == 'false') {
|
||||
return false;
|
||||
}
|
||||
return value;
|
||||
}),
|
||||
];
|
||||
|
||||
if (optional) {
|
||||
decorators.push(Optional());
|
||||
}
|
||||
|
||||
return applyDecorators(...decorators);
|
||||
};
|
||||
|
||||
export function validateCronExpression(expression: string) {
|
||||
try {
|
||||
new CronJob(expression, () => {});
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
type IValue = { value: string };
|
||||
|
||||
export const toEmail = ({ value }: IValue) => value?.toLowerCase();
|
||||
|
||||
export const toSanitized = ({ value }: IValue) => sanitize((value || '').replaceAll('.', ''));
|
||||
|
||||
export function getFileNameWithoutExtension(path: string): string {
|
||||
return basename(path, extname(path));
|
||||
}
|
||||
|
||||
export function getLivePhotoMotionFilename(stillName: string, motionName: string) {
|
||||
return getFileNameWithoutExtension(stillName) + extname(motionName);
|
||||
}
|
||||
|
||||
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]}`;
|
||||
}
|
||||
|
||||
export interface PaginationOptions {
|
||||
take: number;
|
||||
skip?: number;
|
||||
}
|
||||
|
||||
export enum PaginationMode {
|
||||
LIMIT_OFFSET = 'limit-offset',
|
||||
SKIP_TAKE = 'skip-take',
|
||||
}
|
||||
|
||||
export interface PaginatedBuilderOptions {
|
||||
take: number;
|
||||
skip?: number;
|
||||
mode?: PaginationMode;
|
||||
}
|
||||
|
||||
export interface PaginationResult<T> {
|
||||
items: T[];
|
||||
hasNextPage: boolean;
|
||||
}
|
||||
|
||||
export type Paginated<T> = Promise<PaginationResult<T>>;
|
||||
|
||||
export async function* usePagination<T>(
|
||||
pageSize: number,
|
||||
getNextPage: (pagination: PaginationOptions) => PaginationResult<T> | Paginated<T>,
|
||||
) {
|
||||
let hasNextPage = true;
|
||||
|
||||
for (let skip = 0; hasNextPage; skip += pageSize) {
|
||||
const result = await getNextPage({ take: pageSize, skip });
|
||||
hasNextPage = result.hasNextPage;
|
||||
yield result.items;
|
||||
}
|
||||
}
|
||||
|
||||
export interface OptionalOptions extends ValidationOptions {
|
||||
nullable?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if value is missing and if so, ignores all validators.
|
||||
*
|
||||
* @param validationOptions {@link OptionalOptions}
|
||||
*
|
||||
* @see IsOptional exported from `class-validator.
|
||||
*/
|
||||
// https://stackoverflow.com/a/71353929
|
||||
export function Optional({ nullable, ...validationOptions }: OptionalOptions = {}) {
|
||||
if (nullable === true) {
|
||||
return IsOptional(validationOptions);
|
||||
}
|
||||
|
||||
return ValidateIf((object: any, v: any) => v !== undefined, validationOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Chunks an array or set into smaller collections of the same type and specified size.
|
||||
*
|
||||
* @param collection The collection to chunk.
|
||||
* @param size The size of each chunk.
|
||||
*/
|
||||
export function chunks<T>(collection: Array<T>, size: number): Array<Array<T>>;
|
||||
export function chunks<T>(collection: Set<T>, size: number): Array<Set<T>>;
|
||||
export function chunks<T>(collection: Array<T> | Set<T>, size: number): Array<Array<T>> | Array<Set<T>> {
|
||||
if (collection instanceof Set) {
|
||||
const result = [];
|
||||
let chunk = new Set<T>();
|
||||
for (const element of collection) {
|
||||
chunk.add(element);
|
||||
if (chunk.size === size) {
|
||||
result.push(chunk);
|
||||
chunk = new Set<T>();
|
||||
}
|
||||
}
|
||||
if (chunk.size > 0) {
|
||||
result.push(chunk);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
return _.chunk(collection, size);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: The following Set utils have been added here, to easily determine where they are used.
|
||||
// They should be replaced with native Set operations, when they are added to the language.
|
||||
// Proposal reference: https://github.com/tc39/proposal-set-methods
|
||||
|
||||
export const setUnion = <T>(...sets: Set<T>[]): Set<T> => {
|
||||
const union = new Set(sets[0]);
|
||||
for (const set of sets.slice(1)) {
|
||||
for (const element of set) {
|
||||
union.add(element);
|
||||
}
|
||||
}
|
||||
return union;
|
||||
};
|
||||
|
||||
export const setDifference = <T>(setA: Set<T>, ...sets: Set<T>[]): Set<T> => {
|
||||
const difference = new Set(setA);
|
||||
for (const set of sets) {
|
||||
for (const element of set) {
|
||||
difference.delete(element);
|
||||
}
|
||||
}
|
||||
return difference;
|
||||
};
|
||||
|
||||
export const setIsSuperset = <T>(set: Set<T>, subset: Set<T>): boolean => {
|
||||
for (const element of subset) {
|
||||
if (!set.has(element)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export const setIsEqual = <T>(setA: Set<T>, setB: Set<T>): boolean => {
|
||||
return setA.size === setB.size && setIsSuperset(setA, setB);
|
||||
};
|
||||
|
||||
export const handlePromiseError = <T>(promise: Promise<T>, logger: ImmichLogger): void => {
|
||||
promise.catch((error: Error | any) => logger.error(`Promise error: ${error}`, error?.stack));
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsInt, IsPositive } from 'class-validator';
|
||||
import { Optional, ValidateUUID } from 'src/domain/domain.util';
|
||||
import { Optional, ValidateUUID } from 'src/validation';
|
||||
|
||||
export class DownloadInfoDto {
|
||||
@ValidateUUID({ each: true, optional: true })
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
import { when } from 'jest-when';
|
||||
import { CacheControl, ImmichFileResponse } from 'src/domain/domain.util';
|
||||
import { DownloadResponseDto } from 'src/domain/download/download.dto';
|
||||
import { DownloadService } from 'src/domain/download/download.service';
|
||||
import { IAssetRepository } from 'src/domain/repositories/asset.repository';
|
||||
import { IStorageRepository } from 'src/domain/repositories/storage.repository';
|
||||
import { CacheControl, ImmichFileResponse } from 'src/utils';
|
||||
import { assetStub } from 'test/fixtures/asset.stub';
|
||||
import { authStub } from 'test/fixtures/auth.stub';
|
||||
import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock';
|
||||
|
||||
@@ -4,12 +4,12 @@ import { AccessCore, Permission } from 'src/domain/access/access.core';
|
||||
import { AssetIdsDto } from 'src/domain/asset/dto/asset-ids.dto';
|
||||
import { AuthDto } from 'src/domain/auth/auth.dto';
|
||||
import { mimeTypes } from 'src/domain/domain.constant';
|
||||
import { CacheControl, HumanReadableSize, ImmichFileResponse, usePagination } from 'src/domain/domain.util';
|
||||
import { DownloadArchiveInfo, DownloadInfoDto, DownloadResponseDto } from 'src/domain/download/download.dto';
|
||||
import { IAccessRepository } from 'src/domain/repositories/access.repository';
|
||||
import { IAssetRepository } from 'src/domain/repositories/asset.repository';
|
||||
import { IStorageRepository, ImmichReadStream } from 'src/domain/repositories/storage.repository';
|
||||
import { AssetEntity } from 'src/infra/entities/asset.entity';
|
||||
import { CacheControl, HumanReadableSize, ImmichFileResponse, usePagination } from 'src/utils';
|
||||
|
||||
@Injectable()
|
||||
export class DownloadService {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsEnum, IsNotEmpty } from 'class-validator';
|
||||
import { ValidateBoolean } from 'src/domain/domain.util';
|
||||
import { JobCommand, QueueName } from 'src/domain/job/job.constants';
|
||||
import { ValidateBoolean } from 'src/validation';
|
||||
|
||||
export class JobIdParamDto {
|
||||
@IsNotEmpty()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { ArrayMaxSize, ArrayUnique, IsEnum, IsNotEmpty, IsString } from 'class-validator';
|
||||
import { Optional, ValidateBoolean, ValidateUUID } from 'src/domain/domain.util';
|
||||
import { LibraryEntity, LibraryType } from 'src/infra/entities/library.entity';
|
||||
import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation';
|
||||
|
||||
export class CreateLibraryDto {
|
||||
@IsEnum(LibraryType)
|
||||
|
||||
@@ -7,7 +7,6 @@ import { Stats } from 'node:fs';
|
||||
import path, { basename, parse } from 'node:path';
|
||||
import picomatch from 'picomatch';
|
||||
import { mimeTypes } from 'src/domain/domain.constant';
|
||||
import { handlePromiseError, usePagination, validateCronExpression } from 'src/domain/domain.util';
|
||||
import { JOBS_ASSET_PAGINATION_SIZE, JobName } from 'src/domain/job/job.constants';
|
||||
import { IBaseJob, IEntityJob, ILibraryFileJob, ILibraryRefreshJob } from 'src/domain/job/job.interface';
|
||||
import {
|
||||
@@ -35,6 +34,8 @@ import { SystemConfigCore } from 'src/domain/system-config/system-config.core';
|
||||
import { AssetType } from 'src/infra/entities/asset.entity';
|
||||
import { LibraryEntity, LibraryType } from 'src/infra/entities/library.entity';
|
||||
import { ImmichLogger } from 'src/infra/logger';
|
||||
import { handlePromiseError, usePagination } from 'src/utils';
|
||||
import { validateCronExpression } from 'src/validation';
|
||||
|
||||
const LIBRARY_SCAN_BATCH_SIZE = 5000;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Inject, Injectable, UnsupportedMediaTypeException } from '@nestjs/common';
|
||||
import { usePagination } from 'src/domain/domain.util';
|
||||
import { JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from 'src/domain/job/job.constants';
|
||||
import { IBaseJob, IEntityJob } from 'src/domain/job/job.interface';
|
||||
import {
|
||||
@@ -39,6 +38,7 @@ import {
|
||||
VideoCodec,
|
||||
} from 'src/infra/entities/system-config.entity';
|
||||
import { ImmichLogger } from 'src/infra/logger';
|
||||
import { usePagination } from 'src/utils';
|
||||
|
||||
@Injectable()
|
||||
export class MediaService {
|
||||
|
||||
@@ -6,7 +6,6 @@ import { Duration } from 'luxon';
|
||||
import { constants } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { handlePromiseError, usePagination } from 'src/domain/domain.util';
|
||||
import { JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from 'src/domain/job/job.constants';
|
||||
import { IBaseJob, IEntityJob, ISidecarWriteJob } from 'src/domain/job/job.interface';
|
||||
import { IAlbumRepository } from 'src/domain/repositories/album.repository';
|
||||
@@ -26,6 +25,7 @@ import { FeatureFlag, SystemConfigCore } from 'src/domain/system-config/system-c
|
||||
import { AssetEntity, AssetType } from 'src/infra/entities/asset.entity';
|
||||
import { ExifEntity } from 'src/infra/entities/exif.entity';
|
||||
import { ImmichLogger } from 'src/infra/logger';
|
||||
import { handlePromiseError, usePagination } from 'src/utils';
|
||||
|
||||
/** look for a date from these tags (in order) */
|
||||
const EXIF_DATE_TAGS: Array<keyof Tags> = [
|
||||
|
||||
@@ -2,9 +2,9 @@ import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsArray, IsNotEmpty, IsString, MaxDate, ValidateNested } from 'class-validator';
|
||||
import { AuthDto } from 'src/domain/auth/auth.dto';
|
||||
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/domain/domain.util';
|
||||
import { AssetFaceEntity } from 'src/infra/entities/asset-face.entity';
|
||||
import { PersonEntity } from 'src/infra/entities/person.entity';
|
||||
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation';
|
||||
|
||||
export class PersonCreateDto {
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { BadRequestException, NotFoundException } from '@nestjs/common';
|
||||
import { BulkIdErrorReason } from 'src/domain/asset/response-dto/asset-ids-response.dto';
|
||||
import { CacheControl, ImmichFileResponse } from 'src/domain/domain.util';
|
||||
import { JobName } from 'src/domain/job/job.constants';
|
||||
import { PersonResponseDto, mapFaces, mapPerson } from 'src/domain/person/person.dto';
|
||||
import { PersonService } from 'src/domain/person/person.service';
|
||||
@@ -16,6 +15,7 @@ import { IStorageRepository } from 'src/domain/repositories/storage.repository';
|
||||
import { ISystemConfigRepository } from 'src/domain/repositories/system-config.repository';
|
||||
import { AssetFaceEntity } from 'src/infra/entities/asset-face.entity';
|
||||
import { Colorspace, SystemConfigKey } from 'src/infra/entities/system-config.entity';
|
||||
import { CacheControl, ImmichFileResponse } from 'src/utils';
|
||||
import { assetStub } from 'test/fixtures/asset.stub';
|
||||
import { authStub } from 'test/fixtures/auth.stub';
|
||||
import { faceStub } from 'test/fixtures/face.stub';
|
||||
|
||||
@@ -4,7 +4,6 @@ import { BulkIdErrorReason, BulkIdResponseDto } from 'src/domain/asset/response-
|
||||
import { AssetResponseDto, mapAsset } from 'src/domain/asset/response-dto/asset-response.dto';
|
||||
import { AuthDto } from 'src/domain/auth/auth.dto';
|
||||
import { mimeTypes } from 'src/domain/domain.constant';
|
||||
import { CacheControl, ImmichFileResponse, usePagination } from 'src/domain/domain.util';
|
||||
import { JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from 'src/domain/job/job.constants';
|
||||
import { IBaseJob, IDeferrableJob, IEntityJob } from 'src/domain/job/job.interface';
|
||||
import { FACE_THUMBNAIL_SIZE } from 'src/domain/media/media.constant';
|
||||
@@ -39,6 +38,7 @@ import { SystemConfigCore } from 'src/domain/system-config/system-config.core';
|
||||
import { PersonPathType } from 'src/infra/entities/move.entity';
|
||||
import { PersonEntity } from 'src/infra/entities/person.entity';
|
||||
import { ImmichLogger } from 'src/infra/logger';
|
||||
import { CacheControl, ImmichFileResponse, usePagination } from 'src/utils';
|
||||
import { IsNull } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Paginated, PaginationOptions } from 'src/domain/domain.util';
|
||||
import { ReverseGeocodeResult } from 'src/domain/repositories/metadata.repository';
|
||||
import { AssetSearchOptions, SearchExploreItem } from 'src/domain/repositories/search.repository';
|
||||
import { AssetOrder } from 'src/infra/entities/album.entity';
|
||||
import { AssetJobStatusEntity } from 'src/infra/entities/asset-job-status.entity';
|
||||
import { AssetEntity, AssetType } from 'src/infra/entities/asset.entity';
|
||||
import { ExifEntity } from 'src/infra/entities/exif.entity';
|
||||
import { Paginated, PaginationOptions } from 'src/utils';
|
||||
import { FindOptionsRelations, FindOptionsSelect } from 'typeorm';
|
||||
|
||||
export type AssetStats = Record<AssetType, number>;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Paginated, PaginationOptions } from 'src/domain/domain.util';
|
||||
import { AssetFaceEntity } from 'src/infra/entities/asset-face.entity';
|
||||
import { AssetEntity } from 'src/infra/entities/asset.entity';
|
||||
import { PersonEntity } from 'src/infra/entities/person.entity';
|
||||
import { Paginated, PaginationOptions } from 'src/utils';
|
||||
import { FindManyOptions, FindOptionsRelations, FindOptionsSelect } from 'typeorm';
|
||||
|
||||
export const IPersonRepository = 'IPersonRepository';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Paginated } from 'src/domain/domain.util';
|
||||
import { AssetFaceEntity } from 'src/infra/entities/asset-face.entity';
|
||||
import { AssetEntity, AssetType } from 'src/infra/entities/asset.entity';
|
||||
import { GeodataPlacesEntity } from 'src/infra/entities/geodata-places.entity';
|
||||
import { SmartInfoEntity } from 'src/infra/entities/smart-info.entity';
|
||||
import { Paginated } from 'src/utils';
|
||||
|
||||
export const ISearchRepository = 'ISearchRepository';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
|
||||
import { Optional } from 'src/domain/domain.util';
|
||||
import { Optional } from 'src/validation';
|
||||
|
||||
export enum SearchSuggestionType {
|
||||
COUNTRY = 'country',
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsEnum, IsInt, IsNotEmpty, IsString, Max, Min } from 'class-validator';
|
||||
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/domain/domain.util';
|
||||
import { AssetOrder } from 'src/infra/entities/album.entity';
|
||||
import { AssetType } from 'src/infra/entities/asset.entity';
|
||||
import { GeodataPlacesEntity } from 'src/infra/entities/geodata-places.entity';
|
||||
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation';
|
||||
|
||||
class BaseSearchDto {
|
||||
@ValidateUUID({ optional: true })
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DateTime } from 'luxon';
|
||||
import { Version, isDev, mimeTypes, serverVersion } from 'src/domain/domain.constant';
|
||||
import { asHumanReadable } from 'src/domain/domain.util';
|
||||
import { ClientEvent, ICommunicationRepository } from 'src/domain/repositories/communication.repository';
|
||||
import { IServerInfoRepository } from 'src/domain/repositories/server-info.repository';
|
||||
import { IStorageRepository } from 'src/domain/repositories/storage.repository';
|
||||
@@ -21,6 +20,7 @@ import { StorageCore, StorageFolder } from 'src/domain/storage/storage.core';
|
||||
import { SystemConfigCore } from 'src/domain/system-config/system-config.core';
|
||||
import { SystemMetadataKey } from 'src/infra/entities/system-metadata.entity';
|
||||
import { ImmichLogger } from 'src/infra/logger';
|
||||
import { asHumanReadable } from 'src/utils';
|
||||
|
||||
@Injectable()
|
||||
export class ServerInfoService {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsEnum, IsString } from 'class-validator';
|
||||
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/domain/domain.util';
|
||||
import { SharedLinkType } from 'src/infra/entities/shared-link.entity';
|
||||
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation';
|
||||
|
||||
export class SharedLinkCreateDto {
|
||||
@IsEnum(SharedLinkType)
|
||||
|
||||
@@ -3,7 +3,6 @@ import { AccessCore, Permission } from 'src/domain/access/access.core';
|
||||
import { AssetIdsDto } from 'src/domain/asset/dto/asset-ids.dto';
|
||||
import { AssetIdErrorReason, AssetIdsResponseDto } from 'src/domain/asset/response-dto/asset-ids-response.dto';
|
||||
import { AuthDto } from 'src/domain/auth/auth.dto';
|
||||
import { OpenGraphTags } from 'src/domain/domain.util';
|
||||
import { IAccessRepository } from 'src/domain/repositories/access.repository';
|
||||
import { ICryptoRepository } from 'src/domain/repositories/crypto.repository';
|
||||
import { ISharedLinkRepository } from 'src/domain/repositories/shared-link.repository';
|
||||
@@ -15,6 +14,7 @@ import {
|
||||
import { SharedLinkCreateDto, SharedLinkEditDto, SharedLinkPasswordDto } from 'src/domain/shared-link/shared-link.dto';
|
||||
import { AssetEntity } from 'src/infra/entities/asset.entity';
|
||||
import { SharedLinkEntity, SharedLinkType } from 'src/infra/entities/shared-link.entity';
|
||||
import { OpenGraphTags } from 'src/utils';
|
||||
|
||||
@Injectable()
|
||||
export class SharedLinkService {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsEnum, IsNotEmpty, IsNumber, IsString, Max, Min } from 'class-validator';
|
||||
import { Optional, ValidateBoolean } from 'src/domain/domain.util';
|
||||
import { CLIPMode, ModelType } from 'src/domain/repositories/machine-learning.repository';
|
||||
import { Optional, ValidateBoolean } from 'src/validation';
|
||||
|
||||
export class ModelConfig {
|
||||
@ValidateBoolean()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { usePagination } from 'src/domain/domain.util';
|
||||
import { JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from 'src/domain/job/job.constants';
|
||||
import { IBaseJob, IEntityJob } from 'src/domain/job/job.interface';
|
||||
import { IAssetRepository, WithoutProperty } from 'src/domain/repositories/asset.repository';
|
||||
@@ -10,6 +9,7 @@ import { ISearchRepository } from 'src/domain/repositories/search.repository';
|
||||
import { ISystemConfigRepository } from 'src/domain/repositories/system-config.repository';
|
||||
import { SystemConfigCore } from 'src/domain/system-config/system-config.core';
|
||||
import { ImmichLogger } from 'src/infra/logger';
|
||||
import { usePagination } from 'src/utils';
|
||||
|
||||
@Injectable()
|
||||
export class SmartInfoService {
|
||||
|
||||
@@ -4,7 +4,6 @@ import handlebar from 'handlebars';
|
||||
import { DateTime } from 'luxon';
|
||||
import path from 'node:path';
|
||||
import sanitize from 'sanitize-filename';
|
||||
import { getLivePhotoMotionFilename, usePagination } from 'src/domain/domain.util';
|
||||
import { JOBS_ASSET_PAGINATION_SIZE } from 'src/domain/job/job.constants';
|
||||
import { IEntityJob } from 'src/domain/job/job.interface';
|
||||
import { IAlbumRepository } from 'src/domain/repositories/album.repository';
|
||||
@@ -33,6 +32,7 @@ import { AssetEntity, AssetType } from 'src/infra/entities/asset.entity';
|
||||
import { AssetPathType } from 'src/infra/entities/move.entity';
|
||||
import { SystemConfig } from 'src/infra/entities/system-config.entity';
|
||||
import { ImmichLogger } from 'src/infra/logger';
|
||||
import { getLivePhotoMotionFilename, usePagination } from 'src/utils';
|
||||
|
||||
export interface MoveAssetMetadata {
|
||||
storageLabel: string | null;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsEnum, IsInt, IsString, Max, Min } from 'class-validator';
|
||||
import { ValidateBoolean } from 'src/domain/domain.util';
|
||||
import {
|
||||
AudioCodec,
|
||||
CQMode,
|
||||
@@ -10,6 +9,7 @@ import {
|
||||
TranscodePolicy,
|
||||
VideoCodec,
|
||||
} from 'src/infra/entities/system-config.entity';
|
||||
import { ValidateBoolean } from 'src/validation';
|
||||
|
||||
export class SystemConfigFFmpegDto {
|
||||
@IsInt()
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
ValidatorConstraint,
|
||||
ValidatorConstraintInterface,
|
||||
} from 'class-validator';
|
||||
import { ValidateBoolean, validateCronExpression } from 'src/domain/domain.util';
|
||||
import { ValidateBoolean, validateCronExpression } from 'src/validation';
|
||||
|
||||
const isEnabled = (config: SystemConfigLibraryScanDto) => config.enabled;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsEnum } from 'class-validator';
|
||||
import { ValidateBoolean } from 'src/domain/domain.util';
|
||||
import { LogLevel } from 'src/infra/entities/system-config.entity';
|
||||
import { ValidateBoolean } from 'src/validation';
|
||||
|
||||
export class SystemConfigLoggingDto {
|
||||
@ValidateBoolean()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsObject, IsUrl, ValidateIf, ValidateNested } from 'class-validator';
|
||||
import { ValidateBoolean } from 'src/domain/domain.util';
|
||||
import { CLIPConfig, RecognitionConfig } from 'src/domain/smart-info/dto/model-config.dto';
|
||||
import { ValidateBoolean } from 'src/validation';
|
||||
|
||||
export class SystemConfigMachineLearningDto {
|
||||
@ValidateBoolean()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IsString } from 'class-validator';
|
||||
import { ValidateBoolean } from 'src/domain/domain.util';
|
||||
import { ValidateBoolean } from 'src/validation';
|
||||
|
||||
export class SystemConfigMapDto {
|
||||
@ValidateBoolean()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ValidateBoolean } from 'src/domain/domain.util';
|
||||
import { ValidateBoolean } from 'src/validation';
|
||||
|
||||
export class SystemConfigNewVersionCheckDto {
|
||||
@ValidateBoolean()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IsNotEmpty, IsNumber, IsString, IsUrl, Min, ValidateIf } from 'class-validator';
|
||||
import { ValidateBoolean } from 'src/domain/domain.util';
|
||||
import { ValidateBoolean } from 'src/validation';
|
||||
|
||||
const isEnabled = (config: SystemConfigOAuthDto) => config.enabled;
|
||||
const isOverrideEnabled = (config: SystemConfigOAuthDto) => config.mobileOverrideEnabled;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ValidateBoolean } from 'src/domain/domain.util';
|
||||
import { ValidateBoolean } from 'src/validation';
|
||||
|
||||
export class SystemConfigPasswordLoginDto {
|
||||
@ValidateBoolean()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ValidateBoolean } from 'src/domain/domain.util';
|
||||
import { ValidateBoolean } from 'src/validation';
|
||||
|
||||
export class SystemConfigReverseGeocodingDto {
|
||||
@ValidateBoolean()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
import { ValidateBoolean } from 'src/domain/domain.util';
|
||||
import { ValidateBoolean } from 'src/validation';
|
||||
|
||||
export class SystemConfigStorageTemplateDto {
|
||||
@ValidateBoolean()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsInt, Min } from 'class-validator';
|
||||
import { ValidateBoolean } from 'src/domain/domain.util';
|
||||
import { ValidateBoolean } from 'src/validation';
|
||||
|
||||
export class SystemConfigTrashDto {
|
||||
@ValidateBoolean()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
|
||||
import { Optional } from 'src/domain/domain.util';
|
||||
import { TagType } from 'src/infra/entities/tag.entity';
|
||||
import { Optional } from 'src/validation';
|
||||
|
||||
export class CreateTagDto {
|
||||
@IsString()
|
||||
|
||||
@@ -3,12 +3,12 @@ import { DateTime } from 'luxon';
|
||||
import { AccessCore, Permission } from 'src/domain/access/access.core';
|
||||
import { BulkIdsDto } from 'src/domain/asset/response-dto/asset-ids-response.dto';
|
||||
import { AuthDto } from 'src/domain/auth/auth.dto';
|
||||
import { usePagination } from 'src/domain/domain.util';
|
||||
import { JOBS_ASSET_PAGINATION_SIZE, JobName } from 'src/domain/job/job.constants';
|
||||
import { IAccessRepository } from 'src/domain/repositories/access.repository';
|
||||
import { IAssetRepository } from 'src/domain/repositories/asset.repository';
|
||||
import { ClientEvent, ICommunicationRepository } from 'src/domain/repositories/communication.repository';
|
||||
import { IJobRepository } from 'src/domain/repositories/job.repository';
|
||||
import { usePagination } from 'src/utils';
|
||||
|
||||
export class TrashService {
|
||||
private access: AccessCore;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Transform } from 'class-transformer';
|
||||
import { IsEmail, IsNotEmpty, IsNumber, IsPositive, IsString } from 'class-validator';
|
||||
import { Optional, ValidateBoolean, toEmail, toSanitized } from 'src/domain/domain.util';
|
||||
import { Optional, ValidateBoolean, toEmail, toSanitized } from 'src/validation';
|
||||
|
||||
export class CreateUserDto {
|
||||
@IsEmail({ require_tld: false })
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ValidateBoolean } from 'src/domain/domain.util';
|
||||
import { ValidateBoolean } from 'src/validation';
|
||||
|
||||
export class DeleteUserDto {
|
||||
@ValidateBoolean({ optional: true })
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Transform } from 'class-transformer';
|
||||
import { IsEmail, IsEnum, IsNotEmpty, IsNumber, IsPositive, IsString, IsUUID } from 'class-validator';
|
||||
import { Optional, ValidateBoolean, toEmail, toSanitized } from 'src/domain/domain.util';
|
||||
import { UserAvatarColor } from 'src/infra/entities/user.entity';
|
||||
import { Optional, ValidateBoolean, toEmail, toSanitized } from 'src/validation';
|
||||
|
||||
export class UpdateUserDto {
|
||||
@Optional()
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { when } from 'jest-when';
|
||||
import { CacheControl, ImmichFileResponse } from 'src/domain/domain.util';
|
||||
import { JobName } from 'src/domain/job/job.constants';
|
||||
import { IAlbumRepository } from 'src/domain/repositories/album.repository';
|
||||
import { ICryptoRepository } from 'src/domain/repositories/crypto.repository';
|
||||
@@ -18,6 +17,7 @@ import { UpdateUserDto } from 'src/domain/user/dto/update-user.dto';
|
||||
import { mapUser } from 'src/domain/user/response-dto/user-response.dto';
|
||||
import { UserService } from 'src/domain/user/user.service';
|
||||
import { UserEntity, UserStatus } from 'src/infra/entities/user.entity';
|
||||
import { CacheControl, ImmichFileResponse } from 'src/utils';
|
||||
import { authStub } from 'test/fixtures/auth.stub';
|
||||
import { systemConfigStub } from 'test/fixtures/system-config.stub';
|
||||
import { userStub } from 'test/fixtures/user.stub';
|
||||
|
||||
@@ -2,7 +2,6 @@ import { BadRequestException, ForbiddenException, Inject, Injectable, NotFoundEx
|
||||
import { DateTime } from 'luxon';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { AuthDto } from 'src/domain/auth/auth.dto';
|
||||
import { CacheControl, ImmichFileResponse } from 'src/domain/domain.util';
|
||||
import { JobName } from 'src/domain/job/job.constants';
|
||||
import { IEntityJob } from 'src/domain/job/job.interface';
|
||||
import { IAlbumRepository } from 'src/domain/repositories/album.repository';
|
||||
@@ -25,6 +24,7 @@ import { UserResponseDto, mapUser } from 'src/domain/user/response-dto/user-resp
|
||||
import { UserCore } from 'src/domain/user/user.core';
|
||||
import { UserEntity, UserStatus } from 'src/infra/entities/user.entity';
|
||||
import { ImmichLogger } from 'src/infra/logger';
|
||||
import { CacheControl, ImmichFileResponse } from 'src/utils';
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
|
||||
Reference in New Issue
Block a user