refactor: count all return type (#17529)

This commit is contained in:
Jason Rasmussen 2025-04-10 14:38:49 -04:00 committed by GitHub
parent abde0fbe60
commit 0b22d3348e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 52 additions and 55 deletions

2
server/src/db.d.ts vendored
View File

@ -17,7 +17,7 @@ import {
SyncEntityType,
} from 'src/enum';
import { UserTable } from 'src/schema/tables/user.table';
import {OnThisDayData, UserMetadataItem} from 'src/types';
import { OnThisDayData, UserMetadataItem } from 'src/types';
export type ArrayType<T> = ArrayTypeImpl<T> extends (infer U)[] ? U[] : ArrayTypeImpl<T>;

View File

@ -277,15 +277,15 @@ select
count(*) filter (
where
(
"assets"."type" = $1
and "assets"."isVisible" = $2
"assets"."type" = 'IMAGE'
and "assets"."isVisible" = true
)
) as "photos",
count(*) filter (
where
(
"assets"."type" = $3
and "assets"."isVisible" = $4
"assets"."type" = 'VIDEO'
and "assets"."isVisible" = true
)
) as "videos",
coalesce(
@ -300,7 +300,7 @@ select
where
(
"assets"."libraryId" is null
and "assets"."type" = $5
and "assets"."type" = 'IMAGE'
)
),
0
@ -310,7 +310,7 @@ select
where
(
"assets"."libraryId" is null
and "assets"."type" = $6
and "assets"."type" = 'VIDEO'
)
),
0

View File

