mirror of
https://github.com/immich-app/immich.git
synced 2025-05-17 05:02:16 +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:
parent
016d7a6ceb
commit
d33ce13561
90 changed files with 1137 additions and 867 deletions
e2e/src
mobile
lib/services
openapi
open-api
server
src
controllers
database.tsdb.d.tsdtos
enum.tsqueries
access.repository.sqlasset.job.repository.sqlasset.repository.sqllibrary.repository.sqlmap.repository.sqlperson.repository.sqlsearch.repository.sqlsync.repository.sqluser.repository.sqlview.repository.sql
repositories
access.repository.tsasset-job.repository.tsasset.repository.tsdownload.repository.tslibrary.repository.tsmap.repository.tsperson.repository.tssearch.repository.tsuser.repository.tsview-repository.ts
schema
services
asset-media.service.spec.tsasset-media.service.tsasset.service.spec.tsasset.service.tsduplicate.service.spec.tsduplicate.service.tsjob.service.tsmedia.service.tsmetadata.service.spec.tsmetadata.service.tsperson.service.tssmart-info.service.tssync.service.tstimeline.service.spec.tstimeline.service.ts
utils
validation.tstest
web/src
lib
components
stores
utils
routes/(user)
albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]
archive/[[photos=photos]]/[[assetId=id]]
favorites/[[photos=photos]]/[[assetId=id]]
partners/[userId]/[[photos=photos]]/[[assetId=id]]
people/[personId]/[[photos=photos]]/[[assetId=id]]
photos/[[assetId=id]]
search/[[photos=photos]]/[[assetId=id]]
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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) }),
|
||||
|
|
|
@ -197,7 +197,7 @@ class AssetService {
|
|||
ids: assets.map((e) => e.remoteId!).toList(),
|
||||
dateTimeOriginal: updateAssetDto.dateTimeOriginal,
|
||||
isFavorite: updateAssetDto.isFavorite,
|
||||
isArchived: updateAssetDto.isArchived,
|
||||
visibility: updateAssetDto.visibility,
|
||||
latitude: updateAssetDto.latitude,
|
||||
longitude: updateAssetDto.longitude,
|
||||
),
|
||||
|
@ -229,7 +229,13 @@ class AssetService {
|
|||
bool isArchived,
|
||||
) async {
|
||||
try {
|
||||
await updateAssets(assets, UpdateAssetDto(isArchived: isArchived));
|
||||
await updateAssets(
|
||||
assets,
|
||||
UpdateAssetDto(
|
||||
visibility:
|
||||
isArchived ? AssetVisibility.archive : AssetVisibility.timeline,
|
||||
),
|
||||
);
|
||||
|
||||
for (var element in assets) {
|
||||
element.isArchived = isArchived;
|
||||
|
|
|
@ -68,7 +68,9 @@ class SearchService {
|
|||
model: filter.camera.model,
|
||||
takenAfter: filter.date.takenAfter,
|
||||
takenBefore: filter.date.takenBefore,
|
||||
isArchived: filter.display.isArchive ? true : null,
|
||||
visibility: filter.display.isArchive
|
||||
? AssetVisibility.archive
|
||||
: AssetVisibility.timeline,
|
||||
isFavorite: filter.display.isFavorite ? true : null,
|
||||
isNotInAlbum: filter.display.isNotInAlbum ? true : null,
|
||||
personIds: filter.people.map((e) => e.id).toList(),
|
||||
|
@ -95,7 +97,9 @@ class SearchService {
|
|||
model: filter.camera.model,
|
||||
takenAfter: filter.date.takenAfter,
|
||||
takenBefore: filter.date.takenBefore,
|
||||
isArchived: filter.display.isArchive ? true : null,
|
||||
visibility: filter.display.isArchive
|
||||
? AssetVisibility.archive
|
||||
: AssetVisibility.timeline,
|
||||
isFavorite: filter.display.isFavorite ? true : null,
|
||||
isNotInAlbum: filter.display.isNotInAlbum ? true : null,
|
||||
personIds: filter.people.map((e) => e.id).toList(),
|
||||
|
|
1
mobile/openapi/README.md
generated
1
mobile/openapi/README.md
generated
|
@ -302,6 +302,7 @@ Class | Method | HTTP request | Description
|
|||
- [AssetStackResponseDto](doc//AssetStackResponseDto.md)
|
||||
- [AssetStatsResponseDto](doc//AssetStatsResponseDto.md)
|
||||
- [AssetTypeEnum](doc//AssetTypeEnum.md)
|
||||
- [AssetVisibility](doc//AssetVisibility.md)
|
||||
- [AudioCodec](doc//AudioCodec.md)
|
||||
- [AvatarUpdate](doc//AvatarUpdate.md)
|
||||
- [BulkIdResponseDto](doc//BulkIdResponseDto.md)
|
||||
|
|
1
mobile/openapi/lib/api.dart
generated
1
mobile/openapi/lib/api.dart
generated
|
@ -106,6 +106,7 @@ part 'model/asset_response_dto.dart';
|
|||
part 'model/asset_stack_response_dto.dart';
|
||||
part 'model/asset_stats_response_dto.dart';
|
||||
part 'model/asset_type_enum.dart';
|
||||
part 'model/asset_visibility.dart';
|
||||
part 'model/audio_codec.dart';
|
||||
part 'model/avatar_update.dart';
|
||||
part 'model/bulk_id_response_dto.dart';
|
||||
|
|
50
mobile/openapi/lib/api/assets_api.dart
generated
50
mobile/openapi/lib/api/assets_api.dart
generated
|
@ -342,12 +342,12 @@ class AssetsApi {
|
|||
/// Performs an HTTP 'GET /assets/statistics' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [bool] isArchived:
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
///
|
||||
/// * [bool] isTrashed:
|
||||
Future<Response> getAssetStatisticsWithHttpInfo({ bool? isArchived, bool? isFavorite, bool? isTrashed, }) async {
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
Future<Response> getAssetStatisticsWithHttpInfo({ bool? isFavorite, bool? isTrashed, AssetVisibility? visibility, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets/statistics';
|
||||
|
||||
|
@ -358,15 +358,15 @@ class AssetsApi {
|
|||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
if (isArchived != null) {
|
||||
queryParams.addAll(_queryParams('', 'isArchived', isArchived));
|
||||
}
|
||||
if (isFavorite != null) {
|
||||
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
|
||||
}
|
||||
if (isTrashed != null) {
|
||||
queryParams.addAll(_queryParams('', 'isTrashed', isTrashed));
|
||||
}
|
||||
if (visibility != null) {
|
||||
queryParams.addAll(_queryParams('', 'visibility', visibility));
|
||||
}
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
@ -384,13 +384,13 @@ class AssetsApi {
|
|||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [bool] isArchived:
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
///
|
||||
/// * [bool] isTrashed:
|
||||
Future<AssetStatsResponseDto?> getAssetStatistics({ bool? isArchived, bool? isFavorite, bool? isTrashed, }) async {
|
||||
final response = await getAssetStatisticsWithHttpInfo( isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, );
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
Future<AssetStatsResponseDto?> getAssetStatistics({ bool? isFavorite, bool? isTrashed, AssetVisibility? visibility, }) async {
|
||||
final response = await getAssetStatisticsWithHttpInfo( isFavorite: isFavorite, isTrashed: isTrashed, visibility: visibility, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
|
@ -788,16 +788,14 @@ class AssetsApi {
|
|||
///
|
||||
/// * [String] duration:
|
||||
///
|
||||
/// * [bool] isArchived:
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
///
|
||||
/// * [bool] isVisible:
|
||||
///
|
||||
/// * [String] livePhotoVideoId:
|
||||
///
|
||||
/// * [MultipartFile] sidecarData:
|
||||
Future<Response> uploadAssetWithHttpInfo(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? xImmichChecksum, String? duration, bool? isArchived, bool? isFavorite, bool? isVisible, String? livePhotoVideoId, MultipartFile? sidecarData, }) async {
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
Future<Response> uploadAssetWithHttpInfo(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? xImmichChecksum, String? duration, bool? isFavorite, String? livePhotoVideoId, MultipartFile? sidecarData, AssetVisibility? visibility, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets';
|
||||
|
||||
|
@ -845,18 +843,10 @@ class AssetsApi {
|
|||
hasFields = true;
|
||||
mp.fields[r'fileModifiedAt'] = parameterToString(fileModifiedAt);
|
||||
}
|
||||
if (isArchived != null) {
|
||||
hasFields = true;
|
||||
mp.fields[r'isArchived'] = parameterToString(isArchived);
|
||||
}
|
||||
if (isFavorite != null) {
|
||||
hasFields = true;
|
||||
mp.fields[r'isFavorite'] = parameterToString(isFavorite);
|
||||
}
|
||||
if (isVisible != null) {
|
||||
hasFields = true;
|
||||
mp.fields[r'isVisible'] = parameterToString(isVisible);
|
||||
}
|
||||
if (livePhotoVideoId != null) {
|
||||
hasFields = true;
|
||||
mp.fields[r'livePhotoVideoId'] = parameterToString(livePhotoVideoId);
|
||||
|
@ -866,6 +856,10 @@ class AssetsApi {
|
|||
mp.fields[r'sidecarData'] = sidecarData.field;
|
||||
mp.files.add(sidecarData);
|
||||
}
|
||||
if (visibility != null) {
|
||||
hasFields = true;
|
||||
mp.fields[r'visibility'] = parameterToString(visibility);
|
||||
}
|
||||
if (hasFields) {
|
||||
postBody = mp;
|
||||
}
|
||||
|
@ -900,17 +894,15 @@ class AssetsApi {
|
|||
///
|
||||
/// * [String] duration:
|
||||
///
|
||||
/// * [bool] isArchived:
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
///
|
||||
/// * [bool] isVisible:
|
||||
///
|
||||
/// * [String] livePhotoVideoId:
|
||||
///
|
||||
/// * [MultipartFile] sidecarData:
|
||||
Future<AssetMediaResponseDto?> uploadAsset(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? xImmichChecksum, String? duration, bool? isArchived, bool? isFavorite, bool? isVisible, String? livePhotoVideoId, MultipartFile? sidecarData, }) async {
|
||||
final response = await uploadAssetWithHttpInfo(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key: key, xImmichChecksum: xImmichChecksum, duration: duration, isArchived: isArchived, isFavorite: isFavorite, isVisible: isVisible, livePhotoVideoId: livePhotoVideoId, sidecarData: sidecarData, );
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
Future<AssetMediaResponseDto?> uploadAsset(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? xImmichChecksum, String? duration, bool? isFavorite, String? livePhotoVideoId, MultipartFile? sidecarData, AssetVisibility? visibility, }) async {
|
||||
final response = await uploadAssetWithHttpInfo(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key: key, xImmichChecksum: xImmichChecksum, duration: duration, isFavorite: isFavorite, livePhotoVideoId: livePhotoVideoId, sidecarData: sidecarData, visibility: visibility, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
|
|
40
mobile/openapi/lib/api/timeline_api.dart
generated
40
mobile/openapi/lib/api/timeline_api.dart
generated
|
@ -25,8 +25,6 @@ class TimelineApi {
|
|||
///
|
||||
/// * [String] albumId:
|
||||
///
|
||||
/// * [bool] isArchived:
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
///
|
||||
/// * [bool] isTrashed:
|
||||
|
@ -41,10 +39,12 @@ class TimelineApi {
|
|||
///
|
||||
/// * [String] userId:
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
///
|
||||
/// * [bool] withPartners:
|
||||
///
|
||||
/// * [bool] withStacked:
|
||||
Future<Response> getTimeBucketWithHttpInfo(TimeBucketSize size, String timeBucket, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async {
|
||||
Future<Response> getTimeBucketWithHttpInfo(TimeBucketSize size, String timeBucket, { String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, AssetVisibility? visibility, bool? withPartners, bool? withStacked, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/timeline/bucket';
|
||||
|
||||
|
@ -58,9 +58,6 @@ class TimelineApi {
|
|||
if (albumId != null) {
|
||||
queryParams.addAll(_queryParams('', 'albumId', albumId));
|
||||
}
|
||||
if (isArchived != null) {
|
||||
queryParams.addAll(_queryParams('', 'isArchived', isArchived));
|
||||
}
|
||||
if (isFavorite != null) {
|
||||
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
|
||||
}
|
||||
|
@ -84,6 +81,9 @@ class TimelineApi {
|
|||
if (userId != null) {
|
||||
queryParams.addAll(_queryParams('', 'userId', userId));
|
||||
}
|
||||
if (visibility != null) {
|
||||
queryParams.addAll(_queryParams('', 'visibility', visibility));
|
||||
}
|
||||
if (withPartners != null) {
|
||||
queryParams.addAll(_queryParams('', 'withPartners', withPartners));
|
||||
}
|
||||
|
@ -113,8 +113,6 @@ class TimelineApi {
|
|||
///
|
||||
/// * [String] albumId:
|
||||
///
|
||||
/// * [bool] isArchived:
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
///
|
||||
/// * [bool] isTrashed:
|
||||
|
@ -129,11 +127,13 @@ class TimelineApi {
|
|||
///
|
||||
/// * [String] userId:
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
///
|
||||
/// * [bool] withPartners:
|
||||
///
|
||||
/// * [bool] withStacked:
|
||||
Future<List<AssetResponseDto>?> getTimeBucket(TimeBucketSize size, String timeBucket, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async {
|
||||
final response = await getTimeBucketWithHttpInfo(size, timeBucket, albumId: albumId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, tagId: tagId, userId: userId, withPartners: withPartners, withStacked: withStacked, );
|
||||
Future<List<AssetResponseDto>?> getTimeBucket(TimeBucketSize size, String timeBucket, { String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, AssetVisibility? visibility, bool? withPartners, bool? withStacked, }) async {
|
||||
final response = await getTimeBucketWithHttpInfo(size, timeBucket, albumId: albumId, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, tagId: tagId, userId: userId, visibility: visibility, withPartners: withPartners, withStacked: withStacked, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
|
@ -157,8 +157,6 @@ class TimelineApi {
|
|||
///
|
||||
/// * [String] albumId:
|
||||
///
|
||||
/// * [bool] isArchived:
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
///
|
||||
/// * [bool] isTrashed:
|
||||
|
@ -173,10 +171,12 @@ class TimelineApi {
|
|||
///
|
||||
/// * [String] userId:
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
///
|
||||
/// * [bool] withPartners:
|
||||
///
|
||||
/// * [bool] withStacked:
|
||||
Future<Response> getTimeBucketsWithHttpInfo(TimeBucketSize size, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async {
|
||||
Future<Response> getTimeBucketsWithHttpInfo(TimeBucketSize size, { String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, AssetVisibility? visibility, bool? withPartners, bool? withStacked, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/timeline/buckets';
|
||||
|
||||
|
@ -190,9 +190,6 @@ class TimelineApi {
|
|||
if (albumId != null) {
|
||||
queryParams.addAll(_queryParams('', 'albumId', albumId));
|
||||
}
|
||||
if (isArchived != null) {
|
||||
queryParams.addAll(_queryParams('', 'isArchived', isArchived));
|
||||
}
|
||||
if (isFavorite != null) {
|
||||
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
|
||||
}
|
||||
|
@ -215,6 +212,9 @@ class TimelineApi {
|
|||
if (userId != null) {
|
||||
queryParams.addAll(_queryParams('', 'userId', userId));
|
||||
}
|
||||
if (visibility != null) {
|
||||
queryParams.addAll(_queryParams('', 'visibility', visibility));
|
||||
}
|
||||
if (withPartners != null) {
|
||||
queryParams.addAll(_queryParams('', 'withPartners', withPartners));
|
||||
}
|
||||
|
@ -242,8 +242,6 @@ class TimelineApi {
|
|||
///
|
||||
/// * [String] albumId:
|
||||
///
|
||||
/// * [bool] isArchived:
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
///
|
||||
/// * [bool] isTrashed:
|
||||
|
@ -258,11 +256,13 @@ class TimelineApi {
|
|||
///
|
||||
/// * [String] userId:
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
///
|
||||
/// * [bool] withPartners:
|
||||
///
|
||||
/// * [bool] withStacked:
|
||||
Future<List<TimeBucketResponseDto>?> getTimeBuckets(TimeBucketSize size, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async {
|
||||
final response = await getTimeBucketsWithHttpInfo(size, albumId: albumId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, tagId: tagId, userId: userId, withPartners: withPartners, withStacked: withStacked, );
|
||||
Future<List<TimeBucketResponseDto>?> getTimeBuckets(TimeBucketSize size, { String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, AssetVisibility? visibility, bool? withPartners, bool? withStacked, }) async {
|
||||
final response = await getTimeBucketsWithHttpInfo(size, albumId: albumId, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, tagId: tagId, userId: userId, visibility: visibility, withPartners: withPartners, withStacked: withStacked, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
|
|
2
mobile/openapi/lib/api_client.dart
generated
2
mobile/openapi/lib/api_client.dart
generated
|
@ -268,6 +268,8 @@ class ApiClient {
|
|||
return AssetStatsResponseDto.fromJson(value);
|
||||
case 'AssetTypeEnum':
|
||||
return AssetTypeEnumTypeTransformer().decode(value);
|
||||
case 'AssetVisibility':
|
||||
return AssetVisibilityTypeTransformer().decode(value);
|
||||
case 'AudioCodec':
|
||||
return AudioCodecTypeTransformer().decode(value);
|
||||
case 'AvatarUpdate':
|
||||
|
|
3
mobile/openapi/lib/api_helper.dart
generated
3
mobile/openapi/lib/api_helper.dart
generated
|
@ -73,6 +73,9 @@ String parameterToString(dynamic value) {
|
|||
if (value is AssetTypeEnum) {
|
||||
return AssetTypeEnumTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is AssetVisibility) {
|
||||
return AssetVisibilityTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is AudioCodec) {
|
||||
return AudioCodecTypeTransformer().encode(value).toString();
|
||||
}
|
||||
|
|
40
mobile/openapi/lib/model/asset_bulk_update_dto.dart
generated
40
mobile/openapi/lib/model/asset_bulk_update_dto.dart
generated
|
@ -16,11 +16,11 @@ class AssetBulkUpdateDto {
|
|||
this.dateTimeOriginal,
|
||||
this.duplicateId,
|
||||
this.ids = const [],
|
||||
this.isArchived,
|
||||
this.isFavorite,
|
||||
this.latitude,
|
||||
this.longitude,
|
||||
this.rating,
|
||||
this.visibility,
|
||||
});
|
||||
|
||||
///
|
||||
|
@ -35,14 +35,6 @@ class AssetBulkUpdateDto {
|
|||
|
||||
List<String> ids;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
bool? isArchived;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
@ -77,16 +69,24 @@ class AssetBulkUpdateDto {
|
|||
///
|
||||
num? rating;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
AssetVisibility? visibility;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetBulkUpdateDto &&
|
||||
other.dateTimeOriginal == dateTimeOriginal &&
|
||||
other.duplicateId == duplicateId &&
|
||||
_deepEquality.equals(other.ids, ids) &&
|
||||
other.isArchived == isArchived &&
|
||||
other.isFavorite == isFavorite &&
|
||||
other.latitude == latitude &&
|
||||
other.longitude == longitude &&
|
||||
other.rating == rating;
|
||||
other.rating == rating &&
|
||||
other.visibility == visibility;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
|
@ -94,14 +94,14 @@ class AssetBulkUpdateDto {
|
|||
(dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) +
|
||||
(duplicateId == null ? 0 : duplicateId!.hashCode) +
|
||||
(ids.hashCode) +
|
||||
(isArchived == null ? 0 : isArchived!.hashCode) +
|
||||
(isFavorite == null ? 0 : isFavorite!.hashCode) +
|
||||
(latitude == null ? 0 : latitude!.hashCode) +
|
||||
(longitude == null ? 0 : longitude!.hashCode) +
|
||||
(rating == null ? 0 : rating!.hashCode);
|
||||
(rating == null ? 0 : rating!.hashCode) +
|
||||
(visibility == null ? 0 : visibility!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AssetBulkUpdateDto[dateTimeOriginal=$dateTimeOriginal, duplicateId=$duplicateId, ids=$ids, isArchived=$isArchived, isFavorite=$isFavorite, latitude=$latitude, longitude=$longitude, rating=$rating]';
|
||||
String toString() => 'AssetBulkUpdateDto[dateTimeOriginal=$dateTimeOriginal, duplicateId=$duplicateId, ids=$ids, isFavorite=$isFavorite, latitude=$latitude, longitude=$longitude, rating=$rating, visibility=$visibility]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
|
@ -116,11 +116,6 @@ class AssetBulkUpdateDto {
|
|||
// json[r'duplicateId'] = null;
|
||||
}
|
||||
json[r'ids'] = this.ids;
|
||||
if (this.isArchived != null) {
|
||||
json[r'isArchived'] = this.isArchived;
|
||||
} else {
|
||||
// json[r'isArchived'] = null;
|
||||
}
|
||||
if (this.isFavorite != null) {
|
||||
json[r'isFavorite'] = this.isFavorite;
|
||||
} else {
|
||||
|
@ -141,6 +136,11 @@ class AssetBulkUpdateDto {
|
|||
} else {
|
||||
// json[r'rating'] = null;
|
||||
}
|
||||
if (this.visibility != null) {
|
||||
json[r'visibility'] = this.visibility;
|
||||
} else {
|
||||
// json[r'visibility'] = null;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
|
@ -158,11 +158,11 @@ class AssetBulkUpdateDto {
|
|||
ids: json[r'ids'] is Iterable
|
||||
? (json[r'ids'] as Iterable).cast<String>().toList(growable: false)
|
||||
: const [],
|
||||
isArchived: mapValueOfType<bool>(json, r'isArchived'),
|
||||
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
|
||||
latitude: num.parse('${json[r'latitude']}'),
|
||||
longitude: num.parse('${json[r'longitude']}'),
|
||||
rating: num.parse('${json[r'rating']}'),
|
||||
visibility: AssetVisibility.fromJson(json[r'visibility']),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
|
88
mobile/openapi/lib/model/asset_visibility.dart
generated
Normal file
88
mobile/openapi/lib/model/asset_visibility.dart
generated
Normal file
|
@ -0,0 +1,88 @@
|
|||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
|
||||
class AssetVisibility {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const AssetVisibility._(this.value);
|
||||
|
||||
/// The underlying value of this enum member.
|
||||
final String value;
|
||||
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
String toJson() => value;
|
||||
|
||||
static const archive = AssetVisibility._(r'archive');
|
||||
static const timeline = AssetVisibility._(r'timeline');
|
||||
static const hidden = AssetVisibility._(r'hidden');
|
||||
|
||||
/// List of all possible values in this [enum][AssetVisibility].
|
||||
static const values = <AssetVisibility>[
|
||||
archive,
|
||||
timeline,
|
||||
hidden,
|
||||
];
|
||||
|
||||
static AssetVisibility? fromJson(dynamic value) => AssetVisibilityTypeTransformer().decode(value);
|
||||
|
||||
static List<AssetVisibility> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetVisibility>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetVisibility.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformation class that can [encode] an instance of [AssetVisibility] to String,
|
||||
/// and [decode] dynamic data back to [AssetVisibility].
|
||||
class AssetVisibilityTypeTransformer {
|
||||
factory AssetVisibilityTypeTransformer() => _instance ??= const AssetVisibilityTypeTransformer._();
|
||||
|
||||
const AssetVisibilityTypeTransformer._();
|
||||
|
||||
String encode(AssetVisibility data) => data.value;
|
||||
|
||||
/// Decodes a [dynamic value][data] to a AssetVisibility.
|
||||
///
|
||||
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
||||
///
|
||||
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||
/// and users are still using an old app with the old code.
|
||||
AssetVisibility? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'archive': return AssetVisibility.archive;
|
||||
case r'timeline': return AssetVisibility.timeline;
|
||||
case r'hidden': return AssetVisibility.hidden;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Singleton [AssetVisibilityTypeTransformer] instance.
|
||||
static AssetVisibilityTypeTransformer? _instance;
|
||||
}
|
||||
|
58
mobile/openapi/lib/model/metadata_search_dto.dart
generated
58
mobile/openapi/lib/model/metadata_search_dto.dart
generated
|
@ -23,13 +23,11 @@ class MetadataSearchDto {
|
|||
this.deviceId,
|
||||
this.encodedVideoPath,
|
||||
this.id,
|
||||
this.isArchived,
|
||||
this.isEncoded,
|
||||
this.isFavorite,
|
||||
this.isMotion,
|
||||
this.isNotInAlbum,
|
||||
this.isOffline,
|
||||
this.isVisible,
|
||||
this.lensModel,
|
||||
this.libraryId,
|
||||
this.make,
|
||||
|
@ -52,7 +50,7 @@ class MetadataSearchDto {
|
|||
this.type,
|
||||
this.updatedAfter,
|
||||
this.updatedBefore,
|
||||
this.withArchived = false,
|
||||
this.visibility,
|
||||
this.withDeleted,
|
||||
this.withExif,
|
||||
this.withPeople,
|
||||
|
@ -127,14 +125,6 @@ class MetadataSearchDto {
|
|||
///
|
||||
String? id;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
bool? isArchived;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
@ -175,14 +165,6 @@ class MetadataSearchDto {
|
|||
///
|
||||
bool? isOffline;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
bool? isVisible;
|
||||
|
||||
String? lensModel;
|
||||
|
||||
String? libraryId;
|
||||
|
@ -322,7 +304,13 @@ class MetadataSearchDto {
|
|||
///
|
||||
DateTime? updatedBefore;
|
||||
|
||||
bool withArchived;
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
AssetVisibility? visibility;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
|
@ -368,13 +356,11 @@ class MetadataSearchDto {
|
|||
other.deviceId == deviceId &&
|
||||
other.encodedVideoPath == encodedVideoPath &&
|
||||
other.id == id &&
|
||||
other.isArchived == isArchived &&
|
||||
other.isEncoded == isEncoded &&
|
||||
other.isFavorite == isFavorite &&
|
||||
other.isMotion == isMotion &&
|
||||
other.isNotInAlbum == isNotInAlbum &&
|
||||
other.isOffline == isOffline &&
|
||||
other.isVisible == isVisible &&
|
||||
other.lensModel == lensModel &&
|
||||
other.libraryId == libraryId &&
|
||||
other.make == make &&
|
||||
|
@ -397,7 +383,7 @@ class MetadataSearchDto {
|
|||
other.type == type &&
|
||||
other.updatedAfter == updatedAfter &&
|
||||
other.updatedBefore == updatedBefore &&
|
||||
other.withArchived == withArchived &&
|
||||
other.visibility == visibility &&
|
||||
other.withDeleted == withDeleted &&
|
||||
other.withExif == withExif &&
|
||||
other.withPeople == withPeople &&
|
||||
|
@ -416,13 +402,11 @@ class MetadataSearchDto {
|
|||
(deviceId == null ? 0 : deviceId!.hashCode) +
|
||||
(encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) +
|
||||
(id == null ? 0 : id!.hashCode) +
|
||||
(isArchived == null ? 0 : isArchived!.hashCode) +
|
||||
(isEncoded == null ? 0 : isEncoded!.hashCode) +
|
||||
(isFavorite == null ? 0 : isFavorite!.hashCode) +
|
||||
(isMotion == null ? 0 : isMotion!.hashCode) +
|
||||
(isNotInAlbum == null ? 0 : isNotInAlbum!.hashCode) +
|
||||
(isOffline == null ? 0 : isOffline!.hashCode) +
|
||||
(isVisible == null ? 0 : isVisible!.hashCode) +
|
||||
(lensModel == null ? 0 : lensModel!.hashCode) +
|
||||
(libraryId == null ? 0 : libraryId!.hashCode) +
|
||||
(make == null ? 0 : make!.hashCode) +
|
||||
|
@ -445,14 +429,14 @@ class MetadataSearchDto {
|
|||
(type == null ? 0 : type!.hashCode) +
|
||||
(updatedAfter == null ? 0 : updatedAfter!.hashCode) +
|
||||
(updatedBefore == null ? 0 : updatedBefore!.hashCode) +
|
||||
(withArchived.hashCode) +
|
||||
(visibility == null ? 0 : visibility!.hashCode) +
|
||||
(withDeleted == null ? 0 : withDeleted!.hashCode) +
|
||||
(withExif == null ? 0 : withExif!.hashCode) +
|
||||
(withPeople == null ? 0 : withPeople!.hashCode) +
|
||||
(withStacked == null ? 0 : withStacked!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'MetadataSearchDto[checksum=$checksum, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, description=$description, deviceAssetId=$deviceAssetId, deviceId=$deviceId, encodedVideoPath=$encodedVideoPath, id=$id, isArchived=$isArchived, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, isVisible=$isVisible, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, order=$order, originalFileName=$originalFileName, originalPath=$originalPath, page=$page, personIds=$personIds, previewPath=$previewPath, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, thumbnailPath=$thumbnailPath, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, withArchived=$withArchived, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]';
|
||||
String toString() => 'MetadataSearchDto[checksum=$checksum, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, description=$description, deviceAssetId=$deviceAssetId, deviceId=$deviceId, encodedVideoPath=$encodedVideoPath, id=$id, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, order=$order, originalFileName=$originalFileName, originalPath=$originalPath, page=$page, personIds=$personIds, previewPath=$previewPath, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, thumbnailPath=$thumbnailPath, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
|
@ -506,11 +490,6 @@ class MetadataSearchDto {
|
|||
} else {
|
||||
// json[r'id'] = null;
|
||||
}
|
||||
if (this.isArchived != null) {
|
||||
json[r'isArchived'] = this.isArchived;
|
||||
} else {
|
||||
// json[r'isArchived'] = null;
|
||||
}
|
||||
if (this.isEncoded != null) {
|
||||
json[r'isEncoded'] = this.isEncoded;
|
||||
} else {
|
||||
|
@ -536,11 +515,6 @@ class MetadataSearchDto {
|
|||
} else {
|
||||
// json[r'isOffline'] = null;
|
||||
}
|
||||
if (this.isVisible != null) {
|
||||
json[r'isVisible'] = this.isVisible;
|
||||
} else {
|
||||
// json[r'isVisible'] = null;
|
||||
}
|
||||
if (this.lensModel != null) {
|
||||
json[r'lensModel'] = this.lensModel;
|
||||
} else {
|
||||
|
@ -639,7 +613,11 @@ class MetadataSearchDto {
|
|||
} else {
|
||||
// json[r'updatedBefore'] = null;
|
||||
}
|
||||
json[r'withArchived'] = this.withArchived;
|
||||
if (this.visibility != null) {
|
||||
json[r'visibility'] = this.visibility;
|
||||
} else {
|
||||
// json[r'visibility'] = null;
|
||||
}
|
||||
if (this.withDeleted != null) {
|
||||
json[r'withDeleted'] = this.withDeleted;
|
||||
} else {
|
||||
|
@ -682,13 +660,11 @@ class MetadataSearchDto {
|
|||
deviceId: mapValueOfType<String>(json, r'deviceId'),
|
||||
encodedVideoPath: mapValueOfType<String>(json, r'encodedVideoPath'),
|
||||
id: mapValueOfType<String>(json, r'id'),
|
||||
isArchived: mapValueOfType<bool>(json, r'isArchived'),
|
||||
isEncoded: mapValueOfType<bool>(json, r'isEncoded'),
|
||||
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
|
||||
isMotion: mapValueOfType<bool>(json, r'isMotion'),
|
||||
isNotInAlbum: mapValueOfType<bool>(json, r'isNotInAlbum'),
|
||||
isOffline: mapValueOfType<bool>(json, r'isOffline'),
|
||||
isVisible: mapValueOfType<bool>(json, r'isVisible'),
|
||||
lensModel: mapValueOfType<String>(json, r'lensModel'),
|
||||
libraryId: mapValueOfType<String>(json, r'libraryId'),
|
||||
make: mapValueOfType<String>(json, r'make'),
|
||||
|
@ -715,7 +691,7 @@ class MetadataSearchDto {
|
|||
type: AssetTypeEnum.fromJson(json[r'type']),
|
||||
updatedAfter: mapDateTime(json, r'updatedAfter', r''),
|
||||
updatedBefore: mapDateTime(json, r'updatedBefore', r''),
|
||||
withArchived: mapValueOfType<bool>(json, r'withArchived') ?? false,
|
||||
visibility: AssetVisibility.fromJson(json[r'visibility']),
|
||||
withDeleted: mapValueOfType<bool>(json, r'withDeleted'),
|
||||
withExif: mapValueOfType<bool>(json, r'withExif'),
|
||||
withPeople: mapValueOfType<bool>(json, r'withPeople'),
|
||||
|
|
58
mobile/openapi/lib/model/random_search_dto.dart
generated
58
mobile/openapi/lib/model/random_search_dto.dart
generated
|
@ -18,13 +18,11 @@ class RandomSearchDto {
|
|||
this.createdAfter,
|
||||
this.createdBefore,
|
||||
this.deviceId,
|
||||
this.isArchived,
|
||||
this.isEncoded,
|
||||
this.isFavorite,
|
||||
this.isMotion,
|
||||
this.isNotInAlbum,
|
||||
this.isOffline,
|
||||
this.isVisible,
|
||||
this.lensModel,
|
||||
this.libraryId,
|
||||
this.make,
|
||||
|
@ -41,7 +39,7 @@ class RandomSearchDto {
|
|||
this.type,
|
||||
this.updatedAfter,
|
||||
this.updatedBefore,
|
||||
this.withArchived = false,
|
||||
this.visibility,
|
||||
this.withDeleted,
|
||||
this.withExif,
|
||||
this.withPeople,
|
||||
|
@ -76,14 +74,6 @@ class RandomSearchDto {
|
|||
///
|
||||
String? deviceId;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
bool? isArchived;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
@ -124,14 +114,6 @@ class RandomSearchDto {
|
|||
///
|
||||
bool? isOffline;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
bool? isVisible;
|
||||
|
||||
String? lensModel;
|
||||
|
||||
String? libraryId;
|
||||
|
@ -228,7 +210,13 @@ class RandomSearchDto {
|
|||
///
|
||||
DateTime? updatedBefore;
|
||||
|
||||
bool withArchived;
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
AssetVisibility? visibility;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
|
@ -269,13 +257,11 @@ class RandomSearchDto {
|
|||
other.createdAfter == createdAfter &&
|
||||
other.createdBefore == createdBefore &&
|
||||
other.deviceId == deviceId &&
|
||||
other.isArchived == isArchived &&
|
||||
other.isEncoded == isEncoded &&
|
||||
other.isFavorite == isFavorite &&
|
||||
other.isMotion == isMotion &&
|
||||
other.isNotInAlbum == isNotInAlbum &&
|
||||
other.isOffline == isOffline &&
|
||||
other.isVisible == isVisible &&
|
||||
other.lensModel == lensModel &&
|
||||
other.libraryId == libraryId &&
|
||||
other.make == make &&
|
||||
|
@ -292,7 +278,7 @@ class RandomSearchDto {
|
|||
other.type == type &&
|
||||
other.updatedAfter == updatedAfter &&
|
||||
other.updatedBefore == updatedBefore &&
|
||||
other.withArchived == withArchived &&
|
||||
other.visibility == visibility &&
|
||||
other.withDeleted == withDeleted &&
|
||||
other.withExif == withExif &&
|
||||
other.withPeople == withPeople &&
|
||||
|
@ -306,13 +292,11 @@ class RandomSearchDto {
|
|||
(createdAfter == null ? 0 : createdAfter!.hashCode) +
|
||||
(createdBefore == null ? 0 : createdBefore!.hashCode) +
|
||||
(deviceId == null ? 0 : deviceId!.hashCode) +
|
||||
(isArchived == null ? 0 : isArchived!.hashCode) +
|
||||
(isEncoded == null ? 0 : isEncoded!.hashCode) +
|
||||
(isFavorite == null ? 0 : isFavorite!.hashCode) +
|
||||
(isMotion == null ? 0 : isMotion!.hashCode) +
|
||||
(isNotInAlbum == null ? 0 : isNotInAlbum!.hashCode) +
|
||||
(isOffline == null ? 0 : isOffline!.hashCode) +
|
||||
(isVisible == null ? 0 : isVisible!.hashCode) +
|
||||
(lensModel == null ? 0 : lensModel!.hashCode) +
|
||||
(libraryId == null ? 0 : libraryId!.hashCode) +
|
||||
(make == null ? 0 : make!.hashCode) +
|
||||
|
@ -329,14 +313,14 @@ class RandomSearchDto {
|
|||
(type == null ? 0 : type!.hashCode) +
|
||||
(updatedAfter == null ? 0 : updatedAfter!.hashCode) +
|
||||
(updatedBefore == null ? 0 : updatedBefore!.hashCode) +
|
||||
(withArchived.hashCode) +
|
||||
(visibility == null ? 0 : visibility!.hashCode) +
|
||||
(withDeleted == null ? 0 : withDeleted!.hashCode) +
|
||||
(withExif == null ? 0 : withExif!.hashCode) +
|
||||
(withPeople == null ? 0 : withPeople!.hashCode) +
|
||||
(withStacked == null ? 0 : withStacked!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'RandomSearchDto[city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isArchived=$isArchived, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, isVisible=$isVisible, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, personIds=$personIds, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, withArchived=$withArchived, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]';
|
||||
String toString() => 'RandomSearchDto[city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, personIds=$personIds, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
|
@ -365,11 +349,6 @@ class RandomSearchDto {
|
|||
} else {
|
||||
// json[r'deviceId'] = null;
|
||||
}
|
||||
if (this.isArchived != null) {
|
||||
json[r'isArchived'] = this.isArchived;
|
||||
} else {
|
||||
// json[r'isArchived'] = null;
|
||||
}
|
||||
if (this.isEncoded != null) {
|
||||
json[r'isEncoded'] = this.isEncoded;
|
||||
} else {
|
||||
|
@ -395,11 +374,6 @@ class RandomSearchDto {
|
|||
} else {
|
||||
// json[r'isOffline'] = null;
|
||||
}
|
||||
if (this.isVisible != null) {
|
||||
json[r'isVisible'] = this.isVisible;
|
||||
} else {
|
||||
// json[r'isVisible'] = null;
|
||||
}
|
||||
if (this.lensModel != null) {
|
||||
json[r'lensModel'] = this.lensModel;
|
||||
} else {
|
||||
|
@ -472,7 +446,11 @@ class RandomSearchDto {
|
|||
} else {
|
||||
// json[r'updatedBefore'] = null;
|
||||
}
|
||||
json[r'withArchived'] = this.withArchived;
|
||||
if (this.visibility != null) {
|
||||
json[r'visibility'] = this.visibility;
|
||||
} else {
|
||||
// json[r'visibility'] = null;
|
||||
}
|
||||
if (this.withDeleted != null) {
|
||||
json[r'withDeleted'] = this.withDeleted;
|
||||
} else {
|
||||
|
@ -510,13 +488,11 @@ class RandomSearchDto {
|
|||
createdAfter: mapDateTime(json, r'createdAfter', r''),
|
||||
createdBefore: mapDateTime(json, r'createdBefore', r''),
|
||||
deviceId: mapValueOfType<String>(json, r'deviceId'),
|
||||
isArchived: mapValueOfType<bool>(json, r'isArchived'),
|
||||
isEncoded: mapValueOfType<bool>(json, r'isEncoded'),
|
||||
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
|
||||
isMotion: mapValueOfType<bool>(json, r'isMotion'),
|
||||
isNotInAlbum: mapValueOfType<bool>(json, r'isNotInAlbum'),
|
||||
isOffline: mapValueOfType<bool>(json, r'isOffline'),
|
||||
isVisible: mapValueOfType<bool>(json, r'isVisible'),
|
||||
lensModel: mapValueOfType<String>(json, r'lensModel'),
|
||||
libraryId: mapValueOfType<String>(json, r'libraryId'),
|
||||
make: mapValueOfType<String>(json, r'make'),
|
||||
|
@ -537,7 +513,7 @@ class RandomSearchDto {
|
|||
type: AssetTypeEnum.fromJson(json[r'type']),
|
||||
updatedAfter: mapDateTime(json, r'updatedAfter', r''),
|
||||
updatedBefore: mapDateTime(json, r'updatedBefore', r''),
|
||||
withArchived: mapValueOfType<bool>(json, r'withArchived') ?? false,
|
||||
visibility: AssetVisibility.fromJson(json[r'visibility']),
|
||||
withDeleted: mapValueOfType<bool>(json, r'withDeleted'),
|
||||
withExif: mapValueOfType<bool>(json, r'withExif'),
|
||||
withPeople: mapValueOfType<bool>(json, r'withPeople'),
|
||||
|
|
58
mobile/openapi/lib/model/smart_search_dto.dart
generated
58
mobile/openapi/lib/model/smart_search_dto.dart
generated
|
@ -18,13 +18,11 @@ class SmartSearchDto {
|
|||
this.createdAfter,
|
||||
this.createdBefore,
|
||||
this.deviceId,
|
||||
this.isArchived,
|
||||
this.isEncoded,
|
||||
this.isFavorite,
|
||||
this.isMotion,
|
||||
this.isNotInAlbum,
|
||||
this.isOffline,
|
||||
this.isVisible,
|
||||
this.language,
|
||||
this.lensModel,
|
||||
this.libraryId,
|
||||
|
@ -44,7 +42,7 @@ class SmartSearchDto {
|
|||
this.type,
|
||||
this.updatedAfter,
|
||||
this.updatedBefore,
|
||||
this.withArchived = false,
|
||||
this.visibility,
|
||||
this.withDeleted,
|
||||
this.withExif,
|
||||
});
|
||||
|
@ -77,14 +75,6 @@ class SmartSearchDto {
|
|||
///
|
||||
String? deviceId;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
bool? isArchived;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
@ -125,14 +115,6 @@ class SmartSearchDto {
|
|||
///
|
||||
bool? isOffline;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
bool? isVisible;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
@ -248,7 +230,13 @@ class SmartSearchDto {
|
|||
///
|
||||
DateTime? updatedBefore;
|
||||
|
||||
bool withArchived;
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
AssetVisibility? visibility;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
|
@ -273,13 +261,11 @@ class SmartSearchDto {
|
|||
other.createdAfter == createdAfter &&
|
||||
other.createdBefore == createdBefore &&
|
||||
other.deviceId == deviceId &&
|
||||
other.isArchived == isArchived &&
|
||||
other.isEncoded == isEncoded &&
|
||||
other.isFavorite == isFavorite &&
|
||||
other.isMotion == isMotion &&
|
||||
other.isNotInAlbum == isNotInAlbum &&
|
||||
other.isOffline == isOffline &&
|
||||
other.isVisible == isVisible &&
|
||||
other.language == language &&
|
||||
other.lensModel == lensModel &&
|
||||
other.libraryId == libraryId &&
|
||||
|
@ -299,7 +285,7 @@ class SmartSearchDto {
|
|||
other.type == type &&
|
||||
other.updatedAfter == updatedAfter &&
|
||||
other.updatedBefore == updatedBefore &&
|
||||
other.withArchived == withArchived &&
|
||||
other.visibility == visibility &&
|
||||
other.withDeleted == withDeleted &&
|
||||
other.withExif == withExif;
|
||||
|
||||
|
@ -311,13 +297,11 @@ class SmartSearchDto {
|
|||
(createdAfter == null ? 0 : createdAfter!.hashCode) +
|
||||
(createdBefore == null ? 0 : createdBefore!.hashCode) +
|
||||
(deviceId == null ? 0 : deviceId!.hashCode) +
|
||||
(isArchived == null ? 0 : isArchived!.hashCode) +
|
||||
(isEncoded == null ? 0 : isEncoded!.hashCode) +
|
||||
(isFavorite == null ? 0 : isFavorite!.hashCode) +
|
||||
(isMotion == null ? 0 : isMotion!.hashCode) +
|
||||
(isNotInAlbum == null ? 0 : isNotInAlbum!.hashCode) +
|
||||
(isOffline == null ? 0 : isOffline!.hashCode) +
|
||||
(isVisible == null ? 0 : isVisible!.hashCode) +
|
||||
(language == null ? 0 : language!.hashCode) +
|
||||
(lensModel == null ? 0 : lensModel!.hashCode) +
|
||||
(libraryId == null ? 0 : libraryId!.hashCode) +
|
||||
|
@ -337,12 +321,12 @@ class SmartSearchDto {
|
|||
(type == null ? 0 : type!.hashCode) +
|
||||
(updatedAfter == null ? 0 : updatedAfter!.hashCode) +
|
||||
(updatedBefore == null ? 0 : updatedBefore!.hashCode) +
|
||||
(withArchived.hashCode) +
|
||||
(visibility == null ? 0 : visibility!.hashCode) +
|
||||
(withDeleted == null ? 0 : withDeleted!.hashCode) +
|
||||
(withExif == null ? 0 : withExif!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SmartSearchDto[city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isArchived=$isArchived, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, isVisible=$isVisible, language=$language, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, page=$page, personIds=$personIds, query=$query, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, withArchived=$withArchived, withDeleted=$withDeleted, withExif=$withExif]';
|
||||
String toString() => 'SmartSearchDto[city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, language=$language, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, page=$page, personIds=$personIds, query=$query, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
|
@ -371,11 +355,6 @@ class SmartSearchDto {
|
|||
} else {
|
||||
// json[r'deviceId'] = null;
|
||||
}
|
||||
if (this.isArchived != null) {
|
||||
json[r'isArchived'] = this.isArchived;
|
||||
} else {
|
||||
// json[r'isArchived'] = null;
|
||||
}
|
||||
if (this.isEncoded != null) {
|
||||
json[r'isEncoded'] = this.isEncoded;
|
||||
} else {
|
||||
|
@ -401,11 +380,6 @@ class SmartSearchDto {
|
|||
} else {
|
||||
// json[r'isOffline'] = null;
|
||||
}
|
||||
if (this.isVisible != null) {
|
||||
json[r'isVisible'] = this.isVisible;
|
||||
} else {
|
||||
// json[r'isVisible'] = null;
|
||||
}
|
||||
if (this.language != null) {
|
||||
json[r'language'] = this.language;
|
||||
} else {
|
||||
|
@ -489,7 +463,11 @@ class SmartSearchDto {
|
|||
} else {
|
||||
// json[r'updatedBefore'] = null;
|
||||
}
|
||||
json[r'withArchived'] = this.withArchived;
|
||||
if (this.visibility != null) {
|
||||
json[r'visibility'] = this.visibility;
|
||||
} else {
|
||||
// json[r'visibility'] = null;
|
||||
}
|
||||
if (this.withDeleted != null) {
|
||||
json[r'withDeleted'] = this.withDeleted;
|
||||
} else {
|
||||
|
@ -517,13 +495,11 @@ class SmartSearchDto {
|
|||
createdAfter: mapDateTime(json, r'createdAfter', r''),
|
||||
createdBefore: mapDateTime(json, r'createdBefore', r''),
|
||||
deviceId: mapValueOfType<String>(json, r'deviceId'),
|
||||
isArchived: mapValueOfType<bool>(json, r'isArchived'),
|
||||
isEncoded: mapValueOfType<bool>(json, r'isEncoded'),
|
||||
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
|
||||
isMotion: mapValueOfType<bool>(json, r'isMotion'),
|
||||
isNotInAlbum: mapValueOfType<bool>(json, r'isNotInAlbum'),
|
||||
isOffline: mapValueOfType<bool>(json, r'isOffline'),
|
||||
isVisible: mapValueOfType<bool>(json, r'isVisible'),
|
||||
language: mapValueOfType<String>(json, r'language'),
|
||||
lensModel: mapValueOfType<String>(json, r'lensModel'),
|
||||
libraryId: mapValueOfType<String>(json, r'libraryId'),
|
||||
|
@ -547,7 +523,7 @@ class SmartSearchDto {
|
|||
type: AssetTypeEnum.fromJson(json[r'type']),
|
||||
updatedAfter: mapDateTime(json, r'updatedAfter', r''),
|
||||
updatedBefore: mapDateTime(json, r'updatedBefore', r''),
|
||||
withArchived: mapValueOfType<bool>(json, r'withArchived') ?? false,
|
||||
visibility: AssetVisibility.fromJson(json[r'visibility']),
|
||||
withDeleted: mapValueOfType<bool>(json, r'withDeleted'),
|
||||
withExif: mapValueOfType<bool>(json, r'withExif'),
|
||||
);
|
||||
|
|
99
mobile/openapi/lib/model/sync_asset_v1.dart
generated
99
mobile/openapi/lib/model/sync_asset_v1.dart
generated
|
@ -19,11 +19,11 @@ class SyncAssetV1 {
|
|||
required this.fileModifiedAt,
|
||||
required this.id,
|
||||
required this.isFavorite,
|
||||
required this.isVisible,
|
||||
required this.localDateTime,
|
||||
required this.ownerId,
|
||||
required this.thumbhash,
|
||||
required this.type,
|
||||
required this.visibility,
|
||||
});
|
||||
|
||||
String checksum;
|
||||
|
@ -38,8 +38,6 @@ class SyncAssetV1 {
|
|||
|
||||
bool isFavorite;
|
||||
|
||||
bool isVisible;
|
||||
|
||||
DateTime? localDateTime;
|
||||
|
||||
String ownerId;
|
||||
|
@ -48,6 +46,8 @@ class SyncAssetV1 {
|
|||
|
||||
SyncAssetV1TypeEnum type;
|
||||
|
||||
SyncAssetV1VisibilityEnum visibility;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is SyncAssetV1 &&
|
||||
other.checksum == checksum &&
|
||||
|
@ -56,11 +56,11 @@ class SyncAssetV1 {
|
|||
other.fileModifiedAt == fileModifiedAt &&
|
||||
other.id == id &&
|
||||
other.isFavorite == isFavorite &&
|
||||
other.isVisible == isVisible &&
|
||||
other.localDateTime == localDateTime &&
|
||||
other.ownerId == ownerId &&
|
||||
other.thumbhash == thumbhash &&
|
||||
other.type == type;
|
||||
other.type == type &&
|
||||
other.visibility == visibility;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
|
@ -71,14 +71,14 @@ class SyncAssetV1 {
|
|||
(fileModifiedAt == null ? 0 : fileModifiedAt!.hashCode) +
|
||||
(id.hashCode) +
|
||||
(isFavorite.hashCode) +
|
||||
(isVisible.hashCode) +
|
||||
(localDateTime == null ? 0 : localDateTime!.hashCode) +
|
||||
(ownerId.hashCode) +
|
||||
(thumbhash == null ? 0 : thumbhash!.hashCode) +
|
||||
(type.hashCode);
|
||||
(type.hashCode) +
|
||||
(visibility.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SyncAssetV1[checksum=$checksum, deletedAt=$deletedAt, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, id=$id, isFavorite=$isFavorite, isVisible=$isVisible, localDateTime=$localDateTime, ownerId=$ownerId, thumbhash=$thumbhash, type=$type]';
|
||||
String toString() => 'SyncAssetV1[checksum=$checksum, deletedAt=$deletedAt, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, id=$id, isFavorite=$isFavorite, localDateTime=$localDateTime, ownerId=$ownerId, thumbhash=$thumbhash, type=$type, visibility=$visibility]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
|
@ -100,7 +100,6 @@ class SyncAssetV1 {
|
|||
}
|
||||
json[r'id'] = this.id;
|
||||
json[r'isFavorite'] = this.isFavorite;
|
||||
json[r'isVisible'] = this.isVisible;
|
||||
if (this.localDateTime != null) {
|
||||
json[r'localDateTime'] = this.localDateTime!.toUtc().toIso8601String();
|
||||
} else {
|
||||
|
@ -113,6 +112,7 @@ class SyncAssetV1 {
|
|||
// json[r'thumbhash'] = null;
|
||||
}
|
||||
json[r'type'] = this.type;
|
||||
json[r'visibility'] = this.visibility;
|
||||
return json;
|
||||
}
|
||||
|
||||
|
@ -131,11 +131,11 @@ class SyncAssetV1 {
|
|||
fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r''),
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
isFavorite: mapValueOfType<bool>(json, r'isFavorite')!,
|
||||
isVisible: mapValueOfType<bool>(json, r'isVisible')!,
|
||||
localDateTime: mapDateTime(json, r'localDateTime', r''),
|
||||
ownerId: mapValueOfType<String>(json, r'ownerId')!,
|
||||
thumbhash: mapValueOfType<String>(json, r'thumbhash'),
|
||||
type: SyncAssetV1TypeEnum.fromJson(json[r'type'])!,
|
||||
visibility: SyncAssetV1VisibilityEnum.fromJson(json[r'visibility'])!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
@ -189,11 +189,11 @@ class SyncAssetV1 {
|
|||
'fileModifiedAt',
|
||||
'id',
|
||||
'isFavorite',
|
||||
'isVisible',
|
||||
'localDateTime',
|
||||
'ownerId',
|
||||
'thumbhash',
|
||||
'type',
|
||||
'visibility',
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -277,3 +277,80 @@ class SyncAssetV1TypeEnumTypeTransformer {
|
|||
}
|
||||
|
||||
|
||||
|
||||
class SyncAssetV1VisibilityEnum {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const SyncAssetV1VisibilityEnum._(this.value);
|
||||
|
||||
/// The underlying value of this enum member.
|
||||
final String value;
|
||||
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
String toJson() => value;
|
||||
|
||||
static const archive = SyncAssetV1VisibilityEnum._(r'archive');
|
||||
static const timeline = SyncAssetV1VisibilityEnum._(r'timeline');
|
||||
static const hidden = SyncAssetV1VisibilityEnum._(r'hidden');
|
||||
|
||||
/// List of all possible values in this [enum][SyncAssetV1VisibilityEnum].
|
||||
static const values = <SyncAssetV1VisibilityEnum>[
|
||||
archive,
|
||||
timeline,
|
||||
hidden,
|
||||
];
|
||||
|
||||
static SyncAssetV1VisibilityEnum? fromJson(dynamic value) => SyncAssetV1VisibilityEnumTypeTransformer().decode(value);
|
||||
|
||||
static List<SyncAssetV1VisibilityEnum> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <SyncAssetV1VisibilityEnum>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = SyncAssetV1VisibilityEnum.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformation class that can [encode] an instance of [SyncAssetV1VisibilityEnum] to String,
|
||||
/// and [decode] dynamic data back to [SyncAssetV1VisibilityEnum].
|
||||
class SyncAssetV1VisibilityEnumTypeTransformer {
|
||||
factory SyncAssetV1VisibilityEnumTypeTransformer() => _instance ??= const SyncAssetV1VisibilityEnumTypeTransformer._();
|
||||
|
||||
const SyncAssetV1VisibilityEnumTypeTransformer._();
|
||||
|
||||
String encode(SyncAssetV1VisibilityEnum data) => data.value;
|
||||
|
||||
/// Decodes a [dynamic value][data] to a SyncAssetV1VisibilityEnum.
|
||||
///
|
||||
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
||||
///
|
||||
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||
/// and users are still using an old app with the old code.
|
||||
SyncAssetV1VisibilityEnum? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'archive': return SyncAssetV1VisibilityEnum.archive;
|
||||
case r'timeline': return SyncAssetV1VisibilityEnum.timeline;
|
||||
case r'hidden': return SyncAssetV1VisibilityEnum.hidden;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Singleton [SyncAssetV1VisibilityEnumTypeTransformer] instance.
|
||||
static SyncAssetV1VisibilityEnumTypeTransformer? _instance;
|
||||
}
|
||||
|
||||
|
||||
|
|
40
mobile/openapi/lib/model/update_asset_dto.dart
generated
40
mobile/openapi/lib/model/update_asset_dto.dart
generated
|
@ -15,12 +15,12 @@ class UpdateAssetDto {
|
|||
UpdateAssetDto({
|
||||
this.dateTimeOriginal,
|
||||
this.description,
|
||||
this.isArchived,
|
||||
this.isFavorite,
|
||||
this.latitude,
|
||||
this.livePhotoVideoId,
|
||||
this.longitude,
|
||||
this.rating,
|
||||
this.visibility,
|
||||
});
|
||||
|
||||
///
|
||||
|
@ -39,14 +39,6 @@ class UpdateAssetDto {
|
|||
///
|
||||
String? description;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
bool? isArchived;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
@ -83,31 +75,39 @@ class UpdateAssetDto {
|
|||
///
|
||||
num? rating;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
AssetVisibility? visibility;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is UpdateAssetDto &&
|
||||
other.dateTimeOriginal == dateTimeOriginal &&
|
||||
other.description == description &&
|
||||
other.isArchived == isArchived &&
|
||||
other.isFavorite == isFavorite &&
|
||||
other.latitude == latitude &&
|
||||
other.livePhotoVideoId == livePhotoVideoId &&
|
||||
other.longitude == longitude &&
|
||||
other.rating == rating;
|
||||
other.rating == rating &&
|
||||
other.visibility == visibility;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) +
|
||||
(description == null ? 0 : description!.hashCode) +
|
||||
(isArchived == null ? 0 : isArchived!.hashCode) +
|
||||
(isFavorite == null ? 0 : isFavorite!.hashCode) +
|
||||
(latitude == null ? 0 : latitude!.hashCode) +
|
||||
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) +
|
||||
(longitude == null ? 0 : longitude!.hashCode) +
|
||||
(rating == null ? 0 : rating!.hashCode);
|
||||
(rating == null ? 0 : rating!.hashCode) +
|
||||
(visibility == null ? 0 : visibility!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'UpdateAssetDto[dateTimeOriginal=$dateTimeOriginal, description=$description, isArchived=$isArchived, isFavorite=$isFavorite, latitude=$latitude, livePhotoVideoId=$livePhotoVideoId, longitude=$longitude, rating=$rating]';
|
||||
String toString() => 'UpdateAssetDto[dateTimeOriginal=$dateTimeOriginal, description=$description, isFavorite=$isFavorite, latitude=$latitude, livePhotoVideoId=$livePhotoVideoId, longitude=$longitude, rating=$rating, visibility=$visibility]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
|
@ -121,11 +121,6 @@ class UpdateAssetDto {
|
|||
} else {
|
||||
// json[r'description'] = null;
|
||||
}
|
||||
if (this.isArchived != null) {
|
||||
json[r'isArchived'] = this.isArchived;
|
||||
} else {
|
||||
// json[r'isArchived'] = null;
|
||||
}
|
||||
if (this.isFavorite != null) {
|
||||
json[r'isFavorite'] = this.isFavorite;
|
||||
} else {
|
||||
|
@ -151,6 +146,11 @@ class UpdateAssetDto {
|
|||
} else {
|
||||
// json[r'rating'] = null;
|
||||
}
|
||||
if (this.visibility != null) {
|
||||
json[r'visibility'] = this.visibility;
|
||||
} else {
|
||||
// json[r'visibility'] = null;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
|
@ -165,12 +165,12 @@ class UpdateAssetDto {
|
|||
return UpdateAssetDto(
|
||||
dateTimeOriginal: mapValueOfType<String>(json, r'dateTimeOriginal'),
|
||||
description: mapValueOfType<String>(json, r'description'),
|
||||
isArchived: mapValueOfType<bool>(json, r'isArchived'),
|
||||
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
|
||||
latitude: num.parse('${json[r'latitude']}'),
|
||||
livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'),
|
||||
longitude: num.parse('${json[r'longitude']}'),
|
||||
rating: num.parse('${json[r'rating']}'),
|
||||
visibility: AssetVisibility.fromJson(json[r'visibility']),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -1781,14 +1781,6 @@
|
|||
"get": {
|
||||
"operationId": "getAssetStatistics",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "isArchived",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "isFavorite",
|
||||
"required": false,
|
||||
|
@ -1804,6 +1796,14 @@
|
|||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "visibility",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AssetVisibility"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
@ -6909,14 +6909,6 @@
|
|||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "isArchived",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "isFavorite",
|
||||
"required": false,
|
||||
|
@ -6992,6 +6984,14 @@
|
|||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "visibility",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AssetVisibility"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "withPartners",
|
||||
"required": false,
|
||||
|
@ -7053,14 +7053,6 @@
|
|||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "isArchived",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "isFavorite",
|
||||
"required": false,
|
||||
|
@ -7128,6 +7120,14 @@
|
|||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "visibility",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AssetVisibility"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "withPartners",
|
||||
"required": false,
|
||||
|
@ -8273,9 +8273,6 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"isArchived": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isFavorite": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
@ -8289,6 +8286,13 @@
|
|||
"maximum": 5,
|
||||
"minimum": -1,
|
||||
"type": "number"
|
||||
},
|
||||
"visibility": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetVisibility"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -8713,15 +8717,9 @@
|
|||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"isArchived": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isFavorite": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isVisible": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"livePhotoVideoId": {
|
||||
"format": "uuid",
|
||||
"type": "string"
|
||||
|
@ -8729,6 +8727,13 @@
|
|||
"sidecarData": {
|
||||
"format": "binary",
|
||||
"type": "string"
|
||||
},
|
||||
"visibility": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetVisibility"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -9009,6 +9014,14 @@
|
|||
],
|
||||
"type": "string"
|
||||
},
|
||||
"AssetVisibility": {
|
||||
"enum": [
|
||||
"archive",
|
||||
"timeline",
|
||||
"hidden"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"AudioCodec": {
|
||||
"enum": [
|
||||
"mp3",
|
||||
|
@ -10204,9 +10217,6 @@
|
|||
"format": "uuid",
|
||||
"type": "string"
|
||||
},
|
||||
"isArchived": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isEncoded": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
@ -10222,9 +10232,6 @@
|
|||
"isOffline": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isVisible": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"lensModel": {
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
|
@ -10324,9 +10331,12 @@
|
|||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"withArchived": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
"visibility": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetVisibility"
|
||||
}
|
||||
]
|
||||
},
|
||||
"withDeleted": {
|
||||
"type": "boolean"
|
||||
|
@ -11041,9 +11051,6 @@
|
|||
"deviceId": {
|
||||
"type": "string"
|
||||
},
|
||||
"isArchived": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isEncoded": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
@ -11059,9 +11066,6 @@
|
|||
"isOffline": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isVisible": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"lensModel": {
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
|
@ -11137,9 +11141,12 @@
|
|||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"withArchived": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
"visibility": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetVisibility"
|
||||
}
|
||||
]
|
||||
},
|
||||
"withDeleted": {
|
||||
"type": "boolean"
|
||||
|
@ -11989,9 +11996,6 @@
|
|||
"deviceId": {
|
||||
"type": "string"
|
||||
},
|
||||
"isArchived": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isEncoded": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
@ -12007,9 +12011,6 @@
|
|||
"isOffline": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isVisible": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"language": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -12095,9 +12096,12 @@
|
|||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"withArchived": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
"visibility": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetVisibility"
|
||||
}
|
||||
]
|
||||
},
|
||||
"withDeleted": {
|
||||
"type": "boolean"
|
||||
|
@ -12381,9 +12385,6 @@
|
|||
"isFavorite": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isVisible": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"localDateTime": {
|
||||
"format": "date-time",
|
||||
"nullable": true,
|
||||
|
@ -12404,6 +12405,14 @@
|
|||
"OTHER"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"visibility": {
|
||||
"enum": [
|
||||
"archive",
|
||||
"timeline",
|
||||
"hidden"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -12413,11 +12422,11 @@
|
|||
"fileModifiedAt",
|
||||
"id",
|
||||
"isFavorite",
|
||||
"isVisible",
|
||||
"localDateTime",
|
||||
"ownerId",
|
||||
"thumbhash",
|
||||
"type"
|
||||
"type",
|
||||
"visibility"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
|
@ -13671,9 +13680,6 @@
|
|||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"isArchived": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isFavorite": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
@ -13692,6 +13698,13 @@
|
|||
"maximum": 5,
|
||||
"minimum": -1,
|
||||
"type": "number"
|
||||
},
|
||||
"visibility": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetVisibility"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
|
|
@ -413,11 +413,10 @@ export type AssetMediaCreateDto = {
|
|||
duration?: string;
|
||||
fileCreatedAt: string;
|
||||
fileModifiedAt: string;
|
||||
isArchived?: boolean;
|
||||
isFavorite?: boolean;
|
||||
isVisible?: boolean;
|
||||
livePhotoVideoId?: string;
|
||||
sidecarData?: Blob;
|
||||
visibility?: AssetVisibility;
|
||||
};
|
||||
export type AssetMediaResponseDto = {
|
||||
id: string;
|
||||
|
@ -427,11 +426,11 @@ export type AssetBulkUpdateDto = {
|
|||
dateTimeOriginal?: string;
|
||||
duplicateId?: string | null;
|
||||
ids: string[];
|
||||
isArchived?: boolean;
|
||||
isFavorite?: boolean;
|
||||
latitude?: number;
|
||||
longitude?: number;
|
||||
rating?: number;
|
||||
visibility?: AssetVisibility;
|
||||
};
|
||||
export type AssetBulkUploadCheckItem = {
|
||||
/** base64 or hex encoded sha1 hash */
|
||||
|
@ -470,12 +469,12 @@ export type AssetStatsResponseDto = {
|
|||
export type UpdateAssetDto = {
|
||||
dateTimeOriginal?: string;
|
||||
description?: string;
|
||||
isArchived?: boolean;
|
||||
isFavorite?: boolean;
|
||||
latitude?: number;
|
||||
livePhotoVideoId?: string | null;
|
||||
longitude?: number;
|
||||
rating?: number;
|
||||
visibility?: AssetVisibility;
|
||||
};
|
||||
export type AssetMediaReplaceDto = {
|
||||
assetData: Blob;
|
||||
|
@ -815,13 +814,11 @@ export type MetadataSearchDto = {
|
|||
deviceId?: string;
|
||||
encodedVideoPath?: string;
|
||||
id?: string;
|
||||
isArchived?: boolean;
|
||||
isEncoded?: boolean;
|
||||
isFavorite?: boolean;
|
||||
isMotion?: boolean;
|
||||
isNotInAlbum?: boolean;
|
||||
isOffline?: boolean;
|
||||
isVisible?: boolean;
|
||||
lensModel?: string | null;
|
||||
libraryId?: string | null;
|
||||
make?: string;
|
||||
|
@ -844,7 +841,7 @@ export type MetadataSearchDto = {
|
|||
"type"?: AssetTypeEnum;
|
||||
updatedAfter?: string;
|
||||
updatedBefore?: string;
|
||||
withArchived?: boolean;
|
||||
visibility?: AssetVisibility;
|
||||
withDeleted?: boolean;
|
||||
withExif?: boolean;
|
||||
withPeople?: boolean;
|
||||
|
@ -888,13 +885,11 @@ export type RandomSearchDto = {
|
|||
createdAfter?: string;
|
||||
createdBefore?: string;
|
||||
deviceId?: string;
|
||||
isArchived?: boolean;
|
||||
isEncoded?: boolean;
|
||||
isFavorite?: boolean;
|
||||
isMotion?: boolean;
|
||||
isNotInAlbum?: boolean;
|
||||
isOffline?: boolean;
|
||||
isVisible?: boolean;
|
||||
lensModel?: string | null;
|
||||
libraryId?: string | null;
|
||||
make?: string;
|
||||
|
@ -911,7 +906,7 @@ export type RandomSearchDto = {
|
|||
"type"?: AssetTypeEnum;
|
||||
updatedAfter?: string;
|
||||
updatedBefore?: string;
|
||||
withArchived?: boolean;
|
||||
visibility?: AssetVisibility;
|
||||
withDeleted?: boolean;
|
||||
withExif?: boolean;
|
||||
withPeople?: boolean;
|
||||
|
@ -923,13 +918,11 @@ export type SmartSearchDto = {
|
|||
createdAfter?: string;
|
||||
createdBefore?: string;
|
||||
deviceId?: string;
|
||||
isArchived?: boolean;
|
||||
isEncoded?: boolean;
|
||||
isFavorite?: boolean;
|
||||
isMotion?: boolean;
|
||||
isNotInAlbum?: boolean;
|
||||
isOffline?: boolean;
|
||||
isVisible?: boolean;
|
||||
language?: string;
|
||||
lensModel?: string | null;
|
||||
libraryId?: string | null;
|
||||
|
@ -949,7 +942,7 @@ export type SmartSearchDto = {
|
|||
"type"?: AssetTypeEnum;
|
||||
updatedAfter?: string;
|
||||
updatedBefore?: string;
|
||||
withArchived?: boolean;
|
||||
visibility?: AssetVisibility;
|
||||
withDeleted?: boolean;
|
||||
withExif?: boolean;
|
||||
};
|
||||
|
@ -1877,18 +1870,18 @@ export function getRandom({ count }: {
|
|||
...opts
|
||||
}));
|
||||
}
|
||||
export function getAssetStatistics({ isArchived, isFavorite, isTrashed }: {
|
||||
isArchived?: boolean;
|
||||
export function getAssetStatistics({ isFavorite, isTrashed, visibility }: {
|
||||
isFavorite?: boolean;
|
||||
isTrashed?: boolean;
|
||||
visibility?: AssetVisibility;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 200;
|
||||
data: AssetStatsResponseDto;
|
||||
}>(`/assets/statistics${QS.query(QS.explode({
|
||||
isArchived,
|
||||
isFavorite,
|
||||
isTrashed
|
||||
isTrashed,
|
||||
visibility
|
||||
}))}`, {
|
||||
...opts
|
||||
}));
|
||||
|
@ -3242,9 +3235,8 @@ export function tagAssets({ id, bulkIdsDto }: {
|
|||
body: bulkIdsDto
|
||||
})));
|
||||
}
|
||||
export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key, order, personId, size, tagId, timeBucket, userId, withPartners, withStacked }: {
|
||||
export function getTimeBucket({ albumId, isFavorite, isTrashed, key, order, personId, size, tagId, timeBucket, userId, visibility, withPartners, withStacked }: {
|
||||
albumId?: string;
|
||||
isArchived?: boolean;
|
||||
isFavorite?: boolean;
|
||||
isTrashed?: boolean;
|
||||
key?: string;
|
||||
|
@ -3254,6 +3246,7 @@ export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key,
|
|||
tagId?: string;
|
||||
timeBucket: string;
|
||||
userId?: string;
|
||||
visibility?: AssetVisibility;
|
||||
withPartners?: boolean;
|
||||
withStacked?: boolean;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
|
@ -3262,7 +3255,6 @@ export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key,
|
|||
data: AssetResponseDto[];
|
||||
}>(`/timeline/bucket${QS.query(QS.explode({
|
||||
albumId,
|
||||
isArchived,
|
||||
isFavorite,
|
||||
isTrashed,
|
||||
key,
|
||||
|
@ -3272,15 +3264,15 @@ export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key,
|
|||
tagId,
|
||||
timeBucket,
|
||||
userId,
|
||||
visibility,
|
||||
withPartners,
|
||||
withStacked
|
||||
}))}`, {
|
||||
...opts
|
||||
}));
|
||||
}
|
||||
export function getTimeBuckets({ albumId, isArchived, isFavorite, isTrashed, key, order, personId, size, tagId, userId, withPartners, withStacked }: {
|
||||
export function getTimeBuckets({ albumId, isFavorite, isTrashed, key, order, personId, size, tagId, userId, visibility, withPartners, withStacked }: {
|
||||
albumId?: string;
|
||||
isArchived?: boolean;
|
||||
isFavorite?: boolean;
|
||||
isTrashed?: boolean;
|
||||
key?: string;
|
||||
|
@ -3289,6 +3281,7 @@ export function getTimeBuckets({ albumId, isArchived, isFavorite, isTrashed, key
|
|||
size: TimeBucketSize;
|
||||
tagId?: string;
|
||||
userId?: string;
|
||||
visibility?: AssetVisibility;
|
||||
withPartners?: boolean;
|
||||
withStacked?: boolean;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
|
@ -3297,7 +3290,6 @@ export function getTimeBuckets({ albumId, isArchived, isFavorite, isTrashed, key
|
|||
data: TimeBucketResponseDto[];
|
||||
}>(`/timeline/buckets${QS.query(QS.explode({
|
||||
albumId,
|
||||
isArchived,
|
||||
isFavorite,
|
||||
isTrashed,
|
||||
key,
|
||||
|
@ -3306,6 +3298,7 @@ export function getTimeBuckets({ albumId, isArchived, isFavorite, isTrashed, key
|
|||
size,
|
||||
tagId,
|
||||
userId,
|
||||
visibility,
|
||||
withPartners,
|
||||
withStacked
|
||||
}))}`, {
|
||||
|
@ -3620,6 +3613,11 @@ export enum Permission {
|
|||
AdminUserUpdate = "admin.user.update",
|
||||
AdminUserDelete = "admin.user.delete"
|
||||
}
|
||||
export enum AssetVisibility {
|
||||
Archive = "archive",
|
||||
Timeline = "timeline",
|
||||
Hidden = "hidden"
|
||||
}
|
||||
export enum AssetMediaStatus {
|
||||
Created = "created",
|
||||
Replaced = "replaced",
|
||||
|
|
|
@ -100,38 +100,29 @@ describe(AssetMediaController.name, () => {
|
|||
expect(body).toEqual(factory.responses.badRequest());
|
||||
});
|
||||
|
||||
it('should throw if `isVisible` is not a boolean', async () => {
|
||||
it('should throw if `visibility` is not an enum', async () => {
|
||||
const { status, body } = await request(ctx.getHttpServer())
|
||||
.post('/assets')
|
||||
.attach('assetData', assetData, filename)
|
||||
.field({ ...makeUploadDto(), isVisible: 'not-a-boolean' });
|
||||
.field({ ...makeUploadDto(), visibility: 'not-a-boolean' });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(factory.responses.badRequest());
|
||||
});
|
||||
|
||||
it('should throw if `isArchived` is not a boolean', async () => {
|
||||
const { status, body } = await request(ctx.getHttpServer())
|
||||
.post('/assets')
|
||||
.attach('assetData', assetData, filename)
|
||||
.field({ ...makeUploadDto(), isArchived: 'not-a-boolean' });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(factory.responses.badRequest());
|
||||
// TODO figure out how to deal with `sendFile`
|
||||
describe.skip('GET /assets/:id/original', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).get(`/assets/${factory.uuid()}/original`);
|
||||
expect(ctx.authenticate).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// TODO figure out how to deal with `sendFile`
|
||||
describe.skip('GET /assets/:id/original', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).get(`/assets/${factory.uuid()}/original`);
|
||||
expect(ctx.authenticate).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// TODO figure out how to deal with `sendFile`
|
||||
describe.skip('GET /assets/:id/thumbnail', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).get(`/assets/${factory.uuid()}/thumbnail`);
|
||||
expect(ctx.authenticate).toHaveBeenCalled();
|
||||
// TODO figure out how to deal with `sendFile`
|
||||
describe.skip('GET /assets/:id/thumbnail', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).get(`/assets/${factory.uuid()}/thumbnail`);
|
||||
expect(ctx.authenticate).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -60,12 +60,14 @@ describe(SearchController.name, () => {
|
|||
expect(body).toEqual(errorDto.badRequest(['size must not be less than 1', 'size must be an integer number']));
|
||||
});
|
||||
|
||||
it('should reject an isArchived as not a boolean', async () => {
|
||||
it('should reject an visibility as not an enum', async () => {
|
||||
const { status, body } = await request(ctx.getHttpServer())
|
||||
.post('/search/metadata')
|
||||
.send({ isArchived: 'immich' });
|
||||
.send({ visibility: 'immich' });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['isArchived must be a boolean value']));
|
||||
expect(body).toEqual(
|
||||
errorDto.badRequest(['visibility must be one of the following values: archive, timeline, hidden']),
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject an isFavorite as not a boolean', async () => {
|
||||
|
@ -98,104 +100,98 @@ describe(SearchController.name, () => {
|
|||
expect(body).toEqual(errorDto.badRequest(['isMotion must be a boolean value']));
|
||||
});
|
||||
|
||||
it('should reject an isVisible as not a boolean', async () => {
|
||||
const { status, body } = await request(ctx.getHttpServer())
|
||||
.post('/search/metadata')
|
||||
.send({ isVisible: 'immich' });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['isVisible must be a boolean value']));
|
||||
});
|
||||
});
|
||||
describe('POST /search/random', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).post('/search/random');
|
||||
expect(ctx.authenticate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('POST /search/random', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).post('/search/random');
|
||||
expect(ctx.authenticate).toHaveBeenCalled();
|
||||
it('should reject if withStacked is not a boolean', async () => {
|
||||
const { status, body } = await request(ctx.getHttpServer())
|
||||
.post('/search/random')
|
||||
.send({ withStacked: 'immich' });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['withStacked must be a boolean value']));
|
||||
});
|
||||
|
||||
it('should reject if withPeople is not a boolean', async () => {
|
||||
const { status, body } = await request(ctx.getHttpServer())
|
||||
.post('/search/random')
|
||||
.send({ withPeople: 'immich' });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['withPeople must be a boolean value']));
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject if withStacked is not a boolean', async () => {
|
||||
const { status, body } = await request(ctx.getHttpServer())
|
||||
.post('/search/random')
|
||||
.send({ withStacked: 'immich' });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['withStacked must be a boolean value']));
|
||||
describe('POST /search/smart', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).post('/search/smart');
|
||||
expect(ctx.authenticate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should require a query', async () => {
|
||||
const { status, body } = await request(ctx.getHttpServer()).post('/search/smart').send({});
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['query should not be empty', 'query must be a string']));
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject if withPeople is not a boolean', async () => {
|
||||
const { status, body } = await request(ctx.getHttpServer()).post('/search/random').send({ withPeople: 'immich' });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['withPeople must be a boolean value']));
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /search/smart', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).post('/search/smart');
|
||||
expect(ctx.authenticate).toHaveBeenCalled();
|
||||
describe('GET /search/explore', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).get('/search/explore');
|
||||
expect(ctx.authenticate).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should require a query', async () => {
|
||||
const { status, body } = await request(ctx.getHttpServer()).post('/search/smart').send({});
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['query should not be empty', 'query must be a string']));
|
||||
});
|
||||
});
|
||||
describe('POST /search/person', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).get('/search/person');
|
||||
expect(ctx.authenticate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('GET /search/explore', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).get('/search/explore');
|
||||
expect(ctx.authenticate).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /search/person', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).get('/search/person');
|
||||
expect(ctx.authenticate).toHaveBeenCalled();
|
||||
it('should require a name', async () => {
|
||||
const { status, body } = await request(ctx.getHttpServer()).get('/search/person').send({});
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['name should not be empty', 'name must be a string']));
|
||||
});
|
||||
});
|
||||
|
||||
it('should require a name', async () => {
|
||||
const { status, body } = await request(ctx.getHttpServer()).get('/search/person').send({});
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['name should not be empty', 'name must be a string']));
|
||||
});
|
||||
});
|
||||
describe('GET /search/places', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).get('/search/places');
|
||||
expect(ctx.authenticate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('GET /search/places', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).get('/search/places');
|
||||
expect(ctx.authenticate).toHaveBeenCalled();
|
||||
it('should require a name', async () => {
|
||||
const { status, body } = await request(ctx.getHttpServer()).get('/search/places').send({});
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['name should not be empty', 'name must be a string']));
|
||||
});
|
||||
});
|
||||
|
||||
it('should require a name', async () => {
|
||||
const { status, body } = await request(ctx.getHttpServer()).get('/search/places').send({});
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['name should not be empty', 'name must be a string']));
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /search/cities', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).get('/search/cities');
|
||||
expect(ctx.authenticate).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /search/suggestions', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).get('/search/suggestions');
|
||||
expect(ctx.authenticate).toHaveBeenCalled();
|
||||
describe('GET /search/cities', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).get('/search/cities');
|
||||
expect(ctx.authenticate).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should require a type', async () => {
|
||||
const { status, body } = await request(ctx.getHttpServer()).get('/search/suggestions').send({});
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(
|
||||
errorDto.badRequest([
|
||||
'type should not be empty',
|
||||
expect.stringContaining('type must be one of the following values:'),
|
||||
]),
|
||||
);
|
||||
describe('GET /search/suggestions', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).get('/search/suggestions');
|
||||
expect(ctx.authenticate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should require a type', async () => {
|
||||
const { status, body } = await request(ctx.getHttpServer()).get('/search/suggestions').send({});
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(
|
||||
errorDto.badRequest([
|
||||
'type should not be empty',
|
||||
expect.stringContaining('type must be one of the following values:'),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
AlbumUserRole,
|
||||
AssetFileType,
|
||||
AssetType,
|
||||
AssetVisibility,
|
||||
MemoryType,
|
||||
Permission,
|
||||
SharedLinkType,
|
||||
|
@ -108,7 +109,7 @@ export type Asset = {
|
|||
fileCreatedAt: Date;
|
||||
fileModifiedAt: Date;
|
||||
isExternal: boolean;
|
||||
isVisible: boolean;
|
||||
visibility: AssetVisibility;
|
||||
libraryId: string | null;
|
||||
livePhotoVideoId: string | null;
|
||||
localDateTime: Date;
|
||||
|
@ -285,7 +286,7 @@ export const columns = {
|
|||
'assets.fileCreatedAt',
|
||||
'assets.fileModifiedAt',
|
||||
'assets.isExternal',
|
||||
'assets.isVisible',
|
||||
'assets.visibility',
|
||||
'assets.libraryId',
|
||||
'assets.livePhotoVideoId',
|
||||
'assets.localDateTime',
|
||||
|
@ -345,7 +346,7 @@ export const columns = {
|
|||
'type',
|
||||
'deletedAt',
|
||||
'isFavorite',
|
||||
'isVisible',
|
||||
'visibility',
|
||||
'updateId',
|
||||
],
|
||||
stack: ['stack.id', 'stack.primaryAssetId', 'ownerId'],
|
||||
|
|
4
server/src/db.d.ts
vendored
4
server/src/db.d.ts
vendored
|
@ -10,6 +10,7 @@ import {
|
|||
AssetOrder,
|
||||
AssetStatus,
|
||||
AssetType,
|
||||
AssetVisibility,
|
||||
MemoryType,
|
||||
NotificationLevel,
|
||||
NotificationType,
|
||||
|
@ -148,11 +149,10 @@ export interface Assets {
|
|||
fileCreatedAt: Timestamp;
|
||||
fileModifiedAt: Timestamp;
|
||||
id: Generated<string>;
|
||||
isArchived: Generated<boolean>;
|
||||
isExternal: Generated<boolean>;
|
||||
isFavorite: Generated<boolean>;
|
||||
isOffline: Generated<boolean>;
|
||||
isVisible: Generated<boolean>;
|
||||
visibility: Generated<AssetVisibility>;
|
||||
libraryId: string | null;
|
||||
livePhotoVideoId: string | null;
|
||||
localDateTime: Timestamp;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import { ArrayNotEmpty, IsArray, IsEnum, IsNotEmpty, IsString, ValidateNested } from 'class-validator';
|
||||
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation';
|
||||
import { AssetVisibility } from 'src/enum';
|
||||
import { Optional, ValidateAssetVisibility, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation';
|
||||
|
||||
export enum AssetMediaSize {
|
||||
/**
|
||||
|
@ -55,11 +56,8 @@ export class AssetMediaCreateDto extends AssetMediaBase {
|
|||
@ValidateBoolean({ optional: true })
|
||||
isFavorite?: boolean;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
isArchived?: boolean;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
isVisible?: boolean;
|
||||
@ValidateAssetVisibility({ optional: true })
|
||||
visibility?: AssetVisibility;
|
||||
|
||||
@ValidateUUID({ optional: true })
|
||||
livePhotoVideoId?: string;
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
} from 'src/dtos/person.dto';
|
||||
import { TagResponseDto, mapTag } from 'src/dtos/tag.dto';
|
||||
import { UserResponseDto, mapUser } from 'src/dtos/user.dto';
|
||||
import { AssetStatus, AssetType } from 'src/enum';
|
||||
import { AssetStatus, AssetType, AssetVisibility } from 'src/enum';
|
||||
import { mimeTypes } from 'src/utils/mime-types';
|
||||
|
||||
export class SanitizedAssetResponseDto {
|
||||
|
@ -74,11 +74,10 @@ export type MapAsset = {
|
|||
fileCreatedAt: Date;
|
||||
fileModifiedAt: Date;
|
||||
files?: AssetFile[];
|
||||
isArchived: boolean;
|
||||
isExternal: boolean;
|
||||
isFavorite: boolean;
|
||||
isOffline: boolean;
|
||||
isVisible: boolean;
|
||||
visibility: AssetVisibility;
|
||||
libraryId: string | null;
|
||||
livePhotoVideoId: string | null;
|
||||
localDateTime: Date;
|
||||
|
@ -183,7 +182,7 @@ export function mapAsset(entity: MapAsset, options: AssetMapOptions = {}): Asset
|
|||
localDateTime: entity.localDateTime,
|
||||
updatedAt: entity.updatedAt,
|
||||
isFavorite: options.auth?.user.id === entity.ownerId ? entity.isFavorite : false,
|
||||
isArchived: entity.isArchived,
|
||||
isArchived: entity.visibility === AssetVisibility.ARCHIVE,
|
||||
isTrashed: !!entity.deletedAt,
|
||||
duration: entity.duration ?? '0:00:00.00000',
|
||||
exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined,
|
||||
|
|
|
@ -14,9 +14,9 @@ import {
|
|||
ValidateIf,
|
||||
} from 'class-validator';
|
||||
import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
||||
import { AssetType } from 'src/enum';
|
||||
import { AssetType, AssetVisibility } from 'src/enum';
|
||||
import { AssetStats } from 'src/repositories/asset.repository';
|
||||
import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation';
|
||||
import { Optional, ValidateAssetVisibility, ValidateBoolean, ValidateUUID } from 'src/validation';
|
||||
|
||||
export class DeviceIdDto {
|
||||
@IsNotEmpty()
|
||||
|
@ -32,8 +32,8 @@ export class UpdateAssetBase {
|
|||
@ValidateBoolean({ optional: true })
|
||||
isFavorite?: boolean;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
isArchived?: boolean;
|
||||
@ValidateAssetVisibility({ optional: true })
|
||||
visibility?: AssetVisibility;
|
||||
|
||||
@Optional()
|
||||
@IsDateString()
|
||||
|
@ -105,8 +105,8 @@ export class AssetJobsDto extends AssetIdsDto {
|
|||
}
|
||||
|
||||
export class AssetStatsDto {
|
||||
@ValidateBoolean({ optional: true })
|
||||
isArchived?: boolean;
|
||||
@ValidateAssetVisibility({ optional: true })
|
||||
visibility?: AssetVisibility;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
isFavorite?: boolean;
|
||||
|
|
|
@ -5,8 +5,8 @@ import { Place } from 'src/database';
|
|||
import { PropertyLifecycle } from 'src/decorators';
|
||||
import { AlbumResponseDto } from 'src/dtos/album.dto';
|
||||
import { AssetResponseDto } from 'src/dtos/asset-response.dto';
|
||||
import { AssetOrder, AssetType } from 'src/enum';
|
||||
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation';
|
||||
import { AssetOrder, AssetType, AssetVisibility } from 'src/enum';
|
||||
import { Optional, ValidateAssetVisibility, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation';
|
||||
|
||||
class BaseSearchDto {
|
||||
@ValidateUUID({ optional: true, nullable: true })
|
||||
|
@ -22,13 +22,6 @@ class BaseSearchDto {
|
|||
@ApiProperty({ enumName: 'AssetTypeEnum', enum: AssetType })
|
||||
type?: AssetType;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
isArchived?: boolean;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
@ApiProperty({ default: false })
|
||||
withArchived?: boolean;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
isEncoded?: boolean;
|
||||
|
||||
|
@ -41,8 +34,8 @@ class BaseSearchDto {
|
|||
@ValidateBoolean({ optional: true })
|
||||
isOffline?: boolean;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
isVisible?: boolean;
|
||||
@ValidateAssetVisibility({ optional: true })
|
||||
visibility?: AssetVisibility;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
withDeleted?: boolean;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsEnum, IsInt, IsPositive, IsString } from 'class-validator';
|
||||
import { AssetResponseDto } from 'src/dtos/asset-response.dto';
|
||||
import { AssetType, SyncEntityType, SyncRequestType } from 'src/enum';
|
||||
import { AssetType, AssetVisibility, SyncEntityType, SyncRequestType } from 'src/enum';
|
||||
import { Optional, ValidateDate, ValidateUUID } from 'src/validation';
|
||||
|
||||
export class AssetFullSyncDto {
|
||||
|
@ -67,7 +67,7 @@ export class SyncAssetV1 {
|
|||
type!: AssetType;
|
||||
deletedAt!: Date | null;
|
||||
isFavorite!: boolean;
|
||||
isVisible!: boolean;
|
||||
visibility!: AssetVisibility;
|
||||
}
|
||||
|
||||
export class SyncAssetDeleteV1 {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
|
||||
import { AssetOrder } from 'src/enum';
|
||||
import { AssetOrder, AssetVisibility } from 'src/enum';
|
||||
import { TimeBucketSize } from 'src/repositories/asset.repository';
|
||||
import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation';
|
||||
import { Optional, ValidateAssetVisibility, ValidateBoolean, ValidateUUID } from 'src/validation';
|
||||
|
||||
export class TimeBucketDto {
|
||||
@IsNotEmpty()
|
||||
|
@ -22,9 +22,6 @@ export class TimeBucketDto {
|
|||
@ValidateUUID({ optional: true })
|
||||
tagId?: string;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
isArchived?: boolean;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
isFavorite?: boolean;
|
||||
|
||||
|
@ -41,6 +38,9 @@ export class TimeBucketDto {
|
|||
@Optional()
|
||||
@ApiProperty({ enum: AssetOrder, enumName: 'AssetOrder' })
|
||||
order?: AssetOrder;
|
||||
|
||||
@ValidateAssetVisibility({ optional: true })
|
||||
visibility?: AssetVisibility;
|
||||
}
|
||||
|
||||
export class TimeBucketAssetDto extends TimeBucketDto {
|
||||
|
|
|
@ -618,3 +618,13 @@ export enum DatabaseSslMode {
|
|||
Require = 'require',
|
||||
VerifyFull = 'verify-full',
|
||||
}
|
||||
|
||||
export enum AssetVisibility {
|
||||
ARCHIVE = 'archive',
|
||||
TIMELINE = 'timeline',
|
||||
|
||||
/**
|
||||
* Video part of the LivePhotos and MotionPhotos
|
||||
*/
|
||||
HIDDEN = 'hidden',
|
||||
}
|
||||
|
|
|
@ -110,8 +110,11 @@ from
|
|||
and "assets"."deletedAt" is null
|
||||
where
|
||||
"partner"."sharedWithId" = $1
|
||||
and "assets"."isArchived" = $2
|
||||
and "assets"."id" in ($3)
|
||||
and (
|
||||
"assets"."visibility" = 'timeline'
|
||||
or "assets"."visibility" = 'hidden'
|
||||
)
|
||||
and "assets"."id" in ($2)
|
||||
|
||||
-- AccessRepository.asset.checkSharedLinkAccess
|
||||
select
|
||||
|
|
|
@ -7,7 +7,7 @@ select
|
|||
"ownerId",
|
||||
"duplicateId",
|
||||
"stackId",
|
||||
"isVisible",
|
||||
"visibility",
|
||||
"smart_search"."embedding",
|
||||
(
|
||||
select
|
||||
|
@ -83,7 +83,7 @@ from
|
|||
inner join "asset_job_status" on "asset_job_status"."assetId" = "assets"."id"
|
||||
where
|
||||
"assets"."deletedAt" is null
|
||||
and "assets"."isVisible" = $1
|
||||
and "assets"."visibility" != $1
|
||||
and (
|
||||
"asset_job_status"."previewAt" is null
|
||||
or "asset_job_status"."thumbnailAt" is null
|
||||
|
@ -118,7 +118,7 @@ where
|
|||
-- AssetJobRepository.getForGenerateThumbnailJob
|
||||
select
|
||||
"assets"."id",
|
||||
"assets"."isVisible",
|
||||
"assets"."visibility",
|
||||
"assets"."originalFileName",
|
||||
"assets"."originalPath",
|
||||
"assets"."ownerId",
|
||||
|
@ -155,7 +155,7 @@ select
|
|||
"assets"."fileCreatedAt",
|
||||
"assets"."fileModifiedAt",
|
||||
"assets"."isExternal",
|
||||
"assets"."isVisible",
|
||||
"assets"."visibility",
|
||||
"assets"."libraryId",
|
||||
"assets"."livePhotoVideoId",
|
||||
"assets"."localDateTime",
|
||||
|
@ -201,7 +201,7 @@ from
|
|||
"assets"
|
||||
inner join "asset_job_status" as "job_status" on "assetId" = "assets"."id"
|
||||
where
|
||||
"assets"."isVisible" = $1
|
||||
"assets"."visibility" != $1
|
||||
and "assets"."deletedAt" is null
|
||||
and "job_status"."previewAt" is not null
|
||||
and not exists (
|
||||
|
@ -220,7 +220,7 @@ from
|
|||
"assets"
|
||||
inner join "asset_job_status" as "job_status" on "assetId" = "assets"."id"
|
||||
where
|
||||
"assets"."isVisible" = $1
|
||||
"assets"."visibility" != $1
|
||||
and "assets"."deletedAt" is null
|
||||
and "job_status"."previewAt" is not null
|
||||
and not exists (
|
||||
|
@ -234,7 +234,7 @@ where
|
|||
-- AssetJobRepository.getForClipEncoding
|
||||
select
|
||||
"assets"."id",
|
||||
"assets"."isVisible",
|
||||
"assets"."visibility",
|
||||
(
|
||||
select
|
||||
coalesce(json_agg(agg), '[]')
|
||||
|
@ -259,7 +259,7 @@ where
|
|||
-- AssetJobRepository.getForDetectFacesJob
|
||||
select
|
||||
"assets"."id",
|
||||
"assets"."isVisible",
|
||||
"assets"."visibility",
|
||||
to_json("exif") as "exifInfo",
|
||||
(
|
||||
select
|
||||
|
@ -312,7 +312,7 @@ where
|
|||
-- AssetJobRepository.getForAssetDeletion
|
||||
select
|
||||
"assets"."id",
|
||||
"assets"."isVisible",
|
||||
"assets"."visibility",
|
||||
"assets"."libraryId",
|
||||
"assets"."ownerId",
|
||||
"assets"."livePhotoVideoId",
|
||||
|
@ -372,7 +372,7 @@ from
|
|||
"assets" as "stacked"
|
||||
where
|
||||
"stacked"."deletedAt" is not null
|
||||
and "stacked"."isArchived" = $1
|
||||
and "stacked"."visibility" != $1
|
||||
and "stacked"."stackId" = "asset_stack"."id"
|
||||
group by
|
||||
"asset_stack"."id"
|
||||
|
@ -391,7 +391,7 @@ where
|
|||
"assets"."encodedVideoPath" is null
|
||||
or "assets"."encodedVideoPath" = $2
|
||||
)
|
||||
and "assets"."isVisible" = $3
|
||||
and "assets"."visibility" != $3
|
||||
and "assets"."deletedAt" is null
|
||||
|
||||
-- AssetJobRepository.getForVideoConversion
|
||||
|
@ -417,7 +417,7 @@ where
|
|||
"asset_job_status"."metadataExtractedAt" is null
|
||||
or "asset_job_status"."assetId" is null
|
||||
)
|
||||
and "assets"."isVisible" = $1
|
||||
and "assets"."visibility" != $1
|
||||
and "assets"."deletedAt" is null
|
||||
|
||||
-- AssetJobRepository.getForStorageTemplateJob
|
||||
|
@ -480,7 +480,7 @@ where
|
|||
"assets"."sidecarPath" = $1
|
||||
or "assets"."sidecarPath" is null
|
||||
)
|
||||
and "assets"."isVisible" = $2
|
||||
and "assets"."visibility" != $2
|
||||
|
||||
-- AssetJobRepository.streamForDetectFacesJob
|
||||
select
|
||||
|
@ -489,7 +489,7 @@ from
|
|||
"assets"
|
||||
inner join "asset_job_status" as "job_status" on "assetId" = "assets"."id"
|
||||
where
|
||||
"assets"."isVisible" = $1
|
||||
"assets"."visibility" != $1
|
||||
and "assets"."deletedAt" is null
|
||||
and "job_status"."previewAt" is not null
|
||||
and "job_status"."facesRecognizedAt" is null
|
||||
|
|
|
@ -43,21 +43,20 @@ with
|
|||
"asset_job_status"."previewAt" is not null
|
||||
and (assets."localDateTime" at time zone 'UTC')::date = today.date
|
||||
and "assets"."ownerId" = any ($3::uuid[])
|
||||
and "assets"."isVisible" = $4
|
||||
and "assets"."isArchived" = $5
|
||||
and "assets"."visibility" = $4
|
||||
and exists (
|
||||
select
|
||||
from
|
||||
"asset_files"
|
||||
where
|
||||
"assetId" = "assets"."id"
|
||||
and "asset_files"."type" = $6
|
||||
and "asset_files"."type" = $5
|
||||
)
|
||||
and "assets"."deletedAt" is null
|
||||
order by
|
||||
(assets."localDateTime" at time zone 'UTC')::date desc
|
||||
limit
|
||||
$7
|
||||
$6
|
||||
) as "a" on true
|
||||
inner join "exif" on "a"."id" = "exif"."assetId"
|
||||
)
|
||||
|
@ -159,7 +158,7 @@ from
|
|||
where
|
||||
"ownerId" = $1::uuid
|
||||
and "deviceId" = $2
|
||||
and "isVisible" = $3
|
||||
and "visibility" != $3
|
||||
and "deletedAt" is null
|
||||
|
||||
-- AssetRepository.getLivePhotoCount
|
||||
|
@ -241,7 +240,10 @@ with
|
|||
"assets"
|
||||
where
|
||||
"assets"."deletedAt" is null
|
||||
and "assets"."isVisible" = $2
|
||||
and (
|
||||
"assets"."visibility" = $2
|
||||
or "assets"."visibility" = $3
|
||||
)
|
||||
)
|
||||
select
|
||||
"timeBucket",
|
||||
|
@ -271,7 +273,7 @@ from
|
|||
where
|
||||
"stacked"."stackId" = "asset_stack"."id"
|
||||
and "stacked"."deletedAt" is null
|
||||
and "stacked"."isArchived" = $1
|
||||
and "stacked"."visibility" != $1
|
||||
group by
|
||||
"asset_stack"."id"
|
||||
) as "stacked_assets" on "asset_stack"."id" is not null
|
||||
|
@ -281,8 +283,11 @@ where
|
|||
or "assets"."stackId" is null
|
||||
)
|
||||
and "assets"."deletedAt" is null
|
||||
and "assets"."isVisible" = $2
|
||||
and date_trunc($3, "localDateTime" at time zone 'UTC') at time zone 'UTC' = $4
|
||||
and (
|
||||
"assets"."visibility" = $2
|
||||
or "assets"."visibility" = $3
|
||||
)
|
||||
and date_trunc($4, "localDateTime" at time zone 'UTC') at time zone 'UTC' = $5
|
||||
order by
|
||||
"assets"."localDateTime" desc
|
||||
|
||||
|
@ -307,7 +312,7 @@ with
|
|||
"assets"."ownerId" = $1::uuid
|
||||
and "assets"."duplicateId" is not null
|
||||
and "assets"."deletedAt" is null
|
||||
and "assets"."isVisible" = $2
|
||||
and "assets"."visibility" != $2
|
||||
and "assets"."stackId" is null
|
||||
group by
|
||||
"assets"."duplicateId"
|
||||
|
@ -365,12 +370,11 @@ from
|
|||
inner join "cities" on "exif"."city" = "cities"."city"
|
||||
where
|
||||
"ownerId" = $2::uuid
|
||||
and "isVisible" = $3
|
||||
and "isArchived" = $4
|
||||
and "type" = $5
|
||||
and "visibility" = $3
|
||||
and "type" = $4
|
||||
and "deletedAt" is null
|
||||
limit
|
||||
$6
|
||||
$5
|
||||
|
||||
-- AssetRepository.getAllForUserFullSync
|
||||
select
|
||||
|
@ -394,7 +398,7 @@ from
|
|||
) as "stacked_assets" on "asset_stack"."id" is not null
|
||||
where
|
||||
"assets"."ownerId" = $1::uuid
|
||||
and "assets"."isVisible" = $2
|
||||
and "assets"."visibility" != $2
|
||||
and "assets"."updatedAt" <= $3
|
||||
and "assets"."id" > $4
|
||||
order by
|
||||
|
@ -424,7 +428,7 @@ from
|
|||
) as "stacked_assets" on "asset_stack"."id" is not null
|
||||
where
|
||||
"assets"."ownerId" = any ($1::uuid[])
|
||||
and "assets"."isVisible" = $2
|
||||
and "assets"."visibility" != $2
|
||||
and "assets"."updatedAt" > $3
|
||||
limit
|
||||
$4
|
||||
|
|
|
@ -35,14 +35,14 @@ select
|
|||
where
|
||||
(
|
||||
"assets"."type" = $1
|
||||
and "assets"."isVisible" = $2
|
||||
and "assets"."visibility" != $2
|
||||
)
|
||||
) as "photos",
|
||||
count(*) filter (
|
||||
where
|
||||
(
|
||||
"assets"."type" = $3
|
||||
and "assets"."isVisible" = $4
|
||||
and "assets"."visibility" != $4
|
||||
)
|
||||
) as "videos",
|
||||
coalesce(sum("exif"."fileSizeInByte"), $5) as "usage"
|
||||
|
|
|
@ -14,7 +14,7 @@ from
|
|||
and "exif"."latitude" is not null
|
||||
and "exif"."longitude" is not null
|
||||
where
|
||||
"isVisible" = $1
|
||||
"assets"."visibility" = $1
|
||||
and "deletedAt" is null
|
||||
and (
|
||||
"ownerId" in ($2)
|
||||
|
|
|
@ -107,7 +107,7 @@ select
|
|||
(
|
||||
select
|
||||
"assets"."ownerId",
|
||||
"assets"."isArchived",
|
||||
"assets"."visibility",
|
||||
"assets"."fileCreatedAt"
|
||||
from
|
||||
"assets"
|
||||
|
@ -203,7 +203,7 @@ from
|
|||
"asset_faces"
|
||||
left join "assets" on "assets"."id" = "asset_faces"."assetId"
|
||||
and "asset_faces"."personId" = $1
|
||||
and "assets"."isArchived" = $2
|
||||
and "assets"."visibility" != $2
|
||||
and "assets"."deletedAt" is null
|
||||
where
|
||||
"asset_faces"."deletedAt" is null
|
||||
|
@ -220,7 +220,7 @@ from
|
|||
inner join "asset_faces" on "asset_faces"."personId" = "person"."id"
|
||||
inner join "assets" on "assets"."id" = "asset_faces"."assetId"
|
||||
and "assets"."deletedAt" is null
|
||||
and "assets"."isArchived" = $2
|
||||
and "assets"."visibility" != $2
|
||||
where
|
||||
"person"."ownerId" = $3
|
||||
and "asset_faces"."deletedAt" is null
|
||||
|
|
|
@ -7,11 +7,11 @@ from
|
|||
"assets"
|
||||
inner join "exif" on "assets"."id" = "exif"."assetId"
|
||||
where
|
||||
"assets"."fileCreatedAt" >= $1
|
||||
and "exif"."lensModel" = $2
|
||||
and "assets"."ownerId" = any ($3::uuid[])
|
||||
and "assets"."isFavorite" = $4
|
||||
and "assets"."isArchived" = $5
|
||||
"assets"."visibility" = $1
|
||||
and "assets"."fileCreatedAt" >= $2
|
||||
and "exif"."lensModel" = $3
|
||||
and "assets"."ownerId" = any ($4::uuid[])
|
||||
and "assets"."isFavorite" = $5
|
||||
and "assets"."deletedAt" is null
|
||||
order by
|
||||
"assets"."fileCreatedAt" desc
|
||||
|
@ -28,11 +28,11 @@ offset
|
|||
"assets"
|
||||
inner join "exif" on "assets"."id" = "exif"."assetId"
|
||||
where
|
||||
"assets"."fileCreatedAt" >= $1
|
||||
and "exif"."lensModel" = $2
|
||||
and "assets"."ownerId" = any ($3::uuid[])
|
||||
and "assets"."isFavorite" = $4
|
||||
and "assets"."isArchived" = $5
|
||||
"assets"."visibility" = $1
|
||||
and "assets"."fileCreatedAt" >= $2
|
||||
and "exif"."lensModel" = $3
|
||||
and "assets"."ownerId" = any ($4::uuid[])
|
||||
and "assets"."isFavorite" = $5
|
||||
and "assets"."deletedAt" is null
|
||||
and "assets"."id" < $6
|
||||
order by
|
||||
|
@ -48,11 +48,11 @@ union all
|
|||
"assets"
|
||||
inner join "exif" on "assets"."id" = "exif"."assetId"
|
||||
where
|
||||
"assets"."fileCreatedAt" >= $8
|
||||
and "exif"."lensModel" = $9
|
||||
and "assets"."ownerId" = any ($10::uuid[])
|
||||
and "assets"."isFavorite" = $11
|
||||
and "assets"."isArchived" = $12
|
||||
"assets"."visibility" = $8
|
||||
and "assets"."fileCreatedAt" >= $9
|
||||
and "exif"."lensModel" = $10
|
||||
and "assets"."ownerId" = any ($11::uuid[])
|
||||
and "assets"."isFavorite" = $12
|
||||
and "assets"."deletedAt" is null
|
||||
and "assets"."id" > $13
|
||||
order by
|
||||
|
@ -71,11 +71,11 @@ from
|
|||
inner join "exif" on "assets"."id" = "exif"."assetId"
|
||||
inner join "smart_search" on "assets"."id" = "smart_search"."assetId"
|
||||
where
|
||||
"assets"."fileCreatedAt" >= $1
|
||||
and "exif"."lensModel" = $2
|
||||
and "assets"."ownerId" = any ($3::uuid[])
|
||||
and "assets"."isFavorite" = $4
|
||||
and "assets"."isArchived" = $5
|
||||
"assets"."visibility" = $1
|
||||
and "assets"."fileCreatedAt" >= $2
|
||||
and "exif"."lensModel" = $3
|
||||
and "assets"."ownerId" = any ($4::uuid[])
|
||||
and "assets"."isFavorite" = $5
|
||||
and "assets"."deletedAt" is null
|
||||
order by
|
||||
smart_search.embedding <=> $6
|
||||
|
@ -97,7 +97,7 @@ with
|
|||
where
|
||||
"assets"."ownerId" = any ($2::uuid[])
|
||||
and "assets"."deletedAt" is null
|
||||
and "assets"."isVisible" = $3
|
||||
and "assets"."visibility" != $3
|
||||
and "assets"."type" = $4
|
||||
and "assets"."id" != $5::uuid
|
||||
and "assets"."stackId" is null
|
||||
|
@ -176,14 +176,13 @@ with recursive
|
|||
inner join "assets" on "assets"."id" = "exif"."assetId"
|
||||
where
|
||||
"assets"."ownerId" = any ($1::uuid[])
|
||||
and "assets"."isVisible" = $2
|
||||
and "assets"."isArchived" = $3
|
||||
and "assets"."type" = $4
|
||||
and "assets"."visibility" = $2
|
||||
and "assets"."type" = $3
|
||||
and "assets"."deletedAt" is null
|
||||
order by
|
||||
"city"
|
||||
limit
|
||||
$5
|
||||
$4
|
||||
)
|
||||
union all
|
||||
(
|
||||
|
@ -200,16 +199,15 @@ with recursive
|
|||
"exif"
|
||||
inner join "assets" on "assets"."id" = "exif"."assetId"
|
||||
where
|
||||
"assets"."ownerId" = any ($6::uuid[])
|
||||
and "assets"."isVisible" = $7
|
||||
and "assets"."isArchived" = $8
|
||||
and "assets"."type" = $9
|
||||
"assets"."ownerId" = any ($5::uuid[])
|
||||
and "assets"."visibility" = $6
|
||||
and "assets"."type" = $7
|
||||
and "assets"."deletedAt" is null
|
||||
and "exif"."city" > "cte"."city"
|
||||
order by
|
||||
"city"
|
||||
limit
|
||||
$10
|
||||
$8
|
||||
) as "l" on true
|
||||
)
|
||||
)
|
||||
|
@ -231,7 +229,7 @@ from
|
|||
inner join "assets" on "assets"."id" = "exif"."assetId"
|
||||
where
|
||||
"ownerId" = any ($1::uuid[])
|
||||
and "isVisible" = $2
|
||||
and "visibility" != $2
|
||||
and "deletedAt" is null
|
||||
and "state" is not null
|
||||
|
||||
|
@ -243,7 +241,7 @@ from
|
|||
inner join "assets" on "assets"."id" = "exif"."assetId"
|
||||
where
|
||||
"ownerId" = any ($1::uuid[])
|
||||
and "isVisible" = $2
|
||||
and "visibility" != $2
|
||||
and "deletedAt" is null
|
||||
and "city" is not null
|
||||
|
||||
|
@ -255,7 +253,7 @@ from
|
|||
inner join "assets" on "assets"."id" = "exif"."assetId"
|
||||
where
|
||||
"ownerId" = any ($1::uuid[])
|
||||
and "isVisible" = $2
|
||||
and "visibility" != $2
|
||||
and "deletedAt" is null
|
||||
and "make" is not null
|
||||
|
||||
|
@ -267,6 +265,6 @@ from
|
|||
inner join "assets" on "assets"."id" = "exif"."assetId"
|
||||
where
|
||||
"ownerId" = any ($1::uuid[])
|
||||
and "isVisible" = $2
|
||||
and "visibility" != $2
|
||||
and "deletedAt" is null
|
||||
and "model" is not null
|
||||
|
|
|
@ -84,7 +84,7 @@ select
|
|||
"type",
|
||||
"deletedAt",
|
||||
"isFavorite",
|
||||
"isVisible",
|
||||
"visibility",
|
||||
"updateId"
|
||||
from
|
||||
"assets"
|
||||
|
@ -106,7 +106,7 @@ select
|
|||
"type",
|
||||
"deletedAt",
|
||||
"isFavorite",
|
||||
"isVisible",
|
||||
"visibility",
|
||||
"updateId"
|
||||
from
|
||||
"assets"
|
||||
|
|
|
@ -285,14 +285,14 @@ select
|
|||
where
|
||||
(
|
||||
"assets"."type" = 'IMAGE'
|
||||
and "assets"."isVisible" = true
|
||||
and "assets"."visibility" != 'hidden'
|
||||
)
|
||||
) as "photos",
|
||||
count(*) filter (
|
||||
where
|
||||
(
|
||||
"assets"."type" = 'VIDEO'
|
||||
and "assets"."isVisible" = true
|
||||
and "assets"."visibility" != 'hidden'
|
||||
)
|
||||
) as "videos",
|
||||
coalesce(
|
||||
|
|
|
@ -7,8 +7,7 @@ from
|
|||
"assets"
|
||||
where
|
||||
"ownerId" = $2::uuid
|
||||
and "isVisible" = $3
|
||||
and "isArchived" = $4
|
||||
and "visibility" = $3
|
||||
and "deletedAt" is null
|
||||
and "fileCreatedAt" is not null
|
||||
and "fileModifiedAt" is not null
|
||||
|
@ -23,13 +22,12 @@ from
|
|||
left join "exif" on "assets"."id" = "exif"."assetId"
|
||||
where
|
||||
"ownerId" = $1::uuid
|
||||
and "isVisible" = $2
|
||||
and "isArchived" = $3
|
||||
and "visibility" = $2
|
||||
and "deletedAt" is null
|
||||
and "fileCreatedAt" is not null
|
||||
and "fileModifiedAt" is not null
|
||||
and "localDateTime" is not null
|
||||
and "originalPath" like $4
|
||||
and "originalPath" not like $5
|
||||
and "originalPath" like $3
|
||||
and "originalPath" not like $4
|
||||
order by
|
||||
regexp_replace("assets"."originalPath", $6, $7) asc
|
||||
regexp_replace("assets"."originalPath", $5, $6) asc
|
||||
|
|
|
@ -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)));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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!))
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { AssetVisibility } from 'src/enum';
|
||||
import { asset_face_source_type, assets_status_enum } from 'src/schema/enums';
|
||||
import {
|
||||
assets_delete_audit,
|
||||
|
@ -45,7 +46,12 @@ import { UserAuditTable } from 'src/schema/tables/user-audit.table';
|
|||
import { UserMetadataTable } from 'src/schema/tables/user-metadata.table';
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import { VersionHistoryTable } from 'src/schema/tables/version-history.table';
|
||||
import { ConfigurationParameter, Database, Extensions } from 'src/sql-tools';
|
||||
import { ConfigurationParameter, Database, Extensions, registerEnum } from 'src/sql-tools';
|
||||
|
||||
export const asset_visibility_enum = registerEnum({
|
||||
name: 'asset_visibility_enum',
|
||||
values: Object.values(AssetVisibility),
|
||||
});
|
||||
|
||||
@Extensions(['uuid-ossp', 'unaccent', 'cube', 'earthdistance', 'pg_trgm', 'plpgsql'])
|
||||
@ConfigurationParameter({ name: 'search_path', value: () => '"$user", public, vectors', scope: 'database' })
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import { Kysely, sql } from 'kysely';
|
||||
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await sql`CREATE TYPE "asset_visibility_enum" AS ENUM ('archive','timeline','hidden');`.execute(db);
|
||||
await sql`ALTER TABLE "assets"
|
||||
ADD "visibility" asset_visibility_enum NOT NULL DEFAULT 'timeline';`.execute(db);
|
||||
|
||||
await sql`
|
||||
UPDATE "assets"
|
||||
SET "visibility" = CASE
|
||||
WHEN "isArchived" THEN 'archive'::asset_visibility_enum
|
||||
WHEN "isVisible" THEN 'timeline'::asset_visibility_enum
|
||||
ELSE 'hidden'::asset_visibility_enum
|
||||
END;
|
||||
`.execute(db);
|
||||
|
||||
await sql`ALTER TABLE "assets" DROP COLUMN "isVisible";`.execute(db);
|
||||
await sql`ALTER TABLE "assets" DROP COLUMN "isArchived";`.execute(db);
|
||||
}
|
||||
|
||||
export async function down(db: Kysely<any>): Promise<void> {
|
||||
await sql`ALTER TABLE "assets" ADD COLUMN "isArchived" BOOLEAN NOT NULL DEFAULT FALSE;`.execute(db);
|
||||
await sql`ALTER TABLE "assets" ADD COLUMN "isVisible" BOOLEAN NOT NULL DEFAULT TRUE;`.execute(db);
|
||||
|
||||
await sql`
|
||||
UPDATE "assets"
|
||||
SET
|
||||
"isArchived" = ("visibility" = 'archive'::asset_visibility_enum),
|
||||
"isVisible" = CASE
|
||||
WHEN "visibility" = 'timeline'::asset_visibility_enum THEN TRUE
|
||||
WHEN "visibility" = 'archive'::asset_visibility_enum THEN TRUE
|
||||
ELSE FALSE
|
||||
END;
|
||||
`.execute(db);
|
||||
await sql`ALTER TABLE "assets" DROP COLUMN "visibility";`.execute(db);
|
||||
await sql`DROP TYPE "asset_visibility_enum";`.execute(db);
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
||||
import { AssetStatus, AssetType } from 'src/enum';
|
||||
import { AssetStatus, AssetType, AssetVisibility } from 'src/enum';
|
||||
import { asset_visibility_enum } from 'src/schema';
|
||||
import { assets_status_enum } from 'src/schema/enums';
|
||||
import { assets_delete_audit } from 'src/schema/functions';
|
||||
import { LibraryTable } from 'src/schema/tables/library.table';
|
||||
|
@ -95,9 +96,6 @@ export class AssetTable {
|
|||
@Column({ type: 'bytea', index: true })
|
||||
checksum!: Buffer; // sha1 checksum
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
isVisible!: boolean;
|
||||
|
||||
@ForeignKeyColumn(() => AssetTable, { nullable: true, onUpdate: 'CASCADE', onDelete: 'SET NULL' })
|
||||
livePhotoVideoId!: string | null;
|
||||
|
||||
|
@ -107,9 +105,6 @@ export class AssetTable {
|
|||
@CreateDateColumn()
|
||||
createdAt!: Date;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
isArchived!: boolean;
|
||||
|
||||
@Column({ index: true })
|
||||
originalFileName!: string;
|
||||
|
||||
|
@ -145,4 +140,7 @@ export class AssetTable {
|
|||
|
||||
@UpdateIdColumn({ indexName: 'IDX_assets_update_id' })
|
||||
updateId?: string;
|
||||
|
||||
@Column({ enum: asset_visibility_enum, default: AssetVisibility.TIMELINE })
|
||||
visibility!: AssetVisibility;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import { AssetFile } from 'src/database';
|
|||
import { AssetMediaStatus, AssetRejectReason, AssetUploadAction } from 'src/dtos/asset-media-response.dto';
|
||||
import { AssetMediaCreateDto, AssetMediaReplaceDto, AssetMediaSize, UploadFieldName } from 'src/dtos/asset-media.dto';
|
||||
import { MapAsset } from 'src/dtos/asset-response.dto';
|
||||
import { AssetFileType, AssetStatus, AssetType, CacheControl, JobName } from 'src/enum';
|
||||
import { AssetFileType, AssetStatus, AssetType, AssetVisibility, CacheControl, JobName } from 'src/enum';
|
||||
import { AuthRequest } from 'src/middleware/auth.guard';
|
||||
import { AssetMediaService } from 'src/services/asset-media.service';
|
||||
import { ASSET_CHECKSUM_CONSTRAINT } from 'src/utils/database';
|
||||
|
@ -142,7 +142,6 @@ const createDto = Object.freeze({
|
|||
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||
fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||
isFavorite: false,
|
||||
isArchived: false,
|
||||
duration: '0:00:00.000000',
|
||||
}) as AssetMediaCreateDto;
|
||||
|
||||
|
@ -164,7 +163,6 @@ const assetEntity = Object.freeze({
|
|||
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||
updatedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||
isFavorite: false,
|
||||
isArchived: false,
|
||||
encodedVideoPath: '',
|
||||
duration: '0:00:00.000000',
|
||||
files: [] as AssetFile[],
|
||||
|
@ -437,7 +435,10 @@ describe(AssetMediaService.name, () => {
|
|||
});
|
||||
|
||||
it('should hide the linked motion asset', async () => {
|
||||
mocks.asset.getById.mockResolvedValueOnce({ ...assetStub.livePhotoMotionAsset, isVisible: true });
|
||||
mocks.asset.getById.mockResolvedValueOnce({
|
||||
...assetStub.livePhotoMotionAsset,
|
||||
visibility: AssetVisibility.TIMELINE,
|
||||
});
|
||||
mocks.asset.create.mockResolvedValueOnce(assetStub.livePhotoStillAsset);
|
||||
|
||||
await expect(
|
||||
|
@ -452,7 +453,10 @@ describe(AssetMediaService.name, () => {
|
|||
});
|
||||
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith('live-photo-motion-asset');
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith({ id: 'live-photo-motion-asset', isVisible: false });
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith({
|
||||
id: 'live-photo-motion-asset',
|
||||
visibility: AssetVisibility.HIDDEN,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle a sidecar file', async () => {
|
||||
|
|
|
@ -21,7 +21,7 @@ import {
|
|||
UploadFieldName,
|
||||
} from 'src/dtos/asset-media.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { AssetStatus, AssetType, CacheControl, JobName, Permission, StorageFolder } from 'src/enum';
|
||||
import { AssetStatus, AssetType, AssetVisibility, CacheControl, JobName, Permission, StorageFolder } from 'src/enum';
|
||||
import { AuthRequest } from 'src/middleware/auth.guard';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { UploadFile } from 'src/types';
|
||||
|
@ -146,7 +146,6 @@ export class AssetMediaService extends BaseService {
|
|||
{ userId: auth.user.id, livePhotoVideoId: dto.livePhotoVideoId },
|
||||
);
|
||||
}
|
||||
|
||||
const asset = await this.create(auth.user.id, dto, file, sidecarFile);
|
||||
|
||||
await this.userRepository.updateUsage(auth.user.id, file.size);
|
||||
|
@ -416,9 +415,8 @@ export class AssetMediaService extends BaseService {
|
|||
|
||||
type: mimeTypes.assetType(file.originalPath),
|
||||
isFavorite: dto.isFavorite,
|
||||
isArchived: dto.isArchived ?? false,
|
||||
duration: dto.duration || null,
|
||||
isVisible: dto.isVisible ?? true,
|
||||
visibility: dto.visibility ?? AssetVisibility.TIMELINE,
|
||||
livePhotoVideoId: dto.livePhotoVideoId,
|
||||
originalFileName: file.originalName,
|
||||
sidecarPath: sidecarFile?.originalPath,
|
||||
|
|
|
@ -2,7 +2,7 @@ import { BadRequestException } from '@nestjs/common';
|
|||
import { DateTime } from 'luxon';
|
||||
import { MapAsset } from 'src/dtos/asset-response.dto';
|
||||
import { AssetJobName, AssetStatsResponseDto } from 'src/dtos/asset.dto';
|
||||
import { AssetStatus, AssetType, JobName, JobStatus } from 'src/enum';
|
||||
import { AssetStatus, AssetType, AssetVisibility, JobName, JobStatus } from 'src/enum';
|
||||
import { AssetStats } from 'src/repositories/asset.repository';
|
||||
import { AssetService } from 'src/services/asset.service';
|
||||
import { assetStub } from 'test/fixtures/asset.stub';
|
||||
|
@ -46,14 +46,22 @@ describe(AssetService.name, () => {
|
|||
describe('getStatistics', () => {
|
||||
it('should get the statistics for a user, excluding archived assets', async () => {
|
||||
mocks.asset.getStatistics.mockResolvedValue(stats);
|
||||
await expect(sut.getStatistics(authStub.admin, { isArchived: false })).resolves.toEqual(statResponse);
|
||||
expect(mocks.asset.getStatistics).toHaveBeenCalledWith(authStub.admin.user.id, { isArchived: false });
|
||||
await expect(sut.getStatistics(authStub.admin, { visibility: AssetVisibility.TIMELINE })).resolves.toEqual(
|
||||
statResponse,
|
||||
);
|
||||
expect(mocks.asset.getStatistics).toHaveBeenCalledWith(authStub.admin.user.id, {
|
||||
visibility: AssetVisibility.TIMELINE,
|
||||
});
|
||||
});
|
||||
|
||||
it('should get the statistics for a user for archived assets', async () => {
|
||||
mocks.asset.getStatistics.mockResolvedValue(stats);
|
||||
await expect(sut.getStatistics(authStub.admin, { isArchived: true })).resolves.toEqual(statResponse);
|
||||
expect(mocks.asset.getStatistics).toHaveBeenCalledWith(authStub.admin.user.id, { isArchived: true });
|
||||
await expect(sut.getStatistics(authStub.admin, { visibility: AssetVisibility.ARCHIVE })).resolves.toEqual(
|
||||
statResponse,
|
||||
);
|
||||
expect(mocks.asset.getStatistics).toHaveBeenCalledWith(authStub.admin.user.id, {
|
||||
visibility: AssetVisibility.ARCHIVE,
|
||||
});
|
||||
});
|
||||
|
||||
it('should get the statistics for a user for favorite assets', async () => {
|
||||
|
@ -192,9 +200,9 @@ describe(AssetService.name, () => {
|
|||
|
||||
describe('update', () => {
|
||||
it('should require asset write access for the id', async () => {
|
||||
await expect(sut.update(authStub.admin, 'asset-1', { isArchived: false })).rejects.toBeInstanceOf(
|
||||
BadRequestException,
|
||||
);
|
||||
await expect(
|
||||
sut.update(authStub.admin, 'asset-1', { visibility: AssetVisibility.TIMELINE }),
|
||||
).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
expect(mocks.asset.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
@ -242,7 +250,10 @@ describe(AssetService.name, () => {
|
|||
id: assetStub.livePhotoStillAsset.id,
|
||||
livePhotoVideoId: assetStub.livePhotoMotionAsset.id,
|
||||
});
|
||||
expect(mocks.asset.update).not.toHaveBeenCalledWith({ id: assetStub.livePhotoMotionAsset.id, isVisible: true });
|
||||
expect(mocks.asset.update).not.toHaveBeenCalledWith({
|
||||
id: assetStub.livePhotoMotionAsset.id,
|
||||
visibility: AssetVisibility.TIMELINE,
|
||||
});
|
||||
expect(mocks.event.emit).not.toHaveBeenCalledWith('asset.show', {
|
||||
assetId: assetStub.livePhotoMotionAsset.id,
|
||||
userId: userStub.admin.id,
|
||||
|
@ -263,7 +274,10 @@ describe(AssetService.name, () => {
|
|||
id: assetStub.livePhotoStillAsset.id,
|
||||
livePhotoVideoId: assetStub.livePhotoMotionAsset.id,
|
||||
});
|
||||
expect(mocks.asset.update).not.toHaveBeenCalledWith({ id: assetStub.livePhotoMotionAsset.id, isVisible: true });
|
||||
expect(mocks.asset.update).not.toHaveBeenCalledWith({
|
||||
id: assetStub.livePhotoMotionAsset.id,
|
||||
visibility: AssetVisibility.TIMELINE,
|
||||
});
|
||||
expect(mocks.event.emit).not.toHaveBeenCalledWith('asset.show', {
|
||||
assetId: assetStub.livePhotoMotionAsset.id,
|
||||
userId: userStub.admin.id,
|
||||
|
@ -284,7 +298,10 @@ describe(AssetService.name, () => {
|
|||
id: assetStub.livePhotoStillAsset.id,
|
||||
livePhotoVideoId: assetStub.livePhotoMotionAsset.id,
|
||||
});
|
||||
expect(mocks.asset.update).not.toHaveBeenCalledWith({ id: assetStub.livePhotoMotionAsset.id, isVisible: true });
|
||||
expect(mocks.asset.update).not.toHaveBeenCalledWith({
|
||||
id: assetStub.livePhotoMotionAsset.id,
|
||||
visibility: AssetVisibility.TIMELINE,
|
||||
});
|
||||
expect(mocks.event.emit).not.toHaveBeenCalledWith('asset.show', {
|
||||
assetId: assetStub.livePhotoMotionAsset.id,
|
||||
userId: userStub.admin.id,
|
||||
|
@ -296,7 +313,7 @@ describe(AssetService.name, () => {
|
|||
mocks.asset.getById.mockResolvedValueOnce({
|
||||
...assetStub.livePhotoMotionAsset,
|
||||
ownerId: authStub.admin.user.id,
|
||||
isVisible: true,
|
||||
visibility: AssetVisibility.TIMELINE,
|
||||
});
|
||||
mocks.asset.getById.mockResolvedValueOnce(assetStub.image);
|
||||
mocks.asset.update.mockResolvedValue(assetStub.image);
|
||||
|
@ -305,7 +322,10 @@ describe(AssetService.name, () => {
|
|||
livePhotoVideoId: assetStub.livePhotoMotionAsset.id,
|
||||
});
|
||||
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith({ id: assetStub.livePhotoMotionAsset.id, isVisible: false });
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith({
|
||||
id: assetStub.livePhotoMotionAsset.id,
|
||||
visibility: AssetVisibility.HIDDEN,
|
||||
});
|
||||
expect(mocks.event.emit).toHaveBeenCalledWith('asset.hide', {
|
||||
assetId: assetStub.livePhotoMotionAsset.id,
|
||||
userId: userStub.admin.id,
|
||||
|
@ -335,7 +355,10 @@ describe(AssetService.name, () => {
|
|||
id: assetStub.livePhotoStillAsset.id,
|
||||
livePhotoVideoId: null,
|
||||
});
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith({ id: assetStub.livePhotoMotionAsset.id, isVisible: true });
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith({
|
||||
id: assetStub.livePhotoMotionAsset.id,
|
||||
visibility: assetStub.livePhotoStillAsset.visibility,
|
||||
});
|
||||
expect(mocks.event.emit).toHaveBeenCalledWith('asset.show', {
|
||||
assetId: assetStub.livePhotoMotionAsset.id,
|
||||
userId: userStub.admin.id,
|
||||
|
@ -361,7 +384,6 @@ describe(AssetService.name, () => {
|
|||
await expect(
|
||||
sut.updateAll(authStub.admin, {
|
||||
ids: ['asset-1'],
|
||||
isArchived: false,
|
||||
}),
|
||||
).rejects.toBeInstanceOf(BadRequestException);
|
||||
});
|
||||
|
@ -369,9 +391,11 @@ describe(AssetService.name, () => {
|
|||
it('should update all assets', async () => {
|
||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2']));
|
||||
|
||||
await sut.updateAll(authStub.admin, { ids: ['asset-1', 'asset-2'], isArchived: true });
|
||||
await sut.updateAll(authStub.admin, { ids: ['asset-1', 'asset-2'], visibility: AssetVisibility.ARCHIVE });
|
||||
|
||||
expect(mocks.asset.updateAll).toHaveBeenCalledWith(['asset-1', 'asset-2'], { isArchived: true });
|
||||
expect(mocks.asset.updateAll).toHaveBeenCalledWith(['asset-1', 'asset-2'], {
|
||||
visibility: AssetVisibility.ARCHIVE,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not update Assets table if no relevant fields are provided', async () => {
|
||||
|
@ -381,7 +405,6 @@ describe(AssetService.name, () => {
|
|||
ids: ['asset-1'],
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
isArchived: undefined,
|
||||
isFavorite: undefined,
|
||||
duplicateId: undefined,
|
||||
rating: undefined,
|
||||
|
@ -389,14 +412,14 @@ describe(AssetService.name, () => {
|
|||
expect(mocks.asset.updateAll).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should update Assets table if isArchived field is provided', async () => {
|
||||
it('should update Assets table if visibility field is provided', async () => {
|
||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
||||
|
||||
await sut.updateAll(authStub.admin, {
|
||||
ids: ['asset-1'],
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
isArchived: undefined,
|
||||
visibility: undefined,
|
||||
isFavorite: false,
|
||||
duplicateId: undefined,
|
||||
rating: undefined,
|
||||
|
@ -416,7 +439,6 @@ describe(AssetService.name, () => {
|
|||
latitude: 30,
|
||||
longitude: 50,
|
||||
dateTimeOriginal,
|
||||
isArchived: undefined,
|
||||
isFavorite: false,
|
||||
duplicateId: undefined,
|
||||
rating: undefined,
|
||||
|
@ -439,7 +461,6 @@ describe(AssetService.name, () => {
|
|||
ids: ['asset-1'],
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
isArchived: undefined,
|
||||
isFavorite: undefined,
|
||||
duplicateId: null,
|
||||
rating: undefined,
|
||||
|
|
|
@ -92,8 +92,12 @@ export class AssetService extends BaseService {
|
|||
|
||||
const asset = await this.assetRepository.update({ id, ...rest });
|
||||
|
||||
if (previousMotion) {
|
||||
await onAfterUnlink(repos, { userId: auth.user.id, livePhotoVideoId: previousMotion.id });
|
||||
if (previousMotion && asset) {
|
||||
await onAfterUnlink(repos, {
|
||||
userId: auth.user.id,
|
||||
livePhotoVideoId: previousMotion.id,
|
||||
visibility: asset.visibility,
|
||||
});
|
||||
}
|
||||
|
||||
if (!asset) {
|
||||
|
@ -115,7 +119,7 @@ export class AssetService extends BaseService {
|
|||
}
|
||||
|
||||
if (
|
||||
options.isArchived !== undefined ||
|
||||
options.visibility !== undefined ||
|
||||
options.isFavorite !== undefined ||
|
||||
options.duplicateId !== undefined ||
|
||||
options.rating !== undefined
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { AssetFileType, AssetType, JobName, JobStatus } from 'src/enum';
|
||||
import { AssetFileType, AssetType, AssetVisibility, JobName, JobStatus } from 'src/enum';
|
||||
import { DuplicateService } from 'src/services/duplicate.service';
|
||||
import { SearchService } from 'src/services/search.service';
|
||||
import { assetStub } from 'test/fixtures/asset.stub';
|
||||
|
@ -22,11 +22,11 @@ const hasEmbedding = {
|
|||
updateId: 'update-1',
|
||||
},
|
||||
],
|
||||
isVisible: true,
|
||||
stackId: null,
|
||||
type: AssetType.IMAGE,
|
||||
duplicateId: null,
|
||||
embedding: '[1, 2, 3, 4]',
|
||||
visibility: AssetVisibility.TIMELINE,
|
||||
};
|
||||
|
||||
const hasDupe = {
|
||||
|
@ -207,7 +207,10 @@ describe(SearchService.name, () => {
|
|||
|
||||
it('should skip if asset is not visible', async () => {
|
||||
const id = assetStub.livePhotoMotionAsset.id;
|
||||
mocks.assetJob.getForSearchDuplicatesJob.mockResolvedValue({ ...hasEmbedding, isVisible: false });
|
||||
mocks.assetJob.getForSearchDuplicatesJob.mockResolvedValue({
|
||||
...hasEmbedding,
|
||||
visibility: AssetVisibility.HIDDEN,
|
||||
});
|
||||
|
||||
const result = await sut.handleSearchDuplicates({ id });
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import { OnJob } from 'src/decorators';
|
|||
import { mapAsset } from 'src/dtos/asset-response.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { DuplicateResponseDto } from 'src/dtos/duplicate.dto';
|
||||
import { AssetFileType, JobName, JobStatus, QueueName } from 'src/enum';
|
||||
import { AssetFileType, AssetVisibility, JobName, JobStatus, QueueName } from 'src/enum';
|
||||
import { AssetDuplicateResult } from 'src/repositories/search.repository';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { JobItem, JobOf } from 'src/types';
|
||||
|
@ -65,7 +65,7 @@ export class DuplicateService extends BaseService {
|
|||
return JobStatus.SKIPPED;
|
||||
}
|
||||
|
||||
if (!asset.isVisible) {
|
||||
if (asset.visibility == AssetVisibility.HIDDEN) {
|
||||
this.logger.debug(`Asset ${id} is not visible, skipping`);
|
||||
return JobStatus.SKIPPED;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import { mapAsset } from 'src/dtos/asset-response.dto';
|
|||
import { AllJobStatusResponseDto, JobCommandDto, JobCreateDto, JobStatusDto } from 'src/dtos/job.dto';
|
||||
import {
|
||||
AssetType,
|
||||
AssetVisibility,
|
||||
BootstrapEventPriority,
|
||||
ImmichWorker,
|
||||
JobCommand,
|
||||
|
@ -301,7 +302,7 @@ export class JobService extends BaseService {
|
|||
}
|
||||
|
||||
await this.jobRepository.queueAll(jobs);
|
||||
if (asset.isVisible) {
|
||||
if (asset.visibility === AssetVisibility.TIMELINE || asset.visibility === AssetVisibility.ARCHIVE) {
|
||||
this.eventRepository.clientSend('on_upload_success', asset.ownerId, mapAsset(asset));
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
AssetFileType,
|
||||
AssetPathType,
|
||||
AssetType,
|
||||
AssetVisibility,
|
||||
AudioCodec,
|
||||
Colorspace,
|
||||
JobName,
|
||||
|
@ -152,7 +153,7 @@ export class MediaService extends BaseService {
|
|||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
if (!asset.isVisible) {
|
||||
if (asset.visibility === AssetVisibility.HIDDEN) {
|
||||
this.logger.verbose(`Thumbnail generation skipped for asset ${id}: not visible`);
|
||||
return JobStatus.SKIPPED;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Stats } from 'node:fs';
|
|||
import { constants } from 'node:fs/promises';
|
||||
import { defaults } from 'src/config';
|
||||
import { MapAsset } from 'src/dtos/asset-response.dto';
|
||||
import { AssetType, ExifOrientation, ImmichWorker, JobName, JobStatus, SourceType } from 'src/enum';
|
||||
import { AssetType, AssetVisibility, ExifOrientation, ImmichWorker, JobName, JobStatus, SourceType } from 'src/enum';
|
||||
import { ImmichTags } from 'src/repositories/metadata.repository';
|
||||
import { MetadataService } from 'src/services/metadata.service';
|
||||
import { assetStub } from 'test/fixtures/asset.stub';
|
||||
|
@ -504,7 +504,10 @@ describe(MetadataService.name, () => {
|
|||
});
|
||||
|
||||
it('should not apply motion photos if asset is video', async () => {
|
||||
mocks.assetJob.getForMetadataExtraction.mockResolvedValue({ ...assetStub.livePhotoMotionAsset, isVisible: true });
|
||||
mocks.assetJob.getForMetadataExtraction.mockResolvedValue({
|
||||
...assetStub.livePhotoMotionAsset,
|
||||
visibility: AssetVisibility.TIMELINE,
|
||||
});
|
||||
mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer);
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.livePhotoMotionAsset.id });
|
||||
|
@ -513,7 +516,7 @@ describe(MetadataService.name, () => {
|
|||
expect(mocks.job.queue).not.toHaveBeenCalled();
|
||||
expect(mocks.job.queueAll).not.toHaveBeenCalled();
|
||||
expect(mocks.asset.update).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({ assetType: AssetType.VIDEO, isVisible: false }),
|
||||
expect.objectContaining({ assetType: AssetType.VIDEO, visibility: AssetVisibility.HIDDEN }),
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -580,7 +583,7 @@ describe(MetadataService.name, () => {
|
|||
fileCreatedAt: assetStub.livePhotoWithOriginalFileName.fileCreatedAt,
|
||||
fileModifiedAt: assetStub.livePhotoWithOriginalFileName.fileModifiedAt,
|
||||
id: fileStub.livePhotoMotion.uuid,
|
||||
isVisible: false,
|
||||
visibility: AssetVisibility.HIDDEN,
|
||||
libraryId: assetStub.livePhotoWithOriginalFileName.libraryId,
|
||||
localDateTime: assetStub.livePhotoWithOriginalFileName.fileCreatedAt,
|
||||
originalFileName: 'asset_1.mp4',
|
||||
|
@ -638,7 +641,7 @@ describe(MetadataService.name, () => {
|
|||
fileCreatedAt: assetStub.livePhotoWithOriginalFileName.fileCreatedAt,
|
||||
fileModifiedAt: assetStub.livePhotoWithOriginalFileName.fileModifiedAt,
|
||||
id: fileStub.livePhotoMotion.uuid,
|
||||
isVisible: false,
|
||||
visibility: AssetVisibility.HIDDEN,
|
||||
libraryId: assetStub.livePhotoWithOriginalFileName.libraryId,
|
||||
localDateTime: assetStub.livePhotoWithOriginalFileName.fileCreatedAt,
|
||||
originalFileName: 'asset_1.mp4',
|
||||
|
@ -696,7 +699,7 @@ describe(MetadataService.name, () => {
|
|||
fileCreatedAt: assetStub.livePhotoWithOriginalFileName.fileCreatedAt,
|
||||
fileModifiedAt: assetStub.livePhotoWithOriginalFileName.fileModifiedAt,
|
||||
id: fileStub.livePhotoMotion.uuid,
|
||||
isVisible: false,
|
||||
visibility: AssetVisibility.HIDDEN,
|
||||
libraryId: assetStub.livePhotoWithOriginalFileName.libraryId,
|
||||
localDateTime: assetStub.livePhotoWithOriginalFileName.fileCreatedAt,
|
||||
originalFileName: 'asset_1.mp4',
|
||||
|
@ -773,14 +776,17 @@ describe(MetadataService.name, () => {
|
|||
MicroVideoOffset: 1,
|
||||
});
|
||||
mocks.crypto.hashSha1.mockReturnValue(randomBytes(512));
|
||||
mocks.asset.getByChecksum.mockResolvedValue({ ...assetStub.livePhotoMotionAsset, isVisible: true });
|
||||
mocks.asset.getByChecksum.mockResolvedValue({
|
||||
...assetStub.livePhotoMotionAsset,
|
||||
visibility: AssetVisibility.TIMELINE,
|
||||
});
|
||||
const video = randomBytes(512);
|
||||
mocks.storage.readFile.mockResolvedValue(video);
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.livePhotoStillAsset.id });
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith({
|
||||
id: assetStub.livePhotoMotionAsset.id,
|
||||
isVisible: false,
|
||||
visibility: AssetVisibility.HIDDEN,
|
||||
});
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith({
|
||||
id: assetStub.livePhotoStillAsset.id,
|
||||
|
@ -1301,7 +1307,9 @@ describe(MetadataService.name, () => {
|
|||
|
||||
expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.image.id);
|
||||
expect(mocks.asset.findLivePhotoMatch).not.toHaveBeenCalled();
|
||||
expect(mocks.asset.update).not.toHaveBeenCalledWith(expect.objectContaining({ isVisible: false }));
|
||||
expect(mocks.asset.update).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({ visibility: AssetVisibility.HIDDEN }),
|
||||
);
|
||||
expect(mocks.album.removeAsset).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
@ -1320,7 +1328,9 @@ describe(MetadataService.name, () => {
|
|||
libraryId: null,
|
||||
type: AssetType.IMAGE,
|
||||
});
|
||||
expect(mocks.asset.update).not.toHaveBeenCalledWith(expect.objectContaining({ isVisible: false }));
|
||||
expect(mocks.asset.update).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({ visibility: AssetVisibility.HIDDEN }),
|
||||
);
|
||||
expect(mocks.album.removeAsset).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
@ -1342,7 +1352,10 @@ describe(MetadataService.name, () => {
|
|||
id: assetStub.livePhotoStillAsset.id,
|
||||
livePhotoVideoId: assetStub.livePhotoMotionAsset.id,
|
||||
});
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith({ id: assetStub.livePhotoMotionAsset.id, isVisible: false });
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith({
|
||||
id: assetStub.livePhotoMotionAsset.id,
|
||||
visibility: AssetVisibility.HIDDEN,
|
||||
});
|
||||
expect(mocks.album.removeAsset).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.id);
|
||||
});
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import { AssetFaces, Exif, Person } from 'src/db';
|
|||
import { OnEvent, OnJob } from 'src/decorators';
|
||||
import {
|
||||
AssetType,
|
||||
AssetVisibility,
|
||||
DatabaseLock,
|
||||
ExifOrientation,
|
||||
ImmichWorker,
|
||||
|
@ -156,7 +157,7 @@ export class MetadataService extends BaseService {
|
|||
const [photoAsset, motionAsset] = asset.type === AssetType.IMAGE ? [asset, match] : [match, asset];
|
||||
await Promise.all([
|
||||
this.assetRepository.update({ id: photoAsset.id, livePhotoVideoId: motionAsset.id }),
|
||||
this.assetRepository.update({ id: motionAsset.id, isVisible: false }),
|
||||
this.assetRepository.update({ id: motionAsset.id, visibility: AssetVisibility.HIDDEN }),
|
||||
this.albumRepository.removeAsset(motionAsset.id),
|
||||
]);
|
||||
|
||||
|
@ -527,8 +528,11 @@ export class MetadataService extends BaseService {
|
|||
});
|
||||
|
||||
// Hide the motion photo video asset if it's not already hidden to prepare for linking
|
||||
if (motionAsset.isVisible) {
|
||||
await this.assetRepository.update({ id: motionAsset.id, isVisible: false });
|
||||
if (motionAsset.visibility === AssetVisibility.TIMELINE) {
|
||||
await this.assetRepository.update({
|
||||
id: motionAsset.id,
|
||||
visibility: AssetVisibility.HIDDEN,
|
||||
});
|
||||
this.logger.log(`Hid unlinked motion photo video asset (${motionAsset.id})`);
|
||||
}
|
||||
} else {
|
||||
|
@ -544,7 +548,7 @@ export class MetadataService extends BaseService {
|
|||
ownerId: asset.ownerId,
|
||||
originalPath: StorageCore.getAndroidMotionPath(asset, motionAssetId),
|
||||
originalFileName: `${path.parse(asset.originalFileName).name}.mp4`,
|
||||
isVisible: false,
|
||||
visibility: AssetVisibility.HIDDEN,
|
||||
deviceAssetId: 'NONE',
|
||||
deviceId: 'NONE',
|
||||
});
|
||||
|
@ -863,7 +867,7 @@ export class MetadataService extends BaseService {
|
|||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
if (!isSync && (!asset.isVisible || asset.sidecarPath) && !asset.isExternal) {
|
||||
if (!isSync && (asset.visibility === AssetVisibility.HIDDEN || asset.sidecarPath) && !asset.isExternal) {
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
} from 'src/dtos/person.dto';
|
||||
import {
|
||||
AssetType,
|
||||
AssetVisibility,
|
||||
CacheControl,
|
||||
ImageFormat,
|
||||
JobName,
|
||||
|
@ -296,7 +297,7 @@ export class PersonService extends BaseService {
|
|||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
if (!asset.isVisible) {
|
||||
if (asset.visibility === AssetVisibility.HIDDEN) {
|
||||
return JobStatus.SKIPPED;
|
||||
}
|
||||
|
||||
|
@ -484,7 +485,9 @@ export class PersonService extends BaseService {
|
|||
|
||||
this.logger.debug(`Face ${id} has ${matches.length} matches`);
|
||||
|
||||
const isCore = matches.length >= machineLearning.facialRecognition.minFaces && !face.asset.isArchived;
|
||||
const isCore =
|
||||
matches.length >= machineLearning.facialRecognition.minFaces &&
|
||||
face.asset.visibility === AssetVisibility.TIMELINE;
|
||||
if (!isCore && !deferred) {
|
||||
this.logger.debug(`Deferring non-core face ${id} for later processing`);
|
||||
await this.jobRepository.queue({ name: JobName.FACIAL_RECOGNITION, data: { id, deferred: true } });
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
|
|||
import { SystemConfig } from 'src/config';
|
||||
import { JOBS_ASSET_PAGINATION_SIZE } from 'src/constants';
|
||||
import { OnEvent, OnJob } from 'src/decorators';
|
||||
import { DatabaseLock, ImmichWorker, JobName, JobStatus, QueueName } from 'src/enum';
|
||||
import { AssetVisibility, DatabaseLock, ImmichWorker, JobName, JobStatus, QueueName } from 'src/enum';
|
||||
import { ArgOf } from 'src/repositories/event.repository';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { JobItem, JobOf } from 'src/types';
|
||||
|
@ -104,7 +104,7 @@ export class SmartInfoService extends BaseService {
|
|||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
if (!asset.isVisible) {
|
||||
if (asset.visibility === AssetVisibility.HIDDEN) {
|
||||
return JobStatus.SKIPPED;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
SyncAckSetDto,
|
||||
SyncStreamDto,
|
||||
} from 'src/dtos/sync.dto';
|
||||
import { DatabaseAction, EntityType, Permission, SyncEntityType, SyncRequestType } from 'src/enum';
|
||||
import { AssetVisibility, DatabaseAction, EntityType, Permission, SyncEntityType, SyncRequestType } from 'src/enum';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { SyncAck } from 'src/types';
|
||||
import { getMyPartnerIds } from 'src/utils/asset.util';
|
||||
|
@ -262,7 +262,10 @@ export class SyncService extends BaseService {
|
|||
needsFullSync: false,
|
||||
upserted: upserted
|
||||
// do not return archived assets for partner users
|
||||
.filter((a) => a.ownerId === auth.user.id || (a.ownerId !== auth.user.id && !a.isArchived))
|
||||
.filter(
|
||||
(a) =>
|
||||
a.ownerId === auth.user.id || (a.ownerId !== auth.user.id && a.visibility === AssetVisibility.TIMELINE),
|
||||
)
|
||||
.map((a) =>
|
||||
mapAsset(a, {
|
||||
auth,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { BadRequestException } from '@nestjs/common';
|
||||
import { AssetVisibility } from 'src/enum';
|
||||
import { TimeBucketSize } from 'src/repositories/asset.repository';
|
||||
import { TimelineService } from 'src/services/timeline.service';
|
||||
import { assetStub } from 'test/fixtures/asset.stub';
|
||||
|
@ -54,7 +55,7 @@ describe(TimelineService.name, () => {
|
|||
sut.getTimeBucket(authStub.admin, {
|
||||
size: TimeBucketSize.DAY,
|
||||
timeBucket: 'bucket',
|
||||
isArchived: true,
|
||||
visibility: AssetVisibility.ARCHIVE,
|
||||
userId: authStub.admin.user.id,
|
||||
}),
|
||||
).resolves.toEqual(expect.arrayContaining([expect.objectContaining({ id: 'asset-id' })]));
|
||||
|
@ -63,7 +64,7 @@ describe(TimelineService.name, () => {
|
|||
expect.objectContaining({
|
||||
size: TimeBucketSize.DAY,
|
||||
timeBucket: 'bucket',
|
||||
isArchived: true,
|
||||
visibility: AssetVisibility.ARCHIVE,
|
||||
userIds: [authStub.admin.user.id],
|
||||
}),
|
||||
);
|
||||
|
@ -77,7 +78,7 @@ describe(TimelineService.name, () => {
|
|||
sut.getTimeBucket(authStub.admin, {
|
||||
size: TimeBucketSize.DAY,
|
||||
timeBucket: 'bucket',
|
||||
isArchived: false,
|
||||
visibility: AssetVisibility.TIMELINE,
|
||||
userId: authStub.admin.user.id,
|
||||
withPartners: true,
|
||||
}),
|
||||
|
@ -85,7 +86,7 @@ describe(TimelineService.name, () => {
|
|||
expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith('bucket', {
|
||||
size: TimeBucketSize.DAY,
|
||||
timeBucket: 'bucket',
|
||||
isArchived: false,
|
||||
visibility: AssetVisibility.TIMELINE,
|
||||
withPartners: true,
|
||||
userIds: [authStub.admin.user.id],
|
||||
});
|
||||
|
@ -120,7 +121,7 @@ describe(TimelineService.name, () => {
|
|||
const buckets = await sut.getTimeBucket(auth, {
|
||||
size: TimeBucketSize.DAY,
|
||||
timeBucket: 'bucket',
|
||||
isArchived: true,
|
||||
visibility: AssetVisibility.ARCHIVE,
|
||||
albumId: 'album-id',
|
||||
});
|
||||
|
||||
|
@ -129,7 +130,7 @@ describe(TimelineService.name, () => {
|
|||
expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith('bucket', {
|
||||
size: TimeBucketSize.DAY,
|
||||
timeBucket: 'bucket',
|
||||
isArchived: true,
|
||||
visibility: AssetVisibility.ARCHIVE,
|
||||
albumId: 'album-id',
|
||||
});
|
||||
});
|
||||
|
@ -154,12 +155,12 @@ describe(TimelineService.name, () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should throw an error if withParners is true and isArchived true or undefined', async () => {
|
||||
it('should throw an error if withParners is true and visibility true or undefined', async () => {
|
||||
await expect(
|
||||
sut.getTimeBucket(authStub.admin, {
|
||||
size: TimeBucketSize.DAY,
|
||||
timeBucket: 'bucket',
|
||||
isArchived: true,
|
||||
visibility: AssetVisibility.ARCHIVE,
|
||||
withPartners: true,
|
||||
userId: authStub.admin.user.id,
|
||||
}),
|
||||
|
@ -169,7 +170,7 @@ describe(TimelineService.name, () => {
|
|||
sut.getTimeBucket(authStub.admin, {
|
||||
size: TimeBucketSize.DAY,
|
||||
timeBucket: 'bucket',
|
||||
isArchived: undefined,
|
||||
visibility: undefined,
|
||||
withPartners: true,
|
||||
userId: authStub.admin.user.id,
|
||||
}),
|
||||
|
|
|
@ -2,7 +2,7 @@ import { BadRequestException, Injectable } from '@nestjs/common';
|
|||
import { AssetResponseDto, SanitizedAssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { TimeBucketAssetDto, TimeBucketDto, TimeBucketResponseDto } from 'src/dtos/time-bucket.dto';
|
||||
import { Permission } from 'src/enum';
|
||||
import { AssetVisibility, Permission } from 'src/enum';
|
||||
import { TimeBucketOptions } from 'src/repositories/asset.repository';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { getMyPartnerIds } from 'src/utils/asset.util';
|
||||
|
@ -55,7 +55,7 @@ export class TimelineService extends BaseService {
|
|||
|
||||
if (dto.userId) {
|
||||
await this.requireAccess({ auth, permission: Permission.TIMELINE_READ, ids: [dto.userId] });
|
||||
if (dto.isArchived !== false) {
|
||||
if (dto.visibility === AssetVisibility.ARCHIVE) {
|
||||
await this.requireAccess({ auth, permission: Permission.ARCHIVE_READ, ids: [dto.userId] });
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ export class TimelineService extends BaseService {
|
|||
}
|
||||
|
||||
if (dto.withPartners) {
|
||||
const requestedArchived = dto.isArchived === true || dto.isArchived === undefined;
|
||||
const requestedArchived = dto.visibility === AssetVisibility.ARCHIVE || dto.visibility === undefined;
|
||||
const requestedFavorite = dto.isFavorite === true || dto.isFavorite === false;
|
||||
const requestedTrash = dto.isTrashed === true;
|
||||
|
||||
|
|
|
@ -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 });
|
||||
};
|
||||
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
IsArray,
|
||||
IsBoolean,
|
||||
IsDate,
|
||||
IsEnum,
|
||||
IsHexColor,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
|
@ -29,6 +30,7 @@ import {
|
|||
import { CronJob } from 'cron';
|
||||
import { DateTime } from 'luxon';
|
||||
import sanitize from 'sanitize-filename';
|
||||
import { AssetVisibility } from 'src/enum';
|
||||
import { isIP, isIPRange } from 'validator';
|
||||
|
||||
@Injectable()
|
||||
|
@ -146,6 +148,17 @@ export const ValidateDate = (options?: DateOptions) => {
|
|||
return applyDecorators(...decorators);
|
||||
};
|
||||
|
||||
type AssetVisibilityOptions = { optional?: boolean };
|
||||
export const ValidateAssetVisibility = (options?: AssetVisibilityOptions) => {
|
||||
const { optional } = { optional: false, ...options };
|
||||
const decorators = [IsEnum(AssetVisibility), ApiProperty({ enumName: 'AssetVisibility', enum: AssetVisibility })];
|
||||
|
||||
if (optional) {
|
||||
decorators.push(Optional());
|
||||
}
|
||||
return applyDecorators(...decorators);
|
||||
};
|
||||
|
||||
type BooleanOptions = { optional?: boolean };
|
||||
export const ValidateBoolean = (options?: BooleanOptions) => {
|
||||
const { optional } = { optional: false, ...options };
|
||||
|
|
65
server/test/fixtures/asset.stub.ts
vendored
65
server/test/fixtures/asset.stub.ts
vendored
|
@ -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,
|
||||
}),
|
||||
};
|
||||
|
|
4
server/test/fixtures/shared-link.stub.ts
vendored
4
server/test/fixtures/shared-link.stub.ts
vendored
|
@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts" module>
|
||||
import type { SearchLocationFilter } from './search-location-section.svelte';
|
||||
import type { SearchDisplayFilters } from './search-display-section.svelte';
|
||||
import type { SearchDateFilter } from './search-date-section.svelte';
|
||||
import { MediaType, QueryType, validQueryTypes } from '$lib/constants';
|
||||
import type { SearchDateFilter } from './search-date-section.svelte';
|
||||
import type { SearchDisplayFilters } from './search-display-section.svelte';
|
||||
import type { SearchLocationFilter } from './search-location-section.svelte';
|
||||
|
||||
export type SearchFilter = {
|
||||
query: string;
|
||||
|
@ -19,24 +19,24 @@
|
|||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
||||
import { preferences } from '$lib/stores/user.store';
|
||||
import { parseUtcDate } from '$lib/utils/date-time';
|
||||
import { generateId } from '$lib/utils/generate-id';
|
||||
import { AssetTypeEnum, AssetVisibility, type MetadataSearchDto, type SmartSearchDto } from '@immich/sdk';
|
||||
import { Button } from '@immich/ui';
|
||||
import { AssetTypeEnum, type SmartSearchDto, type MetadataSearchDto } from '@immich/sdk';
|
||||
import SearchPeopleSection from './search-people-section.svelte';
|
||||
import SearchTagsSection from './search-tags-section.svelte';
|
||||
import SearchLocationSection from './search-location-section.svelte';
|
||||
import { mdiTune } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
import SearchCameraSection, { type SearchCameraFilter } from './search-camera-section.svelte';
|
||||
import SearchDateSection from './search-date-section.svelte';
|
||||
import SearchMediaSection from './search-media-section.svelte';
|
||||
import SearchRatingsSection from './search-ratings-section.svelte';
|
||||
import { parseUtcDate } from '$lib/utils/date-time';
|
||||
import SearchDisplaySection from './search-display-section.svelte';
|
||||
import SearchLocationSection from './search-location-section.svelte';
|
||||
import SearchMediaSection from './search-media-section.svelte';
|
||||
import SearchPeopleSection from './search-people-section.svelte';
|
||||
import SearchRatingsSection from './search-ratings-section.svelte';
|
||||
import SearchTagsSection from './search-tags-section.svelte';
|
||||
import SearchTextSection from './search-text-section.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
||||
import { mdiTune } from '@mdi/js';
|
||||
import { generateId } from '$lib/utils/generate-id';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
import { preferences } from '$lib/stores/user.store';
|
||||
|
||||
interface Props {
|
||||
searchQuery: MetadataSearchDto | SmartSearchDto;
|
||||
|
@ -83,7 +83,7 @@
|
|||
takenBefore: searchQuery.takenBefore ? toStartOfDayDate(searchQuery.takenBefore) : undefined,
|
||||
},
|
||||
display: {
|
||||
isArchive: searchQuery.isArchived,
|
||||
isArchive: searchQuery.visibility === AssetVisibility.Archive,
|
||||
isFavorite: searchQuery.isFavorite,
|
||||
isNotInAlbum: 'isNotInAlbum' in searchQuery ? searchQuery.isNotInAlbum : undefined,
|
||||
},
|
||||
|
@ -132,7 +132,7 @@
|
|||
model: filter.camera.model,
|
||||
takenAfter: parseOptionalDate(filter.date.takenAfter)?.startOf('day').toISO() || undefined,
|
||||
takenBefore: parseOptionalDate(filter.date.takenBefore)?.endOf('day').toISO() || undefined,
|
||||
isArchived: filter.display.isArchive || undefined,
|
||||
visibility: filter.display.isArchive ? AssetVisibility.Archive : undefined,
|
||||
isFavorite: filter.display.isFavorite || undefined,
|
||||
isNotInAlbum: filter.display.isNotInAlbum || undefined,
|
||||
personIds: filter.personIds.size > 0 ? [...filter.personIds] : undefined,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { locale } from '$lib/stores/preferences.store';
|
||||
import {
|
||||
AssetVisibility,
|
||||
getAlbumStatistics,
|
||||
getAssetStatistics,
|
||||
type AlbumStatisticsResponseDto,
|
||||
|
@ -41,9 +42,9 @@
|
|||
|
||||
const getUsage = async () => {
|
||||
[timelineStats, favoriteStats, archiveStats, trashStats, albumStats] = await Promise.all([
|
||||
getAssetStatistics({ isArchived: false }),
|
||||
getAssetStatistics({ visibility: AssetVisibility.Timeline }),
|
||||
getAssetStatistics({ isFavorite: true }),
|
||||
getAssetStatistics({ isArchived: true }),
|
||||
getAssetStatistics({ visibility: AssetVisibility.Archive }),
|
||||
getAssetStatistics({ isTrashed: true }),
|
||||
getAlbumStatistics(),
|
||||
]);
|
||||
|
|
|
@ -10,6 +10,7 @@ import { formatDateGroupTitle, fromLocalDateTime } from '$lib/utils/timeline-uti
|
|||
import { TUNABLES } from '$lib/utils/tunables';
|
||||
import {
|
||||
AssetOrder,
|
||||
AssetVisibility,
|
||||
getAssetInfo,
|
||||
getTimeBucket,
|
||||
getTimeBuckets,
|
||||
|
@ -1375,7 +1376,7 @@ export class AssetStore {
|
|||
|
||||
isExcluded(asset: AssetResponseDto) {
|
||||
return (
|
||||
isMismatched(this.#options.isArchived, asset.isArchived) ||
|
||||
isMismatched(this.#options.visibility === AssetVisibility.Archive, asset.isArchived) ||
|
||||
isMismatched(this.#options.isFavorite, asset.isFavorite) ||
|
||||
isMismatched(this.#options.isTrashed, asset.isTrashed)
|
||||
);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { goto } from '$app/navigation';
|
||||
import FormatBoldMessage from '$lib/components/i18n/format-bold-message.svelte';
|
||||
import type { InterpolationValues } from '$lib/components/i18n/format-message';
|
||||
import { NotificationType, notificationController } from '$lib/components/shared-components/notification/notification';
|
||||
import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification';
|
||||
import { AppRoute } from '$lib/constants';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { downloadManager } from '$lib/managers/download-manager.svelte';
|
||||
|
@ -15,6 +15,7 @@ import { getFormatter } from '$lib/utils/i18n';
|
|||
import { navigate } from '$lib/utils/navigation';
|
||||
import {
|
||||
addAssetsToAlbum as addAssets,
|
||||
AssetVisibility,
|
||||
createStack,
|
||||
deleteAssets,
|
||||
deleteStacks,
|
||||
|
@ -507,7 +508,7 @@ export const toggleArchive = async (asset: AssetResponseDto) => {
|
|||
const data = await updateAsset({
|
||||
id: asset.id,
|
||||
updateAssetDto: {
|
||||
isArchived: !asset.isArchived,
|
||||
visibility: asset.isArchived ? AssetVisibility.Timeline : AssetVisibility.Archive,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -531,7 +532,9 @@ export const archiveAssets = async (assets: AssetResponseDto[], archive: boolean
|
|||
|
||||
try {
|
||||
if (ids.length > 0) {
|
||||
await updateAssets({ assetBulkUpdateDto: { ids, isArchived } });
|
||||
await updateAssets({
|
||||
assetBulkUpdateDto: { ids, visibility: isArchived ? AssetVisibility.Archive : AssetVisibility.Timeline },
|
||||
});
|
||||
}
|
||||
|
||||
for (const asset of assets) {
|
||||
|
|
|
@ -372,7 +372,10 @@
|
|||
if (viewMode === AlbumPageViewMode.VIEW) {
|
||||
void assetStore.updateOptions({ albumId, order: albumOrder });
|
||||
} else if (viewMode === AlbumPageViewMode.SELECT_ASSETS) {
|
||||
void assetStore.updateOptions({ isArchived: false, withPartners: true, timelineAlbumId: albumId });
|
||||
void assetStore.updateOptions({
|
||||
withPartners: true,
|
||||
timelineAlbumId: albumId,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -385,9 +388,6 @@
|
|||
activityManager.reset();
|
||||
assetStore.destroy();
|
||||
});
|
||||
// let timelineStore = new AssetStore();
|
||||
// $effect(() => void timelineStore.updateOptions({ isArchived: false, withPartners: true, timelineAlbumId: albumId }));
|
||||
// onDestroy(() => timelineStore.destroy());
|
||||
|
||||
let isOwned = $derived($user.id == album.ownerId);
|
||||
|
||||
|
|
|
@ -8,17 +8,18 @@
|
|||
import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
|
||||
import SelectAllAssets from '$lib/components/photos-page/actions/select-all-assets.svelte';
|
||||
import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
|
||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
||||
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
|
||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
||||
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
||||
import { AssetAction } from '$lib/constants';
|
||||
|
||||
import type { PageData } from './$types';
|
||||
import { mdiPlus, mdiDotsVertical } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||
import { AssetStore } from '$lib/stores/assets-store.svelte';
|
||||
import { AssetVisibility } from '@immich/sdk';
|
||||
import { mdiDotsVertical, mdiPlus } from '@mdi/js';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
interface Props {
|
||||
data: PageData;
|
||||
|
@ -26,7 +27,7 @@
|
|||
|
||||
let { data }: Props = $props();
|
||||
const assetStore = new AssetStore();
|
||||
void assetStore.updateOptions({ isArchived: true });
|
||||
void assetStore.updateOptions({ visibility: AssetVisibility.Archive });
|
||||
onDestroy(() => assetStore.destroy());
|
||||
|
||||
const assetInteraction = new AssetInteraction();
|
||||
|
|
|
@ -9,19 +9,19 @@
|
|||
import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
|
||||
import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
|
||||
import SelectAllAssets from '$lib/components/photos-page/actions/select-all-assets.svelte';
|
||||
import TagAction from '$lib/components/photos-page/actions/tag-action.svelte';
|
||||
import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
|
||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
||||
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
|
||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
||||
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
||||
import { AssetAction } from '$lib/constants';
|
||||
import { AssetStore } from '$lib/stores/assets-store.svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { mdiDotsVertical, mdiPlus } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { preferences } from '$lib/stores/user.store';
|
||||
import TagAction from '$lib/components/photos-page/actions/tag-action.svelte';
|
||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||
import { AssetStore } from '$lib/stores/assets-store.svelte';
|
||||
import { preferences } from '$lib/stores/user.store';
|
||||
import { mdiDotsVertical, mdiPlus } from '@mdi/js';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
interface Props {
|
||||
data: PageData;
|
||||
|
|
|
@ -4,16 +4,17 @@
|
|||
import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte';
|
||||
import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
|
||||
import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
|
||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
||||
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
|
||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
||||
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
|
||||
import { AppRoute } from '$lib/constants';
|
||||
import { AssetStore } from '$lib/stores/assets-store.svelte';
|
||||
import { onDestroy } from 'svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { mdiPlus, mdiArrowLeft } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||
import { AssetStore } from '$lib/stores/assets-store.svelte';
|
||||
import { AssetVisibility } from '@immich/sdk';
|
||||
import { mdiArrowLeft, mdiPlus } from '@mdi/js';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
interface Props {
|
||||
data: PageData;
|
||||
|
@ -22,7 +23,14 @@
|
|||
let { data }: Props = $props();
|
||||
|
||||
const assetStore = new AssetStore();
|
||||
$effect(() => void assetStore.updateOptions({ userId: data.partner.id, isArchived: false, withStacked: true }));
|
||||
$effect(
|
||||
() =>
|
||||
void assetStore.updateOptions({
|
||||
userId: data.partner.id,
|
||||
visibility: AssetVisibility.Timeline,
|
||||
withStacked: true,
|
||||
}),
|
||||
);
|
||||
onDestroy(() => assetStore.destroy());
|
||||
const assetInteraction = new AssetInteraction();
|
||||
|
||||
|
|
|
@ -34,12 +34,14 @@
|
|||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||
import { AssetStore } from '$lib/stores/assets-store.svelte';
|
||||
import { locale } from '$lib/stores/preferences.store';
|
||||
import { preferences } from '$lib/stores/user.store';
|
||||
import { websocketEvents } from '$lib/stores/websocket';
|
||||
import { getPeopleThumbnailUrl, handlePromiseError } from '$lib/utils';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { isExternalUrl } from '$lib/utils/navigation';
|
||||
import {
|
||||
AssetVisibility,
|
||||
getPersonStatistics,
|
||||
mergePerson,
|
||||
searchPerson,
|
||||
|
@ -59,11 +61,10 @@
|
|||
mdiHeartOutline,
|
||||
mdiPlus,
|
||||
} from '@mdi/js';
|
||||
import { DateTime } from 'luxon';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { PageData } from './$types';
|
||||
import { locale } from '$lib/stores/preferences.store';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
interface Props {
|
||||
data: PageData;
|
||||
|
@ -75,7 +76,7 @@
|
|||
let { isViewing: showAssetViewer } = assetViewingStore;
|
||||
|
||||
const assetStore = new AssetStore();
|
||||
$effect(() => void assetStore.updateOptions({ isArchived: false, personId: data.person.id }));
|
||||
$effect(() => void assetStore.updateOptions({ visibility: AssetVisibility.Timeline, personId: data.person.id }));
|
||||
onDestroy(() => assetStore.destroy());
|
||||
|
||||
const assetInteraction = new AssetInteraction();
|
||||
|
|
|
@ -32,14 +32,14 @@
|
|||
type OnUnlink,
|
||||
} from '$lib/utils/actions';
|
||||
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
||||
import { AssetTypeEnum } from '@immich/sdk';
|
||||
import { AssetTypeEnum, AssetVisibility } from '@immich/sdk';
|
||||
import { mdiDotsVertical, mdiPlus } from '@mdi/js';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
let { isViewing: showAssetViewer } = assetViewingStore;
|
||||
const assetStore = new AssetStore();
|
||||
void assetStore.updateOptions({ isArchived: false, withStacked: true, withPartners: true });
|
||||
void assetStore.updateOptions({ visibility: AssetVisibility.Timeline, withStacked: true, withPartners: true });
|
||||
onDestroy(() => assetStore.destroy());
|
||||
|
||||
const assetInteraction = new AssetInteraction();
|
||||
|
|
|
@ -1,25 +1,38 @@
|
|||
<script lang="ts">
|
||||
import { afterNavigate, goto } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import { shortcut } from '$lib/actions/shortcut';
|
||||
import AlbumCardGroup from '$lib/components/album-page/album-card-group.svelte';
|
||||
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||
import Icon from '$lib/components/elements/icon.svelte';
|
||||
import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte';
|
||||
import ArchiveAction from '$lib/components/photos-page/actions/archive-action.svelte';
|
||||
import AssetJobActions from '$lib/components/photos-page/actions/asset-job-actions.svelte';
|
||||
import ChangeDate from '$lib/components/photos-page/actions/change-date-action.svelte';
|
||||
import ChangeLocation from '$lib/components/photos-page/actions/change-location-action.svelte';
|
||||
import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte';
|
||||
import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte';
|
||||
import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
|
||||
import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
|
||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
||||
import TagAction from '$lib/components/photos-page/actions/tag-action.svelte';
|
||||
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
|
||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
||||
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
|
||||
import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
|
||||
import { cancelMultiselect } from '$lib/utils/asset-utils';
|
||||
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
||||
import SearchBar from '$lib/components/shared-components/search-bar/search-bar.svelte';
|
||||
import { AppRoute, QueryParameter } from '$lib/constants';
|
||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||
import { shortcut } from '$lib/actions/shortcut';
|
||||
import type { Viewport } from '$lib/stores/assets-store.svelte';
|
||||
import { lang, locale } from '$lib/stores/preferences.store';
|
||||
import { featureFlags } from '$lib/stores/server-config.store';
|
||||
import { preferences } from '$lib/stores/user.store';
|
||||
import { handlePromiseError } from '$lib/utils';
|
||||
import { cancelMultiselect } from '$lib/utils/asset-utils';
|
||||
import { parseUtcDate } from '$lib/utils/date-time';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { isAlbumsRoute, isPeopleRoute } from '$lib/utils/navigation';
|
||||
import {
|
||||
type AlbumResponseDto,
|
||||
type AssetResponseDto,
|
||||
|
@ -31,21 +44,8 @@
|
|||
type SmartSearchDto,
|
||||
} from '@immich/sdk';
|
||||
import { mdiArrowLeft, mdiDotsVertical, mdiImageOffOutline, mdiPlus, mdiSelectAll } from '@mdi/js';
|
||||
import type { Viewport } from '$lib/stores/assets-store.svelte';
|
||||
import { lang, locale } from '$lib/stores/preferences.store';
|
||||
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
||||
import { handlePromiseError } from '$lib/utils';
|
||||
import { parseUtcDate } from '$lib/utils/date-time';
|
||||
import { featureFlags } from '$lib/stores/server-config.store';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import AlbumCardGroup from '$lib/components/album-page/album-card-group.svelte';
|
||||
import { isAlbumsRoute, isPeopleRoute } from '$lib/utils/navigation';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { tick } from 'svelte';
|
||||
import AssetJobActions from '$lib/components/photos-page/actions/asset-job-actions.svelte';
|
||||
import { preferences } from '$lib/stores/user.store';
|
||||
import TagAction from '$lib/components/photos-page/actions/tag-action.svelte';
|
||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
const MAX_ASSET_COUNT = 5000;
|
||||
let { isViewing: showAssetViewer } = assetViewingStore;
|
||||
|
@ -186,7 +186,7 @@
|
|||
const keyMap: Partial<Record<keyof SearchTerms, string>> = {
|
||||
takenAfter: $t('start_date'),
|
||||
takenBefore: $t('end_date'),
|
||||
isArchived: $t('in_archive'),
|
||||
visibility: $t('in_archive'),
|
||||
isFavorite: $t('favorite'),
|
||||
isNotInAlbum: $t('not_in_any_album'),
|
||||
type: $t('media_type'),
|
||||
|
@ -313,7 +313,7 @@
|
|||
<div class="flex place-content-center place-items-center text-xs">
|
||||
<div
|
||||
class="bg-immich-primary py-2 px-4 text-white dark:text-black dark:bg-immich-dark-primary
|
||||
{value === true ? 'rounded-full' : 'roudned-s-full'}"
|
||||
{value === true ? 'rounded-full' : 'rounded-s-full'}"
|
||||
>
|
||||
{getHumanReadableSearchKey(key as keyof SearchTerms)}
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue