import { AssetEntity } from '@app/infra/entities'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { MoreThan } from 'typeorm'; import { In } from 'typeorm/find-options/operator/In'; import { Repository } from 'typeorm/repository/Repository'; import { AssetSearchDto } from './dto/asset-search.dto'; import { CheckExistingAssetsDto } from './dto/check-existing-assets.dto'; import { SearchPropertiesDto } from './dto/search-properties.dto'; import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto'; import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto'; export interface AssetCheck { id: string; checksum: Buffer; } export interface AssetOwnerCheck extends AssetCheck { ownerId: string; } export interface IAssetRepository { get(id: string): Promise; create( asset: Omit, ): Promise; remove(asset: AssetEntity): Promise; getAllByUserId(userId: string, dto: AssetSearchDto): Promise; getAllByDeviceId(userId: string, deviceId: string): Promise; getById(assetId: string): Promise; getLocationsByUserId(userId: string): Promise; getDetectedObjectsByUserId(userId: string): Promise; getSearchPropertiesByUserId(userId: string): Promise; getAssetsByChecksums(userId: string, checksums: Buffer[]): Promise; getExistingAssets(userId: string, checkDuplicateAssetDto: CheckExistingAssetsDto): Promise; getByOriginalPath(originalPath: string): Promise; } export const IAssetRepository = 'IAssetRepository'; @Injectable() export class AssetRepository implements IAssetRepository { constructor(@InjectRepository(AssetEntity) private assetRepository: Repository) {} getSearchPropertiesByUserId(userId: string): Promise { return this.assetRepository .createQueryBuilder('asset') .where('asset.ownerId = :userId', { userId: userId }) .andWhere('asset.isVisible = true') .leftJoin('asset.exifInfo', 'ei') .leftJoin('asset.smartInfo', 'si') .select('si.tags', 'tags') .addSelect('si.objects', 'objects') .addSelect('asset.type', 'assetType') .addSelect('ei.orientation', 'orientation') .addSelect('ei."lensModel"', 'lensModel') .addSelect('ei.make', 'make') .addSelect('ei.model', 'model') .addSelect('ei.city', 'city') .addSelect('ei.state', 'state') .addSelect('ei.country', 'country') .distinctOn(['si.tags']) .getRawMany(); } getDetectedObjectsByUserId(userId: string): Promise { return this.assetRepository.query( ` SELECT DISTINCT ON (unnest(si.objects)) a.id, unnest(si.objects) as "object", a."resizePath", a."deviceAssetId", a."deviceId" FROM assets a LEFT JOIN smart_info si ON a.id = si."assetId" WHERE a."ownerId" = $1 AND a."isVisible" = true AND si.objects IS NOT NULL `, [userId], ); } getLocationsByUserId(userId: string): Promise { return this.assetRepository.query( ` SELECT DISTINCT ON (e.city) a.id, e.city, a."resizePath", a."deviceAssetId", a."deviceId" FROM assets a LEFT JOIN exif e ON a.id = e."assetId" WHERE a."ownerId" = $1 AND a."isVisible" = true AND e.city IS NOT NULL AND a.type = 'IMAGE'; `, [userId], ); } /** * Get a single asset information by its ID * - include exif info * @param assetId */ getById(assetId: string): Promise { return this.assetRepository.findOneOrFail({ where: { id: assetId, }, relations: { exifInfo: true, tags: true, sharedLinks: true, smartInfo: true, faces: { person: true, }, }, }); } /** * Get all assets belong to the user on the database * @param ownerId */ getAllByUserId(ownerId: string, dto: AssetSearchDto): Promise { return this.assetRepository.find({ where: { ownerId, isVisible: true, isFavorite: dto.isFavorite, isArchived: dto.isArchived, updatedAt: dto.updatedAfter ? MoreThan(dto.updatedAfter) : undefined, }, relations: { exifInfo: true, tags: true, }, skip: dto.skip || 0, order: { fileCreatedAt: 'DESC', }, }); } get(id: string): Promise { return this.assetRepository.findOne({ where: { id }, relations: { faces: { person: true, }, }, }); } create( asset: Omit, ): Promise { return this.assetRepository.save(asset); } async remove(asset: AssetEntity): Promise { await this.assetRepository.remove(asset); } /** * Get assets by device's Id on the database * @param ownerId * @param deviceId * * @returns Promise - Array of assetIds belong to the device */ async getAllByDeviceId(ownerId: string, deviceId: string): Promise { const items = await this.assetRepository.find({ select: { deviceAssetId: true }, where: { ownerId, deviceId, isVisible: true, }, }); return items.map((asset) => asset.deviceAssetId); } /** * Get assets by checksums on the database * @param ownerId * @param checksums * */ getAssetsByChecksums(ownerId: string, checksums: Buffer[]): Promise { return this.assetRepository.find({ select: { id: true, checksum: true, }, where: { ownerId, checksum: In(checksums), }, }); } async getExistingAssets(ownerId: string, checkDuplicateAssetDto: CheckExistingAssetsDto): Promise { const assets = await this.assetRepository.find({ select: { deviceAssetId: true }, where: { deviceAssetId: In(checkDuplicateAssetDto.deviceAssetIds), deviceId: checkDuplicateAssetDto.deviceId, ownerId, }, }); return assets.map((asset) => asset.deviceAssetId); } getByOriginalPath(originalPath: string): Promise { return this.assetRepository.findOne({ select: { id: true, ownerId: true, checksum: true, }, where: { originalPath, }, }); } }