mirror of
https://github.com/immich-app/immich.git
synced 2026-03-22 14:29:26 +03:00
fix(server): use correct day ordering in timeline buckets (#26821)
* fix(web): sort timeline day groups received from server * fix(server): use correct day ordering in timeline buckets
This commit is contained in:
@@ -438,6 +438,7 @@ with
|
|||||||
and "stack"."primaryAssetId" != "asset"."id"
|
and "stack"."primaryAssetId" != "asset"."id"
|
||||||
)
|
)
|
||||||
order by
|
order by
|
||||||
|
(asset."localDateTime" AT TIME ZONE 'UTC')::date desc,
|
||||||
"asset"."fileCreatedAt" desc
|
"asset"."fileCreatedAt" desc
|
||||||
),
|
),
|
||||||
"agg" as (
|
"agg" as (
|
||||||
|
|||||||
@@ -744,6 +744,7 @@ export class AssetRepository {
|
|||||||
params: [DummyValue.TIME_BUCKET, { withStacked: true }, { user: { id: DummyValue.UUID } }],
|
params: [DummyValue.TIME_BUCKET, { withStacked: true }, { user: { id: DummyValue.UUID } }],
|
||||||
})
|
})
|
||||||
getTimeBucket(timeBucket: string, options: TimeBucketOptions, auth: AuthDto) {
|
getTimeBucket(timeBucket: string, options: TimeBucketOptions, auth: AuthDto) {
|
||||||
|
const order = options.order ?? 'desc';
|
||||||
const query = this.db
|
const query = this.db
|
||||||
.with('cte', (qb) =>
|
.with('cte', (qb) =>
|
||||||
qb
|
qb
|
||||||
@@ -841,7 +842,8 @@ export class AssetRepository {
|
|||||||
)
|
)
|
||||||
.$if(!!options.isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.Deleted))
|
.$if(!!options.isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.Deleted))
|
||||||
.$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!))
|
.$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!))
|
||||||
.orderBy('asset.fileCreatedAt', options.order ?? 'desc'),
|
.orderBy(sql`(asset."localDateTime" AT TIME ZONE 'UTC')::date`, order)
|
||||||
|
.orderBy('asset.fileCreatedAt', order),
|
||||||
)
|
)
|
||||||
.with('agg', (qb) =>
|
.with('agg', (qb) =>
|
||||||
qb
|
qb
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { Kysely } from 'kysely';
|
import { Kysely } from 'kysely';
|
||||||
|
import { AssetOrder, AssetVisibility } from 'src/enum';
|
||||||
import { AssetRepository } from 'src/repositories/asset.repository';
|
import { AssetRepository } from 'src/repositories/asset.repository';
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
import { DB } from 'src/schema';
|
import { DB } from 'src/schema';
|
||||||
import { BaseService } from 'src/services/base.service';
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { newMediumService } from 'test/medium.factory';
|
import { newMediumService } from 'test/medium.factory';
|
||||||
|
import { factory } from 'test/small.factory';
|
||||||
import { getKyselyDB } from 'test/utils';
|
import { getKyselyDB } from 'test/utils';
|
||||||
|
|
||||||
let defaultDatabase: Kysely<DB>;
|
let defaultDatabase: Kysely<DB>;
|
||||||
@@ -22,6 +24,61 @@ beforeAll(async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe(AssetRepository.name, () => {
|
describe(AssetRepository.name, () => {
|
||||||
|
describe('getTimeBucket', () => {
|
||||||
|
it('should order assets by local day first and fileCreatedAt within each day', async () => {
|
||||||
|
const { ctx, sut } = setup();
|
||||||
|
const { user } = await ctx.newUser();
|
||||||
|
const auth = factory.auth({ user: { id: user.id } });
|
||||||
|
|
||||||
|
const [{ asset: previousLocalDayAsset }, { asset: nextLocalDayEarlierAsset }, { asset: nextLocalDayLaterAsset }] =
|
||||||
|
await Promise.all([
|
||||||
|
ctx.newAsset({
|
||||||
|
ownerId: user.id,
|
||||||
|
fileCreatedAt: new Date('2026-03-09T00:30:00.000Z'),
|
||||||
|
localDateTime: new Date('2026-03-08T22:30:00.000Z'),
|
||||||
|
}),
|
||||||
|
ctx.newAsset({
|
||||||
|
ownerId: user.id,
|
||||||
|
fileCreatedAt: new Date('2026-03-08T23:30:00.000Z'),
|
||||||
|
localDateTime: new Date('2026-03-09T01:30:00.000Z'),
|
||||||
|
}),
|
||||||
|
ctx.newAsset({
|
||||||
|
ownerId: user.id,
|
||||||
|
fileCreatedAt: new Date('2026-03-08T23:45:00.000Z'),
|
||||||
|
localDateTime: new Date('2026-03-09T01:45:00.000Z'),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
ctx.newExif({ assetId: previousLocalDayAsset.id, timeZone: 'UTC-2' }),
|
||||||
|
ctx.newExif({ assetId: nextLocalDayEarlierAsset.id, timeZone: 'UTC+2' }),
|
||||||
|
ctx.newExif({ assetId: nextLocalDayLaterAsset.id, timeZone: 'UTC+2' }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const descendingBucket = await sut.getTimeBucket(
|
||||||
|
'2026-03-01',
|
||||||
|
{ order: AssetOrder.Desc, userIds: [user.id], visibility: AssetVisibility.Timeline },
|
||||||
|
auth,
|
||||||
|
);
|
||||||
|
expect(JSON.parse(descendingBucket.assets)).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: [nextLocalDayLaterAsset.id, nextLocalDayEarlierAsset.id, previousLocalDayAsset.id],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const ascendingBucket = await sut.getTimeBucket(
|
||||||
|
'2026-03-01',
|
||||||
|
{ order: AssetOrder.Asc, userIds: [user.id], visibility: AssetVisibility.Timeline },
|
||||||
|
auth,
|
||||||
|
);
|
||||||
|
expect(JSON.parse(ascendingBucket.assets)).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: [previousLocalDayAsset.id, nextLocalDayEarlierAsset.id, nextLocalDayLaterAsset.id],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('upsertExif', () => {
|
describe('upsertExif', () => {
|
||||||
it('should append to locked columns', async () => {
|
it('should append to locked columns', async () => {
|
||||||
const { ctx, sut } = setup();
|
const { ctx, sut } = setup();
|
||||||
|
|||||||
Reference in New Issue
Block a user