mirror of
https://github.com/KevinMidboe/immich.git
synced 2026-02-10 02:09:23 +00:00
refactor(server)*: tsconfigs (#2689)
* refactor(server): tsconfigs * chore: dummy commit * fix: start.sh * chore: restore original entry scripts
This commit is contained in:
54
server/src/domain/asset/asset.repository.ts
Normal file
54
server/src/domain/asset/asset.repository.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { AssetEntity, AssetType } from '@app/infra/entities';
|
||||
import { Paginated, PaginationOptions } from '../domain.util';
|
||||
|
||||
export interface AssetSearchOptions {
|
||||
isVisible?: boolean;
|
||||
type?: AssetType;
|
||||
}
|
||||
|
||||
export interface LivePhotoSearchOptions {
|
||||
ownerId: string;
|
||||
livePhotoCID: string;
|
||||
otherAssetId: string;
|
||||
type: AssetType;
|
||||
}
|
||||
|
||||
export interface MapMarkerSearchOptions {
|
||||
isFavorite?: boolean;
|
||||
fileCreatedBefore?: Date;
|
||||
fileCreatedAfter?: Date;
|
||||
}
|
||||
|
||||
export interface MapMarker {
|
||||
id: string;
|
||||
lat: number;
|
||||
lon: number;
|
||||
}
|
||||
|
||||
export enum WithoutProperty {
|
||||
THUMBNAIL = 'thumbnail',
|
||||
ENCODED_VIDEO = 'encoded-video',
|
||||
EXIF = 'exif',
|
||||
CLIP_ENCODING = 'clip-embedding',
|
||||
OBJECT_TAGS = 'object-tags',
|
||||
FACES = 'faces',
|
||||
SIDECAR = 'sidecar',
|
||||
}
|
||||
|
||||
export enum WithProperty {
|
||||
SIDECAR = 'sidecar',
|
||||
}
|
||||
|
||||
export const IAssetRepository = 'IAssetRepository';
|
||||
|
||||
export interface IAssetRepository {
|
||||
getByIds(ids: string[]): Promise<AssetEntity[]>;
|
||||
getWithout(pagination: PaginationOptions, property: WithoutProperty): Paginated<AssetEntity>;
|
||||
getWith(pagination: PaginationOptions, property: WithProperty): Paginated<AssetEntity>;
|
||||
getFirstAssetForAlbumId(albumId: string): Promise<AssetEntity | null>;
|
||||
deleteAll(ownerId: string): Promise<void>;
|
||||
getAll(pagination: PaginationOptions, options?: AssetSearchOptions): Paginated<AssetEntity>;
|
||||
save(asset: Partial<AssetEntity>): Promise<AssetEntity>;
|
||||
findLivePhotoMatch(options: LivePhotoSearchOptions): Promise<AssetEntity | null>;
|
||||
getMapMarkers(ownerId: string, options?: MapMarkerSearchOptions): Promise<MapMarker[]>;
|
||||
}
|
||||
41
server/src/domain/asset/asset.service.spec.ts
Normal file
41
server/src/domain/asset/asset.service.spec.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { assetEntityStub, authStub, newAssetRepositoryMock } from '@test';
|
||||
import { AssetService, IAssetRepository } from '.';
|
||||
|
||||
describe(AssetService.name, () => {
|
||||
let sut: AssetService;
|
||||
let assetMock: jest.Mocked<IAssetRepository>;
|
||||
|
||||
it('should work', () => {
|
||||
expect(sut).toBeDefined();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
assetMock = newAssetRepositoryMock();
|
||||
sut = new AssetService(assetMock);
|
||||
});
|
||||
|
||||
describe('get map markers', () => {
|
||||
it('should get geo information of assets', async () => {
|
||||
assetMock.getMapMarkers.mockResolvedValue(
|
||||
[assetEntityStub.withLocation].map((asset) => ({
|
||||
id: asset.id,
|
||||
|
||||
/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */
|
||||
lat: asset.exifInfo!.latitude!,
|
||||
|
||||
/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */
|
||||
lon: asset.exifInfo!.longitude!,
|
||||
})),
|
||||
);
|
||||
|
||||
const markers = await sut.getMapMarkers(authStub.user1, {});
|
||||
|
||||
expect(markers).toHaveLength(1);
|
||||
expect(markers[0]).toEqual({
|
||||
id: assetEntityStub.withLocation.id,
|
||||
lat: 100,
|
||||
lon: 100,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
13
server/src/domain/asset/asset.service.ts
Normal file
13
server/src/domain/asset/asset.service.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { IAssetRepository } from './asset.repository';
|
||||
import { MapMarkerDto } from './dto/map-marker.dto';
|
||||
import { MapMarkerResponseDto } from './response-dto';
|
||||
|
||||
export class AssetService {
|
||||
constructor(@Inject(IAssetRepository) private assetRepository: IAssetRepository) {}
|
||||
|
||||
getMapMarkers(authUser: AuthUserDto, options: MapMarkerDto): Promise<MapMarkerResponseDto[]> {
|
||||
return this.assetRepository.getMapMarkers(authUser.id, options);
|
||||
}
|
||||
}
|
||||
6
server/src/domain/asset/dto/asset-ids.dto.ts
Normal file
6
server/src/domain/asset/dto/asset-ids.dto.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { ValidateUUID } from '@app/immich/decorators/validate-uuid.decorator';
|
||||
|
||||
export class AssetIdsDto {
|
||||
@ValidateUUID({ each: true })
|
||||
assetIds!: string[];
|
||||
}
|
||||
2
server/src/domain/asset/dto/index.ts
Normal file
2
server/src/domain/asset/dto/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './asset-ids.dto';
|
||||
export * from './map-marker.dto';
|
||||
22
server/src/domain/asset/dto/map-marker.dto.ts
Normal file
22
server/src/domain/asset/dto/map-marker.dto.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Transform, Type } from 'class-transformer';
|
||||
import { IsBoolean, IsDate, IsOptional } from 'class-validator';
|
||||
import { toBoolean } from '@app/immich/utils/transform.util';
|
||||
|
||||
export class MapMarkerDto {
|
||||
@ApiProperty()
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@Transform(toBoolean)
|
||||
isFavorite?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
fileCreatedAfter?: Date;
|
||||
|
||||
@IsOptional()
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
fileCreatedBefore?: Date;
|
||||
}
|
||||
4
server/src/domain/asset/index.ts
Normal file
4
server/src/domain/asset/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './asset.repository';
|
||||
export * from './asset.service';
|
||||
export * from './dto';
|
||||
export * from './response-dto';
|
||||
@@ -0,0 +1,11 @@
|
||||
export enum AssetIdErrorReason {
|
||||
DUPLICATE = 'duplicate',
|
||||
NO_PERMISSION = 'no_permission',
|
||||
NOT_FOUND = 'not_found',
|
||||
}
|
||||
|
||||
export class AssetIdsResponseDto {
|
||||
assetId!: string;
|
||||
success!: boolean;
|
||||
error?: AssetIdErrorReason;
|
||||
}
|
||||
85
server/src/domain/asset/response-dto/asset-response.dto.ts
Normal file
85
server/src/domain/asset/response-dto/asset-response.dto.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { AssetEntity, AssetType } from '@app/infra/entities';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { mapFace, PersonResponseDto } from '../../person';
|
||||
import { mapTag, TagResponseDto } from '../../tag';
|
||||
import { ExifResponseDto, mapExif } from './exif-response.dto';
|
||||
import { mapSmartInfo, SmartInfoResponseDto } from './smart-info-response.dto';
|
||||
|
||||
export class AssetResponseDto {
|
||||
id!: string;
|
||||
deviceAssetId!: string;
|
||||
ownerId!: string;
|
||||
deviceId!: string;
|
||||
|
||||
@ApiProperty({ enumName: 'AssetTypeEnum', enum: AssetType })
|
||||
type!: AssetType;
|
||||
originalPath!: string;
|
||||
originalFileName!: string;
|
||||
resized!: boolean;
|
||||
fileCreatedAt!: Date;
|
||||
fileModifiedAt!: Date;
|
||||
updatedAt!: Date;
|
||||
isFavorite!: boolean;
|
||||
isArchived!: boolean;
|
||||
mimeType!: string | null;
|
||||
duration!: string;
|
||||
exifInfo?: ExifResponseDto;
|
||||
smartInfo?: SmartInfoResponseDto;
|
||||
livePhotoVideoId?: string | null;
|
||||
tags?: TagResponseDto[];
|
||||
people?: PersonResponseDto[];
|
||||
/**base64 encoded sha1 hash */
|
||||
checksum!: string;
|
||||
}
|
||||
|
||||
export function mapAsset(entity: AssetEntity): AssetResponseDto {
|
||||
return {
|
||||
id: entity.id,
|
||||
deviceAssetId: entity.deviceAssetId,
|
||||
ownerId: entity.ownerId,
|
||||
deviceId: entity.deviceId,
|
||||
type: entity.type,
|
||||
originalPath: entity.originalPath,
|
||||
originalFileName: entity.originalFileName,
|
||||
resized: !!entity.resizePath,
|
||||
fileCreatedAt: entity.fileCreatedAt,
|
||||
fileModifiedAt: entity.fileModifiedAt,
|
||||
updatedAt: entity.updatedAt,
|
||||
isFavorite: entity.isFavorite,
|
||||
isArchived: entity.isArchived,
|
||||
mimeType: entity.mimeType,
|
||||
duration: entity.duration ?? '0:00:00.00000',
|
||||
exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined,
|
||||
smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined,
|
||||
livePhotoVideoId: entity.livePhotoVideoId,
|
||||
tags: entity.tags?.map(mapTag),
|
||||
people: entity.faces?.map(mapFace),
|
||||
checksum: entity.checksum.toString('base64'),
|
||||
};
|
||||
}
|
||||
|
||||
export function mapAssetWithoutExif(entity: AssetEntity): AssetResponseDto {
|
||||
return {
|
||||
id: entity.id,
|
||||
deviceAssetId: entity.deviceAssetId,
|
||||
ownerId: entity.ownerId,
|
||||
deviceId: entity.deviceId,
|
||||
type: entity.type,
|
||||
originalPath: entity.originalPath,
|
||||
originalFileName: entity.originalFileName,
|
||||
resized: !!entity.resizePath,
|
||||
fileCreatedAt: entity.fileCreatedAt,
|
||||
fileModifiedAt: entity.fileModifiedAt,
|
||||
updatedAt: entity.updatedAt,
|
||||
isFavorite: entity.isFavorite,
|
||||
isArchived: entity.isArchived,
|
||||
mimeType: entity.mimeType,
|
||||
duration: entity.duration ?? '0:00:00.00000',
|
||||
exifInfo: undefined,
|
||||
smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined,
|
||||
livePhotoVideoId: entity.livePhotoVideoId,
|
||||
tags: entity.tags?.map(mapTag),
|
||||
people: entity.faces?.map(mapFace),
|
||||
checksum: entity.checksum.toString('base64'),
|
||||
};
|
||||
}
|
||||
52
server/src/domain/asset/response-dto/exif-response.dto.ts
Normal file
52
server/src/domain/asset/response-dto/exif-response.dto.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { ExifEntity } from '@app/infra/entities';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class ExifResponseDto {
|
||||
make?: string | null = null;
|
||||
model?: string | null = null;
|
||||
exifImageWidth?: number | null = null;
|
||||
exifImageHeight?: number | null = null;
|
||||
|
||||
@ApiProperty({ type: 'integer', format: 'int64' })
|
||||
fileSizeInByte?: number | null = null;
|
||||
orientation?: string | null = null;
|
||||
dateTimeOriginal?: Date | null = null;
|
||||
modifyDate?: Date | null = null;
|
||||
timeZone?: string | null = null;
|
||||
lensModel?: string | null = null;
|
||||
fNumber?: number | null = null;
|
||||
focalLength?: number | null = null;
|
||||
iso?: number | null = null;
|
||||
exposureTime?: string | null = null;
|
||||
latitude?: number | null = null;
|
||||
longitude?: number | null = null;
|
||||
city?: string | null = null;
|
||||
state?: string | null = null;
|
||||
country?: string | null = null;
|
||||
description?: string | null = null;
|
||||
}
|
||||
|
||||
export function mapExif(entity: ExifEntity): ExifResponseDto {
|
||||
return {
|
||||
make: entity.make,
|
||||
model: entity.model,
|
||||
exifImageWidth: entity.exifImageWidth,
|
||||
exifImageHeight: entity.exifImageHeight,
|
||||
fileSizeInByte: entity.fileSizeInByte ? parseInt(entity.fileSizeInByte.toString()) : null,
|
||||
orientation: entity.orientation,
|
||||
dateTimeOriginal: entity.dateTimeOriginal,
|
||||
modifyDate: entity.modifyDate,
|
||||
timeZone: entity.timeZone,
|
||||
lensModel: entity.lensModel,
|
||||
fNumber: entity.fNumber,
|
||||
focalLength: entity.focalLength,
|
||||
iso: entity.iso,
|
||||
exposureTime: entity.exposureTime,
|
||||
latitude: entity.latitude,
|
||||
longitude: entity.longitude,
|
||||
city: entity.city,
|
||||
state: entity.state,
|
||||
country: entity.country,
|
||||
description: entity.description,
|
||||
};
|
||||
}
|
||||
5
server/src/domain/asset/response-dto/index.ts
Normal file
5
server/src/domain/asset/response-dto/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from './asset-ids-response.dto';
|
||||
export * from './asset-response.dto';
|
||||
export * from './exif-response.dto';
|
||||
export * from './map-marker-response.dto';
|
||||
export * from './smart-info-response.dto';
|
||||
@@ -0,0 +1,12 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class MapMarkerResponseDto {
|
||||
@ApiProperty()
|
||||
id!: string;
|
||||
|
||||
@ApiProperty({ format: 'double' })
|
||||
lat!: number;
|
||||
|
||||
@ApiProperty({ format: 'double' })
|
||||
lon!: number;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { SmartInfoEntity } from '@app/infra/entities';
|
||||
|
||||
export class SmartInfoResponseDto {
|
||||
tags?: string[] | null;
|
||||
objects?: string[] | null;
|
||||
}
|
||||
|
||||
export function mapSmartInfo(entity: SmartInfoEntity): SmartInfoResponseDto {
|
||||
return {
|
||||
tags: entity.tags,
|
||||
objects: entity.objects,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user