2022-06-25 19:53:06 +02:00
|
|
|
import {
|
|
|
|
|
BadRequestException,
|
|
|
|
|
Injectable,
|
|
|
|
|
InternalServerErrorException,
|
|
|
|
|
Logger,
|
|
|
|
|
NotFoundException,
|
|
|
|
|
StreamableFile,
|
|
|
|
|
} from '@nestjs/common';
|
2022-02-03 10:06:44 -06:00
|
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
2022-06-19 08:16:35 -05:00
|
|
|
import { IsNull, Not, Repository } from 'typeorm';
|
2022-02-03 10:06:44 -06:00
|
|
|
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
2022-06-11 16:12:06 -05:00
|
|
|
import { AssetEntity, AssetType } from '@app/database/entities/asset.entity';
|
2022-07-01 12:00:12 -05:00
|
|
|
import { constants, createReadStream, ReadStream, stat } from 'fs';
|
2022-02-13 15:10:42 -06:00
|
|
|
import { ServeFileDto } from './dto/serve-file.dto';
|
|
|
|
|
import { Response as Res } from 'express';
|
|
|
|
|
import { promisify } from 'util';
|
|
|
|
|
import { DeleteAssetDto } from './dto/delete-asset.dto';
|
2022-03-02 16:44:24 -06:00
|
|
|
import { SearchAssetDto } from './dto/search-asset.dto';
|
2022-07-01 12:00:12 -05:00
|
|
|
import fs from 'fs/promises';
|
2022-07-06 16:12:55 -05:00
|
|
|
import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
|
2022-07-08 21:26:50 -05:00
|
|
|
import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto';
|
|
|
|
|
import { AssetResponseDto, mapAsset } from './response-dto/asset-response.dto';
|
2022-07-10 21:41:45 -05:00
|
|
|
import { AssetFileUploadDto } from './dto/asset-file-upload.dto';
|
|
|
|
|
import { CreateAssetDto } from './dto/create-asset.dto';
|
2022-07-13 07:23:48 -05:00
|
|
|
import { DeleteAssetResponseDto, DeleteAssetStatusEnum } from './response-dto/delete-asset-response.dto';
|
2022-07-15 23:18:17 -05:00
|
|
|
import { GetAssetThumbnailDto, GetAssetThumbnailFormatEnum } from './dto/get-asset-thumbnail.dto';
|
2022-02-13 15:10:42 -06:00
|
|
|
|
|
|
|
|
const fileInfo = promisify(stat);
|
2022-02-03 10:06:44 -06:00
|
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
|
export class AssetService {
|
|
|
|
|
constructor(
|
|
|
|
|
@InjectRepository(AssetEntity)
|
|
|
|
|
private assetRepository: Repository<AssetEntity>,
|
2022-06-11 16:12:06 -05:00
|
|
|
) {}
|
|
|
|
|
|
|
|
|
|
public async updateThumbnailInfo(asset: AssetEntity, thumbnailPath: string): Promise<AssetEntity> {
|
|
|
|
|
const updatedAsset = await this.assetRepository
|
|
|
|
|
.createQueryBuilder('assets')
|
|
|
|
|
.update<AssetEntity>(AssetEntity, { ...asset, resizePath: thumbnailPath })
|
|
|
|
|
.where('assets.id = :id', { id: asset.id })
|
|
|
|
|
.returning('*')
|
|
|
|
|
.updateEntity(true)
|
|
|
|
|
.execute();
|
|
|
|
|
|
|
|
|
|
return updatedAsset.raw[0];
|
2022-03-22 01:22:04 -05:00
|
|
|
}
|
|
|
|
|
|
2022-06-25 19:53:06 +02:00
|
|
|
public async createUserAsset(
|
|
|
|
|
authUser: AuthUserDto,
|
|
|
|
|
assetInfo: CreateAssetDto,
|
|
|
|
|
path: string,
|
|
|
|
|
mimeType: string,
|
|
|
|
|
): Promise<AssetEntity | undefined> {
|
2022-02-03 10:06:44 -06:00
|
|
|
const asset = new AssetEntity();
|
|
|
|
|
asset.deviceAssetId = assetInfo.deviceAssetId;
|
|
|
|
|
asset.userId = authUser.id;
|
|
|
|
|
asset.deviceId = assetInfo.deviceId;
|
|
|
|
|
asset.type = assetInfo.assetType || AssetType.OTHER;
|
|
|
|
|
asset.originalPath = path;
|
|
|
|
|
asset.createdAt = assetInfo.createdAt;
|
|
|
|
|
asset.modifiedAt = assetInfo.modifiedAt;
|
|
|
|
|
asset.isFavorite = assetInfo.isFavorite;
|
|
|
|
|
asset.mimeType = mimeType;
|
2022-06-25 19:53:06 +02:00
|
|
|
asset.duration = assetInfo.duration || null;
|
2022-02-06 00:07:56 -06:00
|
|
|
|
2022-07-06 16:12:55 -05:00
|
|
|
const createdAsset = await this.assetRepository.save(asset);
|
|
|
|
|
if (!createdAsset) {
|
|
|
|
|
throw new Error('Asset not created');
|
2022-02-03 10:06:44 -06:00
|
|
|
}
|
2022-07-06 16:12:55 -05:00
|
|
|
return createdAsset;
|
2022-02-03 10:06:44 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async getUserAssetsByDeviceId(authUser: AuthUserDto, deviceId: string) {
|
|
|
|
|
const rows = await this.assetRepository.find({
|
|
|
|
|
where: {
|
|
|
|
|
userId: authUser.id,
|
|
|
|
|
deviceId: deviceId,
|
|
|
|
|
},
|
|
|
|
|
select: ['deviceAssetId'],
|
|
|
|
|
});
|
|
|
|
|
|
2022-06-25 19:53:06 +02:00
|
|
|
const res: string[] = [];
|
2022-02-03 10:06:44 -06:00
|
|
|
rows.forEach((v) => res.push(v.deviceAssetId));
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-08 21:26:50 -05:00
|
|
|
public async getAllAssets(authUser: AuthUserDto): Promise<AssetResponseDto[]> {
|
|
|
|
|
const assets = await this.assetRepository.find({
|
|
|
|
|
where: {
|
|
|
|
|
userId: authUser.id,
|
|
|
|
|
resizePath: Not(IsNull()),
|
|
|
|
|
},
|
|
|
|
|
relations: ['exifInfo'],
|
|
|
|
|
order: {
|
|
|
|
|
createdAt: 'DESC',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return assets.map((asset) => mapAsset(asset));
|
2022-02-13 15:10:42 -06:00
|
|
|
}
|
|
|
|
|
|
2022-07-08 21:26:50 -05:00
|
|
|
public async findAssetOfDevice(deviceId: string, assetId: string): Promise<AssetResponseDto> {
|
2022-02-03 10:06:44 -06:00
|
|
|
const rows = await this.assetRepository.query(
|
2022-04-23 21:08:45 -05:00
|
|
|
'SELECT * FROM assets a WHERE a."deviceAssetId" = $1 AND a."deviceId" = $2',
|
|
|
|
|
[assetId, deviceId],
|
2022-02-03 10:06:44 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (rows.lengh == 0) {
|
2022-07-08 21:26:50 -05:00
|
|
|
throw new NotFoundException('Not Found');
|
2022-02-03 10:06:44 -06:00
|
|
|
}
|
|
|
|
|
|
2022-07-08 21:26:50 -05:00
|
|
|
const assetOnDevice = rows[0] as AssetEntity;
|
|
|
|
|
|
|
|
|
|
return mapAsset(assetOnDevice);
|
2022-02-03 10:06:44 -06:00
|
|
|
}
|
2022-02-06 20:31:32 -06:00
|
|
|
|
2022-07-08 21:26:50 -05:00
|
|
|
public async getAssetById(authUser: AuthUserDto, assetId: string): Promise<AssetResponseDto> {
|
|
|
|
|
const asset = await this.assetRepository.findOne({
|
2022-02-10 20:40:11 -06:00
|
|
|
where: {
|
|
|
|
|
id: assetId,
|
|
|
|
|
},
|
|
|
|
|
relations: ['exifInfo'],
|
|
|
|
|
});
|
2022-07-08 21:26:50 -05:00
|
|
|
|
|
|
|
|
if (!asset) {
|
|
|
|
|
throw new NotFoundException('Asset not found');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mapAsset(asset);
|
2022-02-10 20:40:11 -06:00
|
|
|
}
|
2022-02-13 15:10:42 -06:00
|
|
|
|
2022-04-23 21:08:45 -05:00
|
|
|
public async downloadFile(query: ServeFileDto, res: Res) {
|
2022-06-11 16:12:06 -05:00
|
|
|
try {
|
2022-07-01 12:00:12 -05:00
|
|
|
let fileReadStream = null;
|
2022-07-08 21:26:50 -05:00
|
|
|
const asset = await this.findAssetOfDevice(query.did, query.aid);
|
2022-04-02 12:31:53 -05:00
|
|
|
|
2022-07-10 21:41:45 -05:00
|
|
|
// Download Video
|
|
|
|
|
if (asset.type === AssetType.VIDEO) {
|
2022-06-11 16:12:06 -05:00
|
|
|
const { size } = await fileInfo(asset.originalPath);
|
2022-07-10 21:41:45 -05:00
|
|
|
|
2022-06-11 16:12:06 -05:00
|
|
|
res.set({
|
|
|
|
|
'Content-Type': asset.mimeType,
|
|
|
|
|
'Content-Length': size,
|
|
|
|
|
});
|
2022-07-01 12:00:12 -05:00
|
|
|
|
|
|
|
|
await fs.access(asset.originalPath, constants.R_OK | constants.W_OK);
|
|
|
|
|
fileReadStream = createReadStream(asset.originalPath);
|
2022-06-11 16:12:06 -05:00
|
|
|
} else {
|
2022-07-10 21:41:45 -05:00
|
|
|
// Download Image
|
|
|
|
|
if (!query.isThumb) {
|
|
|
|
|
/**
|
|
|
|
|
* Download Image Original File
|
|
|
|
|
*/
|
|
|
|
|
const { size } = await fileInfo(asset.originalPath);
|
2022-07-01 12:00:12 -05:00
|
|
|
|
2022-07-10 21:41:45 -05:00
|
|
|
res.set({
|
|
|
|
|
'Content-Type': asset.mimeType,
|
|
|
|
|
'Content-Length': size,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await fs.access(asset.originalPath, constants.R_OK | constants.W_OK);
|
|
|
|
|
fileReadStream = createReadStream(asset.originalPath);
|
|
|
|
|
} else {
|
|
|
|
|
/**
|
|
|
|
|
* Download Image Resize File
|
|
|
|
|
*/
|
|
|
|
|
if (!asset.resizePath) {
|
|
|
|
|
throw new NotFoundException('resizePath not set');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { size } = await fileInfo(asset.resizePath);
|
|
|
|
|
|
|
|
|
|
res.set({
|
|
|
|
|
'Content-Type': 'image/jpeg',
|
|
|
|
|
'Content-Length': size,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await fs.access(asset.resizePath, constants.R_OK | constants.W_OK);
|
|
|
|
|
fileReadStream = createReadStream(asset.resizePath);
|
|
|
|
|
}
|
2022-06-11 16:12:06 -05:00
|
|
|
}
|
2022-04-02 12:31:53 -05:00
|
|
|
|
2022-07-01 12:00:12 -05:00
|
|
|
return new StreamableFile(fileReadStream);
|
2022-06-11 16:12:06 -05:00
|
|
|
} catch (e) {
|
2022-07-10 21:41:45 -05:00
|
|
|
Logger.error(`Error download asset ${e}`, 'downloadFile');
|
2022-06-11 16:12:06 -05:00
|
|
|
throw new InternalServerErrorException(`Failed to download asset ${e}`, 'DownloadFile');
|
|
|
|
|
}
|
2022-04-02 12:31:53 -05:00
|
|
|
}
|
|
|
|
|
|
2022-07-15 23:18:17 -05:00
|
|
|
public async getAssetThumbnail(assetId: string, query: GetAssetThumbnailDto) {
|
2022-07-01 12:00:12 -05:00
|
|
|
let fileReadStream: ReadStream;
|
|
|
|
|
|
|
|
|
|
const asset = await this.assetRepository.findOne({ where: { id: assetId } });
|
|
|
|
|
|
|
|
|
|
if (!asset) {
|
|
|
|
|
throw new NotFoundException('Asset not found');
|
|
|
|
|
}
|
2022-04-23 21:08:45 -05:00
|
|
|
|
2022-07-01 12:00:12 -05:00
|
|
|
try {
|
2022-07-15 23:18:17 -05:00
|
|
|
if (query.format == GetAssetThumbnailFormatEnum.JPEG) {
|
2022-06-25 19:53:06 +02:00
|
|
|
if (!asset.resizePath) {
|
2022-07-10 21:41:45 -05:00
|
|
|
throw new NotFoundException('resizePath not set');
|
2022-06-25 19:53:06 +02:00
|
|
|
}
|
2022-07-01 12:00:12 -05:00
|
|
|
|
|
|
|
|
await fs.access(asset.resizePath, constants.R_OK | constants.W_OK);
|
|
|
|
|
fileReadStream = createReadStream(asset.resizePath);
|
2022-07-15 23:18:17 -05:00
|
|
|
} else {
|
|
|
|
|
if (asset.webpPath && asset.webpPath.length > 0) {
|
|
|
|
|
await fs.access(asset.webpPath, constants.R_OK | constants.W_OK);
|
|
|
|
|
fileReadStream = createReadStream(asset.webpPath);
|
|
|
|
|
} else {
|
|
|
|
|
if (!asset.resizePath) {
|
|
|
|
|
throw new NotFoundException('resizePath not set');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await fs.access(asset.resizePath, constants.R_OK | constants.W_OK);
|
|
|
|
|
fileReadStream = createReadStream(asset.resizePath);
|
|
|
|
|
}
|
2022-06-11 16:12:06 -05:00
|
|
|
}
|
2022-07-01 12:00:12 -05:00
|
|
|
|
|
|
|
|
return new StreamableFile(fileReadStream);
|
2022-06-11 16:12:06 -05:00
|
|
|
} catch (e) {
|
2022-07-01 12:00:12 -05:00
|
|
|
Logger.error(`Cannot create read stream for asset ${asset.id}`, 'getAssetThumbnail');
|
|
|
|
|
throw new InternalServerErrorException(
|
|
|
|
|
e,
|
|
|
|
|
`Cannot read thumbnail file for asset ${asset.id} - contact your administrator`,
|
|
|
|
|
);
|
2022-05-22 06:56:36 -05:00
|
|
|
}
|
2022-04-23 21:08:45 -05:00
|
|
|
}
|
|
|
|
|
|
2022-02-13 15:10:42 -06:00
|
|
|
public async serveFile(authUser: AuthUserDto, query: ServeFileDto, res: Res, headers: any) {
|
2022-07-01 12:00:12 -05:00
|
|
|
let fileReadStream: ReadStream;
|
2022-07-08 21:26:50 -05:00
|
|
|
const asset = await this.findAssetOfDevice(query.did, query.aid);
|
2022-05-27 14:02:06 -05:00
|
|
|
|
2022-04-02 12:31:53 -05:00
|
|
|
if (!asset) {
|
2022-07-01 12:00:12 -05:00
|
|
|
throw new NotFoundException('Asset does not exist');
|
2022-04-02 12:31:53 -05:00
|
|
|
}
|
2022-05-27 14:02:06 -05:00
|
|
|
|
2022-02-13 15:10:42 -06:00
|
|
|
// Handle Sending Images
|
2022-07-10 21:41:45 -05:00
|
|
|
if (asset.type == AssetType.IMAGE) {
|
2022-07-01 12:00:12 -05:00
|
|
|
try {
|
|
|
|
|
/**
|
|
|
|
|
* Serve file viewer on the web
|
|
|
|
|
*/
|
|
|
|
|
if (query.isWeb) {
|
|
|
|
|
res.set({
|
|
|
|
|
'Content-Type': 'image/jpeg',
|
|
|
|
|
});
|
|
|
|
|
if (!asset.resizePath) {
|
|
|
|
|
Logger.error('Error serving IMAGE asset for web', 'ServeFile');
|
|
|
|
|
throw new InternalServerErrorException(`Failed to serve image asset for web`, 'ServeFile');
|
|
|
|
|
}
|
|
|
|
|
await fs.access(asset.resizePath, constants.R_OK | constants.W_OK);
|
|
|
|
|
fileReadStream = createReadStream(asset.resizePath);
|
|
|
|
|
|
|
|
|
|
return new StreamableFile(fileReadStream);
|
2022-06-25 19:53:06 +02:00
|
|
|
}
|
2022-02-13 15:10:42 -06:00
|
|
|
|
2022-06-11 16:12:06 -05:00
|
|
|
/**
|
|
|
|
|
* Serve thumbnail image for both web and mobile app
|
|
|
|
|
*/
|
2022-07-10 21:41:45 -05:00
|
|
|
if (!query.isThumb) {
|
2022-05-27 14:02:06 -05:00
|
|
|
res.set({
|
2022-06-11 16:12:06 -05:00
|
|
|
'Content-Type': asset.mimeType,
|
2022-05-27 14:02:06 -05:00
|
|
|
});
|
2022-07-01 12:00:12 -05:00
|
|
|
|
|
|
|
|
await fs.access(asset.originalPath, constants.R_OK | constants.W_OK);
|
|
|
|
|
fileReadStream = createReadStream(asset.originalPath);
|
2022-05-22 06:56:36 -05:00
|
|
|
} else {
|
2022-06-11 16:12:06 -05:00
|
|
|
if (asset.webpPath && asset.webpPath.length > 0) {
|
|
|
|
|
res.set({
|
|
|
|
|
'Content-Type': 'image/webp',
|
|
|
|
|
});
|
|
|
|
|
|
2022-07-01 12:00:12 -05:00
|
|
|
await fs.access(asset.webpPath, constants.R_OK | constants.W_OK);
|
|
|
|
|
fileReadStream = createReadStream(asset.webpPath);
|
2022-06-11 16:12:06 -05:00
|
|
|
} else {
|
|
|
|
|
res.set({
|
|
|
|
|
'Content-Type': 'image/jpeg',
|
|
|
|
|
});
|
2022-07-01 12:00:12 -05:00
|
|
|
|
2022-06-25 19:53:06 +02:00
|
|
|
if (!asset.resizePath) {
|
|
|
|
|
throw new Error('resizePath not set');
|
|
|
|
|
}
|
2022-07-01 12:00:12 -05:00
|
|
|
|
|
|
|
|
await fs.access(asset.resizePath, constants.R_OK | constants.W_OK);
|
|
|
|
|
fileReadStream = createReadStream(asset.resizePath);
|
2022-06-11 16:12:06 -05:00
|
|
|
}
|
2022-05-22 06:56:36 -05:00
|
|
|
}
|
2022-05-27 14:02:06 -05:00
|
|
|
|
2022-07-01 12:00:12 -05:00
|
|
|
return new StreamableFile(fileReadStream);
|
2022-06-11 16:12:06 -05:00
|
|
|
} catch (e) {
|
2022-07-13 07:23:48 -05:00
|
|
|
Logger.error(`Cannot create read stream for asset ${asset.id} ${JSON.stringify(e)}`, 'serveFile[IMAGE]');
|
2022-07-01 12:00:12 -05:00
|
|
|
throw new InternalServerErrorException(
|
|
|
|
|
e,
|
|
|
|
|
`Cannot read thumbnail file for asset ${asset.id} - contact your administrator`,
|
|
|
|
|
);
|
2022-06-04 18:34:11 -05:00
|
|
|
}
|
2022-07-10 21:41:45 -05:00
|
|
|
} else {
|
2022-06-11 16:12:06 -05:00
|
|
|
try {
|
|
|
|
|
// Handle Video
|
|
|
|
|
let videoPath = asset.originalPath;
|
2022-07-08 21:26:50 -05:00
|
|
|
|
2022-06-11 16:12:06 -05:00
|
|
|
let mimeType = asset.mimeType;
|
|
|
|
|
|
2022-07-01 12:00:12 -05:00
|
|
|
await fs.access(videoPath, constants.R_OK | constants.W_OK);
|
|
|
|
|
|
2022-06-11 16:12:06 -05:00
|
|
|
if (query.isWeb && asset.mimeType == 'video/quicktime') {
|
2022-07-08 21:26:50 -05:00
|
|
|
videoPath = asset.encodedVideoPath == '' ? String(asset.originalPath) : String(asset.encodedVideoPath);
|
2022-06-11 16:12:06 -05:00
|
|
|
mimeType = asset.encodedVideoPath == '' ? asset.mimeType : 'video/mp4';
|
2022-02-13 15:10:42 -06:00
|
|
|
}
|
|
|
|
|
|
2022-06-11 16:12:06 -05:00
|
|
|
const { size } = await fileInfo(videoPath);
|
|
|
|
|
const range = headers.range;
|
|
|
|
|
|
|
|
|
|
if (range) {
|
|
|
|
|
/** Extracting Start and End value from Range Header */
|
|
|
|
|
let [start, end] = range.replace(/bytes=/, '').split('-');
|
|
|
|
|
start = parseInt(start, 10);
|
|
|
|
|
end = end ? parseInt(end, 10) : size - 1;
|
|
|
|
|
|
|
|
|
|
if (!isNaN(start) && isNaN(end)) {
|
|
|
|
|
start = start;
|
|
|
|
|
end = size - 1;
|
|
|
|
|
}
|
|
|
|
|
if (isNaN(start) && !isNaN(end)) {
|
|
|
|
|
start = size - end;
|
|
|
|
|
end = size - 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle unavailable range request
|
|
|
|
|
if (start >= size || end >= size) {
|
|
|
|
|
console.error('Bad Request');
|
|
|
|
|
// Return the 416 Range Not Satisfiable.
|
|
|
|
|
res.status(416).set({
|
|
|
|
|
'Content-Range': `bytes */${size}`,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
throw new BadRequestException('Bad Request Range');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Sending Partial Content With HTTP Code 206 */
|
|
|
|
|
res.status(206).set({
|
|
|
|
|
'Content-Range': `bytes ${start}-${end}/${size}`,
|
|
|
|
|
'Accept-Ranges': 'bytes',
|
|
|
|
|
'Content-Length': end - start + 1,
|
|
|
|
|
'Content-Type': mimeType,
|
2022-02-13 15:10:42 -06:00
|
|
|
});
|
|
|
|
|
|
2022-06-11 16:12:06 -05:00
|
|
|
const videoStream = createReadStream(videoPath, { start: start, end: end });
|
2022-05-27 14:02:06 -05:00
|
|
|
|
2022-06-11 16:12:06 -05:00
|
|
|
return new StreamableFile(videoStream);
|
|
|
|
|
} else {
|
|
|
|
|
res.set({
|
|
|
|
|
'Content-Type': mimeType,
|
|
|
|
|
});
|
2022-02-13 15:10:42 -06:00
|
|
|
|
2022-06-11 16:12:06 -05:00
|
|
|
return new StreamableFile(createReadStream(videoPath));
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
2022-07-01 12:00:12 -05:00
|
|
|
Logger.error(`Error serving VIDEO asset id ${asset.id}`, 'serveFile[VIDEO]');
|
2022-06-11 16:12:06 -05:00
|
|
|
throw new InternalServerErrorException(`Failed to serve video asset ${e}`, 'ServeFile');
|
2022-02-13 15:10:42 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-13 07:23:48 -05:00
|
|
|
public async deleteAssetById(authUser: AuthUserDto, assetIds: DeleteAssetDto): Promise<DeleteAssetResponseDto[]> {
|
|
|
|
|
const result: DeleteAssetResponseDto[] = [];
|
2022-02-13 15:10:42 -06:00
|
|
|
|
|
|
|
|
const target = assetIds.ids;
|
2022-03-27 14:58:54 -05:00
|
|
|
for (const assetId of target) {
|
2022-02-13 15:10:42 -06:00
|
|
|
const res = await this.assetRepository.delete({
|
|
|
|
|
id: assetId,
|
|
|
|
|
userId: authUser.id,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (res.affected) {
|
|
|
|
|
result.push({
|
|
|
|
|
id: assetId,
|
2022-07-13 07:23:48 -05:00
|
|
|
status: DeleteAssetStatusEnum.SUCCESS,
|
2022-02-13 15:10:42 -06:00
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
result.push({
|
|
|
|
|
id: assetId,
|
2022-07-13 07:23:48 -05:00
|
|
|
status: DeleteAssetStatusEnum.FAILED,
|
2022-02-13 15:10:42 -06:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2022-02-27 12:43:29 -06:00
|
|
|
|
2022-03-27 14:58:54 -05:00
|
|
|
async getAssetSearchTerm(authUser: AuthUserDto): Promise<string[]> {
|
|
|
|
|
const possibleSearchTerm = new Set<string>();
|
2022-06-25 19:53:06 +02:00
|
|
|
// TODO: should use query builder
|
2022-02-27 12:43:29 -06:00
|
|
|
const rows = await this.assetRepository.query(
|
|
|
|
|
`
|
2022-07-01 12:00:12 -05:00
|
|
|
SELECT DISTINCT si.tags, si.objects, e.orientation, e."lensModel", e.make, e.model , a.type, e.city, e.state, e.country
|
|
|
|
|
FROM assets a
|
|
|
|
|
LEFT JOIN exif e ON a.id = e."assetId"
|
|
|
|
|
LEFT JOIN smart_info si ON a.id = si."assetId"
|
|
|
|
|
WHERE a."userId" = $1;
|
2022-02-27 12:43:29 -06:00
|
|
|
`,
|
|
|
|
|
[authUser.id],
|
|
|
|
|
);
|
|
|
|
|
|
2022-06-25 19:53:06 +02:00
|
|
|
rows.forEach((row: { [x: string]: any }) => {
|
2022-02-27 12:43:29 -06:00
|
|
|
// tags
|
2022-06-25 19:53:06 +02:00
|
|
|
row['tags']?.map((tag: string) => possibleSearchTerm.add(tag?.toLowerCase()));
|
2022-02-27 12:43:29 -06:00
|
|
|
|
2022-03-27 14:58:54 -05:00
|
|
|
// objects
|
2022-06-25 19:53:06 +02:00
|
|
|
row['objects']?.map((object: string) => possibleSearchTerm.add(object?.toLowerCase()));
|
2022-03-27 14:58:54 -05:00
|
|
|
|
2022-02-27 12:43:29 -06:00
|
|
|
// asset's tyoe
|
|
|
|
|
possibleSearchTerm.add(row['type']?.toLowerCase());
|
|
|
|
|
|
|
|
|
|
// image orientation
|
|
|
|
|
possibleSearchTerm.add(row['orientation']?.toLowerCase());
|
|
|
|
|
|
|
|
|
|
// Lens model
|
|
|
|
|
possibleSearchTerm.add(row['lensModel']?.toLowerCase());
|
|
|
|
|
|
|
|
|
|
// Make and model
|
|
|
|
|
possibleSearchTerm.add(row['make']?.toLowerCase());
|
|
|
|
|
possibleSearchTerm.add(row['model']?.toLowerCase());
|
2022-03-10 16:09:03 -06:00
|
|
|
|
|
|
|
|
// Location
|
|
|
|
|
possibleSearchTerm.add(row['city']?.toLowerCase());
|
|
|
|
|
possibleSearchTerm.add(row['state']?.toLowerCase());
|
|
|
|
|
possibleSearchTerm.add(row['country']?.toLowerCase());
|
2022-02-27 12:43:29 -06:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return Array.from(possibleSearchTerm).filter((x) => x != null);
|
|
|
|
|
}
|
2022-03-02 16:44:24 -06:00
|
|
|
|
2022-07-08 21:26:50 -05:00
|
|
|
async searchAsset(authUser: AuthUserDto, searchAssetDto: SearchAssetDto): Promise<AssetResponseDto[]> {
|
2022-03-02 16:44:24 -06:00
|
|
|
const query = `
|
|
|
|
|
SELECT a.*
|
|
|
|
|
FROM assets a
|
|
|
|
|
LEFT JOIN smart_info si ON a.id = si."assetId"
|
|
|
|
|
LEFT JOIN exif e ON a.id = e."assetId"
|
|
|
|
|
|
|
|
|
|
WHERE a."userId" = $1
|
2022-06-25 19:53:06 +02:00
|
|
|
AND
|
2022-03-02 16:44:24 -06:00
|
|
|
(
|
2022-03-27 14:58:54 -05:00
|
|
|
TO_TSVECTOR('english', ARRAY_TO_STRING(si.tags, ',')) @@ PLAINTO_TSQUERY('english', $2) OR
|
|
|
|
|
TO_TSVECTOR('english', ARRAY_TO_STRING(si.objects, ',')) @@ PLAINTO_TSQUERY('english', $2) OR
|
2022-07-04 20:20:43 +01:00
|
|
|
e."exifTextSearchableColumn" @@ PLAINTO_TSQUERY('english', $2)
|
2022-03-02 16:44:24 -06:00
|
|
|
);
|
|
|
|
|
`;
|
|
|
|
|
|
2022-07-08 21:26:50 -05:00
|
|
|
const searchResults: AssetEntity[] = await this.assetRepository.query(query, [
|
|
|
|
|
authUser.id,
|
|
|
|
|
searchAssetDto.searchTerm,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return searchResults.map((asset) => mapAsset(asset));
|
2022-03-02 16:44:24 -06:00
|
|
|
}
|
2022-03-16 10:19:31 -05:00
|
|
|
|
|
|
|
|
async getCuratedLocation(authUser: AuthUserDto) {
|
2022-03-27 14:58:54 -05:00
|
|
|
return await this.assetRepository.query(
|
2022-03-16 10:19:31 -05:00
|
|
|
`
|
2022-07-01 12:00:12 -05:00
|
|
|
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."userId" = $1
|
|
|
|
|
AND e.city IS NOT NULL
|
|
|
|
|
AND a.type = 'IMAGE';
|
2022-03-16 10:19:31 -05:00
|
|
|
`,
|
|
|
|
|
[authUser.id],
|
|
|
|
|
);
|
2022-03-27 14:58:54 -05:00
|
|
|
}
|
2022-03-16 10:19:31 -05:00
|
|
|
|
2022-07-08 21:26:50 -05:00
|
|
|
async getCuratedObject(authUser: AuthUserDto): Promise<CuratedObjectsResponseDto[]> {
|
|
|
|
|
const curatedObjects: CuratedObjectsResponseDto[] = await this.assetRepository.query(
|
2022-03-27 14:58:54 -05:00
|
|
|
`
|
2022-07-01 12:00:12 -05:00
|
|
|
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."userId" = $1
|
|
|
|
|
AND si.objects IS NOT NULL
|
2022-03-27 14:58:54 -05:00
|
|
|
`,
|
|
|
|
|
[authUser.id],
|
|
|
|
|
);
|
2022-07-08 21:26:50 -05:00
|
|
|
|
|
|
|
|
return curatedObjects;
|
2022-03-16 10:19:31 -05:00
|
|
|
}
|
2022-06-19 08:16:35 -05:00
|
|
|
|
2022-07-08 21:26:50 -05:00
|
|
|
async checkDuplicatedAsset(authUser: AuthUserDto, checkDuplicateAssetDto: CheckDuplicateAssetDto): Promise<boolean> {
|
2022-06-19 08:16:35 -05:00
|
|
|
const res = await this.assetRepository.findOne({
|
|
|
|
|
where: {
|
2022-07-06 16:12:55 -05:00
|
|
|
deviceAssetId: checkDuplicateAssetDto.deviceAssetId,
|
|
|
|
|
deviceId: checkDuplicateAssetDto.deviceId,
|
2022-06-19 08:16:35 -05:00
|
|
|
userId: authUser.id,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return res ? true : false;
|
|
|
|
|
}
|
2022-02-03 10:06:44 -06:00
|
|
|
}
|