mirror of
https://github.com/immich-app/immich.git
synced 2026-03-04 09:57:33 +03:00
refactor: migrate stack repo to kysely (#15440)
* wip * wip: add tags * wip * sql * pr feedback * pr feedback * ergonomic * pr feedback * pr feedback
This commit is contained in:
@@ -36,10 +36,7 @@ class ActivityAccess implements IActivityAccess {
|
||||
.where('activity.id', 'in', [...activityIds])
|
||||
.where('activity.userId', '=', userId)
|
||||
.execute()
|
||||
.then((activities) => {
|
||||
console.log('activities', activities);
|
||||
return new Set(activities.map((activity) => activity.id));
|
||||
});
|
||||
.then((activities) => new Set(activities.map((activity) => activity.id)));
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
|
||||
@@ -1,84 +1,113 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
import { ExpressionBuilder, Kysely, Updateable } from 'kysely';
|
||||
import { jsonArrayFrom } from 'kysely/helpers/postgres';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { DB } from 'src/db';
|
||||
import { DummyValue, GenerateSql } from 'src/decorators';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { StackEntity } from 'src/entities/stack.entity';
|
||||
import { IStackRepository, StackSearch } from 'src/interfaces/stack.interface';
|
||||
import { DataSource, In, Repository } from 'typeorm';
|
||||
import { asUuid } from 'src/utils/database';
|
||||
|
||||
const withAssets = (eb: ExpressionBuilder<DB, 'asset_stack'>, withTags = false) => {
|
||||
return jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom('assets')
|
||||
.selectAll()
|
||||
.$if(withTags, (eb) =>
|
||||
eb.select((eb) =>
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom('tags')
|
||||
.selectAll('tags')
|
||||
.innerJoin('tag_asset', 'tags.id', 'tag_asset.tagsId')
|
||||
.whereRef('tag_asset.assetsId', '=', 'assets.id'),
|
||||
).as('tags'),
|
||||
),
|
||||
)
|
||||
.where('assets.deletedAt', 'is', null)
|
||||
.whereRef('assets.stackId', '=', 'asset_stack.id'),
|
||||
).as('assets');
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class StackRepository implements IStackRepository {
|
||||
constructor(
|
||||
@InjectDataSource() private dataSource: DataSource,
|
||||
@InjectRepository(StackEntity) private repository: Repository<StackEntity>,
|
||||
) {}
|
||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [{ ownerId: DummyValue.UUID }] })
|
||||
search(query: StackSearch): Promise<StackEntity[]> {
|
||||
return this.repository.find({
|
||||
where: {
|
||||
ownerId: query.ownerId,
|
||||
primaryAssetId: query.primaryAssetId,
|
||||
},
|
||||
relations: {
|
||||
assets: {
|
||||
exifInfo: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
return this.db
|
||||
.selectFrom('asset_stack')
|
||||
.selectAll('asset_stack')
|
||||
.select(withAssets)
|
||||
.where('asset_stack.ownerId', '=', query.ownerId)
|
||||
.$if(!!query.primaryAssetId, (eb) => eb.where('asset_stack.primaryAssetId', '=', query.primaryAssetId!))
|
||||
.execute() as unknown as Promise<StackEntity[]>;
|
||||
}
|
||||
|
||||
async create(entity: { ownerId: string; assetIds: string[] }): Promise<StackEntity> {
|
||||
return this.dataSource.manager.transaction(async (manager) => {
|
||||
const stackRepository = manager.getRepository(StackEntity);
|
||||
|
||||
const stacks = await stackRepository.find({
|
||||
where: {
|
||||
ownerId: entity.ownerId,
|
||||
primaryAssetId: In(entity.assetIds),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
assets: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
relations: {
|
||||
assets: {
|
||||
exifInfo: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
return this.db.transaction().execute(async (tx) => {
|
||||
const stacks = await tx
|
||||
.selectFrom('asset_stack')
|
||||
.where('asset_stack.ownerId', '=', entity.ownerId)
|
||||
.where('asset_stack.primaryAssetId', 'in', entity.assetIds)
|
||||
.select('asset_stack.id')
|
||||
.select((eb) =>
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom('assets')
|
||||
.select('assets.id')
|
||||
.whereRef('assets.stackId', '=', 'asset_stack.id')
|
||||
.where('assets.deletedAt', 'is', null),
|
||||
).as('assets'),
|
||||
)
|
||||
.execute();
|
||||
|
||||
const assetIds = new Set<string>(entity.assetIds);
|
||||
|
||||
// children
|
||||
for (const stack of stacks) {
|
||||
for (const asset of stack.assets) {
|
||||
assetIds.add(asset.id);
|
||||
if (stack.assets && stack.assets.length > 0) {
|
||||
for (const asset of stack.assets) {
|
||||
assetIds.add(asset.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stacks.length > 0) {
|
||||
await stackRepository.delete({ id: In(stacks.map((stack) => stack.id)) });
|
||||
await tx
|
||||
.deleteFrom('asset_stack')
|
||||
.where(
|
||||
'id',
|
||||
'in',
|
||||
stacks.map((stack) => stack.id),
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
|
||||
const { id } = await stackRepository.save({
|
||||
ownerId: entity.ownerId,
|
||||
primaryAssetId: entity.assetIds[0],
|
||||
assets: [...assetIds].map((id) => ({ id }) as AssetEntity),
|
||||
});
|
||||
const newRecord = await tx
|
||||
.insertInto('asset_stack')
|
||||
.values({
|
||||
ownerId: entity.ownerId,
|
||||
primaryAssetId: entity.assetIds[0],
|
||||
})
|
||||
.returning('id')
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
return stackRepository.findOneOrFail({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
relations: {
|
||||
assets: {
|
||||
exifInfo: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
await tx
|
||||
.updateTable('assets')
|
||||
.set({
|
||||
stackId: newRecord.id,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where('id', 'in', [...assetIds])
|
||||
.execute();
|
||||
|
||||
return tx
|
||||
.selectFrom('asset_stack')
|
||||
.selectAll('asset_stack')
|
||||
.select(withAssets)
|
||||
.where('id', '=', newRecord.id)
|
||||
.executeTakeFirst() as unknown as Promise<StackEntity>;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -91,12 +120,12 @@ export class StackRepository implements IStackRepository {
|
||||
|
||||
const assetIds = stack.assets.map(({ id }) => id);
|
||||
|
||||
await this.repository.delete(id);
|
||||
|
||||
// Update assets updatedAt
|
||||
await this.dataSource.manager.update(AssetEntity, assetIds, {
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
await this.db.deleteFrom('asset_stack').where('id', '=', asUuid(id)).execute();
|
||||
await this.db
|
||||
.updateTable('assets')
|
||||
.set({ stackId: null, updatedAt: new Date() })
|
||||
.where('id', 'in', assetIds)
|
||||
.execute();
|
||||
}
|
||||
|
||||
async deleteAll(ids: string[]): Promise<void> {
|
||||
@@ -110,54 +139,31 @@ export class StackRepository implements IStackRepository {
|
||||
assetIds.push(...stack.assets.map(({ id }) => id));
|
||||
}
|
||||
|
||||
await this.repository.delete(ids);
|
||||
|
||||
// Update assets updatedAt
|
||||
await this.dataSource.manager.update(AssetEntity, assetIds, {
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
await this.db
|
||||
.updateTable('assets')
|
||||
.set({ updatedAt: new Date(), stackId: null })
|
||||
.where('id', 'in', assetIds)
|
||||
.where('stackId', 'in', ids)
|
||||
.execute();
|
||||
}
|
||||
|
||||
update(entity: Partial<StackEntity>) {
|
||||
return this.save(entity);
|
||||
update(id: string, entity: Updateable<StackEntity>): Promise<StackEntity> {
|
||||
return this.db
|
||||
.updateTable('asset_stack')
|
||||
.set(entity)
|
||||
.where('id', '=', asUuid(id))
|
||||
.returningAll('asset_stack')
|
||||
.returning((eb) => withAssets(eb, true))
|
||||
.executeTakeFirstOrThrow() as unknown as Promise<StackEntity>;
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
async getById(id: string): Promise<StackEntity | null> {
|
||||
return this.repository.findOne({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
relations: {
|
||||
assets: {
|
||||
exifInfo: true,
|
||||
tags: true,
|
||||
},
|
||||
},
|
||||
order: {
|
||||
assets: {
|
||||
fileCreatedAt: 'ASC',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async save(entity: Partial<StackEntity>) {
|
||||
const { id } = await this.repository.save(entity);
|
||||
return this.repository.findOneOrFail({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
relations: {
|
||||
assets: {
|
||||
exifInfo: true,
|
||||
},
|
||||
},
|
||||
order: {
|
||||
assets: {
|
||||
fileCreatedAt: 'ASC',
|
||||
},
|
||||
},
|
||||
});
|
||||
getById(id: string): Promise<StackEntity | undefined> {
|
||||
return this.db
|
||||
.selectFrom('asset_stack')
|
||||
.selectAll()
|
||||
.select((eb) => withAssets(eb, true))
|
||||
.where('id', '=', asUuid(id))
|
||||
.executeTakeFirst() as Promise<StackEntity | undefined>;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user