fix: time bucket grouping (#19329)

This commit is contained in:
Jason Rasmussen 2025-06-20 09:46:30 -04:00 committed by GitHub
parent 11c469907f
commit 33c9f88ba4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 9 additions and 15 deletions

View File

@ -224,7 +224,7 @@ limit
with with
"assets" as ( "assets" as (
select select
date_trunc('MONTH', "localDateTime" at time zone 'UTC') at time zone 'UTC' as "timeBucket" date_trunc('MONTH', "localDateTime" AT TIME ZONE 'UTC') AT TIME ZONE 'UTC' as "timeBucket"
from from
"assets" "assets"
where where
@ -232,7 +232,7 @@ with
and "assets"."visibility" in ('archive', 'timeline') and "assets"."visibility" in ('archive', 'timeline')
) )
select select
"timeBucket"::date::text as "timeBucket", ("timeBucket" AT TIME ZONE 'UTC')::date::text as "timeBucket",
count(*) as "count" count(*) as "count"
from from
"assets" "assets"
@ -300,7 +300,7 @@ with
where where
"assets"."deletedAt" is null "assets"."deletedAt" is null
and "assets"."visibility" in ('archive', 'timeline') and "assets"."visibility" in ('archive', 'timeline')
and date_trunc('MONTH', "localDateTime" at time zone 'UTC') at time zone 'UTC' = $2 and date_trunc('MONTH', "localDateTime" AT TIME ZONE 'UTC') AT TIME ZONE 'UTC' = $2
and not exists ( and not exists (
select select
from from

View File

@ -42,11 +42,6 @@ interface LivePhotoSearchOptions {
type: AssetType; type: AssetType;
} }
export enum TimeBucketSize {
DAY = 'DAY',
MONTH = 'MONTH',
}
interface AssetBuilderOptions { interface AssetBuilderOptions {
isFavorite?: boolean; isFavorite?: boolean;
isTrashed?: boolean; isTrashed?: boolean;
@ -490,13 +485,13 @@ export class AssetRepository {
.execute(); .execute();
} }
@GenerateSql({ params: [{ size: TimeBucketSize.MONTH }] }) @GenerateSql({ params: [{}] })
async getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]> { async getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]> {
return this.db return this.db
.with('assets', (qb) => .with('assets', (qb) =>
qb qb
.selectFrom('assets') .selectFrom('assets')
.select(truncatedDate<Date>(TimeBucketSize.MONTH).as('timeBucket')) .select(truncatedDate<Date>().as('timeBucket'))
.$if(!!options.isTrashed, (qb) => qb.where('assets.status', '!=', AssetStatus.DELETED)) .$if(!!options.isTrashed, (qb) => qb.where('assets.status', '!=', AssetStatus.DELETED))
.where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null) .where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null)
.$if(options.visibility === undefined, withDefaultVisibility) .$if(options.visibility === undefined, withDefaultVisibility)
@ -525,7 +520,7 @@ export class AssetRepository {
.$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!)), .$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!)),
) )
.selectFrom('assets') .selectFrom('assets')
.select(sql<string>`"timeBucket"::date::text`.as('timeBucket')) .select(sql<string>`("timeBucket" AT TIME ZONE 'UTC')::date::text`.as('timeBucket'))
.select((eb) => eb.fn.countAll<number>().as('count')) .select((eb) => eb.fn.countAll<number>().as('count'))
.groupBy('timeBucket') .groupBy('timeBucket')
.orderBy('timeBucket', options.order ?? 'desc') .orderBy('timeBucket', options.order ?? 'desc')
@ -576,7 +571,7 @@ export class AssetRepository {
.where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null) .where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null)
.$if(options.visibility == undefined, withDefaultVisibility) .$if(options.visibility == undefined, withDefaultVisibility)
.$if(!!options.visibility, (qb) => qb.where('assets.visibility', '=', options.visibility!)) .$if(!!options.visibility, (qb) => qb.where('assets.visibility', '=', options.visibility!))
.where(truncatedDate(TimeBucketSize.MONTH), '=', timeBucket.replace(/^[+-]/, '')) .where(truncatedDate(), '=', timeBucket.replace(/^[+-]/, ''))
.$if(!!options.albumId, (qb) => .$if(!!options.albumId, (qb) =>
qb.where((eb) => qb.where((eb) =>
eb.exists( eb.exists(

View File

@ -18,7 +18,6 @@ import postgres, { Notice } from 'postgres';
import { columns, Exif, Person } from 'src/database'; import { columns, Exif, Person } from 'src/database';
import { DB } from 'src/db'; import { DB } from 'src/db';
import { AssetFileType, AssetVisibility, DatabaseExtension, DatabaseSslMode } from 'src/enum'; import { AssetFileType, AssetVisibility, DatabaseExtension, DatabaseSslMode } from 'src/enum';
import { TimeBucketSize } from 'src/repositories/asset.repository';
import { AssetSearchBuilderOptions } from 'src/repositories/search.repository'; import { AssetSearchBuilderOptions } from 'src/repositories/search.repository';
import { DatabaseConnectionParams, VectorExtension } from 'src/types'; import { DatabaseConnectionParams, VectorExtension } from 'src/types';
@ -279,8 +278,8 @@ export function withTags(eb: ExpressionBuilder<DB, 'assets'>) {
).as('tags'); ).as('tags');
} }
export function truncatedDate<O>(size: TimeBucketSize) { export function truncatedDate<O>() {
return sql<O>`date_trunc(${sql.lit(size)}, "localDateTime" at time zone 'UTC') at time zone 'UTC'`; return sql<O>`date_trunc(${sql.lit('MONTH')}, "localDateTime" AT TIME ZONE 'UTC') AT TIME ZONE 'UTC'`;
} }
export function withTagId<O>(qb: SelectQueryBuilder<DB, 'assets', O>, tagId: string) { export function withTagId<O>(qb: SelectQueryBuilder<DB, 'assets', O>, tagId: string) {