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

@ -1,6 +1,6 @@
import { AssetFace, AssetFile, Exif } from 'src/database';
import { MapAsset } from 'src/dtos/asset-response.dto';
import { AssetFileType, AssetStatus, AssetType } from 'src/enum';
import { AssetFileType, AssetStatus, AssetType, AssetVisibility } from 'src/enum';
import { StorageAsset } from 'src/types';
import { authStub } from 'test/fixtures/auth.stub';
import { fileStub } from 'test/fixtures/file.stub';
@ -74,9 +74,7 @@ export const assetStub = {
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
isFavorite: true,
isArchived: false,
duration: null,
isVisible: true,
livePhotoVideo: null,
livePhotoVideoId: null,
sharedLinks: [],
@ -90,6 +88,7 @@ export const assetStub = {
libraryId: null,
stackId: null,
updateId: '42',
visibility: AssetVisibility.TIMELINE,
}),
noWebpPath: Object.freeze({
@ -111,9 +110,7 @@ export const assetStub = {
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
isFavorite: true,
isArchived: false,
duration: null,
isVisible: true,
livePhotoVideo: null,
livePhotoVideoId: null,
sharedLinks: [],
@ -130,6 +127,7 @@ export const assetStub = {
libraryId: null,
stackId: null,
updateId: '42',
visibility: AssetVisibility.TIMELINE,
}),
noThumbhash: Object.freeze({
@ -151,9 +149,7 @@ export const assetStub = {
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
isFavorite: true,
isArchived: false,
duration: null,
isVisible: true,
isExternal: false,
livePhotoVideo: null,
livePhotoVideoId: null,
@ -167,6 +163,7 @@ export const assetStub = {
libraryId: null,
stackId: null,
updateId: '42',
visibility: AssetVisibility.TIMELINE,
}),
primaryImage: Object.freeze({
@ -188,9 +185,7 @@ export const assetStub = {
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
isFavorite: true,
isArchived: false,
duration: null,
isVisible: true,
isExternal: false,
livePhotoVideo: null,
livePhotoVideoId: null,
@ -214,6 +209,7 @@ export const assetStub = {
isOffline: false,
updateId: '42',
libraryId: null,
visibility: AssetVisibility.TIMELINE,
}),
image: Object.freeze({
@ -235,9 +231,7 @@ export const assetStub = {
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
localDateTime: new Date('2025-01-01T01:02:03.456Z'),
isFavorite: true,
isArchived: false,
duration: null,
isVisible: true,
isExternal: false,
livePhotoVideo: null,
livePhotoVideoId: null,
@ -257,6 +251,7 @@ export const assetStub = {
duplicateId: null,
isOffline: false,
stack: null,
visibility: AssetVisibility.TIMELINE,
}),
trashed: Object.freeze({
@ -278,9 +273,7 @@ export const assetStub = {
deletedAt: new Date('2023-02-24T05:06:29.716Z'),
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
isFavorite: false,
isArchived: false,
duration: null,
isVisible: true,
isExternal: false,
livePhotoVideo: null,
livePhotoVideoId: null,
@ -299,6 +292,7 @@ export const assetStub = {
libraryId: null,
stackId: null,
updateId: '42',
visibility: AssetVisibility.TIMELINE,
}),
trashedOffline: Object.freeze({
@ -321,10 +315,8 @@ export const assetStub = {
deletedAt: new Date('2023-02-24T05:06:29.716Z'),
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
isFavorite: false,
isArchived: false,
duration: null,
libraryId: 'library-id',
isVisible: true,
isExternal: false,
livePhotoVideo: null,
livePhotoVideoId: null,
@ -341,6 +333,7 @@ export const assetStub = {
isOffline: true,
stackId: null,
updateId: '42',
visibility: AssetVisibility.TIMELINE,
}),
archived: Object.freeze({
id: 'asset-id',
@ -361,9 +354,7 @@ export const assetStub = {
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
isFavorite: true,
isArchived: true,
duration: null,
isVisible: true,
isExternal: false,
livePhotoVideo: null,
livePhotoVideoId: null,
@ -382,6 +373,7 @@ export const assetStub = {
libraryId: null,
stackId: null,
updateId: '42',
visibility: AssetVisibility.TIMELINE,
}),
external: Object.freeze({
@ -403,10 +395,8 @@ export const assetStub = {
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
isFavorite: true,
isArchived: false,
isExternal: true,
duration: null,
isVisible: true,
livePhotoVideo: null,
livePhotoVideoId: null,
libraryId: 'library-id',
@ -423,6 +413,7 @@ export const assetStub = {
updateId: '42',
stackId: null,
stack: null,
visibility: AssetVisibility.TIMELINE,
}),
image1: Object.freeze({
@ -445,9 +436,7 @@ export const assetStub = {
deletedAt: null,
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
isFavorite: true,
isArchived: false,
duration: null,
isVisible: true,
livePhotoVideo: null,
livePhotoVideoId: null,
isExternal: false,
@ -464,6 +453,7 @@ export const assetStub = {
stackId: null,
libraryId: null,
stack: null,
visibility: AssetVisibility.TIMELINE,
}),
imageFrom2015: Object.freeze({
@ -485,10 +475,8 @@ export const assetStub = {
updatedAt: new Date('2015-02-23T05:06:29.716Z'),
localDateTime: new Date('2015-02-23T05:06:29.716Z'),
isFavorite: true,
isArchived: false,
isExternal: false,
duration: null,
isVisible: true,
livePhotoVideo: null,
livePhotoVideoId: null,
sharedLinks: [],
@ -501,6 +489,7 @@ export const assetStub = {
deletedAt: null,
duplicateId: null,
isOffline: false,
visibility: AssetVisibility.TIMELINE,
}),
video: Object.freeze({
@ -523,10 +512,8 @@ export const assetStub = {
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
isFavorite: true,
isArchived: false,
isExternal: false,
duration: null,
isVisible: true,
livePhotoVideo: null,
livePhotoVideoId: null,
sharedLinks: [],
@ -543,6 +530,7 @@ export const assetStub = {
updateId: '42',
libraryId: null,
stackId: null,
visibility: AssetVisibility.TIMELINE,
}),
livePhotoMotionAsset: Object.freeze({
@ -551,7 +539,6 @@ export const assetStub = {
originalPath: fileStub.livePhotoMotion.originalPath,
ownerId: authStub.user1.user.id,
type: AssetType.VIDEO,
isVisible: false,
fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'),
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
exifInfo: {
@ -559,6 +546,7 @@ export const assetStub = {
timeZone: `America/New_York`,
},
libraryId: null,
visibility: AssetVisibility.HIDDEN,
} as MapAsset & { faces: AssetFace[]; files: AssetFile[]; exifInfo: Exif }),
livePhotoStillAsset: Object.freeze({
@ -568,7 +556,6 @@ export const assetStub = {
ownerId: authStub.user1.user.id,
type: AssetType.IMAGE,
livePhotoVideoId: 'live-photo-motion-asset',
isVisible: true,
fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'),
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
exifInfo: {
@ -577,6 +564,7 @@ export const assetStub = {
},
files,
faces: [] as AssetFace[],
visibility: AssetVisibility.TIMELINE,
} as MapAsset & { faces: AssetFace[] }),
livePhotoWithOriginalFileName: Object.freeze({
@ -587,7 +575,6 @@ export const assetStub = {
ownerId: authStub.user1.user.id,
type: AssetType.IMAGE,
livePhotoVideoId: 'live-photo-motion-asset',
isVisible: true,
fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'),
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
exifInfo: {
@ -596,6 +583,7 @@ export const assetStub = {
},
libraryId: null,
faces: [] as AssetFace[],
visibility: AssetVisibility.TIMELINE,
} as MapAsset & { faces: AssetFace[] }),
withLocation: Object.freeze({
@ -618,10 +606,8 @@ export const assetStub = {
updatedAt: new Date('2023-02-22T05:06:29.716Z'),
localDateTime: new Date('2020-12-31T23:59:00.000Z'),
isFavorite: false,
isArchived: false,
isExternal: false,
duration: null,
isVisible: true,
livePhotoVideo: null,
livePhotoVideoId: null,
updateId: 'foo',
@ -642,6 +628,7 @@ export const assetStub = {
duplicateId: null,
isOffline: false,
tags: [],
visibility: AssetVisibility.TIMELINE,
}),
sidecar: Object.freeze({
@ -663,10 +650,8 @@ export const assetStub = {
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
isFavorite: true,
isArchived: false,
isExternal: false,
duration: null,
isVisible: true,
livePhotoVideo: null,
livePhotoVideoId: null,
sharedLinks: [],
@ -679,6 +664,7 @@ export const assetStub = {
updateId: 'foo',
libraryId: null,
stackId: null,
visibility: AssetVisibility.TIMELINE,
}),
sidecarWithoutExt: Object.freeze({
@ -700,10 +686,8 @@ export const assetStub = {
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
isFavorite: true,
isArchived: false,
isExternal: false,
duration: null,
isVisible: true,
livePhotoVideo: null,
livePhotoVideoId: null,
sharedLinks: [],
@ -713,6 +697,7 @@ export const assetStub = {
deletedAt: null,
duplicateId: null,
isOffline: false,
visibility: AssetVisibility.TIMELINE,
}),
hasEncodedVideo: Object.freeze({
@ -735,10 +720,8 @@ export const assetStub = {
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
isFavorite: true,
isArchived: false,
isExternal: false,
duration: null,
isVisible: true,
livePhotoVideo: null,
livePhotoVideoId: null,
sharedLinks: [],
@ -754,6 +737,7 @@ export const assetStub = {
libraryId: null,
stackId: null,
stack: null,
visibility: AssetVisibility.TIMELINE,
}),
hasFileExtension: Object.freeze({
@ -775,10 +759,8 @@ export const assetStub = {
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
isFavorite: true,
isArchived: false,
isExternal: true,
duration: null,
isVisible: true,
livePhotoVideo: null,
livePhotoVideoId: null,
libraryId: 'library-id',
@ -792,6 +774,7 @@ export const assetStub = {
} as Exif,
duplicateId: null,
isOffline: false,
visibility: AssetVisibility.TIMELINE,
}),
imageDng: Object.freeze({
@ -813,9 +796,7 @@ export const assetStub = {
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
isFavorite: true,
isArchived: false,
duration: null,
isVisible: true,
isExternal: false,
livePhotoVideo: null,
livePhotoVideoId: null,
@ -834,6 +815,7 @@ export const assetStub = {
updateId: '42',
libraryId: null,
stackId: null,
visibility: AssetVisibility.TIMELINE,
}),
imageHif: Object.freeze({
@ -855,9 +837,7 @@ export const assetStub = {
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
isFavorite: true,
isArchived: false,
duration: null,
isVisible: true,
isExternal: false,
livePhotoVideo: null,
livePhotoVideoId: null,
@ -876,5 +856,6 @@ export const assetStub = {
updateId: '42',
libraryId: null,
stackId: null,
visibility: AssetVisibility.TIMELINE,
}),
};

View file

@ -4,7 +4,7 @@ import { AssetResponseDto, MapAsset } from 'src/dtos/asset-response.dto';
import { ExifResponseDto } from 'src/dtos/exif.dto';
import { SharedLinkResponseDto } from 'src/dtos/shared-link.dto';
import { mapUser } from 'src/dtos/user.dto';
import { AssetOrder, AssetStatus, AssetType, SharedLinkType } from 'src/enum';
import { AssetOrder, AssetStatus, AssetType, AssetVisibility, SharedLinkType } from 'src/enum';
import { assetStub } from 'test/fixtures/asset.stub';
import { authStub } from 'test/fixtures/auth.stub';
import { userStub } from 'test/fixtures/user.stub';
@ -206,7 +206,6 @@ export const sharedLinkStub = {
thumbhash: null,
encodedVideoPath: '',
duration: null,
isVisible: true,
livePhotoVideo: null,
livePhotoVideoId: null,
originalFileName: 'asset_1.jpeg',
@ -251,6 +250,7 @@ export const sharedLinkStub = {
updateId: '42',
libraryId: null,
stackId: null,
visibility: AssetVisibility.TIMELINE,
},
],
},

View file

@ -5,7 +5,7 @@ import { createHash, randomBytes } from 'node:crypto';
import { Writable } from 'node:stream';
import { AssetFace } from 'src/database';
import { AssetJobStatus, Assets, DB, FaceSearch, Person, Sessions } from 'src/db';
import { AssetType, SourceType } from 'src/enum';
import { AssetType, AssetVisibility, SourceType } from 'src/enum';
import { ActivityRepository } from 'src/repositories/activity.repository';
import { AlbumRepository } from 'src/repositories/album.repository';
import { AssetJobRepository } from 'src/repositories/asset-job.repository';
@ -227,16 +227,37 @@ const getRepositoryMock = <K extends keyof RepositoryMocks>(key: K) => {
case 'database': {
return automock(DatabaseRepository, {
args: [undefined, { setContext: () => {} }, { getEnv: () => ({ database: { vectorExtension: '' } }) }],
args: [
undefined,
{
setContext: () => {},
},
{ getEnv: () => ({ database: { vectorExtension: '' } }) },
],
});
}
case 'email': {
return automock(EmailRepository, { args: [{ setContext: () => {} }] });
return automock(EmailRepository, {
args: [
{
setContext: () => {},
},
],
});
}
case 'job': {
return automock(JobRepository, { args: [undefined, undefined, undefined, { setContext: () => {} }] });
return automock(JobRepository, {
args: [
undefined,
undefined,
undefined,
{
setContext: () => {},
},
],
});
}
case 'logger': {
@ -345,11 +366,11 @@ const assetInsert = (asset: Partial<Insertable<Assets>> = {}) => {
type: AssetType.IMAGE,
originalPath: '/path/to/something.jpg',
ownerId: '@immich.cloud',
isVisible: true,
isFavorite: false,
fileCreatedAt: now,
fileModifiedAt: now,
localDateTime: now,
visibility: AssetVisibility.TIMELINE,
};
return {

View file

@ -456,9 +456,9 @@ describe(SyncService.name, () => {
fileCreatedAt: asset.fileCreatedAt,
fileModifiedAt: asset.fileModifiedAt,
isFavorite: asset.isFavorite,
isVisible: asset.isVisible,
localDateTime: asset.localDateTime,
type: asset.type,
visibility: asset.visibility,
},
type: 'AssetV1',
},
@ -573,9 +573,9 @@ describe(SyncService.name, () => {
fileCreatedAt: date,
fileModifiedAt: date,
isFavorite: false,
isVisible: true,
localDateTime: date,
type: asset.type,
visibility: asset.visibility,
},
type: SyncEntityType.PartnerAssetV1,
},

View file

@ -15,7 +15,7 @@ import {
} from 'src/database';
import { MapAsset } from 'src/dtos/asset-response.dto';
import { AuthDto } from 'src/dtos/auth.dto';
import { AssetStatus, AssetType, MemoryType, Permission, UserStatus } from 'src/enum';
import { AssetStatus, AssetType, AssetVisibility, MemoryType, Permission, UserStatus } from 'src/enum';
import { OnThisDayData } from 'src/types';
export const newUuid = () => randomUUID() as string;
@ -202,11 +202,9 @@ const assetFactory = (asset: Partial<MapAsset> = {}) => ({
encodedVideoPath: null,
fileCreatedAt: newDate(),
fileModifiedAt: newDate(),
isArchived: false,
isExternal: false,
isFavorite: false,
isOffline: false,
isVisible: true,
libraryId: null,
livePhotoVideoId: null,
localDateTime: newDate(),
@ -217,6 +215,7 @@ const assetFactory = (asset: Partial<MapAsset> = {}) => ({
stackId: null,
thumbhash: null,
type: AssetType.IMAGE,
visibility: AssetVisibility.TIMELINE,
...asset,
});