feat(server): visibility column (#17939)

* feat: private view

* pr feedback

* sql generation

* feat: visibility column

* fix: set visibility value as the same as the still part after unlinked live photos

* fix: test

* pr feedback
This commit is contained in:
Alex
2025-05-06 12:12:48 -05:00
committed by GitHub
parent 016d7a6ceb
commit d33ce13561
90 changed files with 1137 additions and 867 deletions

View File

@@ -3,7 +3,7 @@ import { Kysely, sql } from 'kysely';
import { InjectKysely } from 'nestjs-kysely';
import { DB } from 'src/db';
import { ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
import { AlbumUserRole } from 'src/enum';
import { AlbumUserRole, AssetVisibility } from 'src/enum';
import { asUuid } from 'src/utils/database';
class ActivityAccess {
@@ -199,7 +199,13 @@ class AssetAccess {
)
.select('assets.id')
.where('partner.sharedWithId', '=', userId)
.where('assets.isArchived', '=', false)
.where((eb) =>
eb.or([
eb('assets.visibility', '=', sql.lit(AssetVisibility.TIMELINE)),
eb('assets.visibility', '=', sql.lit(AssetVisibility.HIDDEN)),
]),
)
.where('assets.id', 'in', [...assetIds])
.execute()
.then((assets) => new Set(assets.map((asset) => asset.id)));

View File

@@ -5,7 +5,7 @@ import { InjectKysely } from 'nestjs-kysely';
import { Asset, columns } from 'src/database';
import { DB } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { AssetFileType, AssetType } from 'src/enum';
import { AssetFileType, AssetType, AssetVisibility } from 'src/enum';
import { StorageAsset } from 'src/types';
import {
anyUuid,
@@ -34,7 +34,7 @@ export class AssetJobRepository {
'ownerId',
'duplicateId',
'stackId',
'isVisible',
'visibility',
'smart_search.embedding',
withFiles(eb, AssetFileType.PREVIEW),
])
@@ -70,7 +70,7 @@ export class AssetJobRepository {
.select(['assets.id', 'assets.thumbhash'])
.select(withFiles)
.where('assets.deletedAt', 'is', null)
.where('assets.isVisible', '=', true)
.where('assets.visibility', '!=', AssetVisibility.HIDDEN)
.$if(!force, (qb) =>
qb
// If there aren't any entries, metadata extraction hasn't run yet which is required for thumbnails
@@ -102,7 +102,7 @@ export class AssetJobRepository {
.selectFrom('assets')
.select([
'assets.id',
'assets.isVisible',
'assets.visibility',
'assets.originalFileName',
'assets.originalPath',
'assets.ownerId',
@@ -138,7 +138,7 @@ export class AssetJobRepository {
private assetsWithPreviews() {
return this.db
.selectFrom('assets')
.where('assets.isVisible', '=', true)
.where('assets.visibility', '!=', AssetVisibility.HIDDEN)
.where('assets.deletedAt', 'is', null)
.innerJoin('asset_job_status as job_status', 'assetId', 'assets.id')
.where('job_status.previewAt', 'is not', null);
@@ -169,7 +169,7 @@ export class AssetJobRepository {
getForClipEncoding(id: string) {
return this.db
.selectFrom('assets')
.select(['assets.id', 'assets.isVisible'])
.select(['assets.id', 'assets.visibility'])
.select((eb) => withFiles(eb, AssetFileType.PREVIEW))
.where('assets.id', '=', id)
.executeTakeFirst();
@@ -179,7 +179,7 @@ export class AssetJobRepository {
getForDetectFacesJob(id: string) {
return this.db
.selectFrom('assets')
.select(['assets.id', 'assets.isVisible'])
.select(['assets.id', 'assets.visibility'])
.$call(withExifInner)
.select((eb) => withFaces(eb, true))
.select((eb) => withFiles(eb, AssetFileType.PREVIEW))
@@ -209,7 +209,7 @@ export class AssetJobRepository {
.selectFrom('assets')
.select([
'assets.id',
'assets.isVisible',
'assets.visibility',
'assets.libraryId',
'assets.ownerId',
'assets.livePhotoVideoId',
@@ -228,7 +228,7 @@ export class AssetJobRepository {
.select(['asset_stack.id', 'asset_stack.primaryAssetId'])
.select((eb) => eb.fn<Asset[]>('array_agg', [eb.table('stacked')]).as('assets'))
.where('stacked.deletedAt', 'is not', null)
.where('stacked.isArchived', '=', false)
.where('stacked.visibility', '!=', AssetVisibility.ARCHIVE)
.whereRef('stacked.stackId', '=', 'asset_stack.id')
.groupBy('asset_stack.id')
.as('stacked_assets'),
@@ -248,7 +248,7 @@ export class AssetJobRepository {
.$if(!force, (qb) =>
qb
.where((eb) => eb.or([eb('assets.encodedVideoPath', 'is', null), eb('assets.encodedVideoPath', '=', '')]))
.where('assets.isVisible', '=', true),
.where('assets.visibility', '!=', AssetVisibility.HIDDEN),
)
.where('assets.deletedAt', 'is', null)
.stream();
@@ -275,7 +275,7 @@ export class AssetJobRepository {
.where((eb) =>
eb.or([eb('asset_job_status.metadataExtractedAt', 'is', null), eb('asset_job_status.assetId', 'is', null)]),
)
.where('assets.isVisible', '=', true),
.where('assets.visibility', '!=', AssetVisibility.HIDDEN),
)
.where('assets.deletedAt', 'is', null)
.stream();
@@ -331,7 +331,7 @@ export class AssetJobRepository {
.$if(!force, (qb) =>
qb.where((eb) => eb.or([eb('assets.sidecarPath', '=', ''), eb('assets.sidecarPath', 'is', null)])),
)
.where('assets.isVisible', '=', true)
.where('assets.visibility', '!=', AssetVisibility.HIDDEN)
.stream();
}

View File

@@ -6,7 +6,7 @@ import { Stack } from 'src/database';
import { AssetFiles, AssetJobStatus, Assets, DB, Exif } from 'src/db';
import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
import { MapAsset } from 'src/dtos/asset-response.dto';
import { AssetFileType, AssetOrder, AssetStatus, AssetType } from 'src/enum';
import { AssetFileType, AssetOrder, AssetStatus, AssetType, AssetVisibility } from 'src/enum';
import {
anyUuid,
asUuid,
@@ -14,6 +14,7 @@ import {
removeUndefinedKeys,
truncatedDate,
unnest,
withDefaultVisibility,
withExif,
withFaces,
withFacesAndPeople,
@@ -30,8 +31,8 @@ export type AssetStats = Record<AssetType, number>;
export interface AssetStatsOptions {
isFavorite?: boolean;
isArchived?: boolean;
isTrashed?: boolean;
visibility?: AssetVisibility;
}
export interface LivePhotoSearchOptions {
@@ -52,7 +53,6 @@ export enum TimeBucketSize {
}
export interface AssetBuilderOptions {
isArchived?: boolean;
isFavorite?: boolean;
isTrashed?: boolean;
isDuplicate?: boolean;
@@ -64,6 +64,7 @@ export interface AssetBuilderOptions {
exifInfo?: boolean;
status?: AssetStatus;
assetType?: AssetType;
visibility?: AssetVisibility;
}
export interface TimeBucketOptions extends AssetBuilderOptions {
@@ -258,8 +259,7 @@ export class AssetRepository {
.where('asset_job_status.previewAt', 'is not', null)
.where(sql`(assets."localDateTime" at time zone 'UTC')::date`, '=', sql`today.date`)
.where('assets.ownerId', '=', anyUuid(ownerIds))
.where('assets.isVisible', '=', true)
.where('assets.isArchived', '=', false)
.where('assets.visibility', '=', AssetVisibility.TIMELINE)
.where((eb) =>
eb.exists((qb) =>
qb
@@ -348,7 +348,7 @@ export class AssetRepository {
.select(['deviceAssetId'])
.where('ownerId', '=', asUuid(ownerId))
.where('deviceId', '=', deviceId)
.where('isVisible', '=', true)
.where('visibility', '!=', AssetVisibility.HIDDEN)
.where('deletedAt', 'is', null)
.execute();
@@ -393,7 +393,7 @@ export class AssetRepository {
.whereRef('stacked.stackId', '=', 'asset_stack.id')
.whereRef('stacked.id', '!=', 'asset_stack.primaryAssetId')
.where('stacked.deletedAt', 'is', null)
.where('stacked.isArchived', '=', false)
.where('stacked.visibility', '=', AssetVisibility.TIMELINE)
.groupBy('asset_stack.id')
.as('stacked_assets'),
(join) => join.on('asset_stack.id', 'is not', null),
@@ -503,7 +503,7 @@ export class AssetRepository {
.executeTakeFirst();
}
getStatistics(ownerId: string, { isArchived, isFavorite, isTrashed }: AssetStatsOptions): Promise<AssetStats> {
getStatistics(ownerId: string, { visibility, isFavorite, isTrashed }: AssetStatsOptions): Promise<AssetStats> {
return this.db
.selectFrom('assets')
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.AUDIO).as(AssetType.AUDIO))
@@ -511,8 +511,8 @@ export class AssetRepository {
.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('isVisible', '=', true)
.$if(isArchived !== undefined, (qb) => qb.where('isArchived', '=', isArchived!))
.$if(visibility === undefined, withDefaultVisibility)
.$if(!!visibility, (qb) => qb.where('assets.visibility', '=', visibility!))
.$if(isFavorite !== undefined, (qb) => qb.where('isFavorite', '=', isFavorite!))
.$if(!!isTrashed, (qb) => qb.where('assets.status', '!=', AssetStatus.DELETED))
.where('deletedAt', isTrashed ? 'is not' : 'is', null)
@@ -525,7 +525,7 @@ export class AssetRepository {
.selectAll('assets')
.$call(withExif)
.where('ownerId', '=', anyUuid(userIds))
.where('isVisible', '=', true)
.where('visibility', '!=', AssetVisibility.HIDDEN)
.where('deletedAt', 'is', null)
.orderBy((eb) => eb.fn('random'))
.limit(take)
@@ -542,7 +542,8 @@ export class AssetRepository {
.select(truncatedDate<Date>(options.size).as('timeBucket'))
.$if(!!options.isTrashed, (qb) => qb.where('assets.status', '!=', AssetStatus.DELETED))
.where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null)
.where('assets.isVisible', '=', true)
.$if(options.visibility === undefined, withDefaultVisibility)
.$if(!!options.visibility, (qb) => qb.where('assets.visibility', '=', options.visibility!))
.$if(!!options.albumId, (qb) =>
qb
.innerJoin('albums_assets_assets', 'assets.id', 'albums_assets_assets.assetsId')
@@ -559,7 +560,6 @@ export class AssetRepository {
.where((eb) => eb.or([eb('assets.stackId', 'is', null), eb(eb.table('asset_stack'), 'is not', null)])),
)
.$if(!!options.userIds, (qb) => qb.where('assets.ownerId', '=', anyUuid(options.userIds!)))
.$if(options.isArchived !== undefined, (qb) => qb.where('assets.isArchived', '=', options.isArchived!))
.$if(options.isFavorite !== undefined, (qb) => qb.where('assets.isFavorite', '=', options.isFavorite!))
.$if(!!options.assetType, (qb) => qb.where('assets.type', '=', options.assetType!))
.$if(options.isDuplicate !== undefined, (qb) =>
@@ -594,7 +594,6 @@ export class AssetRepository {
)
.$if(!!options.personId, (qb) => hasPeople(qb, [options.personId!]))
.$if(!!options.userIds, (qb) => qb.where('assets.ownerId', '=', anyUuid(options.userIds!)))
.$if(options.isArchived !== undefined, (qb) => qb.where('assets.isArchived', '=', options.isArchived!))
.$if(options.isFavorite !== undefined, (qb) => qb.where('assets.isFavorite', '=', options.isFavorite!))
.$if(!!options.withStacked, (qb) =>
qb
@@ -610,7 +609,7 @@ export class AssetRepository {
.select((eb) => eb.fn.count(eb.table('stacked')).as('assetCount'))
.whereRef('stacked.stackId', '=', 'asset_stack.id')
.where('stacked.deletedAt', 'is', null)
.where('stacked.isArchived', '=', false)
.where('stacked.visibility', '!=', AssetVisibility.ARCHIVE)
.groupBy('asset_stack.id')
.as('stacked_assets'),
(join) => join.on('asset_stack.id', 'is not', null),
@@ -624,7 +623,8 @@ export class AssetRepository {
.$if(!!options.isTrashed, (qb) => qb.where('assets.status', '!=', AssetStatus.DELETED))
.$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!))
.where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null)
.where('assets.isVisible', '=', true)
.$if(options.visibility == undefined, withDefaultVisibility)
.$if(!!options.visibility, (qb) => qb.where('assets.visibility', '=', options.visibility!))
.where(truncatedDate(options.size), '=', timeBucket.replace(/^[+-]/, ''))
.orderBy('assets.localDateTime', options.order ?? 'desc')
.execute();
@@ -658,7 +658,7 @@ export class AssetRepository {
.where('assets.duplicateId', 'is not', null)
.$narrowType<{ duplicateId: NotNull }>()
.where('assets.deletedAt', 'is', null)
.where('assets.isVisible', '=', true)
.where('assets.visibility', '!=', AssetVisibility.HIDDEN)
.where('assets.stackId', 'is', null)
.groupBy('assets.duplicateId'),
)
@@ -703,8 +703,7 @@ export class AssetRepository {
.select(['assetId as data', 'exif.city as value'])
.$narrowType<{ value: NotNull }>()
.where('ownerId', '=', asUuid(ownerId))
.where('isVisible', '=', true)
.where('isArchived', '=', false)
.where('visibility', '=', AssetVisibility.TIMELINE)
.where('type', '=', AssetType.IMAGE)
.where('deletedAt', 'is', null)
.limit(maxFields)
@@ -743,7 +742,7 @@ export class AssetRepository {
)
.select((eb) => eb.fn.toJson(eb.table('stacked_assets')).$castTo<Stack | null>().as('stack'))
.where('assets.ownerId', '=', asUuid(ownerId))
.where('assets.isVisible', '=', true)
.where('assets.visibility', '!=', AssetVisibility.HIDDEN)
.where('assets.updatedAt', '<=', updatedUntil)
.$if(!!lastId, (qb) => qb.where('assets.id', '>', lastId!))
.orderBy('assets.id')
@@ -771,7 +770,7 @@ export class AssetRepository {
)
.select((eb) => eb.fn.toJson(eb.table('stacked_assets').$castTo<Stack | null>()).as('stack'))
.where('assets.ownerId', '=', anyUuid(options.userIds))
.where('assets.isVisible', '=', true)
.where('assets.visibility', '!=', AssetVisibility.HIDDEN)
.where('assets.updatedAt', '>', options.updatedAfter)
.limit(options.limit)
.execute();

View File

@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { Kysely } from 'kysely';
import { InjectKysely } from 'nestjs-kysely';
import { DB } from 'src/db';
import { AssetVisibility } from 'src/enum';
import { anyUuid } from 'src/utils/database';
const builder = (db: Kysely<DB>) =>
@@ -31,6 +32,9 @@ export class DownloadRepository {
}
downloadUserId(userId: string) {
return builder(this.db).where('assets.ownerId', '=', userId).where('assets.isVisible', '=', true).stream();
return builder(this.db)
.where('assets.ownerId', '=', userId)
.where('assets.visibility', '!=', AssetVisibility.HIDDEN)
.stream();
}
}

View File

@@ -4,7 +4,7 @@ import { InjectKysely } from 'nestjs-kysely';
import { DB, Libraries } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { LibraryStatsResponseDto } from 'src/dtos/library.dto';
import { AssetType } from 'src/enum';
import { AssetType, AssetVisibility } from 'src/enum';
export enum AssetSyncResult {
DO_NOTHING,
@@ -77,13 +77,17 @@ export class LibraryRepository {
.select((eb) =>
eb.fn
.countAll<number>()
.filterWhere((eb) => eb.and([eb('assets.type', '=', AssetType.IMAGE), eb('assets.isVisible', '=', true)]))
.filterWhere((eb) =>
eb.and([eb('assets.type', '=', AssetType.IMAGE), eb('assets.visibility', '!=', AssetVisibility.HIDDEN)]),
)
.as('photos'),
)
.select((eb) =>
eb.fn
.countAll<number>()
.filterWhere((eb) => eb.and([eb('assets.type', '=', AssetType.VIDEO), eb('assets.isVisible', '=', true)]))
.filterWhere((eb) =>
eb.and([eb('assets.type', '=', AssetType.VIDEO), eb('assets.visibility', '!=', AssetVisibility.HIDDEN)]),
)
.as('videos'),
)
.select((eb) => eb.fn.coalesce((eb) => eb.fn.sum('exif.fileSizeInByte'), eb.val(0)).as('usage'))

View File

@@ -8,7 +8,7 @@ import readLine from 'node:readline';
import { citiesFile } from 'src/constants';
import { DB, GeodataPlaces, NaturalearthCountries } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { SystemMetadataKey } from 'src/enum';
import { AssetVisibility, SystemMetadataKey } from 'src/enum';
import { ConfigRepository } from 'src/repositories/config.repository';
import { LoggingRepository } from 'src/repositories/logging.repository';
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
@@ -75,9 +75,11 @@ export class MapRepository {
}
@GenerateSql({ params: [[DummyValue.UUID], [DummyValue.UUID]] })
getMapMarkers(ownerIds: string[], albumIds: string[], options: MapMarkerSearchOptions = {}) {
const { isArchived, isFavorite, fileCreatedAfter, fileCreatedBefore } = options;
getMapMarkers(
ownerIds: string[],
albumIds: string[],
{ isArchived, isFavorite, fileCreatedAfter, fileCreatedBefore }: MapMarkerSearchOptions = {},
) {
return this.db
.selectFrom('assets')
.innerJoin('exif', (builder) =>
@@ -88,8 +90,17 @@ export class MapRepository {
)
.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(isArchived === true, (qb) =>
qb.where((eb) =>
eb.or([
eb('assets.visibility', '=', AssetVisibility.TIMELINE),
eb('assets.visibility', '=', AssetVisibility.ARCHIVE),
]),
),
)
.$if(isArchived === false || isArchived === undefined, (qb) =>
qb.where('assets.visibility', '=', AssetVisibility.TIMELINE),
)
.$if(isFavorite !== undefined, (q) => q.where('isFavorite', '=', isFavorite!))
.$if(fileCreatedAfter !== undefined, (q) => q.where('fileCreatedAt', '>=', fileCreatedAfter!))
.$if(fileCreatedBefore !== undefined, (q) => q.where('fileCreatedAt', '<=', fileCreatedBefore!))

View File

@@ -4,7 +4,7 @@ import { jsonObjectFrom } from 'kysely/helpers/postgres';
import { InjectKysely } from 'nestjs-kysely';
import { AssetFaces, DB, FaceSearch, Person } from 'src/db';
import { ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
import { AssetFileType, SourceType } from 'src/enum';
import { AssetFileType, AssetVisibility, SourceType } from 'src/enum';
import { removeUndefinedKeys } from 'src/utils/database';
import { paginationHelper, PaginationOptions } from 'src/utils/pagination';
@@ -157,7 +157,7 @@ export class PersonRepository {
.innerJoin('assets', (join) =>
join
.onRef('asset_faces.assetId', '=', 'assets.id')
.on('assets.isArchived', '=', false)
.on('assets.visibility', '!=', AssetVisibility.ARCHIVE)
.on('assets.deletedAt', 'is', null),
)
.where('person.ownerId', '=', userId)
@@ -248,7 +248,7 @@ export class PersonRepository {
jsonObjectFrom(
eb
.selectFrom('assets')
.select(['assets.ownerId', 'assets.isArchived', 'assets.fileCreatedAt'])
.select(['assets.ownerId', 'assets.visibility', 'assets.fileCreatedAt'])
.whereRef('assets.id', '=', 'asset_faces.assetId'),
).as('asset'),
)
@@ -346,7 +346,7 @@ export class PersonRepository {
join
.onRef('assets.id', '=', 'asset_faces.assetId')
.on('asset_faces.personId', '=', personId)
.on('assets.isArchived', '=', false)
.on('assets.visibility', '!=', AssetVisibility.ARCHIVE)
.on('assets.deletedAt', 'is', null),
)
.select((eb) => eb.fn.count(eb.fn('distinct', ['assets.id'])).as('count'))
@@ -369,7 +369,7 @@ export class PersonRepository {
join
.onRef('assets.id', '=', 'asset_faces.assetId')
.on('assets.deletedAt', 'is', null)
.on('assets.isArchived', '=', false),
.on('assets.visibility', '!=', AssetVisibility.ARCHIVE),
)
.select((eb) => eb.fn.count(eb.fn('distinct', ['person.id'])).as('total'))
.select((eb) =>

View File

@@ -5,7 +5,7 @@ import { randomUUID } from 'node:crypto';
import { DB, Exif } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { MapAsset } from 'src/dtos/asset-response.dto';
import { AssetStatus, AssetType } from 'src/enum';
import { AssetStatus, AssetType, AssetVisibility } from 'src/enum';
import { ConfigRepository } from 'src/repositories/config.repository';
import { anyUuid, asUuid, searchAssetBuilder, vectorIndexQuery } from 'src/utils/database';
import { paginationHelper } from 'src/utils/pagination';
@@ -26,17 +26,16 @@ export interface SearchUserIdOptions {
export type SearchIdOptions = SearchAssetIdOptions & SearchUserIdOptions;
export interface SearchStatusOptions {
isArchived?: boolean;
isEncoded?: boolean;
isFavorite?: boolean;
isMotion?: boolean;
isOffline?: boolean;
isVisible?: boolean;
isNotInAlbum?: boolean;
type?: AssetType;
status?: AssetStatus;
withArchived?: boolean;
withDeleted?: boolean;
visibility?: AssetVisibility;
}
export interface SearchOneToOneRelationOptions {
@@ -276,7 +275,7 @@ export class SearchRepository {
.innerJoin('smart_search', 'assets.id', 'smart_search.assetId')
.where('assets.ownerId', '=', anyUuid(userIds))
.where('assets.deletedAt', 'is', null)
.where('assets.isVisible', '=', true)
.where('assets.visibility', '!=', AssetVisibility.HIDDEN)
.where('assets.type', '=', type)
.where('assets.id', '!=', asUuid(assetId))
.where('assets.stackId', 'is', null)
@@ -367,8 +366,7 @@ export class SearchRepository {
.select(['city', 'assetId'])
.innerJoin('assets', 'assets.id', 'exif.assetId')
.where('assets.ownerId', '=', anyUuid(userIds))
.where('assets.isVisible', '=', true)
.where('assets.isArchived', '=', false)
.where('assets.visibility', '=', AssetVisibility.TIMELINE)
.where('assets.type', '=', AssetType.IMAGE)
.where('assets.deletedAt', 'is', null)
.orderBy('city')
@@ -384,8 +382,7 @@ export class SearchRepository {
.select(['city', 'assetId'])
.innerJoin('assets', 'assets.id', 'exif.assetId')
.where('assets.ownerId', '=', anyUuid(userIds))
.where('assets.isVisible', '=', true)
.where('assets.isArchived', '=', false)
.where('assets.visibility', '=', AssetVisibility.TIMELINE)
.where('assets.type', '=', AssetType.IMAGE)
.where('assets.deletedAt', 'is', null)
.whereRef('exif.city', '>', 'cte.city')
@@ -518,7 +515,7 @@ export class SearchRepository {
.distinctOn(field)
.innerJoin('assets', 'assets.id', 'exif.assetId')
.where('ownerId', '=', anyUuid(userIds))
.where('isVisible', '=', true)
.where('visibility', '!=', AssetVisibility.HIDDEN)
.where('deletedAt', 'is', null)
.where(field, 'is not', null);
}

View File

@@ -6,7 +6,7 @@ import { InjectKysely } from 'nestjs-kysely';
import { columns } from 'src/database';
import { DB, UserMetadata as DbUserMetadata } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { AssetType, UserStatus } from 'src/enum';
import { AssetType, AssetVisibility, UserStatus } from 'src/enum';
import { UserTable } from 'src/schema/tables/user.table';
import { UserMetadata, UserMetadataItem } from 'src/types';
import { asUuid } from 'src/utils/database';
@@ -205,13 +205,19 @@ export class UserRepository {
eb.fn
.countAll<number>()
.filterWhere((eb) =>
eb.and([eb('assets.type', '=', sql.lit(AssetType.IMAGE)), eb('assets.isVisible', '=', sql.lit(true))]),
eb.and([
eb('assets.type', '=', sql.lit(AssetType.IMAGE)),
eb('assets.visibility', '!=', sql.lit(AssetVisibility.HIDDEN)),
]),
)
.as('photos'),
eb.fn
.countAll<number>()
.filterWhere((eb) =>
eb.and([eb('assets.type', '=', sql.lit(AssetType.VIDEO)), eb('assets.isVisible', '=', sql.lit(true))]),
eb.and([
eb('assets.type', '=', sql.lit(AssetType.VIDEO)),
eb('assets.visibility', '!=', sql.lit(AssetVisibility.HIDDEN)),
]),
)
.as('videos'),
eb.fn

View File

@@ -2,6 +2,7 @@ import { Kysely } from 'kysely';
import { InjectKysely } from 'nestjs-kysely';
import { DB } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { AssetVisibility } from 'src/enum';
import { asUuid, withExif } from 'src/utils/database';
export class ViewRepository {
@@ -14,8 +15,7 @@ export class ViewRepository {
.select((eb) => eb.fn<string>('substring', ['assets.originalPath', eb.val('^(.*/)[^/]*$')]).as('directoryPath'))
.distinct()
.where('ownerId', '=', asUuid(userId))
.where('isVisible', '=', true)
.where('isArchived', '=', false)
.where('visibility', '=', AssetVisibility.TIMELINE)
.where('deletedAt', 'is', null)
.where('fileCreatedAt', 'is not', null)
.where('fileModifiedAt', 'is not', null)
@@ -34,8 +34,7 @@ export class ViewRepository {
.selectAll('assets')
.$call(withExif)
.where('ownerId', '=', asUuid(userId))
.where('isVisible', '=', true)
.where('isArchived', '=', false)
.where('visibility', '=', AssetVisibility.TIMELINE)
.where('deletedAt', 'is', null)
.where('fileCreatedAt', 'is not', null)
.where('fileModifiedAt', 'is not', null)