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
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
90 changed files with 1137 additions and 867 deletions

View file

@ -4,7 +4,7 @@ import { AssetFile } from 'src/database';
import { BulkIdErrorReason, BulkIdResponseDto } from 'src/dtos/asset-ids.response.dto';
import { UploadFieldName } from 'src/dtos/asset-media.dto';
import { AuthDto } from 'src/dtos/auth.dto';
import { AssetFileType, AssetType, Permission } from 'src/enum';
import { AssetFileType, AssetType, AssetVisibility, Permission } from 'src/enum';
import { AuthRequest } from 'src/middleware/auth.guard';
import { AccessRepository } from 'src/repositories/access.repository';
import { AssetRepository } from 'src/repositories/asset.repository';
@ -150,8 +150,8 @@ export const onBeforeLink = async (
throw new BadRequestException('Live photo video does not belong to the user');
}
if (motionAsset?.isVisible) {
await assetRepository.update({ id: livePhotoVideoId, isVisible: false });
if (motionAsset && motionAsset.visibility === AssetVisibility.TIMELINE) {
await assetRepository.update({ id: livePhotoVideoId, visibility: AssetVisibility.HIDDEN });
await eventRepository.emit('asset.hide', { assetId: motionAsset.id, userId });
}
};
@ -174,9 +174,9 @@ export const onBeforeUnlink = async (
export const onAfterUnlink = async (
{ asset: assetRepository, event: eventRepository }: AssetHookRepositories,
{ userId, livePhotoVideoId }: { userId: string; livePhotoVideoId: string },
{ userId, livePhotoVideoId, visibility }: { userId: string; livePhotoVideoId: string; visibility: AssetVisibility },
) => {
await assetRepository.update({ id: livePhotoVideoId, isVisible: true });
await assetRepository.update({ id: livePhotoVideoId, visibility });
await eventRepository.emit('asset.show', { assetId: livePhotoVideoId, userId });
};

View file

@ -17,7 +17,7 @@ import { parse } from 'pg-connection-string';
import postgres, { Notice } from 'postgres';
import { columns, Exif, Person } from 'src/database';
import { DB } from 'src/db';
import { AssetFileType, 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 { DatabaseConnectionParams, VectorExtension } from 'src/types';
@ -155,6 +155,15 @@ export function toJson<DB, TB extends keyof DB & string, T extends TB | Expressi
export const ASSET_CHECKSUM_CONSTRAINT = 'UQ_assets_owner_checksum';
// TODO come up with a better query that only selects the fields we need
export function withDefaultVisibility<O>(qb: SelectQueryBuilder<DB, 'assets', O>) {
return qb.where((qb) =>
qb.or([
qb('assets.visibility', '=', AssetVisibility.TIMELINE),
qb('assets.visibility', '=', AssetVisibility.ARCHIVE),
]),
);
}
export function withExif<O>(qb: SelectQueryBuilder<DB, 'assets', O>) {
return qb
.leftJoin('exif', 'assets.id', 'exif.assetId')
@ -280,12 +289,14 @@ const joinDeduplicationPlugin = new DeduplicateJoinsPlugin();
/** TODO: This should only be used for search-related queries, not as a general purpose query builder */
export function searchAssetBuilder(kysely: Kysely<DB>, options: AssetSearchBuilderOptions) {
options.isArchived ??= options.withArchived ? undefined : false;
options.withDeleted ||= !!(options.trashedAfter || options.trashedBefore || options.isOffline);
const visibility = options.visibility == null ? AssetVisibility.TIMELINE : options.visibility;
return kysely
.withPlugin(joinDeduplicationPlugin)
.selectFrom('assets')
.selectAll('assets')
.where('assets.visibility', '=', visibility)
.$if(!!options.tagIds && options.tagIds.length > 0, (qb) => hasTags(qb, options.tagIds!))
.$if(!!options.personIds && options.personIds.length > 0, (qb) => hasPeople(qb, options.personIds!))
.$if(!!options.createdBefore, (qb) => qb.where('assets.createdAt', '<=', options.createdBefore!))
@ -356,8 +367,6 @@ export function searchAssetBuilder(kysely: Kysely<DB>, options: AssetSearchBuild
.$if(!!options.type, (qb) => qb.where('assets.type', '=', options.type!))
.$if(options.isFavorite !== undefined, (qb) => qb.where('assets.isFavorite', '=', options.isFavorite!))
.$if(options.isOffline !== undefined, (qb) => qb.where('assets.isOffline', '=', options.isOffline!))
.$if(options.isVisible !== undefined, (qb) => qb.where('assets.isVisible', '=', options.isVisible!))
.$if(options.isArchived !== undefined, (qb) => qb.where('assets.isArchived', '=', options.isArchived!))
.$if(options.isEncoded !== undefined, (qb) =>
qb.where('assets.encodedVideoPath', options.isEncoded ? 'is not' : 'is', null),
)