@ -69,7 +69,7 @@ export class ActivityRepository {
async getStatistics({ albumId, assetId }: { albumId: string; assetId?: string }): Promise<number> {
const { count } = await this.db
.selectFrom('activity')
.select((eb) => eb.fn.countAll().as('count'))
.select((eb) => eb.fn.countAll<number>().as('count'))
.innerJoin('users', (join) => join.onRef('users.id', '=', 'activity.userId').on('users.deletedAt', 'is', null))
.leftJoin('assets', 'assets.id', 'activity.assetId')
.$if(!!assetId, (qb) => qb.where('activity.assetId', '=', assetId!))
@ -81,6 +81,6 @@ export class ActivityRepository {
.where('assets.localDateTime', 'is not', null)
.executeTakeFirstOrThrow();
return count as number;
return count;
}
}

View File

@ -470,10 +470,10 @@ export class AssetRepository {
async getLivePhotoCount(motionId: string): Promise<number> {
const [{ count }] = await this.db
.selectFrom('assets')
.select((eb) => eb.fn.countAll().as('count'))
.select((eb) => eb.fn.countAll<number>().as('count'))
.where('livePhotoVideoId', '=', asUuid(motionId))
.execute();
return count as number;
return count;
}
@GenerateSql({ params: [DummyValue.UUID] })
@ -773,10 +773,10 @@ export class AssetRepository {
getStatistics(ownerId: string, { isArchived, isFavorite, isTrashed }: AssetStatsOptions): Promise<AssetStats> {
return this.db
.selectFrom('assets')
.select((eb) => eb.fn.countAll().filterWhere('type', '=', AssetType.AUDIO).as(AssetType.AUDIO))
.select((eb) => eb.fn.countAll().filterWhere('type', '=', AssetType.IMAGE).as(AssetType.IMAGE))
.select((eb) => eb.fn.countAll().filterWhere('type', '=', AssetType.VIDEO).as(AssetType.VIDEO))
.select((eb) => eb.fn.countAll().filterWhere('type', '=', AssetType.OTHER).as(AssetType.OTHER))
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.AUDIO).as(AssetType.AUDIO))
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.IMAGE).as(AssetType.IMAGE))
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.VIDEO).as(AssetType.VIDEO))
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.OTHER).as(AssetType.OTHER))
.where('ownerId', '=', asUuid(ownerId))
.where('assets.fileCreatedAt', 'is not', null)
.where('assets.fileModifiedAt', 'is not', null)
@ -786,7 +786,7 @@ export class AssetRepository {
.$if(isFavorite !== undefined, (qb) => qb.where('isFavorite', '=', isFavorite!))
.$if(!!isTrashed, (qb) => qb.where('assets.status', '!=', AssetStatus.DELETED))
.where('deletedAt', isTrashed ? 'is not' : 'is', null)
.executeTakeFirst() as Promise<AssetStats>;
.executeTakeFirstOrThrow();
}
getRandom(userIds: string[], take: number): Promise<AssetEntity[]> {
@ -847,7 +847,7 @@ export class AssetRepository {
The line below outputs in YYYY-MM-DD format, but needs a change in the web app to work.
.select(sql<string>`"timeBucket"::date::text`.as('timeBucket'))
*/
.select((eb) => eb.fn.countAll().as('count'))
.select((eb) => eb.fn.countAll<number>().as('count'))
.groupBy('timeBucket')
.orderBy('timeBucket', options.order ?? 'desc')
.execute() as any as Promise<TimeBucketItem[]>
@ -1145,10 +1145,10 @@ export class AssetRepository {
async getLibraryAssetCount(libraryId: string): Promise<number> {
const { count } = await this.db
.selectFrom('assets')
.select((eb) => eb.fn.countAll().as('count'))
.select((eb) => eb.fn.countAll<number>().as('count'))
.where('libraryId', '=', asUuid(libraryId))
.executeTakeFirstOrThrow();
return Number(count);
return count;
}
}

View File

@ -250,7 +250,7 @@ const getEnv = (): EnvData => {
},
bigint: {
to: 20,
from: [20],
from: [20, 1700],
parse: (value: string) => Number.parseInt(value),
serialize: (value: number) => value.toString(),
},

View File

@ -76,13 +76,13 @@ export class LibraryRepository {
.leftJoin('exif', 'exif.assetId', 'assets.id')
.select((eb) =>
eb.fn
.countAll()
.countAll<number>()
.filterWhere((eb) => eb.and([eb('assets.type', '=', AssetType.IMAGE), eb('assets.isVisible', '=', true)]))
.as('photos'),
)
.select((eb) =>
eb.fn
.countAll()
.countAll<number>()
.filterWhere((eb) => eb.and([eb('assets.type', '=', AssetType.VIDEO), eb('assets.isVisible', '=', true)]))
.as('videos'),
)
@ -105,10 +105,10 @@ export class LibraryRepository {
}
return {
photos: Number(stats.photos),
videos: Number(stats.videos),
usage: Number(stats.usage),
total: Number(stats.photos) + Number(stats.videos),
photos: stats.photos,
videos: stats.videos,
usage: stats.usage,
total: stats.photos + stats.videos,
};
}

View File

@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { getName } from 'i18n-iso-countries';
import { Expression, Insertable, Kysely, sql, SqlBool } from 'kysely';
import { Expression, Insertable, Kysely, NotNull, sql, SqlBool } from 'kysely';
import { InjectKysely } from 'nestjs-kysely';
import { createReadStream, existsSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
@ -87,6 +87,7 @@ export class MapRepository {
.on('exif.longitude', 'is not', null),
)
.select(['id', 'exif.latitude as lat', 'exif.longitude as lon', 'exif.city', 'exif.state', 'exif.country'])
.$narrowType<{ lat: NotNull; lon: NotNull }>()
.where('isVisible', '=', true)
.$if(isArchived !== undefined, (q) => q.where('isArchived', '=', isArchived!))
.$if(isFavorite !== undefined, (q) => q.where('isFavorite', '=', isFavorite!))
@ -114,7 +115,7 @@ export class MapRepository {
return eb.or(expression);
})
.orderBy('fileCreatedAt', 'desc')
.execute() as Promise<MapMarker[]>;
.execute();
}
async reverseGeocode(point: GeoPoint): Promise<ReverseGeocodeResult> {

View File

@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common';
import { ExpressionBuilder, Insertable, Kysely, Updateable } from 'kysely';
import { ExpressionBuilder, Insertable, Kysely, NotNull, Updateable } from 'kysely';
import { jsonObjectFrom } from 'kysely/helpers/postgres';
import { InjectKysely } from 'nestjs-kysely';
import { columns, Partner } from 'src/database';
import { columns } from 'src/database';
import { DB, Partners } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
@ -44,7 +44,7 @@ export class PartnerRepository {
return this.builder()
.where('sharedWithId', '=', sharedWithId)
.where('sharedById', '=', sharedById)
.executeTakeFirst() as Promise<Partner | undefined>;
.executeTakeFirst();
}
@GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }] })
@ -55,7 +55,8 @@ export class PartnerRepository {
.returningAll()
.returning(withSharedBy)
.returning(withSharedWith)
.executeTakeFirstOrThrow() as Promise<Partner>;
.$narrowType<{ sharedWith: NotNull; sharedBy: NotNull }>()
.executeTakeFirstOrThrow();
}
@GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }, { inTimeline: true }] })
@ -68,7 +69,8 @@ export class PartnerRepository {
.returningAll()
.returning(withSharedBy)
.returning(withSharedWith)
.executeTakeFirstOrThrow() as Promise<Partner>;
.$narrowType<{ sharedWith: NotNull; sharedBy: NotNull }>()
.executeTakeFirstOrThrow();
}
@GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }] })

