feat(web): unlink live photos (#12574)

feat(web): unlink live photo
This commit is contained in:
Jason Rasmussen 2024-09-11 16:26:29 -04:00 committed by GitHub
parent 233372303b
commit 01c7adc24d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 148 additions and 37 deletions

View file

@ -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 {

View file

@ -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 }];

View file

@ -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 {

View file

@ -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);

View file

@ -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;
}

View file

@ -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) {

View file

@ -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 });
};