import { AssetEntity, SystemConfig } from '@app/infra/entities'; import { Inject, Injectable, Logger } from '@nestjs/common'; import { IAssetRepository } from '../asset/asset.repository'; import { APP_MEDIA_LOCATION } from '../domain.constant'; import { getLivePhotoMotionFilename } from '../domain.util'; import { IAssetJob } from '../job'; import { IStorageRepository } from '../storage/storage.repository'; import { INITIAL_SYSTEM_CONFIG, ISystemConfigRepository } from '../system-config'; import { StorageTemplateCore } from './storage-template.core'; @Injectable() export class StorageTemplateService { private logger = new Logger(StorageTemplateService.name); private core: StorageTemplateCore; constructor( @Inject(IAssetRepository) private assetRepository: IAssetRepository, @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository, @Inject(INITIAL_SYSTEM_CONFIG) config: SystemConfig, @Inject(IStorageRepository) private storageRepository: IStorageRepository, ) { this.core = new StorageTemplateCore(configRepository, config, storageRepository); } async handleTemplateMigrationSingle(data: IAssetJob) { const { asset } = data; try { const filename = asset.originalFileName || asset.id; await this.moveAsset(asset, filename); // move motion part of live photo if (asset.livePhotoVideoId) { const [livePhotoVideo] = await this.assetRepository.getByIds([asset.livePhotoVideoId]); const motionFilename = getLivePhotoMotionFilename(filename, livePhotoVideo.originalPath); await this.moveAsset(livePhotoVideo, motionFilename); } } catch (error: any) { this.logger.error('Error running single template migration', error); } } async handleTemplateMigration() { try { console.time('migrating-time'); const assets = await this.assetRepository.getAll(); const livePhotoMap: Record = {}; for (const asset of assets) { if (asset.livePhotoVideoId) { livePhotoMap[asset.livePhotoVideoId] = asset; } } for (const asset of assets) { const livePhotoParentAsset = livePhotoMap[asset.id]; // TODO: remove livePhoto specific stuff once upload is fixed const filename = asset.originalFileName || livePhotoParentAsset?.originalFileName || asset.id; await this.moveAsset(asset, filename); } this.logger.debug('Cleaning up empty directories...'); await this.storageRepository.removeEmptyDirs(APP_MEDIA_LOCATION); } catch (error: any) { this.logger.error('Error running template migration', error); } finally { console.timeEnd('migrating-time'); } } // TODO: use asset core (once in domain) async moveAsset(asset: AssetEntity, originalName: string) { const destination = await this.core.getTemplatePath(asset, originalName); if (asset.originalPath !== destination) { const source = asset.originalPath; try { await this.storageRepository.moveFile(asset.originalPath, destination); try { await this.assetRepository.save({ id: asset.id, originalPath: destination }); asset.originalPath = destination; } catch (error: any) { this.logger.warn('Unable to save new originalPath to database, undoing move', error?.stack); await this.storageRepository.moveFile(destination, source); } } catch (error: any) { this.logger.error(`Problem applying storage template`, error?.stack, { id: asset.id, source, destination }); } } return asset; } }