mirror of
https://github.com/immich-app/immich
synced 2025-11-14 17:36:12 +00:00
parent
233372303b
commit
01c7adc24d
15 changed files with 148 additions and 37 deletions
|
|
@ -69,8 +69,8 @@ export class UpdateAssetDto extends UpdateAssetBase {
|
|||
@IsString()
|
||||
description?: string;
|
||||
|
||||
@ValidateUUID({ optional: true })
|
||||
livePhotoVideoId?: string;
|
||||
@ValidateUUID({ optional: true, nullable: true })
|
||||
livePhotoVideoId?: string | null;
|
||||
}
|
||||
|
||||
export class RandomAssetsDto {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ type EmitEventMap = {
|
|||
'asset.tag': [{ assetId: string }];
|
||||
'asset.untag': [{ assetId: string }];
|
||||
'asset.hide': [{ assetId: string; userId: string }];
|
||||
'asset.show': [{ assetId: string; userId: string }];
|
||||
|
||||
// session events
|
||||
'session.delete': [{ sessionId: string }];
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ export interface IBaseJob {
|
|||
export interface IEntityJob extends IBaseJob {
|
||||
id: string;
|
||||
source?: 'upload' | 'sidecar-write' | 'copy';
|
||||
notify?: boolean;
|
||||
}
|
||||
|
||||
export interface IAssetDeleteJob extends IEntityJob {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ import { IStackRepository } from 'src/interfaces/stack.interface';
|
|||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||
import { requireAccess } from 'src/utils/access';
|
||||
import { getAssetFiles, getMyPartnerIds, onBeforeLink } from 'src/utils/asset.util';
|
||||
import { getAssetFiles, getMyPartnerIds, onAfterUnlink, onBeforeLink, onBeforeUnlink } from 'src/utils/asset.util';
|
||||
import { usePagination } from 'src/utils/pagination';
|
||||
|
||||
export class AssetService {
|
||||
|
|
@ -159,17 +159,26 @@ export class AssetService {
|
|||
await requireAccess(this.access, { auth, permission: Permission.ASSET_UPDATE, ids: [id] });
|
||||
|
||||
const { description, dateTimeOriginal, latitude, longitude, rating, ...rest } = dto;
|
||||
const repos = { asset: this.assetRepository, event: this.eventRepository };
|
||||
|
||||
let previousMotion: AssetEntity | null = null;
|
||||
if (rest.livePhotoVideoId) {
|
||||
await onBeforeLink(
|
||||
{ asset: this.assetRepository, event: this.eventRepository },
|
||||
{ userId: auth.user.id, livePhotoVideoId: rest.livePhotoVideoId },
|
||||
);
|
||||
await onBeforeLink(repos, { userId: auth.user.id, livePhotoVideoId: rest.livePhotoVideoId });
|
||||
} else if (rest.livePhotoVideoId === null) {
|
||||
const asset = await this.findOrFail(id);
|
||||
if (asset.livePhotoVideoId) {
|
||||
previousMotion = await onBeforeUnlink(repos, { livePhotoVideoId: asset.livePhotoVideoId });
|
||||
}
|
||||
}
|
||||
|
||||
await this.updateMetadata({ id, description, dateTimeOriginal, latitude, longitude, rating });
|
||||
|
||||
await this.assetRepository.update({ id, ...rest });
|
||||
|
||||
if (previousMotion) {
|
||||
await onAfterUnlink(repos, { userId: auth.user.id, livePhotoVideoId: previousMotion.id });
|
||||
}
|
||||
|
||||
const asset = await this.assetRepository.getById(id, {
|
||||
exifInfo: true,
|
||||
owner: true,
|
||||
|
|
@ -180,9 +189,11 @@ export class AssetService {
|
|||
},
|
||||
files: true,
|
||||
});
|
||||
|
||||
if (!asset) {
|
||||
throw new BadRequestException('Asset not found');
|
||||
}
|
||||
|
||||
return mapAsset(asset, { auth });
|
||||
}
|
||||
|
||||
|
|
@ -326,6 +337,14 @@ export class AssetService {
|
|||
await this.jobRepository.queueAll(jobs);
|
||||
}
|
||||
|
||||
private async findOrFail(id: string) {
|
||||
const asset = await this.assetRepository.getById(id);
|
||||
if (!asset) {
|
||||
throw new BadRequestException('Asset not found');
|
||||
}
|
||||
return asset;
|
||||
}
|
||||
|
||||
private async updateMetadata(dto: ISidecarWriteJob) {
|
||||
const { id, description, dateTimeOriginal, latitude, longitude, rating } = dto;
|
||||
const writes = _.omitBy({ description, dateTimeOriginal, latitude, longitude, rating }, _.isUndefined);
|
||||
|
|
|
|||
|
|
@ -289,7 +289,7 @@ export class JobService {
|
|||
}
|
||||
|
||||
case JobName.GENERATE_THUMBNAIL: {
|
||||
if (item.data.source !== 'upload') {
|
||||
if (!(item.data.notify || item.data.source === 'upload')) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,11 @@ export class NotificationService {
|
|||
this.eventRepository.clientSend(ClientEvent.ASSET_HIDDEN, userId, assetId);
|
||||
}
|
||||
|
||||
@OnEmit({ event: 'asset.show' })
|
||||
async onAssetShow({ assetId }: ArgOf<'asset.show'>) {
|
||||
await this.jobRepository.queue({ name: JobName.GENERATE_THUMBNAIL, data: { id: assetId, notify: true } });
|
||||
}
|
||||
|
||||
@OnEmit({ event: 'user.signup' })
|
||||
async onUserSignup({ notify, id, tempPassword }: ArgOf<'user.signup'>) {
|
||||
if (notify) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { BadRequestException } from '@nestjs/common';
|
||||
import { StorageCore } from 'src/cores/storage.core';
|
||||
import { BulkIdErrorReason, BulkIdResponseDto } from 'src/dtos/asset-ids.response.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { AssetFileEntity } from 'src/entities/asset-files.entity';
|
||||
|
|
@ -134,8 +135,10 @@ export const getMyPartnerIds = async ({ userId, repository, timelineEnabled }: P
|
|||
return [...partnerIds];
|
||||
};
|
||||
|
||||
export type AssetHookRepositories = { asset: IAssetRepository; event: IEventRepository };
|
||||
|
||||
export const onBeforeLink = async (
|
||||
{ asset: assetRepository, event: eventRepository }: { asset: IAssetRepository; event: IEventRepository },
|
||||
{ asset: assetRepository, event: eventRepository }: AssetHookRepositories,
|
||||
{ userId, livePhotoVideoId }: { userId: string; livePhotoVideoId: string },
|
||||
) => {
|
||||
const motionAsset = await assetRepository.getById(livePhotoVideoId);
|
||||
|
|
@ -154,3 +157,27 @@ export const onBeforeLink = async (
|
|||
await eventRepository.emit('asset.hide', { assetId: motionAsset.id, userId });
|
||||
}
|
||||
};
|
||||
|
||||
export const onBeforeUnlink = async (
|
||||
{ asset: assetRepository }: AssetHookRepositories,
|
||||
{ livePhotoVideoId }: { livePhotoVideoId: string },
|
||||
) => {
|
||||
const motion = await assetRepository.getById(livePhotoVideoId);
|
||||
if (!motion) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (StorageCore.isAndroidMotionPath(motion.originalPath)) {
|
||||
throw new BadRequestException('Cannot unlink Android motion photos');
|
||||
}
|
||||
|
||||
return motion;
|
||||
};
|
||||
|
||||
export const onAfterUnlink = async (
|
||||
{ asset: assetRepository, event: eventRepository }: AssetHookRepositories,
|
||||
{ userId, livePhotoVideoId }: { userId: string; livePhotoVideoId: string },
|
||||
) => {
|
||||
await assetRepository.update({ id: livePhotoVideoId, isVisible: true });
|
||||
await eventRepository.emit('asset.show', { assetId: livePhotoVideoId, userId });
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue