mirror of
https://github.com/immich-app/immich.git
synced 2025-12-25 09:14:58 +03:00
feat: persistent memories (#8330)
* feat: persistent memories * refactor: use new add/remove asset utility
This commit is contained in:
@@ -5,6 +5,7 @@ import { AlbumEntity } from 'src/entities/album.entity';
|
||||
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { LibraryEntity } from 'src/entities/library.entity';
|
||||
import { MemoryEntity } from 'src/entities/memory.entity';
|
||||
import { PartnerEntity } from 'src/entities/partner.entity';
|
||||
import { PersonEntity } from 'src/entities/person.entity';
|
||||
import { SharedLinkEntity } from 'src/entities/shared-link.entity';
|
||||
@@ -19,6 +20,7 @@ type IAssetAccess = IAccessRepository['asset'];
|
||||
type IAuthDeviceAccess = IAccessRepository['authDevice'];
|
||||
type ILibraryAccess = IAccessRepository['library'];
|
||||
type ITimelineAccess = IAccessRepository['timeline'];
|
||||
type IMemoryAccess = IAccessRepository['memory'];
|
||||
type IPersonAccess = IAccessRepository['person'];
|
||||
type IPartnerAccess = IAccessRepository['partner'];
|
||||
|
||||
@@ -345,6 +347,28 @@ class TimelineAccess implements ITimelineAccess {
|
||||
}
|
||||
}
|
||||
|
||||
class MemoryAccess implements IMemoryAccess {
|
||||
constructor(private memoryRepository: Repository<MemoryEntity>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
@ChunkedSet({ paramIndex: 1 })
|
||||
async checkOwnerAccess(userId: string, memoryIds: Set<string>): Promise<Set<string>> {
|
||||
if (memoryIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return this.memoryRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In([...memoryIds]),
|
||||
ownerId: userId,
|
||||
},
|
||||
})
|
||||
.then((memories) => new Set(memories.map((memory) => memory.id)));
|
||||
}
|
||||
}
|
||||
|
||||
class PersonAccess implements IPersonAccess {
|
||||
constructor(
|
||||
private assetFaceRepository: Repository<AssetFaceEntity>,
|
||||
@@ -416,6 +440,7 @@ export class AccessRepository implements IAccessRepository {
|
||||
asset: IAssetAccess;
|
||||
authDevice: IAuthDeviceAccess;
|
||||
library: ILibraryAccess;
|
||||
memory: IMemoryAccess;
|
||||
person: IPersonAccess;
|
||||
partner: IPartnerAccess;
|
||||
timeline: ITimelineAccess;
|
||||
@@ -425,6 +450,7 @@ export class AccessRepository implements IAccessRepository {
|
||||
@InjectRepository(AssetEntity) assetRepository: Repository<AssetEntity>,
|
||||
@InjectRepository(AlbumEntity) albumRepository: Repository<AlbumEntity>,
|
||||
@InjectRepository(LibraryEntity) libraryRepository: Repository<LibraryEntity>,
|
||||
@InjectRepository(MemoryEntity) memoryRepository: Repository<MemoryEntity>,
|
||||
@InjectRepository(PartnerEntity) partnerRepository: Repository<PartnerEntity>,
|
||||
@InjectRepository(PersonEntity) personRepository: Repository<PersonEntity>,
|
||||
@InjectRepository(AssetFaceEntity) assetFaceRepository: Repository<AssetFaceEntity>,
|
||||
@@ -436,6 +462,7 @@ export class AccessRepository implements IAccessRepository {
|
||||
this.asset = new AssetAccess(albumRepository, assetRepository, partnerRepository, sharedLinkRepository);
|
||||
this.authDevice = new AuthDeviceAccess(tokenRepository);
|
||||
this.library = new LibraryAccess(libraryRepository);
|
||||
this.memory = new MemoryAccess(memoryRepository);
|
||||
this.person = new PersonAccess(assetFaceRepository, personRepository);
|
||||
this.partner = new PartnerAccess(partnerRepository);
|
||||
this.timeline = new TimelineAccess(partnerRepository);
|
||||
|
||||
@@ -13,6 +13,7 @@ import { IJobRepository } from 'src/interfaces/job.interface';
|
||||
import { ILibraryRepository } from 'src/interfaces/library.interface';
|
||||
import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
|
||||
import { IMediaRepository } from 'src/interfaces/media.interface';
|
||||
import { IMemoryRepository } from 'src/interfaces/memory.interface';
|
||||
import { IMetadataRepository } from 'src/interfaces/metadata.interface';
|
||||
import { IMetricRepository } from 'src/interfaces/metric.interface';
|
||||
import { IMoveRepository } from 'src/interfaces/move.interface';
|
||||
@@ -42,6 +43,7 @@ import { JobRepository } from 'src/repositories/job.repository';
|
||||
import { LibraryRepository } from 'src/repositories/library.repository';
|
||||
import { MachineLearningRepository } from 'src/repositories/machine-learning.repository';
|
||||
import { MediaRepository } from 'src/repositories/media.repository';
|
||||
import { MemoryRepository } from 'src/repositories/memory.repository';
|
||||
import { MetadataRepository } from 'src/repositories/metadata.repository';
|
||||
import { MetricRepository } from 'src/repositories/metric.repository';
|
||||
import { MoveRepository } from 'src/repositories/move.repository';
|
||||
@@ -72,6 +74,7 @@ export const repositories = [
|
||||
{ provide: ILibraryRepository, useClass: LibraryRepository },
|
||||
{ provide: IKeyRepository, useClass: ApiKeyRepository },
|
||||
{ provide: IMachineLearningRepository, useClass: MachineLearningRepository },
|
||||
{ provide: IMemoryRepository, useClass: MemoryRepository },
|
||||
{ provide: IMetadataRepository, useClass: MetadataRepository },
|
||||
{ provide: IMetricRepository, useClass: MetricRepository },
|
||||
{ provide: IMoveRepository, useClass: MoveRepository },
|
||||
|
||||
104
server/src/repositories/memory.repository.ts
Normal file
104
server/src/repositories/memory.repository.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
import { Chunked, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { MemoryEntity } from 'src/entities/memory.entity';
|
||||
import { IMemoryRepository } from 'src/interfaces/memory.interface';
|
||||
import { Instrumentation } from 'src/utils/instrumentation';
|
||||
import { DataSource, In, Repository } from 'typeorm';
|
||||
|
||||
@Instrumentation()
|
||||
@Injectable()
|
||||
export class MemoryRepository implements IMemoryRepository {
|
||||
constructor(
|
||||
@InjectRepository(AssetEntity) private assetRepository: Repository<AssetEntity>,
|
||||
@InjectRepository(MemoryEntity) private repository: Repository<MemoryEntity>,
|
||||
@InjectDataSource() private dataSource: DataSource,
|
||||
) {}
|
||||
|
||||
search(ownerId: string): Promise<MemoryEntity[]> {
|
||||
return this.repository.find({
|
||||
where: {
|
||||
ownerId,
|
||||
},
|
||||
order: {
|
||||
memoryAt: 'DESC',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
get(id: string): Promise<MemoryEntity | null> {
|
||||
return this.repository.findOne({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
relations: {
|
||||
assets: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
create(memory: Partial<MemoryEntity>): Promise<MemoryEntity> {
|
||||
return this.save(memory);
|
||||
}
|
||||
|
||||
update(memory: Partial<MemoryEntity>): Promise<MemoryEntity> {
|
||||
return this.save(memory);
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
await this.repository.delete({ id });
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] })
|
||||
@ChunkedSet({ paramIndex: 1 })
|
||||
async getAssetIds(id: string, assetIds: string[]): Promise<Set<string>> {
|
||||
if (assetIds.length === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
const results = await this.dataSource
|
||||
.createQueryBuilder()
|
||||
.select('memories_assets.assetsId', 'assetId')
|
||||
.from('memories_assets_assets', 'memories_assets')
|
||||
.where('"memories_assets"."memoriesId" = :memoryId', { memoryId: id })
|
||||
.andWhere('memories_assets.assetsId IN (:...assetIds)', { assetIds })
|
||||
.getRawMany();
|
||||
|
||||
return new Set(results.map((row) => row['assetId']));
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [{ albumId: DummyValue.UUID, assetIds: [DummyValue.UUID] }] })
|
||||
async addAssetIds(id: string, assetIds: string[]): Promise<void> {
|
||||
await this.dataSource
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into('memories_assets_assets', ['memoriesId', 'assetsId'])
|
||||
.values(assetIds.map((assetId) => ({ memoriesId: id, assetsId: assetId })))
|
||||
.execute();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] })
|
||||
@Chunked({ paramIndex: 1 })
|
||||
async removeAssetIds(id: string, assetIds: string[]): Promise<void> {
|
||||
await this.dataSource
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from('memories_assets_assets')
|
||||
.where({
|
||||
memoriesId: id,
|
||||
assetsId: In(assetIds),
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
private async save(memory: Partial<MemoryEntity>): Promise<MemoryEntity> {
|
||||
const { id } = await this.repository.save(memory);
|
||||
return this.repository.findOneOrFail({
|
||||
where: { id },
|
||||
relations: {
|
||||
assets: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user