diff --git a/server/src/repositories/asset.repository.ts b/server/src/repositories/asset.repository.ts index f6fd27b87..032f2ca39 100644 --- a/server/src/repositories/asset.repository.ts +++ b/server/src/repositories/asset.repository.ts @@ -818,6 +818,7 @@ export class AssetRepository { }) getTimeBucket(timeBucket: string, options: TimeBucketOptions, auth: AuthDto) { const order = options.order ?? 'desc'; + const bucket = timeBucket.replace(/^[+-]/, ''); const query = this.db .with('cte', (qb) => qb @@ -869,7 +870,14 @@ export class AssetRepository { return withBoundingBox(withBoundingCircle, bbox); }) - .where(truncatedDate(options.orderBy), '=', timeBucket.replace(/^[+-]/, '')) + .$if(options.orderBy === AssetOrderBy.CreatedAt, (qb) => + qb + .where('asset.createdAt', '>=', sql`${bucket}::timestamp AT TIME ZONE 'UTC'`) + .where('asset.createdAt', '<', sql`(${bucket}::timestamp + interval '1 month') AT TIME ZONE 'UTC'`), + ) + .$if(options.orderBy !== AssetOrderBy.CreatedAt, (qb) => + qb.where(truncatedDate(options.orderBy), '=', bucket), + ) .$if(!!options.albumId, (qb) => qb.where((eb) => eb.exists( diff --git a/server/test/medium/specs/services/timeline.service.spec.ts b/server/test/medium/specs/services/timeline.service.spec.ts index 75fb6e20c..030fc45fa 100644 --- a/server/test/medium/specs/services/timeline.service.spec.ts +++ b/server/test/medium/specs/services/timeline.service.spec.ts @@ -1,6 +1,6 @@ import { BadRequestException } from '@nestjs/common'; import { Kysely } from 'kysely'; -import { AssetVisibility, SharedLinkType } from 'src/enum.js'; +import { AssetOrderBy, AssetVisibility, SharedLinkType } from 'src/enum.js'; import { AccessRepository } from 'src/repositories/access.repository.js'; import { AssetRepository } from 'src/repositories/asset.repository.js'; import { LoggingRepository } from 'src/repositories/logging.repository.js'; @@ -162,6 +162,34 @@ describe(TimelineService.name, () => { const rawResponse = await sut.getTimeBucket(factory.auth(), { timeBucket: '012345-01-01' }); const response = JSON.parse(rawResponse); expect(response).toEqual(expect.objectContaining({ id: [] })); + + const createdAtResponse = await sut.getTimeBucket(factory.auth(), { + timeBucket: '012345-01-01', + orderBy: AssetOrderBy.CreatedAt, + }); + expect(JSON.parse(createdAtResponse)).toEqual(expect.objectContaining({ id: [] })); + }); + + it('should bucket by upload month when ordering by createdAt', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const auth = factory.auth({ user: { id: user.id } }); + const dates = [ + '1970-01-31T23:59:59.999Z', + '1970-02-01T00:00:00.000Z', + '1970-02-28T23:59:59.999Z', + '1970-03-01T00:00:00.000Z', + ]; + const ids: string[] = []; + for (const createdAt of dates) { + const { asset } = await ctx.newAsset({ ownerId: user.id, createdAt, localDateTime: new Date('2000-01-01') }); + await ctx.newExif({ assetId: asset.id, make: 'Canon' }); + ids.push(asset.id); + } + + const rawResponse = await sut.getTimeBucket(auth, { timeBucket: '1970-02-01', orderBy: AssetOrderBy.CreatedAt }); + const response = JSON.parse(rawResponse); + expect(response.id).toEqual([ids[2], ids[1]]); }); it('should return time bucket in trash', async () => {