Show on-this-day memories from the last 20 years with automatic playback

This commit is contained in:
Codex
2026-10-18 19:00:06 +03:00
parent 04a7b4104f
commit ca7766fcd6
7 changed files with 526 additions and 48 deletions
@@ -1,5 +1,5 @@
import { Kysely } from 'kysely';
import { AssetOrder, AssetOrderBy, AssetVisibility } from 'src/enum.js';
import { AssetFileType, AssetOrder, AssetOrderBy, AssetVisibility } from 'src/enum.js';
import { AssetRepository } from 'src/repositories/asset.repository.js';
import { LoggingRepository } from 'src/repositories/logging.repository.js';
import { DB } from 'src/schema/index.js';
@@ -64,6 +64,67 @@ const keyframeRow = (assetId: string, n: number) => ({
});
describe(AssetRepository.name, () => {
describe('getByDayOfYear', () => {
it('should include every year from one through twenty years ago on the same local calendar day', async () => {
const { ctx, sut } = setup();
const { user } = await ctx.newUser();
const { user: otherUser } = await ctx.newUser();
const target = { year: 2026, month: 10, day: 18 };
const expectedAssetIds = [];
for (let yearsAgo = 0; yearsAgo <= 21; yearsAgo++) {
const { asset } = await ctx.newAsset({
ownerId: user.id,
localDateTime: `${target.year - yearsAgo}-10-18T12:00:00.000Z`,
});
await ctx.newJobStatus({ assetId: asset.id });
await ctx.newAssetFile({ assetId: asset.id, type: AssetFileType.Preview, path: '/preview.jpg' });
if (yearsAgo >= 1 && yearsAgo <= 20) {
expectedAssetIds.push(asset.id);
}
}
for (const dto of [
{ ownerId: user.id, localDateTime: '2025-10-17T23:59:59.999Z' },
{ ownerId: user.id, localDateTime: '2025-10-19T00:00:00.000Z' },
{ ownerId: otherUser.id, localDateTime: '2025-10-18T12:00:00.000Z' },
]) {
const { asset } = await ctx.newAsset(dto);
await ctx.newJobStatus({ assetId: asset.id });
await ctx.newAssetFile({ assetId: asset.id, type: AssetFileType.Preview, path: '/preview.jpg' });
}
const memories = await sut.getByDayOfYear([user.id], target);
expect(memories.map(({ year }) => year)).toEqual(Array.from({ length: 20 }, (_, index) => 2025 - index));
expect(memories.flatMap(({ assets }) => assets.map(({ id }) => id))).toEqual(expectedAssetIds);
});
it.each([2028, 2104])('should skip non-leap years on February 29th in %i', async (year) => {
const { ctx, sut } = setup();
const { user } = await ctx.newUser();
const expectedYears = [];
for (let yearsAgo = 1; yearsAgo <= 20; yearsAgo++) {
const assetYear = year - yearsAgo;
const isLeapYear = assetYear % 4 === 0 && (assetYear % 100 !== 0 || assetYear % 400 === 0);
const { asset } = await ctx.newAsset({
ownerId: user.id,
localDateTime: `${assetYear}-${isLeapYear ? '02-29' : '03-01'}T12:00:00.000Z`,
});
await ctx.newJobStatus({ assetId: asset.id });
await ctx.newAssetFile({ assetId: asset.id, type: AssetFileType.Preview, path: '/preview.jpg' });
if (isLeapYear) {
expectedYears.push(assetYear);
}
}
const memories = await sut.getByDayOfYear([user.id], { year, month: 2, day: 29 });
expect(memories.map(({ year }) => year)).toEqual(expectedYears);
});
});
describe('getTimeBucket', () => {
it('should order assets by local day first and fileCreatedAt within each day', async () => {
const { ctx, sut } = setup();