View File

@ -195,30 +195,34 @@ export class UserRepository {
}
@GenerateSql()
async getUserStats(): Promise<UserStatsQueryResponse[]> {
const stats = (await this.db
getUserStats() {
return this.db
.selectFrom('users')
.leftJoin('assets', 'assets.ownerId', 'users.id')
.leftJoin('exif', 'exif.assetId', 'assets.id')
.select(['users.id as userId', 'users.name as userName', 'users.quotaSizeInBytes as quotaSizeInBytes'])
.select((eb) => [
eb.fn
.countAll()
.filterWhere((eb) => eb.and([eb('assets.type', '=', AssetType.IMAGE), eb('assets.isVisible', '=', true)]))
.countAll<number>()
.filterWhere((eb) =>
eb.and([eb('assets.type', '=', sql.lit(AssetType.IMAGE)), eb('assets.isVisible', '=', sql.lit(true))]),
)
.as('photos'),
eb.fn
.countAll()
.filterWhere((eb) => eb.and([eb('assets.type', '=', AssetType.VIDEO), eb('assets.isVisible', '=', true)]))
.countAll<number>()
.filterWhere((eb) =>
eb.and([eb('assets.type', '=', sql.lit(AssetType.VIDEO)), eb('assets.isVisible', '=', sql.lit(true))]),
)
.as('videos'),
eb.fn
.coalesce(eb.fn.sum('exif.fileSizeInByte').filterWhere('assets.libraryId', 'is', null), eb.lit(0))
.coalesce(eb.fn.sum<number>('exif.fileSizeInByte').filterWhere('assets.libraryId', 'is', null), eb.lit(0))
.as('usage'),
eb.fn
.coalesce(
eb.fn
.sum('exif.fileSizeInByte')
.sum<number>('exif.fileSizeInByte')
.filterWhere((eb) =>
eb.and([eb('assets.libraryId', 'is', null), eb('assets.type', '=', AssetType.IMAGE)]),
eb.and([eb('assets.libraryId', 'is', null), eb('assets.type', '=', sql.lit(AssetType.IMAGE))]),
),
eb.lit(0),
)
@ -226,9 +230,9 @@ export class UserRepository {
eb.fn
.coalesce(
eb.fn
.sum('exif.fileSizeInByte')
.sum<number>('exif.fileSizeInByte')
.filterWhere((eb) =>
eb.and([eb('assets.libraryId', 'is', null), eb('assets.type', '=', AssetType.VIDEO)]),
eb.and([eb('assets.libraryId', 'is', null), eb('assets.type', '=', sql.lit(AssetType.VIDEO))]),
),
eb.lit(0),
)
@ -237,17 +241,7 @@ export class UserRepository {
.where('assets.deletedAt', 'is', null)
.groupBy('users.id')
.orderBy('users.createdAt', 'asc')
.execute()) as UserStatsQueryResponse[];
for (const stat of stats) {
stat.photos = Number(stat.photos);
stat.videos = Number(stat.videos);
stat.usage = Number(stat.usage);
stat.usagePhotos = Number(stat.usagePhotos);
stat.usageVideos = Number(stat.usageVideos);
}
return stats;
.execute();
}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.NUMBER] })