1
0
mirror of https://github.com/immich-app/immich.git synced 2025-08-09 23:17:29 +02:00

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,6 +3,7 @@ import {
AssetMediaStatus,
AssetResponseDto,
AssetTypeEnum,
AssetVisibility,
getAssetInfo,
getMyUser,
LoginResponseDto,
@@ -119,9 +120,9 @@ describe('/asset', () => {
// stats
utils.createAsset(statsUser.accessToken),
utils.createAsset(statsUser.accessToken, { isFavorite: true }),
utils.createAsset(statsUser.accessToken, { isArchived: true }),
utils.createAsset(statsUser.accessToken, { visibility: AssetVisibility.Archive }),
utils.createAsset(statsUser.accessToken, {
isArchived: true,
visibility: AssetVisibility.Archive,
isFavorite: true,
assetData: { filename: 'example.mp4' },
}),
@@ -309,7 +310,7 @@ describe('/asset', () => {
});
it('disallows viewing archived assets', async () => {
const asset = await utils.createAsset(user1.accessToken, { isArchived: true });
const asset = await utils.createAsset(user1.accessToken, { visibility: AssetVisibility.Archive });
const { status } = await request(app)
.get(`/assets/${asset.id}`)
@@ -353,7 +354,7 @@ describe('/asset', () => {
const { status, body } = await request(app)
.get('/assets/statistics')
.set('Authorization', `Bearer ${statsUser.accessToken}`)
.query({ isArchived: true });
.query({ visibility: AssetVisibility.Archive });
expect(status).toBe(200);
expect(body).toEqual({ images: 1, videos: 1, total: 2 });
@@ -363,7 +364,7 @@ describe('/asset', () => {
const { status, body } = await request(app)
.get('/assets/statistics')
.set('Authorization', `Bearer ${statsUser.accessToken}`)
.query({ isFavorite: true, isArchived: true });
.query({ isFavorite: true, visibility: AssetVisibility.Archive });
expect(status).toBe(200);
expect(body).toEqual({ images: 0, videos: 1, total: 1 });
@@ -373,7 +374,7 @@ describe('/asset', () => {
const { status, body } = await request(app)
.get('/assets/statistics')
.set('Authorization', `Bearer ${statsUser.accessToken}`)
.query({ isFavorite: false, isArchived: false });
.query({ isFavorite: false, visibility: AssetVisibility.Timeline });
expect(status).toBe(200);
expect(body).toEqual({ images: 1, videos: 0, total: 1 });
@@ -459,7 +460,7 @@ describe('/asset', () => {
const { status, body } = await request(app)
.put(`/assets/${user1Assets[0].id}`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ isArchived: true });
.send({ visibility: AssetVisibility.Archive });
expect(body).toMatchObject({ id: user1Assets[0].id, isArchived: true });
expect(status).toEqual(200);
});

View File

@@ -1,4 +1,4 @@
import { LoginResponseDto } from '@immich/sdk';
import { AssetVisibility, LoginResponseDto } from '@immich/sdk';
import { readFile } from 'node:fs/promises';
import { basename, join } from 'node:path';
import { Socket } from 'socket.io-client';
@@ -44,7 +44,7 @@ describe('/map', () => {
it('should get map markers for all non-archived assets', async () => {
const { status, body } = await request(app)
.get('/map/markers')
.query({ isArchived: false })
.query({ visibility: AssetVisibility.Timeline })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);

View File

@@ -1,4 +1,11 @@
import { AssetMediaResponseDto, AssetResponseDto, deleteAssets, LoginResponseDto, updateAsset } from '@immich/sdk';
import {
AssetMediaResponseDto,
AssetResponseDto,
AssetVisibility,
deleteAssets,
LoginResponseDto,
updateAsset,
} from '@immich/sdk';
import { DateTime } from 'luxon';
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
@@ -49,7 +56,7 @@ describe('/search', () => {
{ filename: '/formats/motionphoto/samsung-one-ui-6.heic' },
{ filename: '/formats/motionphoto/samsung-one-ui-5.jpg' },
{ filename: '/metadata/gps-position/thompson-springs.jpg', dto: { isArchived: true } },
{ filename: '/metadata/gps-position/thompson-springs.jpg', dto: { visibility: AssetVisibility.Archive } },
// used for search suggestions
{ filename: '/formats/png/density_plot.png' },
@@ -171,12 +178,12 @@ describe('/search', () => {
deferred: () => ({ dto: { size: 1, isFavorite: false }, assets: [assetLast] }),
},
{
should: 'should search by isArchived (true)',
deferred: () => ({ dto: { isArchived: true }, assets: [assetSprings] }),
should: 'should search by visibility (AssetVisibility.Archive)',
deferred: () => ({ dto: { visibility: AssetVisibility.Archive }, assets: [assetSprings] }),
},
{
should: 'should search by isArchived (false)',
deferred: () => ({ dto: { size: 1, isArchived: false }, assets: [assetLast] }),
should: 'should search by visibility (AssetVisibility.Timeline)',
deferred: () => ({ dto: { size: 1, visibility: AssetVisibility.Timeline }, assets: [assetLast] }),
},
{
should: 'should search by type (image)',
@@ -185,7 +192,7 @@ describe('/search', () => {
{
should: 'should search by type (video)',
deferred: () => ({
dto: { type: 'VIDEO' },
dto: { type: 'VIDEO', visibility: AssetVisibility.Hidden },
assets: [
// the three live motion photos
{ id: expect.any(String) },
@@ -229,13 +236,6 @@ describe('/search', () => {
should: 'should search by takenAfter (no results)',
deferred: () => ({ dto: { takenAfter: today.plus({ hour: 1 }).toJSDate() }, assets: [] }),
},
// {
// should: 'should search by originalPath',
// deferred: () => ({
// dto: { originalPath: asset1.originalPath },
// assets: [asset1],
// }),
// },
{
should: 'should search by originalFilename',
deferred: () => ({
@@ -265,7 +265,7 @@ describe('/search', () => {
deferred: () => ({
dto: {
city: '',
isVisible: true,
visibility: AssetVisibility.Timeline,
includeNull: true,
},
assets: [assetLast],
@@ -276,7 +276,7 @@ describe('/search', () => {
deferred: () => ({
dto: {
city: null,
isVisible: true,
visibility: AssetVisibility.Timeline,
includeNull: true,
},
assets: [assetLast],
@@ -297,7 +297,7 @@ describe('/search', () => {
deferred: () => ({
dto: {
state: '',
isVisible: true,
visibility: AssetVisibility.Timeline,
withExif: true,
includeNull: true,
},
@@ -309,7 +309,7 @@ describe('/search', () => {
deferred: () => ({
dto: {
state: null,
isVisible: true,
visibility: AssetVisibility.Timeline,
includeNull: true,
},
assets: [assetLast, assetNotocactus],
@@ -330,7 +330,7 @@ describe('/search', () => {
deferred: () => ({
dto: {
country: '',
isVisible: true,
visibility: AssetVisibility.Timeline,
includeNull: true,
},
assets: [assetLast],
@@ -341,7 +341,7 @@ describe('/search', () => {
deferred: () => ({
dto: {
country: null,
isVisible: true,
visibility: AssetVisibility.Timeline,
includeNull: true,
},
assets: [assetLast],

View File

@@ -1,4 +1,4 @@
import { AssetMediaResponseDto, LoginResponseDto, SharedLinkType, TimeBucketSize } from '@immich/sdk';
import { AssetMediaResponseDto, AssetVisibility, LoginResponseDto, SharedLinkType, TimeBucketSize } from '@immich/sdk';
import { DateTime } from 'luxon';
import { createUserDto } from 'src/fixtures';
import { errorDto } from 'src/responses';
@@ -104,7 +104,7 @@ describe('/timeline', () => {
const req1 = await request(app)
.get('/timeline/buckets')
.set('Authorization', `Bearer ${timeBucketUser.accessToken}`)
.query({ size: TimeBucketSize.Month, withPartners: true, isArchived: true });
.query({ size: TimeBucketSize.Month, withPartners: true, visibility: AssetVisibility.Archive });
expect(req1.status).toBe(400);
expect(req1.body).toEqual(errorDto.badRequest());
@@ -112,7 +112,7 @@ describe('/timeline', () => {
const req2 = await request(app)
.get('/timeline/buckets')
.set('Authorization', `Bearer ${user.accessToken}`)
.query({ size: TimeBucketSize.Month, withPartners: true, isArchived: undefined });
.query({ size: TimeBucketSize.Month, withPartners: true, visibility: undefined });
expect(req2.status).toBe(400);
expect(req2.body).toEqual(errorDto.badRequest());

View File

@@ -3,6 +3,7 @@ import {
AssetMediaCreateDto,
AssetMediaResponseDto,
AssetResponseDto,
AssetVisibility,
CheckExistingAssetsDto,
CreateAlbumDto,
CreateLibraryDto,
@@ -429,7 +430,10 @@ export const utils = {
},
archiveAssets: (accessToken: string, ids: string[]) =>
updateAssets({ assetBulkUpdateDto: { ids, isArchived: true } }, { headers: asBearerAuth(accessToken) }),
updateAssets(
{ assetBulkUpdateDto: { ids, visibility: AssetVisibility.Archive } },
{ headers: asBearerAuth(accessToken) },
),
deleteAssets: (accessToken: string, ids: string[]) =>
deleteAssets({ assetBulkDeleteDto: { ids } }, { headers: asBearerAuth(accessToken) }),