chore(server,cli,web): housekeeping and stricter code style (#6751)

* add unicorn to eslint

* fix lint errors for cli

* fix merge

* fix album name extraction

* Update cli/src/commands/upload.command.ts

Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com>

* es2k23

* use lowercase os

* return undefined album name

* fix bug in asset response dto

* auto fix issues

* fix server code style

* es2022 and formatting

* fix compilation error

* fix test

* fix config load

* fix last lint errors

* set string type

* bump ts

* start work on web

* web formatting

* Fix UUIDParamDto as UUIDParamDto

* fix library service lint

* fix web errors

* fix errors

* formatting

* wip

* lints fixed

* web can now start

* alphabetical package json

* rename error

* chore: clean up

---------

Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com>
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
Jonathan Jogenfors
2024-02-02 04:18:00 +01:00
committed by GitHub
parent e4d0560d49
commit f44fa45aa0
218 changed files with 2471 additions and 1244 deletions
@@ -1009,9 +1009,7 @@ describe(AssetService.name, () => {
it('get assets by device id', async () => {
const assets = [assetStub.image, assetStub.image1];
assetMock.getAllByDeviceId.mockImplementation(() =>
Promise.resolve<string[]>(Array.from(assets.map((asset) => asset.deviceAssetId))),
);
assetMock.getAllByDeviceId.mockResolvedValue(assets.map((asset) => asset.deviceAssetId));
const deviceId = 'device-id';
const result = await sut.getUserAssetsByDeviceId(authStub.user1, deviceId);
+31 -26
View File
@@ -3,7 +3,7 @@ import { ImmichLogger } from '@app/infra/logger';
import { BadRequestException, Inject } from '@nestjs/common';
import _ from 'lodash';
import { DateTime, Duration } from 'luxon';
import { extname } from 'path';
import { extname } from 'node:path';
import sanitize from 'sanitize-filename';
import { AccessCore, Permission } from '../access';
import { AuthDto } from '../auth';
@@ -93,7 +93,7 @@ export class AssetService {
}
search(auth: AuthDto, dto: AssetSearchDto) {
let checksum: Buffer | undefined = undefined;
let checksum: Buffer | undefined;
if (dto.checksum) {
const encoding = dto.checksum.length === 28 ? 'base64' : 'hex';
@@ -126,29 +126,33 @@ export class AssetService {
const filename = file.originalName;
switch (fieldName) {
case UploadFieldName.ASSET_DATA:
case UploadFieldName.ASSET_DATA: {
if (mimeTypes.isAsset(filename)) {
return true;
}
break;
}
case UploadFieldName.LIVE_PHOTO_DATA:
case UploadFieldName.LIVE_PHOTO_DATA: {
if (mimeTypes.isVideo(filename)) {
return true;
}
break;
}
case UploadFieldName.SIDECAR_DATA:
case UploadFieldName.SIDECAR_DATA: {
if (mimeTypes.isSidecar(filename)) {
return true;
}
break;
}
case UploadFieldName.PROFILE_DATA:
case UploadFieldName.PROFILE_DATA: {
if (mimeTypes.isProfile(filename)) {
return true;
}
break;
}
}
this.logger.error(`Unsupported file type ${filename}`);
@@ -158,13 +162,13 @@ export class AssetService {
getUploadFilename({ auth, fieldName, file }: UploadRequest): string {
this.access.requireUploadAccess(auth);
const originalExt = extname(file.originalName);
const originalExtension = extname(file.originalName);
const lookup = {
[UploadFieldName.ASSET_DATA]: originalExt,
[UploadFieldName.ASSET_DATA]: originalExtension,
[UploadFieldName.LIVE_PHOTO_DATA]: '.mov',
[UploadFieldName.SIDECAR_DATA]: '.xmp',
[UploadFieldName.PROFILE_DATA]: originalExt,
[UploadFieldName.PROFILE_DATA]: originalExtension,
};
return sanitize(`${file.uuid}${lookup[fieldName]}`);
@@ -247,11 +251,9 @@ export class AssetService {
await this.timeBucketChecks(auth, dto);
const timeBucketOptions = await this.buildTimeBucketOptions(auth, dto);
const assets = await this.assetRepository.getTimeBucket(dto.timeBucket, timeBucketOptions);
if (!auth.sharedLink || auth.sharedLink?.showExif) {
return assets.map((asset) => mapAsset(asset, { withStack: true }));
} else {
return assets.map((asset) => mapAsset(asset, { stripMetadata: true }));
}
return !auth.sharedLink || auth.sharedLink?.showExif
? assets.map((asset) => mapAsset(asset, { withStack: true }))
: assets.map((asset) => mapAsset(asset, { stripMetadata: true }));
}
async buildTimeBucketOptions(auth: AuthDto, dto: TimeBucketDto): Promise<TimeBucketOptions> {
@@ -371,14 +373,14 @@ export class AssetService {
const assetsWithChildren = assets.filter((a) => a.stack && a.stack.assets.length > 0);
ids.push(...assetsWithChildren.flatMap((child) => child.stack!.assets.map((gChild) => gChild.id)));
if (!stack) {
stack = await this.assetStackRepository.create({
if (stack) {
await this.assetStackRepository.update({
id: stack.id,
primaryAssetId: primaryAsset.id,
assets: ids.map((id) => ({ id }) as AssetEntity),
});
} else {
await this.assetStackRepository.update({
id: stack.id,
stack = await this.assetStackRepository.create({
primaryAssetId: primaryAsset.id,
assets: ids.map((id) => ({ id }) as AssetEntity),
});
@@ -394,9 +396,10 @@ export class AssetService {
}
await this.assetRepository.updateAll(ids, options);
const stacksToDelete = (
await Promise.all(stackIdsToCheckForDelete.map((id) => this.assetStackRepository.getById(id)))
)
const stackIdsToDelete = await Promise.all(
stackIdsToCheckForDelete.map((id) => this.assetStackRepository.getById(id)),
);
const stacksToDelete = stackIdsToDelete
.flatMap((stack) => (stack ? [stack] : []))
.filter((stack) => stack.assets.length < 2);
await Promise.all(stacksToDelete.map((as) => this.assetStackRepository.delete(as.id)));
@@ -510,9 +513,8 @@ export class AssetService {
throw new Error('Asset not found or not in a stack');
}
if (oldParent != null) {
childIds.push(oldParent.id);
// Get all children of old parent
childIds.push(...(oldParent.stack?.assets.map((a) => a.id) ?? []));
childIds.push(oldParent.id, ...(oldParent.stack?.assets.map((a) => a.id) ?? []));
}
await this.assetStackRepository.update({
id: oldParent.stackId,
@@ -530,17 +532,20 @@ export class AssetService {
for (const id of dto.assetIds) {
switch (dto.name) {
case AssetJobName.REFRESH_METADATA:
case AssetJobName.REFRESH_METADATA: {
jobs.push({ name: JobName.METADATA_EXTRACTION, data: { id } });
break;
}
case AssetJobName.REGENERATE_THUMBNAIL:
case AssetJobName.REGENERATE_THUMBNAIL: {
jobs.push({ name: JobName.GENERATE_JPEG_THUMBNAIL, data: { id } });
break;
}
case AssetJobName.TRANSCODE_VIDEO:
case AssetJobName.TRANSCODE_VIDEO: {
jobs.push({ name: JobName.VIDEO_CONVERSION, data: { id } });
break;
}
}
}
@@ -26,7 +26,6 @@ export class AssetResponseDto extends SanitizedAssetResponseDto {
libraryId!: string;
originalPath!: string;
originalFileName!: string;
resized!: boolean;
fileCreatedAt!: Date;
fileModifiedAt!: Date;
updatedAt!: Date;
@@ -56,7 +55,7 @@ export type AssetMapOptions = {
const peopleWithFaces = (faces: AssetFaceEntity[]): PersonWithFacesResponseDto[] => {
const result: PersonWithFacesResponseDto[] = [];
if (faces) {
faces.forEach((face) => {
for (const face of faces) {
if (face.person) {
const existingPersonEntry = result.find((item) => item.id === face.person!.id);
if (existingPersonEntry) {
@@ -65,7 +64,7 @@ const peopleWithFaces = (faces: AssetFaceEntity[]): PersonWithFacesResponseDto[]
result.push({ ...mapPerson(face.person!), faces: [mapFacesWithoutPerson(face)] });
}
}
});
}
}
return result;
@@ -33,7 +33,7 @@ export function mapExif(entity: ExifEntity): ExifResponseDto {
model: entity.model,
exifImageWidth: entity.exifImageWidth,
exifImageHeight: entity.exifImageHeight,
fileSizeInByte: entity.fileSizeInByte ? parseInt(entity.fileSizeInByte.toString()) : null,
fileSizeInByte: entity.fileSizeInByte ? Number.parseInt(entity.fileSizeInByte.toString()) : null,
orientation: entity.orientation,
dateTimeOriginal: entity.dateTimeOriginal,
modifyDate: entity.modifyDate,
@@ -55,7 +55,7 @@ export function mapExif(entity: ExifEntity): ExifResponseDto {
export function mapSanitizedExif(entity: ExifEntity): ExifResponseDto {
return {
fileSizeInByte: entity.fileSizeInByte ? parseInt(entity.fileSizeInByte.toString()) : null,
fileSizeInByte: entity.fileSizeInByte ? Number.parseInt(entity.fileSizeInByte.toString()) : null,
orientation: entity.orientation,
dateTimeOriginal: entity.dateTimeOriginal,
timeZone: entity.timeZone,