mirror of
https://github.com/immich-app/immich.git
synced 2025-05-31 20:25:32 -04:00
refactor: migrate memory to kysely (#15314)
This commit is contained in:
parent
43b3181f45
commit
93e2545275
@ -1,4 +1,6 @@
|
|||||||
import { MemoryEntity } from 'src/entities/memory.entity';
|
import { Insertable, Updateable } from 'kysely';
|
||||||
|
import { Memories } from 'src/db';
|
||||||
|
import { MemoryEntity, OnThisDayData } from 'src/entities/memory.entity';
|
||||||
import { IBulkAsset } from 'src/utils/asset.util';
|
import { IBulkAsset } from 'src/utils/asset.util';
|
||||||
|
|
||||||
export const IMemoryRepository = 'IMemoryRepository';
|
export const IMemoryRepository = 'IMemoryRepository';
|
||||||
@ -6,7 +8,10 @@ export const IMemoryRepository = 'IMemoryRepository';
|
|||||||
export interface IMemoryRepository extends IBulkAsset {
|
export interface IMemoryRepository extends IBulkAsset {
|
||||||
search(ownerId: string): Promise<MemoryEntity[]>;
|
search(ownerId: string): Promise<MemoryEntity[]>;
|
||||||
get(id: string): Promise<MemoryEntity | null>;
|
get(id: string): Promise<MemoryEntity | null>;
|
||||||
create(memory: Partial<MemoryEntity>): Promise<MemoryEntity>;
|
create(
|
||||||
update(memory: Partial<MemoryEntity>): Promise<MemoryEntity>;
|
memory: Omit<Insertable<Memories>, 'data'> & { data: OnThisDayData },
|
||||||
|
assetIds: Set<string>,
|
||||||
|
): Promise<MemoryEntity>;
|
||||||
|
update(id: string, memory: Updateable<Memories>): Promise<MemoryEntity>;
|
||||||
delete(id: string): Promise<void>;
|
delete(id: string): Promise<void>;
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,79 @@
|
|||||||
-- NOTE: This file is auto generated by ./sql-generator
|
-- NOTE: This file is auto generated by ./sql-generator
|
||||||
|
|
||||||
|
-- MemoryRepository.search
|
||||||
|
select
|
||||||
|
*
|
||||||
|
from
|
||||||
|
"memories"
|
||||||
|
where
|
||||||
|
"ownerId" = $1
|
||||||
|
order by
|
||||||
|
"memoryAt" desc
|
||||||
|
|
||||||
|
-- MemoryRepository.get
|
||||||
|
select
|
||||||
|
"memories".*,
|
||||||
|
(
|
||||||
|
select
|
||||||
|
coalesce(json_agg(agg), '[]')
|
||||||
|
from
|
||||||
|
(
|
||||||
|
select
|
||||||
|
"assets".*
|
||||||
|
from
|
||||||
|
"assets"
|
||||||
|
inner join "memories_assets_assets" on "assets"."id" = "memories_assets_assets"."assetsId"
|
||||||
|
where
|
||||||
|
"memories_assets_assets"."memoriesId" = "memories"."id"
|
||||||
|
and "assets"."deletedAt" is null
|
||||||
|
) as agg
|
||||||
|
) as "assets"
|
||||||
|
from
|
||||||
|
"memories"
|
||||||
|
where
|
||||||
|
"id" = $1
|
||||||
|
and "deletedAt" is null
|
||||||
|
|
||||||
|
-- MemoryRepository.update
|
||||||
|
update "memories"
|
||||||
|
set
|
||||||
|
"ownerId" = $1,
|
||||||
|
"isSaved" = $2
|
||||||
|
where
|
||||||
|
"id" = $3
|
||||||
|
select
|
||||||
|
"memories".*,
|
||||||
|
(
|
||||||
|
select
|
||||||
|
coalesce(json_agg(agg), '[]')
|
||||||
|
from
|
||||||
|
(
|
||||||
|
select
|
||||||
|
"assets".*
|
||||||
|
from
|
||||||
|
"assets"
|
||||||
|
inner join "memories_assets_assets" on "assets"."id" = "memories_assets_assets"."assetsId"
|
||||||
|
where
|
||||||
|
"memories_assets_assets"."memoriesId" = "memories"."id"
|
||||||
|
and "assets"."deletedAt" is null
|
||||||
|
) as agg
|
||||||
|
) as "assets"
|
||||||
|
from
|
||||||
|
"memories"
|
||||||
|
where
|
||||||
|
"id" = $1
|
||||||
|
and "deletedAt" is null
|
||||||
|
|
||||||
|
-- MemoryRepository.delete
|
||||||
|
delete from "memories"
|
||||||
|
where
|
||||||
|
"id" = $1
|
||||||
|
|
||||||
-- MemoryRepository.getAssetIds
|
-- MemoryRepository.getAssetIds
|
||||||
SELECT
|
select
|
||||||
"memories_assets"."assetsId" AS "assetId"
|
"assetsId"
|
||||||
FROM
|
from
|
||||||
"memories_assets_assets" "memories_assets"
|
"memories_assets_assets"
|
||||||
WHERE
|
where
|
||||||
"memories_assets"."memoriesId" = $1
|
"memoriesId" = $1
|
||||||
AND "memories_assets"."assetsId" IN ($2)
|
and "assetsId" in ($2)
|
||||||
|
@ -1,49 +1,55 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
import { Insertable, Kysely, Updateable } from 'kysely';
|
||||||
|
import { jsonArrayFrom } from 'kysely/helpers/postgres';
|
||||||
|
import { InjectKysely } from 'nestjs-kysely';
|
||||||
|
import { DB, Memories } from 'src/db';
|
||||||
import { Chunked, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
|
import { Chunked, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
|
||||||
import { MemoryEntity } from 'src/entities/memory.entity';
|
import { MemoryEntity } from 'src/entities/memory.entity';
|
||||||
import { IMemoryRepository } from 'src/interfaces/memory.interface';
|
import { IMemoryRepository } from 'src/interfaces/memory.interface';
|
||||||
import { DataSource, In, Repository } from 'typeorm';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MemoryRepository implements IMemoryRepository {
|
export class MemoryRepository implements IMemoryRepository {
|
||||||
constructor(
|
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||||
@InjectRepository(MemoryEntity) private repository: Repository<MemoryEntity>,
|
|
||||||
@InjectDataSource() private dataSource: DataSource,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
search(ownerId: string): Promise<MemoryEntity[]> {
|
search(ownerId: string): Promise<MemoryEntity[]> {
|
||||||
return this.repository.find({
|
return this.db
|
||||||
where: {
|
.selectFrom('memories')
|
||||||
ownerId,
|
.selectAll()
|
||||||
},
|
.where('ownerId', '=', ownerId)
|
||||||
order: {
|
.orderBy('memoryAt', 'desc')
|
||||||
memoryAt: 'DESC',
|
.execute() as Promise<MemoryEntity[]>;
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
get(id: string): Promise<MemoryEntity | null> {
|
get(id: string): Promise<MemoryEntity | null> {
|
||||||
return this.repository.findOne({
|
return this.getByIdBuilder(id).executeTakeFirst() as unknown as Promise<MemoryEntity | null>;
|
||||||
where: {
|
}
|
||||||
id,
|
|
||||||
},
|
async create(memory: Insertable<Memories>, assetIds: Set<string>): Promise<MemoryEntity> {
|
||||||
relations: {
|
const id = await this.db.transaction().execute(async (tx) => {
|
||||||
assets: true,
|
const { id } = await tx.insertInto('memories').values(memory).returning('id').executeTakeFirstOrThrow();
|
||||||
},
|
|
||||||
|
if (assetIds.size > 0) {
|
||||||
|
const values = [...assetIds].map((assetId) => ({ memoriesId: id, assetsId: assetId }));
|
||||||
|
await tx.insertInto('memories_assets_assets').values(values).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return this.getByIdBuilder(id).executeTakeFirstOrThrow() as unknown as Promise<MemoryEntity>;
|
||||||
}
|
}
|
||||||
|
|
||||||
create(memory: Partial<MemoryEntity>): Promise<MemoryEntity> {
|
@GenerateSql({ params: [DummyValue.UUID, { ownerId: DummyValue.UUID, isSaved: true }] })
|
||||||
return this.save(memory);
|
async update(id: string, memory: Updateable<Memories>): Promise<MemoryEntity> {
|
||||||
}
|
await this.db.updateTable('memories').set(memory).where('id', '=', id).execute();
|
||||||
|
return this.getByIdBuilder(id).executeTakeFirstOrThrow() as unknown as Promise<MemoryEntity>;
|
||||||
update(memory: Partial<MemoryEntity>): Promise<MemoryEntity> {
|
|
||||||
return this.save(memory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
async delete(id: string): Promise<void> {
|
async delete(id: string): Promise<void> {
|
||||||
await this.repository.delete({ id });
|
await this.db.deleteFrom('memories').where('id', '=', id).execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] })
|
@GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] })
|
||||||
@ -53,46 +59,49 @@ export class MemoryRepository implements IMemoryRepository {
|
|||||||
return new Set();
|
return new Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = await this.dataSource
|
const results = await this.db
|
||||||
.createQueryBuilder()
|
.selectFrom('memories_assets_assets')
|
||||||
.select('memories_assets.assetsId', 'assetId')
|
.select(['assetsId'])
|
||||||
.from('memories_assets_assets', 'memories_assets')
|
.where('memoriesId', '=', id)
|
||||||
.where('"memories_assets"."memoriesId" = :memoryId', { memoryId: id })
|
.where('assetsId', 'in', assetIds)
|
||||||
.andWhere('memories_assets.assetsId IN (:...assetIds)', { assetIds })
|
.execute();
|
||||||
.getRawMany<{ assetId: string }>();
|
|
||||||
|
|
||||||
return new Set(results.map(({ assetId }) => assetId));
|
return new Set(results.map(({ assetsId }) => assetsId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] })
|
||||||
async addAssetIds(id: string, assetIds: string[]): Promise<void> {
|
async addAssetIds(id: string, assetIds: string[]): Promise<void> {
|
||||||
await this.dataSource
|
await this.db
|
||||||
.createQueryBuilder()
|
.insertInto('memories_assets_assets')
|
||||||
.insert()
|
|
||||||
.into('memories_assets_assets', ['memoriesId', 'assetsId'])
|
|
||||||
.values(assetIds.map((assetId) => ({ memoriesId: id, assetsId: assetId })))
|
.values(assetIds.map((assetId) => ({ memoriesId: id, assetsId: assetId })))
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Chunked({ paramIndex: 1 })
|
@Chunked({ paramIndex: 1 })
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] })
|
||||||
async removeAssetIds(id: string, assetIds: string[]): Promise<void> {
|
async removeAssetIds(id: string, assetIds: string[]): Promise<void> {
|
||||||
await this.dataSource
|
await this.db
|
||||||
.createQueryBuilder()
|
.deleteFrom('memories_assets_assets')
|
||||||
.delete()
|
.where('memoriesId', '=', id)
|
||||||
.from('memories_assets_assets')
|
.where('assetsId', 'in', assetIds)
|
||||||
.where({
|
|
||||||
memoriesId: id,
|
|
||||||
assetsId: In(assetIds),
|
|
||||||
})
|
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async save(memory: Partial<MemoryEntity>): Promise<MemoryEntity> {
|
private getByIdBuilder(id: string) {
|
||||||
const { id } = await this.repository.save(memory);
|
return this.db
|
||||||
return this.repository.findOneOrFail({
|
.selectFrom('memories')
|
||||||
where: { id },
|
.selectAll('memories')
|
||||||
relations: {
|
.select((eb) =>
|
||||||
assets: true,
|
jsonArrayFrom(
|
||||||
},
|
eb
|
||||||
});
|
.selectFrom('assets')
|
||||||
|
.selectAll('assets')
|
||||||
|
.innerJoin('memories_assets_assets', 'assets.id', 'memories_assets_assets.assetsId')
|
||||||
|
.whereRef('memories_assets_assets.memoriesId', '=', 'memories.id')
|
||||||
|
.where('assets.deletedAt', 'is', null),
|
||||||
|
).as('assets'),
|
||||||
|
)
|
||||||
|
.where('id', '=', id)
|
||||||
|
.where('deletedAt', 'is', null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,17 @@ describe(MemoryService.name, () => {
|
|||||||
memoryAt: new Date(2024),
|
memoryAt: new Date(2024),
|
||||||
}),
|
}),
|
||||||
).resolves.toMatchObject({ assets: [] });
|
).resolves.toMatchObject({ assets: [] });
|
||||||
expect(memoryMock.create).toHaveBeenCalledWith(expect.objectContaining({ assets: [] }));
|
expect(memoryMock.create).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
ownerId: 'admin_id',
|
||||||
|
memoryAt: expect.any(Date),
|
||||||
|
type: MemoryType.ON_THIS_DAY,
|
||||||
|
isSaved: undefined,
|
||||||
|
sendAt: undefined,
|
||||||
|
data: { year: 2024 },
|
||||||
|
},
|
||||||
|
new Set(),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a memory', async () => {
|
it('should create a memory', async () => {
|
||||||
@ -80,14 +90,14 @@ describe(MemoryService.name, () => {
|
|||||||
type: MemoryType.ON_THIS_DAY,
|
type: MemoryType.ON_THIS_DAY,
|
||||||
data: { year: 2024 },
|
data: { year: 2024 },
|
||||||
assetIds: ['asset1'],
|
assetIds: ['asset1'],
|
||||||
memoryAt: new Date(2024),
|
memoryAt: new Date(2024, 0, 1),
|
||||||
}),
|
}),
|
||||||
).resolves.toBeDefined();
|
).resolves.toBeDefined();
|
||||||
expect(memoryMock.create).toHaveBeenCalledWith(
|
expect(memoryMock.create).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: userStub.admin.id,
|
ownerId: userStub.admin.id,
|
||||||
assets: [{ id: 'asset1' }],
|
|
||||||
}),
|
}),
|
||||||
|
new Set(['asset1']),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -115,12 +125,7 @@ describe(MemoryService.name, () => {
|
|||||||
accessMock.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1']));
|
accessMock.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1']));
|
||||||
memoryMock.update.mockResolvedValue(memoryStub.memory1);
|
memoryMock.update.mockResolvedValue(memoryStub.memory1);
|
||||||
await expect(sut.update(authStub.admin, 'memory1', { isSaved: true })).resolves.toBeDefined();
|
await expect(sut.update(authStub.admin, 'memory1', { isSaved: true })).resolves.toBeDefined();
|
||||||
expect(memoryMock.update).toHaveBeenCalledWith(
|
expect(memoryMock.update).toHaveBeenCalledWith('memory1', expect.objectContaining({ isSaved: true }));
|
||||||
expect.objectContaining({
|
|
||||||
id: 'memory1',
|
|
||||||
isSaved: true,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ import { BadRequestException, Injectable } from '@nestjs/common';
|
|||||||
import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { MemoryCreateDto, MemoryResponseDto, MemoryUpdateDto, mapMemory } from 'src/dtos/memory.dto';
|
import { MemoryCreateDto, MemoryResponseDto, MemoryUpdateDto, mapMemory } from 'src/dtos/memory.dto';
|
||||||
import { AssetEntity } from 'src/entities/asset.entity';
|
|
||||||
import { Permission } from 'src/enum';
|
import { Permission } from 'src/enum';
|
||||||
import { BaseService } from 'src/services/base.service';
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { addAssets, removeAssets } from 'src/utils/asset.util';
|
import { addAssets, removeAssets } from 'src/utils/asset.util';
|
||||||
@ -29,15 +28,17 @@ export class MemoryService extends BaseService {
|
|||||||
permission: Permission.ASSET_SHARE,
|
permission: Permission.ASSET_SHARE,
|
||||||
ids: assetIds,
|
ids: assetIds,
|
||||||
});
|
});
|
||||||
const memory = await this.memoryRepository.create({
|
const memory = await this.memoryRepository.create(
|
||||||
ownerId: auth.user.id,
|
{
|
||||||
type: dto.type,
|
ownerId: auth.user.id,
|
||||||
data: dto.data,
|
type: dto.type,
|
||||||
isSaved: dto.isSaved,
|
data: dto.data,
|
||||||
memoryAt: dto.memoryAt,
|
isSaved: dto.isSaved,
|
||||||
seenAt: dto.seenAt,
|
memoryAt: dto.memoryAt,
|
||||||
assets: [...allowedAssetIds].map((id) => ({ id }) as AssetEntity),
|
seenAt: dto.seenAt,
|
||||||
});
|
},
|
||||||
|
allowedAssetIds,
|
||||||
|
);
|
||||||
|
|
||||||
return mapMemory(memory);
|
return mapMemory(memory);
|
||||||
}
|
}
|
||||||
@ -45,8 +46,7 @@ export class MemoryService extends BaseService {
|
|||||||
async update(auth: AuthDto, id: string, dto: MemoryUpdateDto): Promise<MemoryResponseDto> {
|
async update(auth: AuthDto, id: string, dto: MemoryUpdateDto): Promise<MemoryResponseDto> {
|
||||||
await this.requireAccess({ auth, permission: Permission.MEMORY_UPDATE, ids: [id] });
|
await this.requireAccess({ auth, permission: Permission.MEMORY_UPDATE, ids: [id] });
|
||||||
|
|
||||||
const memory = await this.memoryRepository.update({
|
const memory = await this.memoryRepository.update(id, {
|
||||||
id,
|
|
||||||
isSaved: dto.isSaved,
|
isSaved: dto.isSaved,
|
||||||
memoryAt: dto.memoryAt,
|
memoryAt: dto.memoryAt,
|
||||||
seenAt: dto.seenAt,
|
seenAt: dto.seenAt,
|
||||||
@ -68,7 +68,7 @@ export class MemoryService extends BaseService {
|
|||||||
|
|
||||||
const hasSuccess = results.find(({ success }) => success);
|
const hasSuccess = results.find(({ success }) => success);
|
||||||
if (hasSuccess) {
|
if (hasSuccess) {
|
||||||
await this.memoryRepository.update({ id, updatedAt: new Date() });
|
await this.memoryRepository.update(id, { updatedAt: new Date() });
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
@ -86,7 +86,7 @@ export class MemoryService extends BaseService {
|
|||||||
|
|
||||||
const hasSuccess = results.find(({ success }) => success);
|
const hasSuccess = results.find(({ success }) => success);
|
||||||
if (hasSuccess) {
|
if (hasSuccess) {
|
||||||
await this.memoryRepository.update({ id, updatedAt: new Date() });
|
await this.memoryRepository.update(id, { id, updatedAt: new Date() });
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user