mirror of
https://github.com/immich-app/immich.git
synced 2025-12-25 09:14:58 +03:00
feat: persistent memories (#15953)
feat: memories refactor chore: use heart as favorite icon fix: linting
This commit is contained in:
@@ -40,6 +40,8 @@ describe(JobService.name, () => {
|
||||
{ name: JobName.ASSET_DELETION_CHECK },
|
||||
{ name: JobName.USER_DELETE_CHECK },
|
||||
{ name: JobName.PERSON_CLEANUP },
|
||||
{ name: JobName.MEMORIES_CLEANUP },
|
||||
{ name: JobName.MEMORIES_CREATE },
|
||||
{ name: JobName.QUEUE_GENERATE_THUMBNAILS, data: { force: false } },
|
||||
{ name: JobName.CLEAN_OLD_AUDIT_LOGS },
|
||||
{ name: JobName.USER_SYNC_USAGE },
|
||||
|
||||
@@ -31,6 +31,14 @@ const asJobItem = (dto: JobCreateDto): JobItem => {
|
||||
return { name: JobName.USER_DELETE_CHECK };
|
||||
}
|
||||
|
||||
case ManualJobName.MEMORY_CLEANUP: {
|
||||
return { name: JobName.MEMORIES_CLEANUP };
|
||||
}
|
||||
|
||||
case ManualJobName.MEMORY_CREATE: {
|
||||
return { name: JobName.MEMORIES_CREATE };
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new BadRequestException('Invalid job name');
|
||||
}
|
||||
@@ -207,6 +215,8 @@ export class JobService extends BaseService {
|
||||
{ name: JobName.ASSET_DELETION_CHECK },
|
||||
{ name: JobName.USER_DELETE_CHECK },
|
||||
{ name: JobName.PERSON_CLEANUP },
|
||||
{ name: JobName.MEMORIES_CLEANUP },
|
||||
{ name: JobName.MEMORIES_CREATE },
|
||||
{ name: JobName.QUEUE_GENERATE_THUMBNAILS, data: { force: false } },
|
||||
{ name: JobName.CLEAN_OLD_AUDIT_LOGS },
|
||||
{ name: JobName.USER_SYNC_USAGE },
|
||||
|
||||
@@ -21,7 +21,7 @@ describe(MemoryService.name, () => {
|
||||
describe('search', () => {
|
||||
it('should search memories', async () => {
|
||||
mocks.memory.search.mockResolvedValue([memoryStub.memory1, memoryStub.empty]);
|
||||
await expect(sut.search(authStub.admin)).resolves.toEqual(
|
||||
await expect(sut.search(authStub.admin, {})).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ id: 'memory1', assets: expect.any(Array) }),
|
||||
expect.objectContaining({ id: 'memoryEmpty', assets: [] }),
|
||||
@@ -30,7 +30,7 @@ describe(MemoryService.name, () => {
|
||||
});
|
||||
|
||||
it('should map ', async () => {
|
||||
await expect(sut.search(authStub.admin)).resolves.toEqual([]);
|
||||
await expect(sut.search(authStub.admin, {})).resolves.toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,16 +1,84 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { DateTime } from 'luxon';
|
||||
import { JsonObject } from 'src/db';
|
||||
import { OnJob } from 'src/decorators';
|
||||
import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { MemoryCreateDto, MemoryResponseDto, MemoryUpdateDto, mapMemory } from 'src/dtos/memory.dto';
|
||||
import { Permission } from 'src/enum';
|
||||
import { MemoryCreateDto, MemoryResponseDto, MemorySearchDto, MemoryUpdateDto, mapMemory } from 'src/dtos/memory.dto';
|
||||
import { OnThisDayData } from 'src/entities/memory.entity';
|
||||
import { JobName, MemoryType, Permission, QueueName, SystemMetadataKey } from 'src/enum';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { addAssets, removeAssets } from 'src/utils/asset.util';
|
||||
import { addAssets, getMyPartnerIds, removeAssets } from 'src/utils/asset.util';
|
||||
|
||||
const DAYS = 3;
|
||||
|
||||
@Injectable()
|
||||
export class MemoryService extends BaseService {
|
||||
async search(auth: AuthDto) {
|
||||
const memories = await this.memoryRepository.search(auth.user.id);
|
||||
@OnJob({ name: JobName.MEMORIES_CREATE, queue: QueueName.BACKGROUND_TASK })
|
||||
async onMemoriesCreate() {
|
||||
const users = await this.userRepository.getList({ withDeleted: false });
|
||||
const userMap: Record<string, string[]> = {};
|
||||
for (const user of users) {
|
||||
const partnerIds = await getMyPartnerIds({
|
||||
userId: user.id,
|
||||
repository: this.partnerRepository,
|
||||
timelineEnabled: true,
|
||||
});
|
||||
userMap[user.id] = [user.id, ...partnerIds];
|
||||
}
|
||||
|
||||
const start = DateTime.utc().startOf('day').minus({ days: DAYS });
|
||||
|
||||
const state = await this.systemMetadataRepository.get(SystemMetadataKey.MEMORIES_STATE);
|
||||
let lastOnThisDayDate = state?.lastOnThisDayDate ? DateTime.fromISO(state?.lastOnThisDayDate) : start;
|
||||
|
||||
// generate a memory +/- X days from today
|
||||
for (let i = 0; i <= DAYS * 2 + 1; i++) {
|
||||
const target = start.plus({ days: i });
|
||||
if (lastOnThisDayDate > target) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const showAt = target.startOf('day').toISO();
|
||||
const hideAt = target.endOf('day').toISO();
|
||||
|
||||
this.logger.log(`Creating memories for month=${target.month}, day=${target.day}`);
|
||||
|
||||
for (const [userId, userIds] of Object.entries(userMap)) {
|
||||
const memories = await this.assetRepository.getByDayOfYear(userIds, target);
|
||||
|
||||
for (const memory of memories) {
|
||||
const data: OnThisDayData = { year: target.year - memory.yearsAgo };
|
||||
await this.memoryRepository.create(
|
||||
{
|
||||
ownerId: userId,
|
||||
type: MemoryType.ON_THIS_DAY,
|
||||
data,
|
||||
memoryAt: target.minus({ years: memory.yearsAgo }).toISO(),
|
||||
showAt,
|
||||
hideAt,
|
||||
},
|
||||
new Set(memory.assets.map(({ id }) => id)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await this.systemMetadataRepository.set(SystemMetadataKey.MEMORIES_STATE, {
|
||||
...state,
|
||||
lastOnThisDayDate: target.toISO(),
|
||||
});
|
||||
|
||||
lastOnThisDayDate = target;
|
||||
}
|
||||
}
|
||||
|
||||
@OnJob({ name: JobName.MEMORIES_CLEANUP, queue: QueueName.BACKGROUND_TASK })
|
||||
async onMemoriesCleanup() {
|
||||
await this.memoryRepository.cleanup();
|
||||
}
|
||||
|
||||
async search(auth: AuthDto, dto: MemorySearchDto) {
|
||||
const memories = await this.memoryRepository.search(auth.user.id, dto);
|
||||
return memories.map((memory) => mapMemory(memory));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user