diff --git a/server/src/db.d.ts b/server/src/db.d.ts index f5a35e3b4a..7115b701ce 100644 --- a/server/src/db.d.ts +++ b/server/src/db.d.ts @@ -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 = ArrayTypeImpl extends (infer U)[] ? U[] : ArrayTypeImpl; diff --git a/server/src/queries/user.repository.sql b/server/src/queries/user.repository.sql index b7d958aa0f..1212d0f2bd 100644 --- a/server/src/queries/user.repository.sql +++ b/server/src/queries/user.repository.sql @@ -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 diff --git a/server/src/repositories/activity.repository.ts b/server/src/repositories/activity.repository.ts index e266022b05..18128fb087 100644 --- a/server/src/repositories/activity.repository.ts +++ b/server/src/repositories/activity.repository.ts @@ -69,7 +69,7 @@ export class ActivityRepository { async getStatistics({ albumId, assetId }: { albumId: string; assetId?: string }): Promise { const { count } = await this.db .selectFrom('activity') - .select((eb) => eb.fn.countAll().as('count')) + .select((eb) => eb.fn.countAll().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; } } diff --git a/server/src/repositories/asset.repository.ts b/server/src/repositories/asset.repository.ts index 3b71cf84fd..e2765d0446 100644 --- a/server/src/repositories/asset.repository.ts +++ b/server/src/repositories/asset.repository.ts @@ -470,10 +470,10 @@ export class AssetRepository { async getLivePhotoCount(motionId: string): Promise { const [{ count }] = await this.db .selectFrom('assets') - .select((eb) => eb.fn.countAll().as('count')) + .select((eb) => eb.fn.countAll().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 { 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().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)) .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; + .executeTakeFirstOrThrow(); } getRandom(userIds: string[], take: number): Promise { @@ -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`"timeBucket"::date::text`.as('timeBucket')) */ - .select((eb) => eb.fn.countAll().as('count')) + .select((eb) => eb.fn.countAll().as('count')) .groupBy('timeBucket') .orderBy('timeBucket', options.order ?? 'desc') .execute() as any as Promise @@ -1145,10 +1145,10 @@ export class AssetRepository { async getLibraryAssetCount(libraryId: string): Promise { const { count } = await this.db .selectFrom('assets') - .select((eb) => eb.fn.countAll().as('count')) + .select((eb) => eb.fn.countAll().as('count')) .where('libraryId', '=', asUuid(libraryId)) .executeTakeFirstOrThrow(); - return Number(count); + return count; } } diff --git a/server/src/repositories/config.repository.ts b/server/src/repositories/config.repository.ts index 8cbb87b0c5..5069b07be1 100644 --- a/server/src/repositories/config.repository.ts +++ b/server/src/repositories/config.repository.ts @@ -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(), }, diff --git a/server/src/repositories/library.repository.ts b/server/src/repositories/library.repository.ts index efa6e880d1..fd9dd81b7b 100644 --- a/server/src/repositories/library.repository.ts +++ b/server/src/repositories/library.repository.ts @@ -76,13 +76,13 @@ export class LibraryRepository { .leftJoin('exif', 'exif.assetId', 'assets.id') .select((eb) => eb.fn - .countAll() + .countAll() .filterWhere((eb) => eb.and([eb('assets.type', '=', AssetType.IMAGE), eb('assets.isVisible', '=', true)])) .as('photos'), ) .select((eb) => eb.fn - .countAll() + .countAll() .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, }; } diff --git a/server/src/repositories/map.repository.ts b/server/src/repositories/map.repository.ts index 3c4cf12ffd..3e2bcf4c2e 100644 --- a/server/src/repositories/map.repository.ts +++ b/server/src/repositories/map.repository.ts @@ -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; + .execute(); } async reverseGeocode(point: GeoPoint): Promise { diff --git a/server/src/repositories/partner.repository.ts b/server/src/repositories/partner.repository.ts index 489793aa77..ea762d0aaf 100644 --- a/server/src/repositories/partner.repository.ts +++ b/server/src/repositories/partner.repository.ts @@ -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; + .executeTakeFirst(); } @GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }] }) @@ -55,7 +55,8 @@ export class PartnerRepository { .returningAll() .returning(withSharedBy) .returning(withSharedWith) - .executeTakeFirstOrThrow() as Promise; + .$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; + .$narrowType<{ sharedWith: NotNull; sharedBy: NotNull }>() + .executeTakeFirstOrThrow(); } @GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }] }) diff --git a/server/src/repositories/user.repository.ts b/server/src/repositories/user.repository.ts index 1f42b8b194..e2e396f7b2 100644 --- a/server/src/repositories/user.repository.ts +++ b/server/src/repositories/user.repository.ts @@ -195,30 +195,34 @@ export class UserRepository { } @GenerateSql() - async getUserStats(): Promise { - 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() + .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() + .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('exif.fileSizeInByte').filterWhere('assets.libraryId', 'is', null), eb.lit(0)) .as('usage'), eb.fn .coalesce( eb.fn - .sum('exif.fileSizeInByte') + .sum('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('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] })