2023-02-25 09:12:03 -05:00
|
|
|
import { Injectable } from '@nestjs/common';
|
2023-08-15 21:34:57 -04:00
|
|
|
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
2024-01-06 20:36:12 -05:00
|
|
|
import _ from 'lodash';
|
2024-03-20 15:04:03 -05:00
|
|
|
import { Chunked, ChunkedArray, DATABASE_PARAMETER_CHUNK_SIZE, DummyValue, GenerateSql } from 'src/decorators';
|
2024-03-20 16:02:51 -05:00
|
|
|
import { AlbumEntity } from 'src/entities/album.entity';
|
|
|
|
|
import { AssetEntity } from 'src/entities/asset.entity';
|
2024-03-29 12:56:16 +01:00
|
|
|
import { AlbumAsset, AlbumAssetCount, AlbumInfoOptions, IAlbumRepository } from 'src/interfaces/album.interface';
|
2024-03-20 22:15:09 -05:00
|
|
|
import { Instrumentation } from 'src/utils/instrumentation';
|
|
|
|
|
import { setUnion } from 'src/utils/set';
|
2023-08-15 21:34:57 -04:00
|
|
|
import { DataSource, FindOptionsOrder, FindOptionsRelations, In, IsNull, Not, Repository } from 'typeorm';
|
2023-02-25 09:12:03 -05:00
|
|
|
|
2024-04-25 06:19:49 +02:00
|
|
|
const withoutDeletedUsers = <T extends AlbumEntity | null>(album: T) => {
|
|
|
|
|
if (album) {
|
|
|
|
|
album.albumUsers = album.albumUsers.filter((albumUser) => albumUser.user && !albumUser.user.deletedAt);
|
|
|
|
|
}
|
|
|
|
|
return album;
|
|
|
|
|
};
|
|
|
|
|
|
2024-03-12 01:19:12 -04:00
|
|
|
@Instrumentation()
|
2023-02-25 09:12:03 -05:00
|
|
|
@Injectable()
|
|
|
|
|
export class AlbumRepository implements IAlbumRepository {
|
2023-08-01 21:29:14 -04:00
|
|
|
constructor(
|
|
|
|
|
@InjectRepository(AssetEntity) private assetRepository: Repository<AssetEntity>,
|
|
|
|
|
@InjectRepository(AlbumEntity) private repository: Repository<AlbumEntity>,
|
2023-08-15 21:34:57 -04:00
|
|
|
@InjectDataSource() private dataSource: DataSource,
|
2023-08-01 21:29:14 -04:00
|
|
|
) {}
|
|
|
|
|
|
2023-11-30 10:10:30 -05:00
|
|
|
@GenerateSql({ params: [DummyValue.UUID, {}] })
|
2024-04-25 06:19:49 +02:00
|
|
|
async getById(id: string, options: AlbumInfoOptions): Promise<AlbumEntity | null> {
|
2023-08-15 14:34:02 -04:00
|
|
|
const relations: FindOptionsRelations<AlbumEntity> = {
|
|
|
|
|
owner: true,
|
2024-04-25 06:19:49 +02:00
|
|
|
albumUsers: { user: true },
|
2023-08-15 14:34:02 -04:00
|
|
|
assets: false,
|
|
|
|
|
sharedLinks: true,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const order: FindOptionsOrder<AlbumEntity> = {};
|
|
|
|
|
|
|
|
|
|
if (options.withAssets) {
|
|
|
|
|
relations.assets = {
|
|
|
|
|
exifInfo: true,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
order.assets = {
|
|
|
|
|
fileCreatedAt: 'DESC',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-25 06:19:49 +02:00
|
|
|
const album = await this.repository.findOne({ where: { id }, relations, order });
|
|
|
|
|
return withoutDeletedUsers(album);
|
2023-08-01 21:29:14 -04:00
|
|
|
}
|
2023-02-25 09:12:03 -05:00
|
|
|
|
2023-11-30 10:10:30 -05:00
|
|
|
@GenerateSql({ params: [[DummyValue.UUID]] })
|
2024-01-06 20:36:12 -05:00
|
|
|
@ChunkedArray()
|
2024-04-25 06:19:49 +02:00
|
|
|
async getByIds(ids: string[]): Promise<AlbumEntity[]> {
|
|
|
|
|
const albums = await this.repository.find({
|
2023-03-18 08:44:42 -05:00
|
|
|
where: {
|
|
|
|
|
id: In(ids),
|
|
|
|
|
},
|
|
|
|
|
relations: {
|
|
|
|
|
owner: true,
|
2024-04-25 06:19:49 +02:00
|
|
|
albumUsers: { user: true },
|
2023-03-18 08:44:42 -05:00
|
|
|
},
|
|
|
|
|
});
|
2024-04-25 06:19:49 +02:00
|
|
|
|
|
|
|
|
return albums.map((album) => withoutDeletedUsers(album));
|
2023-03-18 08:44:42 -05:00
|
|
|
}
|
|
|
|
|
|
2023-11-30 10:10:30 -05:00
|
|
|
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
|
2024-04-25 06:19:49 +02:00
|
|
|
async getByAssetId(ownerId: string, assetId: string): Promise<AlbumEntity[]> {
|
|
|
|
|
const albums = await this.repository.find({
|
2023-09-05 02:49:32 +02:00
|
|
|
where: [
|
|
|
|
|
{ ownerId, assets: { id: assetId } },
|
2024-04-25 06:19:49 +02:00
|
|
|
{ albumUsers: { userId: ownerId }, assets: { id: assetId } },
|
2023-09-05 02:49:32 +02:00
|
|
|
],
|
2024-04-25 06:19:49 +02:00
|
|
|
relations: { owner: true, albumUsers: { user: true } },
|
2023-03-26 04:46:48 +02:00
|
|
|
order: { createdAt: 'DESC' },
|
|
|
|
|
});
|
2024-04-25 06:19:49 +02:00
|
|
|
|
|
|
|
|
return albums.map((album) => withoutDeletedUsers(album));
|
2023-03-26 04:46:48 +02:00
|
|
|
}
|
|
|
|
|
|
2023-11-30 10:10:30 -05:00
|
|
|
@GenerateSql({ params: [[DummyValue.UUID]] })
|
2024-01-06 20:36:12 -05:00
|
|
|
@ChunkedArray()
|
2023-11-26 16:23:43 +01:00
|
|
|
async getMetadataForIds(ids: string[]): Promise<AlbumAssetCount[]> {
|
2023-11-21 10:07:49 -06:00
|
|
|
// Guard against running invalid query when ids list is empty.
|
2024-02-02 04:18:00 +01:00
|
|
|
if (ids.length === 0) {
|
2023-11-21 10:07:49 -06:00
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Only possible with query builder because of GROUP BY.
|
2023-11-26 16:23:43 +01:00
|
|
|
const albumMetadatas = await this.repository
|
2023-11-21 10:07:49 -06:00
|
|
|
.createQueryBuilder('album')
|
|
|
|
|
.select('album.id')
|
2023-11-26 16:23:43 +01:00
|
|
|
.addSelect('MIN(assets.fileCreatedAt)', 'start_date')
|
|
|
|
|
.addSelect('MAX(assets.fileCreatedAt)', 'end_date')
|
2023-12-05 01:22:00 +09:00
|
|
|
.addSelect('COUNT(assets.id)', 'asset_count')
|
2023-11-26 16:23:43 +01:00
|
|
|
.leftJoin('albums_assets_assets', 'album_assets', 'album_assets.albumsId = album.id')
|
|
|
|
|
.leftJoin('assets', 'assets', 'assets.id = album_assets.assetsId')
|
2023-11-21 10:07:49 -06:00
|
|
|
.where('album.id IN (:...ids)', { ids })
|
|
|
|
|
.groupBy('album.id')
|
|
|
|
|
.getRawMany();
|
|
|
|
|
|
2023-11-26 16:23:43 +01:00
|
|
|
return albumMetadatas.map<AlbumAssetCount>((metadatas) => ({
|
|
|
|
|
albumId: metadatas['album_id'],
|
|
|
|
|
assetCount: Number(metadatas['asset_count']),
|
|
|
|
|
startDate: metadatas['end_date'] ? new Date(metadatas['start_date']) : undefined,
|
|
|
|
|
endDate: metadatas['end_date'] ? new Date(metadatas['end_date']) : undefined,
|
2023-11-21 10:07:49 -06:00
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-26 04:46:48 +02:00
|
|
|
/**
|
|
|
|
|
* Returns the album IDs that have an invalid thumbnail, when:
|
|
|
|
|
* - Thumbnail references an asset outside the album
|
|
|
|
|
* - Empty album still has a thumbnail set
|
|
|
|
|
*/
|
2023-11-30 10:10:30 -05:00
|
|
|
@GenerateSql()
|
2023-03-26 04:46:48 +02:00
|
|
|
async getInvalidThumbnail(): Promise<string[]> {
|
|
|
|
|
// Using dataSource, because there is no direct access to albums_assets_assets.
|
2023-08-15 21:34:57 -04:00
|
|
|
const albumHasAssets = this.dataSource
|
2023-03-26 04:46:48 +02:00
|
|
|
.createQueryBuilder()
|
|
|
|
|
.select('1')
|
|
|
|
|
.from('albums_assets_assets', 'albums_assets')
|
|
|
|
|
.where('"albums"."id" = "albums_assets"."albumsId"');
|
|
|
|
|
|
|
|
|
|
const albumContainsThumbnail = albumHasAssets
|
|
|
|
|
.clone()
|
|
|
|
|
.andWhere('"albums"."albumThumbnailAssetId" = "albums_assets"."assetsId"');
|
|
|
|
|
|
|
|
|
|
const albums = await this.repository
|
|
|
|
|
.createQueryBuilder('albums')
|
|
|
|
|
.select('albums.id')
|
|
|
|
|
.where(`"albums"."albumThumbnailAssetId" IS NULL AND EXISTS (${albumHasAssets.getQuery()})`)
|
|
|
|
|
.orWhere(`"albums"."albumThumbnailAssetId" IS NOT NULL AND NOT EXISTS (${albumContainsThumbnail.getQuery()})`)
|
|
|
|
|
.getMany();
|
|
|
|
|
|
|
|
|
|
return albums.map((album) => album.id);
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-30 10:10:30 -05:00
|
|
|
@GenerateSql({ params: [DummyValue.UUID] })
|
2024-04-25 06:19:49 +02:00
|
|
|
async getOwned(ownerId: string): Promise<AlbumEntity[]> {
|
|
|
|
|
const albums = await this.repository.find({
|
|
|
|
|
relations: { albumUsers: { user: true }, sharedLinks: true, owner: true },
|
2023-03-26 04:46:48 +02:00
|
|
|
where: { ownerId },
|
|
|
|
|
order: { createdAt: 'DESC' },
|
|
|
|
|
});
|
2024-04-25 06:19:49 +02:00
|
|
|
|
|
|
|
|
return albums.map((album) => withoutDeletedUsers(album));
|
2023-03-26 04:46:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get albums shared with and shared by owner.
|
|
|
|
|
*/
|
2023-11-30 10:10:30 -05:00
|
|
|
@GenerateSql({ params: [DummyValue.UUID] })
|
2024-04-25 06:19:49 +02:00
|
|
|
async getShared(ownerId: string): Promise<AlbumEntity[]> {
|
|
|
|
|
const albums = await this.repository.find({
|
|
|
|
|
relations: { albumUsers: { user: true }, sharedLinks: true, owner: true },
|
2023-03-26 04:46:48 +02:00
|
|
|
where: [
|
2024-04-25 06:19:49 +02:00
|
|
|
{ albumUsers: { userId: ownerId } },
|
2023-03-26 04:46:48 +02:00
|
|
|
{ sharedLinks: { userId: ownerId } },
|
2024-04-25 06:19:49 +02:00
|
|
|
{ ownerId, albumUsers: { user: Not(IsNull()) } },
|
2023-03-26 04:46:48 +02:00
|
|
|
],
|
|
|
|
|
order: { createdAt: 'DESC' },
|
|
|
|
|
});
|
2024-04-25 06:19:49 +02:00
|
|
|
|
|
|
|
|
return albums.map((album) => withoutDeletedUsers(album));
|
2023-03-26 04:46:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get albums of owner that are _not_ shared
|
|
|
|
|
*/
|
2023-11-30 10:10:30 -05:00
|
|
|
@GenerateSql({ params: [DummyValue.UUID] })
|
2024-04-25 06:19:49 +02:00
|
|
|
async getNotShared(ownerId: string): Promise<AlbumEntity[]> {
|
|
|
|
|
const albums = await this.repository.find({
|
|
|
|
|
relations: { albumUsers: true, sharedLinks: true, owner: true },
|
|
|
|
|
where: { ownerId, albumUsers: { user: IsNull() }, sharedLinks: { id: IsNull() } },
|
2023-03-26 04:46:48 +02:00
|
|
|
order: { createdAt: 'DESC' },
|
|
|
|
|
});
|
2024-04-25 06:19:49 +02:00
|
|
|
|
|
|
|
|
return albums.map((album) => withoutDeletedUsers(album));
|
2023-03-26 04:46:48 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-18 17:56:50 +02:00
|
|
|
async restoreAll(userId: string): Promise<void> {
|
|
|
|
|
await this.repository.restore({ ownerId: userId });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async softDeleteAll(userId: string): Promise<void> {
|
|
|
|
|
await this.repository.softDelete({ ownerId: userId });
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-25 09:12:03 -05:00
|
|
|
async deleteAll(userId: string): Promise<void> {
|
|
|
|
|
await this.repository.delete({ ownerId: userId });
|
|
|
|
|
}
|
2023-03-02 21:47:08 -05:00
|
|
|
|
2023-11-30 10:10:30 -05:00
|
|
|
@GenerateSql()
|
2023-03-02 21:47:08 -05:00
|
|
|
getAll(): Promise<AlbumEntity[]> {
|
2023-03-18 08:44:42 -05:00
|
|
|
return this.repository.find({
|
|
|
|
|
relations: {
|
|
|
|
|
owner: true,
|
|
|
|
|
},
|
|
|
|
|
});
|
2023-03-02 21:47:08 -05:00
|
|
|
}
|
|
|
|
|
|
2024-01-06 12:56:08 -05:00
|
|
|
@GenerateSql({ params: [DummyValue.UUID] })
|
2023-08-15 21:34:57 -04:00
|
|
|
async removeAsset(assetId: string): Promise<void> {
|
|
|
|
|
// Using dataSource, because there is no direct access to albums_assets_assets.
|
|
|
|
|
await this.dataSource
|
|
|
|
|
.createQueryBuilder()
|
|
|
|
|
.delete()
|
|
|
|
|
.from('albums_assets_assets')
|
2024-01-06 12:56:08 -05:00
|
|
|
.where('"albums_assets_assets"."assetsId" = :assetId', { assetId })
|
|
|
|
|
.execute();
|
2023-10-18 11:56:00 -04:00
|
|
|
}
|
|
|
|
|
|
2024-01-06 20:36:12 -05:00
|
|
|
@GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] })
|
|
|
|
|
@Chunked({ paramIndex: 1 })
|
2024-03-29 12:56:16 +01:00
|
|
|
async removeAssetIds(albumId: string, assetIds: string[]): Promise<void> {
|
2023-10-18 11:56:00 -04:00
|
|
|
await this.dataSource
|
|
|
|
|
.createQueryBuilder()
|
|
|
|
|
.delete()
|
|
|
|
|
.from('albums_assets_assets')
|
|
|
|
|
.where({
|
2024-01-06 20:36:12 -05:00
|
|
|
albumsId: albumId,
|
|
|
|
|
assetsId: In(assetIds),
|
2023-10-18 11:56:00 -04:00
|
|
|
})
|
2023-08-15 21:34:57 -04:00
|
|
|
.execute();
|
|
|
|
|
}
|
|
|
|
|
|
fix(server): Check album asset membership in bulk (#4603)
Add `AlbumRepository` method to retrieve an album's asset ids, with an
optional parameter to only filter by the provided asset ids. With this,
we can now check asset membership using a single query.
When adding or removing assets to an album, checking whether each asset
is already present in the album now requires a single query, instead of
one query per asset.
Related to #4539 performance improvements.
Before:
```
// Asset membership and permissions check (2 queries per asset)
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "albums" "AlbumEntity" LEFT JOIN "albums_assets_assets" "AlbumEntity_AlbumEntity__AlbumEntity_assets" ON "AlbumEntity_AlbumEntity__AlbumEntity_assets"."albumsId"="AlbumEntity"."id" LEFT JOIN "assets" "AlbumEntity__AlbumEntity_assets" ON "AlbumEntity__AlbumEntity_assets"."id"="AlbumEntity_AlbumEntity__AlbumEntity_assets"."assetsId" AND ("AlbumEntity__AlbumEntity_assets"."deletedAt" IS NULL) WHERE ( ("AlbumEntity"."id" = $1 AND "AlbumEntity__AlbumEntity_assets"."id" = $2) ) AND ( "AlbumEntity"."deletedAt" IS NULL )) LIMIT 1 -- PARAMETERS: ["3fdf0e58-a1c7-4efe-8288-06e4c3f38df9","b666ae6c-afa8-4d6f-a1ad-7091a0659320"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["b666ae6c-afa8-4d6f-a1ad-7091a0659320","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "albums" "AlbumEntity" LEFT JOIN "albums_assets_assets" "AlbumEntity_AlbumEntity__AlbumEntity_assets" ON "AlbumEntity_AlbumEntity__AlbumEntity_assets"."albumsId"="AlbumEntity"."id" LEFT JOIN "assets" "AlbumEntity__AlbumEntity_assets" ON "AlbumEntity__AlbumEntity_assets"."id"="AlbumEntity_AlbumEntity__AlbumEntity_assets"."assetsId" AND ("AlbumEntity__AlbumEntity_assets"."deletedAt" IS NULL) WHERE ( ("AlbumEntity"."id" = $1 AND "AlbumEntity__AlbumEntity_assets"."id" = $2) ) AND ( "AlbumEntity"."deletedAt" IS NULL )) LIMIT 1 -- PARAMETERS: ["3fdf0e58-a1c7-4efe-8288-06e4c3f38df9","c656ab1c-7775-4ff7-b56f-01308c072a76"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["c656ab1c-7775-4ff7-b56f-01308c072a76","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "albums" "AlbumEntity" LEFT JOIN "albums_assets_assets" "AlbumEntity_AlbumEntity__AlbumEntity_assets" ON "AlbumEntity_AlbumEntity__AlbumEntity_assets"."albumsId"="AlbumEntity"."id" LEFT JOIN "assets" "AlbumEntity__AlbumEntity_assets" ON "AlbumEntity__AlbumEntity_assets"."id"="AlbumEntity_AlbumEntity__AlbumEntity_assets"."assetsId" AND ("AlbumEntity__AlbumEntity_assets"."deletedAt" IS NULL) WHERE ( ("AlbumEntity"."id" = $1 AND "AlbumEntity__AlbumEntity_assets"."id" = $2) ) AND ( "AlbumEntity"."deletedAt" IS NULL )) LIMIT 1 -- PARAMETERS: ["3fdf0e58-a1c7-4efe-8288-06e4c3f38df9","cf82adb2-1fcc-4f9e-9013-8fc03cc8d3a9"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["cf82adb2-1fcc-4f9e-9013-8fc03cc8d3a9","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
```
After:
```
// Asset membership check (1 query for all assets)
immich_server | query: SELECT "albums_assets"."assetsId" AS "assetId" FROM "albums_assets_assets" "albums_assets" WHERE "albums_assets"."albumsId" = $1 AND "albums_assets"."assetsId" IN ($2, $3, $4) -- PARAMETERS: ["ca870d76-6311-4e89-bf9a-f5b51ea2452c","b666ae6c-afa8-4d6f-a1ad-7091a0659320","c656ab1c-7775-4ff7-b56f-01308c072a76","cf82adb2-1fcc-4f9e-9013-8fc03cc8d3a9"]
// Permissions check (1 query per asset)
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["b666ae6c-afa8-4d6f-a1ad-7091a0659320","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["c656ab1c-7775-4ff7-b56f-01308c072a76","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["cf82adb2-1fcc-4f9e-9013-8fc03cc8d3a9","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
```
2023-10-23 09:02:27 -04:00
|
|
|
/**
|
|
|
|
|
* Get asset IDs for the given album ID.
|
|
|
|
|
*
|
|
|
|
|
* @param albumId Album ID to get asset IDs for.
|
|
|
|
|
* @param assetIds Optional list of asset IDs to filter on.
|
|
|
|
|
* @returns Set of Asset IDs for the given album ID.
|
|
|
|
|
*/
|
2023-11-30 10:10:30 -05:00
|
|
|
@GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }, { name: 'no assets', params: [DummyValue.UUID] })
|
fix(server): Check album asset membership in bulk (#4603)
Add `AlbumRepository` method to retrieve an album's asset ids, with an
optional parameter to only filter by the provided asset ids. With this,
we can now check asset membership using a single query.
When adding or removing assets to an album, checking whether each asset
is already present in the album now requires a single query, instead of
one query per asset.
Related to #4539 performance improvements.
Before:
```
// Asset membership and permissions check (2 queries per asset)
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "albums" "AlbumEntity" LEFT JOIN "albums_assets_assets" "AlbumEntity_AlbumEntity__AlbumEntity_assets" ON "AlbumEntity_AlbumEntity__AlbumEntity_assets"."albumsId"="AlbumEntity"."id" LEFT JOIN "assets" "AlbumEntity__AlbumEntity_assets" ON "AlbumEntity__AlbumEntity_assets"."id"="AlbumEntity_AlbumEntity__AlbumEntity_assets"."assetsId" AND ("AlbumEntity__AlbumEntity_assets"."deletedAt" IS NULL) WHERE ( ("AlbumEntity"."id" = $1 AND "AlbumEntity__AlbumEntity_assets"."id" = $2) ) AND ( "AlbumEntity"."deletedAt" IS NULL )) LIMIT 1 -- PARAMETERS: ["3fdf0e58-a1c7-4efe-8288-06e4c3f38df9","b666ae6c-afa8-4d6f-a1ad-7091a0659320"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["b666ae6c-afa8-4d6f-a1ad-7091a0659320","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "albums" "AlbumEntity" LEFT JOIN "albums_assets_assets" "AlbumEntity_AlbumEntity__AlbumEntity_assets" ON "AlbumEntity_AlbumEntity__AlbumEntity_assets"."albumsId"="AlbumEntity"."id" LEFT JOIN "assets" "AlbumEntity__AlbumEntity_assets" ON "AlbumEntity__AlbumEntity_assets"."id"="AlbumEntity_AlbumEntity__AlbumEntity_assets"."assetsId" AND ("AlbumEntity__AlbumEntity_assets"."deletedAt" IS NULL) WHERE ( ("AlbumEntity"."id" = $1 AND "AlbumEntity__AlbumEntity_assets"."id" = $2) ) AND ( "AlbumEntity"."deletedAt" IS NULL )) LIMIT 1 -- PARAMETERS: ["3fdf0e58-a1c7-4efe-8288-06e4c3f38df9","c656ab1c-7775-4ff7-b56f-01308c072a76"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["c656ab1c-7775-4ff7-b56f-01308c072a76","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "albums" "AlbumEntity" LEFT JOIN "albums_assets_assets" "AlbumEntity_AlbumEntity__AlbumEntity_assets" ON "AlbumEntity_AlbumEntity__AlbumEntity_assets"."albumsId"="AlbumEntity"."id" LEFT JOIN "assets" "AlbumEntity__AlbumEntity_assets" ON "AlbumEntity__AlbumEntity_assets"."id"="AlbumEntity_AlbumEntity__AlbumEntity_assets"."assetsId" AND ("AlbumEntity__AlbumEntity_assets"."deletedAt" IS NULL) WHERE ( ("AlbumEntity"."id" = $1 AND "AlbumEntity__AlbumEntity_assets"."id" = $2) ) AND ( "AlbumEntity"."deletedAt" IS NULL )) LIMIT 1 -- PARAMETERS: ["3fdf0e58-a1c7-4efe-8288-06e4c3f38df9","cf82adb2-1fcc-4f9e-9013-8fc03cc8d3a9"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["cf82adb2-1fcc-4f9e-9013-8fc03cc8d3a9","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
```
After:
```
// Asset membership check (1 query for all assets)
immich_server | query: SELECT "albums_assets"."assetsId" AS "assetId" FROM "albums_assets_assets" "albums_assets" WHERE "albums_assets"."albumsId" = $1 AND "albums_assets"."assetsId" IN ($2, $3, $4) -- PARAMETERS: ["ca870d76-6311-4e89-bf9a-f5b51ea2452c","b666ae6c-afa8-4d6f-a1ad-7091a0659320","c656ab1c-7775-4ff7-b56f-01308c072a76","cf82adb2-1fcc-4f9e-9013-8fc03cc8d3a9"]
// Permissions check (1 query per asset)
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["b666ae6c-afa8-4d6f-a1ad-7091a0659320","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["c656ab1c-7775-4ff7-b56f-01308c072a76","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["cf82adb2-1fcc-4f9e-9013-8fc03cc8d3a9","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
```
2023-10-23 09:02:27 -04:00
|
|
|
async getAssetIds(albumId: string, assetIds?: string[]): Promise<Set<string>> {
|
|
|
|
|
const query = this.dataSource
|
|
|
|
|
.createQueryBuilder()
|
|
|
|
|
.select('albums_assets.assetsId', 'assetId')
|
|
|
|
|
.from('albums_assets_assets', 'albums_assets')
|
|
|
|
|
.where('"albums_assets"."albumsId" = :albumId', { albumId });
|
|
|
|
|
|
2024-01-06 20:36:12 -05:00
|
|
|
if (!assetIds?.length) {
|
|
|
|
|
const result = await query.getRawMany();
|
|
|
|
|
return new Set(result.map((row) => row['assetId']));
|
fix(server): Check album asset membership in bulk (#4603)
Add `AlbumRepository` method to retrieve an album's asset ids, with an
optional parameter to only filter by the provided asset ids. With this,
we can now check asset membership using a single query.
When adding or removing assets to an album, checking whether each asset
is already present in the album now requires a single query, instead of
one query per asset.
Related to #4539 performance improvements.
Before:
```
// Asset membership and permissions check (2 queries per asset)
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "albums" "AlbumEntity" LEFT JOIN "albums_assets_assets" "AlbumEntity_AlbumEntity__AlbumEntity_assets" ON "AlbumEntity_AlbumEntity__AlbumEntity_assets"."albumsId"="AlbumEntity"."id" LEFT JOIN "assets" "AlbumEntity__AlbumEntity_assets" ON "AlbumEntity__AlbumEntity_assets"."id"="AlbumEntity_AlbumEntity__AlbumEntity_assets"."assetsId" AND ("AlbumEntity__AlbumEntity_assets"."deletedAt" IS NULL) WHERE ( ("AlbumEntity"."id" = $1 AND "AlbumEntity__AlbumEntity_assets"."id" = $2) ) AND ( "AlbumEntity"."deletedAt" IS NULL )) LIMIT 1 -- PARAMETERS: ["3fdf0e58-a1c7-4efe-8288-06e4c3f38df9","b666ae6c-afa8-4d6f-a1ad-7091a0659320"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["b666ae6c-afa8-4d6f-a1ad-7091a0659320","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "albums" "AlbumEntity" LEFT JOIN "albums_assets_assets" "AlbumEntity_AlbumEntity__AlbumEntity_assets" ON "AlbumEntity_AlbumEntity__AlbumEntity_assets"."albumsId"="AlbumEntity"."id" LEFT JOIN "assets" "AlbumEntity__AlbumEntity_assets" ON "AlbumEntity__AlbumEntity_assets"."id"="AlbumEntity_AlbumEntity__AlbumEntity_assets"."assetsId" AND ("AlbumEntity__AlbumEntity_assets"."deletedAt" IS NULL) WHERE ( ("AlbumEntity"."id" = $1 AND "AlbumEntity__AlbumEntity_assets"."id" = $2) ) AND ( "AlbumEntity"."deletedAt" IS NULL )) LIMIT 1 -- PARAMETERS: ["3fdf0e58-a1c7-4efe-8288-06e4c3f38df9","c656ab1c-7775-4ff7-b56f-01308c072a76"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["c656ab1c-7775-4ff7-b56f-01308c072a76","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "albums" "AlbumEntity" LEFT JOIN "albums_assets_assets" "AlbumEntity_AlbumEntity__AlbumEntity_assets" ON "AlbumEntity_AlbumEntity__AlbumEntity_assets"."albumsId"="AlbumEntity"."id" LEFT JOIN "assets" "AlbumEntity__AlbumEntity_assets" ON "AlbumEntity__AlbumEntity_assets"."id"="AlbumEntity_AlbumEntity__AlbumEntity_assets"."assetsId" AND ("AlbumEntity__AlbumEntity_assets"."deletedAt" IS NULL) WHERE ( ("AlbumEntity"."id" = $1 AND "AlbumEntity__AlbumEntity_assets"."id" = $2) ) AND ( "AlbumEntity"."deletedAt" IS NULL )) LIMIT 1 -- PARAMETERS: ["3fdf0e58-a1c7-4efe-8288-06e4c3f38df9","cf82adb2-1fcc-4f9e-9013-8fc03cc8d3a9"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["cf82adb2-1fcc-4f9e-9013-8fc03cc8d3a9","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
```
After:
```
// Asset membership check (1 query for all assets)
immich_server | query: SELECT "albums_assets"."assetsId" AS "assetId" FROM "albums_assets_assets" "albums_assets" WHERE "albums_assets"."albumsId" = $1 AND "albums_assets"."assetsId" IN ($2, $3, $4) -- PARAMETERS: ["ca870d76-6311-4e89-bf9a-f5b51ea2452c","b666ae6c-afa8-4d6f-a1ad-7091a0659320","c656ab1c-7775-4ff7-b56f-01308c072a76","cf82adb2-1fcc-4f9e-9013-8fc03cc8d3a9"]
// Permissions check (1 query per asset)
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["b666ae6c-afa8-4d6f-a1ad-7091a0659320","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["c656ab1c-7775-4ff7-b56f-01308c072a76","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["cf82adb2-1fcc-4f9e-9013-8fc03cc8d3a9","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
```
2023-10-23 09:02:27 -04:00
|
|
|
}
|
|
|
|
|
|
2024-01-06 20:36:12 -05:00
|
|
|
return Promise.all(
|
|
|
|
|
_.chunk(assetIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
|
|
|
|
query
|
|
|
|
|
.andWhere('"albums_assets"."assetsId" IN (:...assetIds)', { assetIds: idChunk })
|
|
|
|
|
.getRawMany()
|
|
|
|
|
.then((result) => new Set(result.map((row) => row['assetId']))),
|
|
|
|
|
),
|
|
|
|
|
).then((results) => setUnion(...results));
|
fix(server): Check album asset membership in bulk (#4603)
Add `AlbumRepository` method to retrieve an album's asset ids, with an
optional parameter to only filter by the provided asset ids. With this,
we can now check asset membership using a single query.
When adding or removing assets to an album, checking whether each asset
is already present in the album now requires a single query, instead of
one query per asset.
Related to #4539 performance improvements.
Before:
```
// Asset membership and permissions check (2 queries per asset)
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "albums" "AlbumEntity" LEFT JOIN "albums_assets_assets" "AlbumEntity_AlbumEntity__AlbumEntity_assets" ON "AlbumEntity_AlbumEntity__AlbumEntity_assets"."albumsId"="AlbumEntity"."id" LEFT JOIN "assets" "AlbumEntity__AlbumEntity_assets" ON "AlbumEntity__AlbumEntity_assets"."id"="AlbumEntity_AlbumEntity__AlbumEntity_assets"."assetsId" AND ("AlbumEntity__AlbumEntity_assets"."deletedAt" IS NULL) WHERE ( ("AlbumEntity"."id" = $1 AND "AlbumEntity__AlbumEntity_assets"."id" = $2) ) AND ( "AlbumEntity"."deletedAt" IS NULL )) LIMIT 1 -- PARAMETERS: ["3fdf0e58-a1c7-4efe-8288-06e4c3f38df9","b666ae6c-afa8-4d6f-a1ad-7091a0659320"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["b666ae6c-afa8-4d6f-a1ad-7091a0659320","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "albums" "AlbumEntity" LEFT JOIN "albums_assets_assets" "AlbumEntity_AlbumEntity__AlbumEntity_assets" ON "AlbumEntity_AlbumEntity__AlbumEntity_assets"."albumsId"="AlbumEntity"."id" LEFT JOIN "assets" "AlbumEntity__AlbumEntity_assets" ON "AlbumEntity__AlbumEntity_assets"."id"="AlbumEntity_AlbumEntity__AlbumEntity_assets"."assetsId" AND ("AlbumEntity__AlbumEntity_assets"."deletedAt" IS NULL) WHERE ( ("AlbumEntity"."id" = $1 AND "AlbumEntity__AlbumEntity_assets"."id" = $2) ) AND ( "AlbumEntity"."deletedAt" IS NULL )) LIMIT 1 -- PARAMETERS: ["3fdf0e58-a1c7-4efe-8288-06e4c3f38df9","c656ab1c-7775-4ff7-b56f-01308c072a76"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["c656ab1c-7775-4ff7-b56f-01308c072a76","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "albums" "AlbumEntity" LEFT JOIN "albums_assets_assets" "AlbumEntity_AlbumEntity__AlbumEntity_assets" ON "AlbumEntity_AlbumEntity__AlbumEntity_assets"."albumsId"="AlbumEntity"."id" LEFT JOIN "assets" "AlbumEntity__AlbumEntity_assets" ON "AlbumEntity__AlbumEntity_assets"."id"="AlbumEntity_AlbumEntity__AlbumEntity_assets"."assetsId" AND ("AlbumEntity__AlbumEntity_assets"."deletedAt" IS NULL) WHERE ( ("AlbumEntity"."id" = $1 AND "AlbumEntity__AlbumEntity_assets"."id" = $2) ) AND ( "AlbumEntity"."deletedAt" IS NULL )) LIMIT 1 -- PARAMETERS: ["3fdf0e58-a1c7-4efe-8288-06e4c3f38df9","cf82adb2-1fcc-4f9e-9013-8fc03cc8d3a9"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["cf82adb2-1fcc-4f9e-9013-8fc03cc8d3a9","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
```
After:
```
// Asset membership check (1 query for all assets)
immich_server | query: SELECT "albums_assets"."assetsId" AS "assetId" FROM "albums_assets_assets" "albums_assets" WHERE "albums_assets"."albumsId" = $1 AND "albums_assets"."assetsId" IN ($2, $3, $4) -- PARAMETERS: ["ca870d76-6311-4e89-bf9a-f5b51ea2452c","b666ae6c-afa8-4d6f-a1ad-7091a0659320","c656ab1c-7775-4ff7-b56f-01308c072a76","cf82adb2-1fcc-4f9e-9013-8fc03cc8d3a9"]
// Permissions check (1 query per asset)
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["b666ae6c-afa8-4d6f-a1ad-7091a0659320","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["c656ab1c-7775-4ff7-b56f-01308c072a76","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
immich_server | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["cf82adb2-1fcc-4f9e-9013-8fc03cc8d3a9","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
```
2023-10-23 09:02:27 -04:00
|
|
|
}
|
|
|
|
|
|
2023-11-30 10:10:30 -05:00
|
|
|
@GenerateSql({ params: [{ albumId: DummyValue.UUID, assetId: DummyValue.UUID }] })
|
2023-10-18 11:56:00 -04:00
|
|
|
hasAsset(asset: AlbumAsset): Promise<boolean> {
|
2023-06-16 15:01:34 -04:00
|
|
|
return this.repository.exist({
|
2023-05-25 15:37:19 -04:00
|
|
|
where: {
|
2023-10-18 11:56:00 -04:00
|
|
|
id: asset.albumId,
|
2023-05-25 15:37:19 -04:00
|
|
|
assets: {
|
2023-10-18 11:56:00 -04:00
|
|
|
id: asset.assetId,
|
2023-05-25 15:37:19 -04:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
relations: {
|
|
|
|
|
assets: true,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-29 12:56:16 +01:00
|
|
|
@GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] })
|
|
|
|
|
async addAssetIds(albumId: string, assetIds: string[]): Promise<void> {
|
2023-10-18 11:56:00 -04:00
|
|
|
await this.dataSource
|
|
|
|
|
.createQueryBuilder()
|
|
|
|
|
.insert()
|
|
|
|
|
.into('albums_assets_assets', ['albumsId', 'assetsId'])
|
|
|
|
|
.values(assetIds.map((assetId) => ({ albumsId: albumId, assetsId: assetId })))
|
|
|
|
|
.execute();
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-25 15:37:19 -04:00
|
|
|
async create(album: Partial<AlbumEntity>): Promise<AlbumEntity> {
|
|
|
|
|
return this.save(album);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-26 09:04:09 -04:00
|
|
|
async update(album: Partial<AlbumEntity>): Promise<AlbumEntity> {
|
2023-05-24 22:10:45 -04:00
|
|
|
return this.save(album);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-26 09:04:09 -04:00
|
|
|
async delete(album: AlbumEntity): Promise<void> {
|
|
|
|
|
await this.repository.remove(album);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-25 15:37:19 -04:00
|
|
|
private async save(album: Partial<AlbumEntity>) {
|
2023-03-02 21:47:08 -05:00
|
|
|
const { id } = await this.repository.save(album);
|
2023-06-07 10:37:25 -04:00
|
|
|
return this.repository.findOneOrFail({
|
|
|
|
|
where: { id },
|
|
|
|
|
relations: {
|
|
|
|
|
owner: true,
|
2024-04-25 06:19:49 +02:00
|
|
|
albumUsers: { user: true },
|
2023-08-11 12:00:51 -04:00
|
|
|
sharedLinks: true,
|
2023-06-16 11:39:40 -05:00
|
|
|
assets: true,
|
2023-06-07 10:37:25 -04:00
|
|
|
},
|
|
|
|
|
});
|
2023-03-02 21:47:08 -05:00
|
|
|
}
|
2023-08-01 21:29:14 -04:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Makes sure all thumbnails for albums are updated by:
|
|
|
|
|
* - Removing thumbnails from albums without assets
|
|
|
|
|
* - Removing references of thumbnails to assets outside the album
|
|
|
|
|
* - Setting a thumbnail when none is set and the album contains assets
|
|
|
|
|
*
|
|
|
|
|
* @returns Amount of updated album thumbnails or undefined when unknown
|
|
|
|
|
*/
|
2023-11-30 10:10:30 -05:00
|
|
|
@GenerateSql()
|
2023-08-01 21:29:14 -04:00
|
|
|
async updateThumbnails(): Promise<number | undefined> {
|
|
|
|
|
// Subquery for getting a new thumbnail.
|
|
|
|
|
const newThumbnail = this.assetRepository
|
|
|
|
|
.createQueryBuilder('assets')
|
|
|
|
|
.select('albums_assets2.assetsId')
|
|
|
|
|
.addFrom('albums_assets_assets', 'albums_assets2')
|
|
|
|
|
.where('albums_assets2.assetsId = assets.id')
|
|
|
|
|
.andWhere('albums_assets2.albumsId = "albums"."id"') // Reference to albums.id outside this query
|
|
|
|
|
.orderBy('assets.fileCreatedAt', 'DESC')
|
|
|
|
|
.limit(1);
|
|
|
|
|
|
|
|
|
|
// Using dataSource, because there is no direct access to albums_assets_assets.
|
2024-04-27 13:43:45 -04:00
|
|
|
const albumHasAssets = this.dataSource
|
2023-08-01 21:29:14 -04:00
|
|
|
.createQueryBuilder()
|
|
|
|
|
.select('1')
|
|
|
|
|
.from('albums_assets_assets', 'albums_assets')
|
|
|
|
|
.where('"albums"."id" = "albums_assets"."albumsId"');
|
|
|
|
|
|
|
|
|
|
const albumContainsThumbnail = albumHasAssets
|
|
|
|
|
.clone()
|
|
|
|
|
.andWhere('"albums"."albumThumbnailAssetId" = "albums_assets"."assetsId"');
|
|
|
|
|
|
|
|
|
|
const updateAlbums = this.repository
|
|
|
|
|
.createQueryBuilder('albums')
|
|
|
|
|
.update(AlbumEntity)
|
|
|
|
|
.set({ albumThumbnailAssetId: () => `(${newThumbnail.getQuery()})` })
|
|
|
|
|
.where(`"albums"."albumThumbnailAssetId" IS NULL AND EXISTS (${albumHasAssets.getQuery()})`)
|
|
|
|
|
.orWhere(`"albums"."albumThumbnailAssetId" IS NOT NULL AND NOT EXISTS (${albumContainsThumbnail.getQuery()})`);
|
|
|
|
|
|
|
|
|
|
const result = await updateAlbums.execute();
|
|
|
|
|
|
|
|
|
|
return result.affected;
|
|
|
|
|
}
|
2023-02-25 09:12:03 -05:00
|
|
|
}
|