mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
chore!: remove without assets (#27835)
* chore!: remove without assets * fix: linting and e2e --------- Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
This commit is contained in:
@@ -130,12 +130,11 @@ describe('/albums', () => {
|
|||||||
describe('GET /albums', () => {
|
describe('GET /albums', () => {
|
||||||
it("should not show other users' favorites", async () => {
|
it("should not show other users' favorites", async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.get(`/albums/${user1Albums[0].id}?withoutAssets=false`)
|
.get(`/albums/${user1Albums[0].id}`)
|
||||||
.set('Authorization', `Bearer ${user2.accessToken}`);
|
.set('Authorization', `Bearer ${user2.accessToken}`);
|
||||||
expect(status).toEqual(200);
|
expect(status).toEqual(200);
|
||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
...user1Albums[0],
|
...user1Albums[0],
|
||||||
assets: [expect.objectContaining({ isFavorite: false })],
|
|
||||||
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
|
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
|
||||||
lastModifiedAssetTimestamp: expect.any(String),
|
lastModifiedAssetTimestamp: expect.any(String),
|
||||||
startDate: expect.any(String),
|
startDate: expect.any(String),
|
||||||
@@ -304,13 +303,12 @@ describe('/albums', () => {
|
|||||||
describe('GET /albums/:id', () => {
|
describe('GET /albums/:id', () => {
|
||||||
it('should return album info for own album', async () => {
|
it('should return album info for own album', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.get(`/albums/${user1Albums[0].id}?withoutAssets=false`)
|
.get(`/albums/${user1Albums[0].id}`)
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
...user1Albums[0],
|
...user1Albums[0],
|
||||||
assets: [expect.objectContaining({ id: user1Albums[0].assets[0].id })],
|
|
||||||
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
|
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
|
||||||
lastModifiedAssetTimestamp: expect.any(String),
|
lastModifiedAssetTimestamp: expect.any(String),
|
||||||
startDate: expect.any(String),
|
startDate: expect.any(String),
|
||||||
@@ -322,7 +320,7 @@ describe('/albums', () => {
|
|||||||
|
|
||||||
it('should return album info for shared album (editor)', async () => {
|
it('should return album info for shared album (editor)', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.get(`/albums/${user2Albums[0].id}?withoutAssets=false`)
|
.get(`/albums/${user2Albums[0].id}`)
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
@@ -331,14 +329,14 @@ describe('/albums', () => {
|
|||||||
|
|
||||||
it('should return album info for shared album (viewer)', async () => {
|
it('should return album info for shared album (viewer)', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.get(`/albums/${user1Albums[3].id}?withoutAssets=false`)
|
.get(`/albums/${user1Albums[3].id}`)
|
||||||
.set('Authorization', `Bearer ${user2.accessToken}`);
|
.set('Authorization', `Bearer ${user2.accessToken}`);
|
||||||
|
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toMatchObject({ id: user1Albums[3].id });
|
expect(body).toMatchObject({ id: user1Albums[3].id });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return album info with assets when withoutAssets is undefined', async () => {
|
it('should return album info', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.get(`/albums/${user1Albums[0].id}`)
|
.get(`/albums/${user1Albums[0].id}`)
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
@@ -346,25 +344,6 @@ describe('/albums', () => {
|
|||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
...user1Albums[0],
|
...user1Albums[0],
|
||||||
assets: [expect.objectContaining({ id: user1Albums[0].assets[0].id })],
|
|
||||||
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
|
|
||||||
lastModifiedAssetTimestamp: expect.any(String),
|
|
||||||
startDate: expect.any(String),
|
|
||||||
endDate: expect.any(String),
|
|
||||||
albumUsers: expect.any(Array),
|
|
||||||
shared: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return album info without assets when withoutAssets is true', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.get(`/albums/${user1Albums[0].id}?withoutAssets=true`)
|
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toEqual({
|
|
||||||
...user1Albums[0],
|
|
||||||
assets: [],
|
|
||||||
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
|
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
|
||||||
assetCount: 1,
|
assetCount: 1,
|
||||||
lastModifiedAssetTimestamp: expect.any(String),
|
lastModifiedAssetTimestamp: expect.any(String),
|
||||||
@@ -379,13 +358,12 @@ describe('/albums', () => {
|
|||||||
await utils.deleteAssets(user1.accessToken, [user1Asset2.id]);
|
await utils.deleteAssets(user1.accessToken, [user1Asset2.id]);
|
||||||
|
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.get(`/albums/${user2Albums[0].id}?withoutAssets=true`)
|
.get(`/albums/${user2Albums[0].id}`)
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
...user2Albums[0],
|
...user2Albums[0],
|
||||||
assets: [],
|
|
||||||
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
|
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
|
||||||
assetCount: 1,
|
assetCount: 1,
|
||||||
lastModifiedAssetTimestamp: expect.any(String),
|
lastModifiedAssetTimestamp: expect.any(String),
|
||||||
@@ -426,7 +404,6 @@ describe('/albums', () => {
|
|||||||
shared: false,
|
shared: false,
|
||||||
albumUsers: [],
|
albumUsers: [],
|
||||||
hasSharedLink: false,
|
hasSharedLink: false,
|
||||||
assets: [],
|
|
||||||
assetCount: 0,
|
assetCount: 0,
|
||||||
owner: expect.objectContaining({ email: user1.userEmail }),
|
owner: expect.objectContaining({ email: user1.userEmail }),
|
||||||
isActivityEnabled: true,
|
isActivityEnabled: true,
|
||||||
|
|||||||
@@ -427,7 +427,6 @@ export function getAlbum(
|
|||||||
hasSharedLink: false,
|
hasSharedLink: false,
|
||||||
isActivityEnabled: true,
|
isActivityEnabled: true,
|
||||||
assetCount: albumAssets.length,
|
assetCount: albumAssets.length,
|
||||||
assets: albumAssets,
|
|
||||||
startDate: albumAssets.length > 0 ? albumAssets.at(-1)?.fileCreatedAt : undefined,
|
startDate: albumAssets.length > 0 ? albumAssets.at(-1)?.fileCreatedAt : undefined,
|
||||||
endDate: albumAssets.length > 0 ? albumAssets[0].fileCreatedAt : undefined,
|
endDate: albumAssets.length > 0 ? albumAssets[0].fileCreatedAt : undefined,
|
||||||
lastModifiedAssetTimestamp: albumAssets.length > 0 ? albumAssets[0].fileCreatedAt : undefined,
|
lastModifiedAssetTimestamp: albumAssets.length > 0 ? albumAssets[0].fileCreatedAt : undefined,
|
||||||
|
|||||||
Generated
+3
-12
@@ -315,10 +315,7 @@ class AlbumsApi {
|
|||||||
/// * [String] key:
|
/// * [String] key:
|
||||||
///
|
///
|
||||||
/// * [String] slug:
|
/// * [String] slug:
|
||||||
///
|
Future<Response> getAlbumInfoWithHttpInfo(String id, { String? key, String? slug, }) async {
|
||||||
/// * [bool] withoutAssets:
|
|
||||||
/// Exclude assets from response
|
|
||||||
Future<Response> getAlbumInfoWithHttpInfo(String id, { String? key, String? slug, bool? withoutAssets, }) async {
|
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final apiPath = r'/albums/{id}'
|
final apiPath = r'/albums/{id}'
|
||||||
.replaceAll('{id}', id);
|
.replaceAll('{id}', id);
|
||||||
@@ -336,9 +333,6 @@ class AlbumsApi {
|
|||||||
if (slug != null) {
|
if (slug != null) {
|
||||||
queryParams.addAll(_queryParams('', 'slug', slug));
|
queryParams.addAll(_queryParams('', 'slug', slug));
|
||||||
}
|
}
|
||||||
if (withoutAssets != null) {
|
|
||||||
queryParams.addAll(_queryParams('', 'withoutAssets', withoutAssets));
|
|
||||||
}
|
|
||||||
|
|
||||||
const contentTypes = <String>[];
|
const contentTypes = <String>[];
|
||||||
|
|
||||||
@@ -365,11 +359,8 @@ class AlbumsApi {
|
|||||||
/// * [String] key:
|
/// * [String] key:
|
||||||
///
|
///
|
||||||
/// * [String] slug:
|
/// * [String] slug:
|
||||||
///
|
Future<AlbumResponseDto?> getAlbumInfo(String id, { String? key, String? slug, }) async {
|
||||||
/// * [bool] withoutAssets:
|
final response = await getAlbumInfoWithHttpInfo(id, key: key, slug: slug, );
|
||||||
/// Exclude assets from response
|
|
||||||
Future<AlbumResponseDto?> getAlbumInfo(String id, { String? key, String? slug, bool? withoutAssets, }) async {
|
|
||||||
final response = await getAlbumInfoWithHttpInfo(id, key: key, slug: slug, withoutAssets: withoutAssets, );
|
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-9
@@ -17,7 +17,6 @@ class AlbumResponseDto {
|
|||||||
required this.albumThumbnailAssetId,
|
required this.albumThumbnailAssetId,
|
||||||
this.albumUsers = const [],
|
this.albumUsers = const [],
|
||||||
required this.assetCount,
|
required this.assetCount,
|
||||||
this.assets = const [],
|
|
||||||
this.contributorCounts = const [],
|
this.contributorCounts = const [],
|
||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
required this.description,
|
required this.description,
|
||||||
@@ -48,8 +47,6 @@ class AlbumResponseDto {
|
|||||||
/// Maximum value: 9007199254740991
|
/// Maximum value: 9007199254740991
|
||||||
int assetCount;
|
int assetCount;
|
||||||
|
|
||||||
List<AssetResponseDto> assets;
|
|
||||||
|
|
||||||
List<ContributorCountResponseDto> contributorCounts;
|
List<ContributorCountResponseDto> contributorCounts;
|
||||||
|
|
||||||
/// Creation date
|
/// Creation date
|
||||||
@@ -119,7 +116,6 @@ class AlbumResponseDto {
|
|||||||
other.albumThumbnailAssetId == albumThumbnailAssetId &&
|
other.albumThumbnailAssetId == albumThumbnailAssetId &&
|
||||||
_deepEquality.equals(other.albumUsers, albumUsers) &&
|
_deepEquality.equals(other.albumUsers, albumUsers) &&
|
||||||
other.assetCount == assetCount &&
|
other.assetCount == assetCount &&
|
||||||
_deepEquality.equals(other.assets, assets) &&
|
|
||||||
_deepEquality.equals(other.contributorCounts, contributorCounts) &&
|
_deepEquality.equals(other.contributorCounts, contributorCounts) &&
|
||||||
other.createdAt == createdAt &&
|
other.createdAt == createdAt &&
|
||||||
other.description == description &&
|
other.description == description &&
|
||||||
@@ -142,7 +138,6 @@ class AlbumResponseDto {
|
|||||||
(albumThumbnailAssetId == null ? 0 : albumThumbnailAssetId!.hashCode) +
|
(albumThumbnailAssetId == null ? 0 : albumThumbnailAssetId!.hashCode) +
|
||||||
(albumUsers.hashCode) +
|
(albumUsers.hashCode) +
|
||||||
(assetCount.hashCode) +
|
(assetCount.hashCode) +
|
||||||
(assets.hashCode) +
|
|
||||||
(contributorCounts.hashCode) +
|
(contributorCounts.hashCode) +
|
||||||
(createdAt.hashCode) +
|
(createdAt.hashCode) +
|
||||||
(description.hashCode) +
|
(description.hashCode) +
|
||||||
@@ -159,7 +154,7 @@ class AlbumResponseDto {
|
|||||||
(updatedAt.hashCode);
|
(updatedAt.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'AlbumResponseDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, albumUsers=$albumUsers, assetCount=$assetCount, assets=$assets, contributorCounts=$contributorCounts, createdAt=$createdAt, description=$description, endDate=$endDate, hasSharedLink=$hasSharedLink, id=$id, isActivityEnabled=$isActivityEnabled, lastModifiedAssetTimestamp=$lastModifiedAssetTimestamp, order=$order, owner=$owner, ownerId=$ownerId, shared=$shared, startDate=$startDate, updatedAt=$updatedAt]';
|
String toString() => 'AlbumResponseDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, albumUsers=$albumUsers, assetCount=$assetCount, contributorCounts=$contributorCounts, createdAt=$createdAt, description=$description, endDate=$endDate, hasSharedLink=$hasSharedLink, id=$id, isActivityEnabled=$isActivityEnabled, lastModifiedAssetTimestamp=$lastModifiedAssetTimestamp, order=$order, owner=$owner, ownerId=$ownerId, shared=$shared, startDate=$startDate, updatedAt=$updatedAt]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@@ -171,7 +166,6 @@ class AlbumResponseDto {
|
|||||||
}
|
}
|
||||||
json[r'albumUsers'] = this.albumUsers;
|
json[r'albumUsers'] = this.albumUsers;
|
||||||
json[r'assetCount'] = this.assetCount;
|
json[r'assetCount'] = this.assetCount;
|
||||||
json[r'assets'] = this.assets;
|
|
||||||
json[r'contributorCounts'] = this.contributorCounts;
|
json[r'contributorCounts'] = this.contributorCounts;
|
||||||
json[r'createdAt'] = this.createdAt.toUtc().toIso8601String();
|
json[r'createdAt'] = this.createdAt.toUtc().toIso8601String();
|
||||||
json[r'description'] = this.description;
|
json[r'description'] = this.description;
|
||||||
@@ -218,7 +212,6 @@ class AlbumResponseDto {
|
|||||||
albumThumbnailAssetId: mapValueOfType<String>(json, r'albumThumbnailAssetId'),
|
albumThumbnailAssetId: mapValueOfType<String>(json, r'albumThumbnailAssetId'),
|
||||||
albumUsers: AlbumUserResponseDto.listFromJson(json[r'albumUsers']),
|
albumUsers: AlbumUserResponseDto.listFromJson(json[r'albumUsers']),
|
||||||
assetCount: mapValueOfType<int>(json, r'assetCount')!,
|
assetCount: mapValueOfType<int>(json, r'assetCount')!,
|
||||||
assets: AssetResponseDto.listFromJson(json[r'assets']),
|
|
||||||
contributorCounts: ContributorCountResponseDto.listFromJson(json[r'contributorCounts']),
|
contributorCounts: ContributorCountResponseDto.listFromJson(json[r'contributorCounts']),
|
||||||
createdAt: mapDateTime(json, r'createdAt', r'')!,
|
createdAt: mapDateTime(json, r'createdAt', r'')!,
|
||||||
description: mapValueOfType<String>(json, r'description')!,
|
description: mapValueOfType<String>(json, r'description')!,
|
||||||
@@ -284,7 +277,6 @@ class AlbumResponseDto {
|
|||||||
'albumThumbnailAssetId',
|
'albumThumbnailAssetId',
|
||||||
'albumUsers',
|
'albumUsers',
|
||||||
'assetCount',
|
'assetCount',
|
||||||
'assets',
|
|
||||||
'createdAt',
|
'createdAt',
|
||||||
'description',
|
'description',
|
||||||
'hasSharedLink',
|
'hasSharedLink',
|
||||||
|
|||||||
@@ -1956,15 +1956,6 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "withoutAssets",
|
|
||||||
"required": false,
|
|
||||||
"in": "query",
|
|
||||||
"description": "Exclude assets from response",
|
|
||||||
"schema": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@@ -15305,12 +15296,6 @@
|
|||||||
"minimum": 0,
|
"minimum": 0,
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"assets": {
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/components/schemas/AssetResponseDto"
|
|
||||||
},
|
|
||||||
"type": "array"
|
|
||||||
},
|
|
||||||
"contributorCounts": {
|
"contributorCounts": {
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/components/schemas/ContributorCountResponseDto"
|
"$ref": "#/components/schemas/ContributorCountResponseDto"
|
||||||
@@ -15378,7 +15363,6 @@
|
|||||||
"albumThumbnailAssetId",
|
"albumThumbnailAssetId",
|
||||||
"albumUsers",
|
"albumUsers",
|
||||||
"assetCount",
|
"assetCount",
|
||||||
"assets",
|
|
||||||
"createdAt",
|
"createdAt",
|
||||||
"description",
|
"description",
|
||||||
"hasSharedLink",
|
"hasSharedLink",
|
||||||
|
|||||||
@@ -446,172 +446,6 @@ export type AlbumUserResponseDto = {
|
|||||||
role: AlbumUserRole;
|
role: AlbumUserRole;
|
||||||
user: UserResponseDto;
|
user: UserResponseDto;
|
||||||
};
|
};
|
||||||
export type ExifResponseDto = {
|
|
||||||
/** City name */
|
|
||||||
city?: string | null;
|
|
||||||
/** Country name */
|
|
||||||
country?: string | null;
|
|
||||||
/** Original date/time */
|
|
||||||
dateTimeOriginal?: string | null;
|
|
||||||
/** Image description */
|
|
||||||
description?: string | null;
|
|
||||||
/** Image height in pixels */
|
|
||||||
exifImageHeight?: number | null;
|
|
||||||
/** Image width in pixels */
|
|
||||||
exifImageWidth?: number | null;
|
|
||||||
/** Exposure time */
|
|
||||||
exposureTime?: string | null;
|
|
||||||
/** F-number (aperture) */
|
|
||||||
fNumber?: number | null;
|
|
||||||
/** File size in bytes */
|
|
||||||
fileSizeInByte?: number | null;
|
|
||||||
/** Focal length in mm */
|
|
||||||
focalLength?: number | null;
|
|
||||||
/** ISO sensitivity */
|
|
||||||
iso?: number | null;
|
|
||||||
/** GPS latitude */
|
|
||||||
latitude?: number | null;
|
|
||||||
/** Lens model */
|
|
||||||
lensModel?: string | null;
|
|
||||||
/** GPS longitude */
|
|
||||||
longitude?: number | null;
|
|
||||||
/** Camera make */
|
|
||||||
make?: string | null;
|
|
||||||
/** Camera model */
|
|
||||||
model?: string | null;
|
|
||||||
/** Modification date/time */
|
|
||||||
modifyDate?: string | null;
|
|
||||||
/** Image orientation */
|
|
||||||
orientation?: string | null;
|
|
||||||
/** Projection type */
|
|
||||||
projectionType?: string | null;
|
|
||||||
/** Rating */
|
|
||||||
rating?: number | null;
|
|
||||||
/** State/province name */
|
|
||||||
state?: string | null;
|
|
||||||
/** Time zone */
|
|
||||||
timeZone?: string | null;
|
|
||||||
};
|
|
||||||
export type AssetFaceWithoutPersonResponseDto = {
|
|
||||||
/** Bounding box X1 coordinate */
|
|
||||||
boundingBoxX1: number;
|
|
||||||
/** Bounding box X2 coordinate */
|
|
||||||
boundingBoxX2: number;
|
|
||||||
/** Bounding box Y1 coordinate */
|
|
||||||
boundingBoxY1: number;
|
|
||||||
/** Bounding box Y2 coordinate */
|
|
||||||
boundingBoxY2: number;
|
|
||||||
/** Face ID */
|
|
||||||
id: string;
|
|
||||||
/** Image height in pixels */
|
|
||||||
imageHeight: number;
|
|
||||||
/** Image width in pixels */
|
|
||||||
imageWidth: number;
|
|
||||||
sourceType?: SourceType;
|
|
||||||
};
|
|
||||||
export type PersonWithFacesResponseDto = {
|
|
||||||
/** Person date of birth */
|
|
||||||
birthDate: string | null;
|
|
||||||
/** Person color (hex) */
|
|
||||||
color?: string;
|
|
||||||
faces: AssetFaceWithoutPersonResponseDto[];
|
|
||||||
/** Person ID */
|
|
||||||
id: string;
|
|
||||||
/** Is favorite */
|
|
||||||
isFavorite?: boolean;
|
|
||||||
/** Is hidden */
|
|
||||||
isHidden: boolean;
|
|
||||||
/** Person name */
|
|
||||||
name: string;
|
|
||||||
/** Thumbnail path */
|
|
||||||
thumbnailPath: string;
|
|
||||||
/** Last update date */
|
|
||||||
updatedAt?: string;
|
|
||||||
};
|
|
||||||
export type AssetStackResponseDto = {
|
|
||||||
/** Number of assets in stack */
|
|
||||||
assetCount: number;
|
|
||||||
/** Stack ID */
|
|
||||||
id: string;
|
|
||||||
/** Primary asset ID */
|
|
||||||
primaryAssetId: string;
|
|
||||||
};
|
|
||||||
export type TagResponseDto = {
|
|
||||||
/** Tag color (hex) */
|
|
||||||
color?: string;
|
|
||||||
/** Creation date */
|
|
||||||
createdAt: string;
|
|
||||||
/** Tag ID */
|
|
||||||
id: string;
|
|
||||||
/** Tag name */
|
|
||||||
name: string;
|
|
||||||
/** Parent tag ID */
|
|
||||||
parentId?: string;
|
|
||||||
/** Last update date */
|
|
||||||
updatedAt: string;
|
|
||||||
/** Tag value (full path) */
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
export type AssetResponseDto = {
|
|
||||||
/** Base64 encoded SHA1 hash */
|
|
||||||
checksum: string;
|
|
||||||
/** The UTC timestamp when the asset was originally uploaded to Immich. */
|
|
||||||
createdAt: string;
|
|
||||||
/** Duplicate group ID */
|
|
||||||
duplicateId?: string | null;
|
|
||||||
/** Video duration (for videos) */
|
|
||||||
duration: string;
|
|
||||||
exifInfo?: ExifResponseDto;
|
|
||||||
/** The actual UTC timestamp when the file was created/captured, preserving timezone information. This is the authoritative timestamp for chronological sorting within timeline groups. Combined with timezone data, this can be used to determine the exact moment the photo was taken. */
|
|
||||||
fileCreatedAt: string;
|
|
||||||
/** The UTC timestamp when the file was last modified on the filesystem. This reflects the last time the physical file was changed, which may be different from when the photo was originally taken. */
|
|
||||||
fileModifiedAt: string;
|
|
||||||
/** Whether asset has metadata */
|
|
||||||
hasMetadata: boolean;
|
|
||||||
/** Asset height */
|
|
||||||
height: number | null;
|
|
||||||
/** Asset ID */
|
|
||||||
id: string;
|
|
||||||
/** Is archived */
|
|
||||||
isArchived: boolean;
|
|
||||||
/** Is edited */
|
|
||||||
isEdited: boolean;
|
|
||||||
/** Is favorite */
|
|
||||||
isFavorite: boolean;
|
|
||||||
/** Is offline */
|
|
||||||
isOffline: boolean;
|
|
||||||
/** Is trashed */
|
|
||||||
isTrashed: boolean;
|
|
||||||
/** Library ID */
|
|
||||||
libraryId?: string | null;
|
|
||||||
/** Live photo video ID */
|
|
||||||
livePhotoVideoId?: string | null;
|
|
||||||
/** The local date and time when the photo/video was taken, derived from EXIF metadata. This represents the photographer's local time regardless of timezone, stored as a timezone-agnostic timestamp. Used for timeline grouping by "local" days and months. */
|
|
||||||
localDateTime: string;
|
|
||||||
/** Original file name */
|
|
||||||
originalFileName: string;
|
|
||||||
/** Original MIME type */
|
|
||||||
originalMimeType?: string;
|
|
||||||
/** Original file path */
|
|
||||||
originalPath: string;
|
|
||||||
owner?: UserResponseDto;
|
|
||||||
/** Owner user ID */
|
|
||||||
ownerId: string;
|
|
||||||
people?: PersonWithFacesResponseDto[];
|
|
||||||
/** Is resized */
|
|
||||||
resized?: boolean;
|
|
||||||
stack?: (AssetStackResponseDto) | null;
|
|
||||||
tags?: TagResponseDto[];
|
|
||||||
/** Thumbhash for thumbnail generation (base64) also used as the c query param for thumbnail cache busting. */
|
|
||||||
thumbhash: string | null;
|
|
||||||
"type": AssetTypeEnum;
|
|
||||||
unassignedFaces?: AssetFaceWithoutPersonResponseDto[];
|
|
||||||
/** The UTC timestamp when the asset record was last updated in the database. This is automatically maintained by the database and reflects when any field in the asset was last modified. */
|
|
||||||
updatedAt: string;
|
|
||||||
visibility: AssetVisibility;
|
|
||||||
/** Asset width */
|
|
||||||
width: number | null;
|
|
||||||
};
|
|
||||||
export type ContributorCountResponseDto = {
|
export type ContributorCountResponseDto = {
|
||||||
/** Number of assets contributed */
|
/** Number of assets contributed */
|
||||||
assetCount: number;
|
assetCount: number;
|
||||||
@@ -626,7 +460,6 @@ export type AlbumResponseDto = {
|
|||||||
albumUsers: AlbumUserResponseDto[];
|
albumUsers: AlbumUserResponseDto[];
|
||||||
/** Number of assets */
|
/** Number of assets */
|
||||||
assetCount: number;
|
assetCount: number;
|
||||||
assets: AssetResponseDto[];
|
|
||||||
contributorCounts?: ContributorCountResponseDto[];
|
contributorCounts?: ContributorCountResponseDto[];
|
||||||
/** Creation date */
|
/** Creation date */
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
@@ -910,6 +743,172 @@ export type AssetMetadataBulkResponseDto = {
|
|||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
export type ExifResponseDto = {
|
||||||
|
/** City name */
|
||||||
|
city?: string | null;
|
||||||
|
/** Country name */
|
||||||
|
country?: string | null;
|
||||||
|
/** Original date/time */
|
||||||
|
dateTimeOriginal?: string | null;
|
||||||
|
/** Image description */
|
||||||
|
description?: string | null;
|
||||||
|
/** Image height in pixels */
|
||||||
|
exifImageHeight?: number | null;
|
||||||
|
/** Image width in pixels */
|
||||||
|
exifImageWidth?: number | null;
|
||||||
|
/** Exposure time */
|
||||||
|
exposureTime?: string | null;
|
||||||
|
/** F-number (aperture) */
|
||||||
|
fNumber?: number | null;
|
||||||
|
/** File size in bytes */
|
||||||
|
fileSizeInByte?: number | null;
|
||||||
|
/** Focal length in mm */
|
||||||
|
focalLength?: number | null;
|
||||||
|
/** ISO sensitivity */
|
||||||
|
iso?: number | null;
|
||||||
|
/** GPS latitude */
|
||||||
|
latitude?: number | null;
|
||||||
|
/** Lens model */
|
||||||
|
lensModel?: string | null;
|
||||||
|
/** GPS longitude */
|
||||||
|
longitude?: number | null;
|
||||||
|
/** Camera make */
|
||||||
|
make?: string | null;
|
||||||
|
/** Camera model */
|
||||||
|
model?: string | null;
|
||||||
|
/** Modification date/time */
|
||||||
|
modifyDate?: string | null;
|
||||||
|
/** Image orientation */
|
||||||
|
orientation?: string | null;
|
||||||
|
/** Projection type */
|
||||||
|
projectionType?: string | null;
|
||||||
|
/** Rating */
|
||||||
|
rating?: number | null;
|
||||||
|
/** State/province name */
|
||||||
|
state?: string | null;
|
||||||
|
/** Time zone */
|
||||||
|
timeZone?: string | null;
|
||||||
|
};
|
||||||
|
export type AssetFaceWithoutPersonResponseDto = {
|
||||||
|
/** Bounding box X1 coordinate */
|
||||||
|
boundingBoxX1: number;
|
||||||
|
/** Bounding box X2 coordinate */
|
||||||
|
boundingBoxX2: number;
|
||||||
|
/** Bounding box Y1 coordinate */
|
||||||
|
boundingBoxY1: number;
|
||||||
|
/** Bounding box Y2 coordinate */
|
||||||
|
boundingBoxY2: number;
|
||||||
|
/** Face ID */
|
||||||
|
id: string;
|
||||||
|
/** Image height in pixels */
|
||||||
|
imageHeight: number;
|
||||||
|
/** Image width in pixels */
|
||||||
|
imageWidth: number;
|
||||||
|
sourceType?: SourceType;
|
||||||
|
};
|
||||||
|
export type PersonWithFacesResponseDto = {
|
||||||
|
/** Person date of birth */
|
||||||
|
birthDate: string | null;
|
||||||
|
/** Person color (hex) */
|
||||||
|
color?: string;
|
||||||
|
faces: AssetFaceWithoutPersonResponseDto[];
|
||||||
|
/** Person ID */
|
||||||
|
id: string;
|
||||||
|
/** Is favorite */
|
||||||
|
isFavorite?: boolean;
|
||||||
|
/** Is hidden */
|
||||||
|
isHidden: boolean;
|
||||||
|
/** Person name */
|
||||||
|
name: string;
|
||||||
|
/** Thumbnail path */
|
||||||
|
thumbnailPath: string;
|
||||||
|
/** Last update date */
|
||||||
|
updatedAt?: string;
|
||||||
|
};
|
||||||
|
export type AssetStackResponseDto = {
|
||||||
|
/** Number of assets in stack */
|
||||||
|
assetCount: number;
|
||||||
|
/** Stack ID */
|
||||||
|
id: string;
|
||||||
|
/** Primary asset ID */
|
||||||
|
primaryAssetId: string;
|
||||||
|
};
|
||||||
|
export type TagResponseDto = {
|
||||||
|
/** Tag color (hex) */
|
||||||
|
color?: string;
|
||||||
|
/** Creation date */
|
||||||
|
createdAt: string;
|
||||||
|
/** Tag ID */
|
||||||
|
id: string;
|
||||||
|
/** Tag name */
|
||||||
|
name: string;
|
||||||
|
/** Parent tag ID */
|
||||||
|
parentId?: string;
|
||||||
|
/** Last update date */
|
||||||
|
updatedAt: string;
|
||||||
|
/** Tag value (full path) */
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
export type AssetResponseDto = {
|
||||||
|
/** Base64 encoded SHA1 hash */
|
||||||
|
checksum: string;
|
||||||
|
/** The UTC timestamp when the asset was originally uploaded to Immich. */
|
||||||
|
createdAt: string;
|
||||||
|
/** Duplicate group ID */
|
||||||
|
duplicateId?: string | null;
|
||||||
|
/** Video duration (for videos) */
|
||||||
|
duration: string;
|
||||||
|
exifInfo?: ExifResponseDto;
|
||||||
|
/** The actual UTC timestamp when the file was created/captured, preserving timezone information. This is the authoritative timestamp for chronological sorting within timeline groups. Combined with timezone data, this can be used to determine the exact moment the photo was taken. */
|
||||||
|
fileCreatedAt: string;
|
||||||
|
/** The UTC timestamp when the file was last modified on the filesystem. This reflects the last time the physical file was changed, which may be different from when the photo was originally taken. */
|
||||||
|
fileModifiedAt: string;
|
||||||
|
/** Whether asset has metadata */
|
||||||
|
hasMetadata: boolean;
|
||||||
|
/** Asset height */
|
||||||
|
height: number | null;
|
||||||
|
/** Asset ID */
|
||||||
|
id: string;
|
||||||
|
/** Is archived */
|
||||||
|
isArchived: boolean;
|
||||||
|
/** Is edited */
|
||||||
|
isEdited: boolean;
|
||||||
|
/** Is favorite */
|
||||||
|
isFavorite: boolean;
|
||||||
|
/** Is offline */
|
||||||
|
isOffline: boolean;
|
||||||
|
/** Is trashed */
|
||||||
|
isTrashed: boolean;
|
||||||
|
/** Library ID */
|
||||||
|
libraryId?: string | null;
|
||||||
|
/** Live photo video ID */
|
||||||
|
livePhotoVideoId?: string | null;
|
||||||
|
/** The local date and time when the photo/video was taken, derived from EXIF metadata. This represents the photographer's local time regardless of timezone, stored as a timezone-agnostic timestamp. Used for timeline grouping by "local" days and months. */
|
||||||
|
localDateTime: string;
|
||||||
|
/** Original file name */
|
||||||
|
originalFileName: string;
|
||||||
|
/** Original MIME type */
|
||||||
|
originalMimeType?: string;
|
||||||
|
/** Original file path */
|
||||||
|
originalPath: string;
|
||||||
|
owner?: UserResponseDto;
|
||||||
|
/** Owner user ID */
|
||||||
|
ownerId: string;
|
||||||
|
people?: PersonWithFacesResponseDto[];
|
||||||
|
/** Is resized */
|
||||||
|
resized?: boolean;
|
||||||
|
stack?: (AssetStackResponseDto) | null;
|
||||||
|
tags?: TagResponseDto[];
|
||||||
|
/** Thumbhash for thumbnail generation (base64) also used as the c query param for thumbnail cache busting. */
|
||||||
|
thumbhash: string | null;
|
||||||
|
"type": AssetTypeEnum;
|
||||||
|
unassignedFaces?: AssetFaceWithoutPersonResponseDto[];
|
||||||
|
/** The UTC timestamp when the asset record was last updated in the database. This is automatically maintained by the database and reflects when any field in the asset was last modified. */
|
||||||
|
updatedAt: string;
|
||||||
|
visibility: AssetVisibility;
|
||||||
|
/** Asset width */
|
||||||
|
width: number | null;
|
||||||
|
};
|
||||||
export type UpdateAssetDto = {
|
export type UpdateAssetDto = {
|
||||||
/** Original date and time */
|
/** Original date and time */
|
||||||
dateTimeOriginal?: string;
|
dateTimeOriginal?: string;
|
||||||
@@ -3668,19 +3667,17 @@ export function deleteAlbum({ id }: {
|
|||||||
/**
|
/**
|
||||||
* Retrieve an album
|
* Retrieve an album
|
||||||
*/
|
*/
|
||||||
export function getAlbumInfo({ id, key, slug, withoutAssets }: {
|
export function getAlbumInfo({ id, key, slug }: {
|
||||||
id: string;
|
id: string;
|
||||||
key?: string;
|
key?: string;
|
||||||
slug?: string;
|
slug?: string;
|
||||||
withoutAssets?: boolean;
|
|
||||||
}, opts?: Oazapfts.RequestOpts) {
|
}, opts?: Oazapfts.RequestOpts) {
|
||||||
return oazapfts.ok(oazapfts.fetchJson<{
|
return oazapfts.ok(oazapfts.fetchJson<{
|
||||||
status: 200;
|
status: 200;
|
||||||
data: AlbumResponseDto;
|
data: AlbumResponseDto;
|
||||||
}>(`/albums/${encodeURIComponent(id)}${QS.query(QS.explode({
|
}>(`/albums/${encodeURIComponent(id)}${QS.query(QS.explode({
|
||||||
key,
|
key,
|
||||||
slug,
|
slug
|
||||||
withoutAssets
|
|
||||||
}))}`, {
|
}))}`, {
|
||||||
...opts
|
...opts
|
||||||
}));
|
}));
|
||||||
@@ -6729,17 +6726,6 @@ export enum AlbumUserRole {
|
|||||||
Editor = "editor",
|
Editor = "editor",
|
||||||
Viewer = "viewer"
|
Viewer = "viewer"
|
||||||
}
|
}
|
||||||
export enum SourceType {
|
|
||||||
MachineLearning = "machine-learning",
|
|
||||||
Exif = "exif",
|
|
||||||
Manual = "manual"
|
|
||||||
}
|
|
||||||
export enum AssetTypeEnum {
|
|
||||||
Image = "IMAGE",
|
|
||||||
Video = "VIDEO",
|
|
||||||
Audio = "AUDIO",
|
|
||||||
Other = "OTHER"
|
|
||||||
}
|
|
||||||
export enum BulkIdErrorReason {
|
export enum BulkIdErrorReason {
|
||||||
Duplicate = "duplicate",
|
Duplicate = "duplicate",
|
||||||
NoPermission = "no_permission",
|
NoPermission = "no_permission",
|
||||||
@@ -6923,6 +6909,17 @@ export enum AssetJobName {
|
|||||||
RegenerateThumbnail = "regenerate-thumbnail",
|
RegenerateThumbnail = "regenerate-thumbnail",
|
||||||
TranscodeVideo = "transcode-video"
|
TranscodeVideo = "transcode-video"
|
||||||
}
|
}
|
||||||
|
export enum SourceType {
|
||||||
|
MachineLearning = "machine-learning",
|
||||||
|
Exif = "exif",
|
||||||
|
Manual = "manual"
|
||||||
|
}
|
||||||
|
export enum AssetTypeEnum {
|
||||||
|
Image = "IMAGE",
|
||||||
|
Video = "VIDEO",
|
||||||
|
Audio = "AUDIO",
|
||||||
|
Other = "OTHER"
|
||||||
|
}
|
||||||
export enum AssetEditAction {
|
export enum AssetEditAction {
|
||||||
Crop = "crop",
|
Crop = "crop",
|
||||||
Rotate = "rotate",
|
Rotate = "rotate",
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { ApiTags } from '@nestjs/swagger';
|
|||||||
import { Endpoint, HistoryBuilder } from 'src/decorators';
|
import { Endpoint, HistoryBuilder } from 'src/decorators';
|
||||||
import {
|
import {
|
||||||
AddUsersDto,
|
AddUsersDto,
|
||||||
AlbumInfoDto,
|
|
||||||
AlbumResponseDto,
|
AlbumResponseDto,
|
||||||
AlbumsAddAssetsDto,
|
AlbumsAddAssetsDto,
|
||||||
AlbumsAddAssetsResponseDto,
|
AlbumsAddAssetsResponseDto,
|
||||||
@@ -66,12 +65,8 @@ export class AlbumController {
|
|||||||
description: 'Retrieve information about a specific album by its ID.',
|
description: 'Retrieve information about a specific album by its ID.',
|
||||||
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
|
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
|
||||||
})
|
})
|
||||||
getAlbumInfo(
|
getAlbumInfo(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<AlbumResponseDto> {
|
||||||
@Auth() auth: AuthDto,
|
return this.service.get(auth, id);
|
||||||
@Param() { id }: UUIDParamDto,
|
|
||||||
@Query() dto: AlbumInfoDto,
|
|
||||||
): Promise<AlbumResponseDto> {
|
|
||||||
return this.service.get(auth, id, dto);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch(':id')
|
@Patch(':id')
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ describe('mapAlbum', () => {
|
|||||||
.asset({ localDateTime: endDate }, (builder) => builder.exif())
|
.asset({ localDateTime: endDate }, (builder) => builder.exif())
|
||||||
.asset({ localDateTime: startDate }, (builder) => builder.exif())
|
.asset({ localDateTime: startDate }, (builder) => builder.exif())
|
||||||
.build();
|
.build();
|
||||||
const dto = mapAlbum(getForAlbum(album), false);
|
const dto = mapAlbum(getForAlbum(album));
|
||||||
expect(dto.startDate).toEqual(startDate.toISOString());
|
expect(dto.startDate).toEqual(startDate.toISOString());
|
||||||
expect(dto.endDate).toEqual(endDate.toISOString());
|
expect(dto.endDate).toEqual(endDate.toISOString());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not set start and end dates for empty assets', () => {
|
it('should not set start and end dates for empty assets', () => {
|
||||||
const dto = mapAlbum(getForAlbum(AlbumFactory.create()), false);
|
const dto = mapAlbum(getForAlbum(AlbumFactory.create()));
|
||||||
expect(dto.startDate).toBeUndefined();
|
expect(dto.startDate).toBeUndefined();
|
||||||
expect(dto.endDate).toBeUndefined();
|
expect(dto.endDate).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ import _ from 'lodash';
|
|||||||
import { createZodDto } from 'nestjs-zod';
|
import { createZodDto } from 'nestjs-zod';
|
||||||
import { AlbumUser, AuthSharedLink, User } from 'src/database';
|
import { AlbumUser, AuthSharedLink, User } from 'src/database';
|
||||||
import { BulkIdErrorReasonSchema } from 'src/dtos/asset-ids.response.dto';
|
import { BulkIdErrorReasonSchema } from 'src/dtos/asset-ids.response.dto';
|
||||||
import { AssetResponseSchema, MapAsset, mapAsset } from 'src/dtos/asset-response.dto';
|
import { MapAsset } from 'src/dtos/asset-response.dto';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
|
||||||
import { UserResponseSchema, mapUser } from 'src/dtos/user.dto';
|
import { UserResponseSchema, mapUser } from 'src/dtos/user.dto';
|
||||||
import { AlbumUserRole, AlbumUserRoleSchema, AssetOrder, AssetOrderSchema } from 'src/enum';
|
import { AlbumUserRole, AlbumUserRoleSchema, AssetOrder, AssetOrderSchema } from 'src/enum';
|
||||||
import { MaybeDehydrated } from 'src/types';
|
import { MaybeDehydrated } from 'src/types';
|
||||||
@@ -12,12 +11,6 @@ import { asDateString } from 'src/utils/date';
|
|||||||
import { stringToBool } from 'src/validation';
|
import { stringToBool } from 'src/validation';
|
||||||
import z from 'zod';
|
import z from 'zod';
|
||||||
|
|
||||||
const AlbumInfoSchema = z
|
|
||||||
.object({
|
|
||||||
withoutAssets: stringToBool.optional().describe('Exclude assets from response'),
|
|
||||||
})
|
|
||||||
.meta({ id: 'AlbumInfoDto' });
|
|
||||||
|
|
||||||
const AlbumUserAddSchema = z
|
const AlbumUserAddSchema = z
|
||||||
.object({
|
.object({
|
||||||
userId: z.uuidv4().describe('User ID'),
|
userId: z.uuidv4().describe('User ID'),
|
||||||
@@ -122,7 +115,6 @@ export const AlbumResponseSchema = z
|
|||||||
shared: z.boolean().describe('Is shared album'),
|
shared: z.boolean().describe('Is shared album'),
|
||||||
albumUsers: z.array(AlbumUserResponseSchema),
|
albumUsers: z.array(AlbumUserResponseSchema),
|
||||||
hasSharedLink: z.boolean().describe('Has shared link'),
|
hasSharedLink: z.boolean().describe('Has shared link'),
|
||||||
assets: z.array(AssetResponseSchema),
|
|
||||||
owner: UserResponseSchema,
|
owner: UserResponseSchema,
|
||||||
assetCount: z.int().min(0).describe('Number of assets'),
|
assetCount: z.int().min(0).describe('Number of assets'),
|
||||||
// TODO: use `isoDatetimeToDate` when using `ZodSerializerDto` on the controllers.
|
// TODO: use `isoDatetimeToDate` when using `ZodSerializerDto` on the controllers.
|
||||||
@@ -141,7 +133,6 @@ export const AlbumResponseSchema = z
|
|||||||
})
|
})
|
||||||
.meta({ id: 'AlbumResponseDto' });
|
.meta({ id: 'AlbumResponseDto' });
|
||||||
|
|
||||||
export class AlbumInfoDto extends createZodDto(AlbumInfoSchema) {}
|
|
||||||
export class AddUsersDto extends createZodDto(AddUsersSchema) {}
|
export class AddUsersDto extends createZodDto(AddUsersSchema) {}
|
||||||
export class AlbumUserCreateDto extends createZodDto(AlbumUserCreateSchema) {}
|
export class AlbumUserCreateDto extends createZodDto(AlbumUserCreateSchema) {}
|
||||||
export class CreateAlbumDto extends createZodDto(CreateAlbumSchema) {}
|
export class CreateAlbumDto extends createZodDto(CreateAlbumSchema) {}
|
||||||
@@ -170,11 +161,7 @@ export type MapAlbumDto = {
|
|||||||
order: AssetOrder;
|
order: AssetOrder;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mapAlbum = (
|
export const mapAlbum = (entity: MaybeDehydrated<MapAlbumDto>): AlbumResponseDto => {
|
||||||
entity: MaybeDehydrated<MapAlbumDto>,
|
|
||||||
withAssets: boolean,
|
|
||||||
auth?: AuthDto,
|
|
||||||
): AlbumResponseDto => {
|
|
||||||
const albumUsers: AlbumUserResponseDto[] = [];
|
const albumUsers: AlbumUserResponseDto[] = [];
|
||||||
|
|
||||||
if (entity.albumUsers) {
|
if (entity.albumUsers) {
|
||||||
@@ -215,12 +202,8 @@ export const mapAlbum = (
|
|||||||
hasSharedLink,
|
hasSharedLink,
|
||||||
startDate: asDateString(startDate),
|
startDate: asDateString(startDate),
|
||||||
endDate: asDateString(endDate),
|
endDate: asDateString(endDate),
|
||||||
assets: (withAssets ? assets : []).map((asset) => mapAsset(asset, { auth })),
|
|
||||||
assetCount: entity.assets?.length || 0,
|
assetCount: entity.assets?.length || 0,
|
||||||
isActivityEnabled: entity.isActivityEnabled,
|
isActivityEnabled: entity.isActivityEnabled,
|
||||||
order: entity.order,
|
order: entity.order,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mapAlbumWithAssets = (entity: MaybeDehydrated<MapAlbumDto>) => mapAlbum(entity, true);
|
|
||||||
export const mapAlbumWithoutAssets = (entity: MaybeDehydrated<MapAlbumDto>) => mapAlbum(entity, false);
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createZodDto } from 'nestjs-zod';
|
import { createZodDto } from 'nestjs-zod';
|
||||||
import { SharedLink } from 'src/database';
|
import { SharedLink } from 'src/database';
|
||||||
import { HistoryBuilder } from 'src/decorators';
|
import { HistoryBuilder } from 'src/decorators';
|
||||||
import { AlbumResponseSchema, mapAlbumWithoutAssets } from 'src/dtos/album.dto';
|
import { AlbumResponseSchema, mapAlbum } from 'src/dtos/album.dto';
|
||||||
import { AssetResponseSchema, mapAsset } from 'src/dtos/asset-response.dto';
|
import { AssetResponseSchema, mapAsset } from 'src/dtos/asset-response.dto';
|
||||||
import { SharedLinkTypeSchema } from 'src/enum';
|
import { SharedLinkTypeSchema } from 'src/enum';
|
||||||
import { emptyStringToNull, isoDatetimeToDate } from 'src/validation';
|
import { emptyStringToNull, isoDatetimeToDate } from 'src/validation';
|
||||||
@@ -96,7 +96,7 @@ export function mapSharedLink(sharedLink: SharedLink, options: { stripAssetMetad
|
|||||||
createdAt: sharedLink.createdAt,
|
createdAt: sharedLink.createdAt,
|
||||||
expiresAt: sharedLink.expiresAt,
|
expiresAt: sharedLink.expiresAt,
|
||||||
assets: assets.map((asset) => mapAsset(asset, { stripMetadata: options.stripAssetMetadata })),
|
assets: assets.map((asset) => mapAsset(asset, { stripMetadata: options.stripAssetMetadata })),
|
||||||
album: sharedLink.album ? mapAlbumWithoutAssets(sharedLink.album) : undefined,
|
album: sharedLink.album ? mapAlbum(sharedLink.album) : undefined,
|
||||||
allowUpload: sharedLink.allowUpload,
|
allowUpload: sharedLink.allowUpload,
|
||||||
allowDownload: sharedLink.allowDownload,
|
allowDownload: sharedLink.allowDownload,
|
||||||
showMetadata: sharedLink.showExif,
|
showMetadata: sharedLink.showExif,
|
||||||
|
|||||||
@@ -563,9 +563,9 @@ describe(AlbumService.name, () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await sut.get(AuthFactory.create(album.owner), album.id, {});
|
await sut.get(AuthFactory.create(album.owner), album.id);
|
||||||
|
|
||||||
expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: true });
|
expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: false });
|
||||||
expect(mocks.access.album.checkOwnerAccess).toHaveBeenCalledWith(album.owner.id, new Set([album.id]));
|
expect(mocks.access.album.checkOwnerAccess).toHaveBeenCalledWith(album.owner.id, new Set([album.id]));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -584,9 +584,9 @@ describe(AlbumService.name, () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const auth = AuthFactory.from().sharedLink().build();
|
const auth = AuthFactory.from().sharedLink().build();
|
||||||
await sut.get(auth, album.id, {});
|
await sut.get(auth, album.id);
|
||||||
|
|
||||||
expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: true });
|
expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: false });
|
||||||
expect(mocks.access.album.checkSharedLinkAccess).toHaveBeenCalledWith(auth.sharedLink!.id, new Set([album.id]));
|
expect(mocks.access.album.checkSharedLinkAccess).toHaveBeenCalledWith(auth.sharedLink!.id, new Set([album.id]));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -605,9 +605,9 @@ describe(AlbumService.name, () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await sut.get(AuthFactory.create(user), album.id, {});
|
await sut.get(AuthFactory.create(user), album.id);
|
||||||
|
|
||||||
expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: true });
|
expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: false });
|
||||||
expect(mocks.access.album.checkSharedAlbumAccess).toHaveBeenCalledWith(
|
expect(mocks.access.album.checkSharedAlbumAccess).toHaveBeenCalledWith(
|
||||||
user.id,
|
user.id,
|
||||||
new Set([album.id]),
|
new Set([album.id]),
|
||||||
@@ -617,7 +617,7 @@ describe(AlbumService.name, () => {
|
|||||||
|
|
||||||
it('should throw an error for no access', async () => {
|
it('should throw an error for no access', async () => {
|
||||||
const auth = AuthFactory.create();
|
const auth = AuthFactory.create();
|
||||||
await expect(sut.get(auth, 'album-123', {})).rejects.toBeInstanceOf(BadRequestException);
|
await expect(sut.get(auth, 'album-123')).rejects.toBeInstanceOf(BadRequestException);
|
||||||
|
|
||||||
expect(mocks.access.album.checkOwnerAccess).toHaveBeenCalledWith(auth.user.id, new Set(['album-123']));
|
expect(mocks.access.album.checkOwnerAccess).toHaveBeenCalledWith(auth.user.id, new Set(['album-123']));
|
||||||
expect(mocks.access.album.checkSharedAlbumAccess).toHaveBeenCalledWith(
|
expect(mocks.access.album.checkSharedAlbumAccess).toHaveBeenCalledWith(
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
AddUsersDto,
|
AddUsersDto,
|
||||||
AlbumInfoDto,
|
|
||||||
AlbumResponseDto,
|
AlbumResponseDto,
|
||||||
AlbumsAddAssetsDto,
|
AlbumsAddAssetsDto,
|
||||||
AlbumsAddAssetsResponseDto,
|
AlbumsAddAssetsResponseDto,
|
||||||
@@ -10,8 +9,6 @@ import {
|
|||||||
GetAlbumsDto,
|
GetAlbumsDto,
|
||||||
mapAlbum,
|
mapAlbum,
|
||||||
MapAlbumDto,
|
MapAlbumDto,
|
||||||
mapAlbumWithAssets,
|
|
||||||
mapAlbumWithoutAssets,
|
|
||||||
UpdateAlbumDto,
|
UpdateAlbumDto,
|
||||||
UpdateAlbumUserDto,
|
UpdateAlbumUserDto,
|
||||||
} from 'src/dtos/album.dto';
|
} from 'src/dtos/album.dto';
|
||||||
@@ -64,7 +61,7 @@ export class AlbumService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return albums.map((album) => ({
|
return albums.map((album) => ({
|
||||||
...mapAlbumWithoutAssets(album),
|
...mapAlbum(album),
|
||||||
sharedLinks: undefined,
|
sharedLinks: undefined,
|
||||||
startDate: asDateString(albumMetadata[album.id]?.startDate ?? undefined),
|
startDate: asDateString(albumMetadata[album.id]?.startDate ?? undefined),
|
||||||
endDate: asDateString(albumMetadata[album.id]?.endDate ?? undefined),
|
endDate: asDateString(albumMetadata[album.id]?.endDate ?? undefined),
|
||||||
@@ -74,11 +71,10 @@ export class AlbumService extends BaseService {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(auth: AuthDto, id: string, dto: AlbumInfoDto): Promise<AlbumResponseDto> {
|
async get(auth: AuthDto, id: string): Promise<AlbumResponseDto> {
|
||||||
await this.requireAccess({ auth, permission: Permission.AlbumRead, ids: [id] });
|
await this.requireAccess({ auth, permission: Permission.AlbumRead, ids: [id] });
|
||||||
await this.albumRepository.updateThumbnails();
|
await this.albumRepository.updateThumbnails();
|
||||||
const withAssets = dto.withoutAssets === undefined ? true : !dto.withoutAssets;
|
const album = await this.findOrFail(id, { withAssets: false });
|
||||||
const album = await this.findOrFail(id, { withAssets });
|
|
||||||
const [albumMetadataForIds] = await this.albumRepository.getMetadataForIds([album.id]);
|
const [albumMetadataForIds] = await this.albumRepository.getMetadataForIds([album.id]);
|
||||||
|
|
||||||
const hasSharedUsers = album.albumUsers && album.albumUsers.length > 0;
|
const hasSharedUsers = album.albumUsers && album.albumUsers.length > 0;
|
||||||
@@ -86,7 +82,7 @@ export class AlbumService extends BaseService {
|
|||||||
const isShared = hasSharedUsers || hasSharedLink;
|
const isShared = hasSharedUsers || hasSharedLink;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...mapAlbum(album, withAssets, auth),
|
...mapAlbum(album),
|
||||||
startDate: asDateString(albumMetadataForIds?.startDate ?? undefined),
|
startDate: asDateString(albumMetadataForIds?.startDate ?? undefined),
|
||||||
endDate: asDateString(albumMetadataForIds?.endDate ?? undefined),
|
endDate: asDateString(albumMetadataForIds?.endDate ?? undefined),
|
||||||
assetCount: albumMetadataForIds?.assetCount ?? 0,
|
assetCount: albumMetadataForIds?.assetCount ?? 0,
|
||||||
@@ -144,7 +140,7 @@ export class AlbumService extends BaseService {
|
|||||||
await this.eventRepository.emit('AlbumInvite', { id: album.id, userId });
|
await this.eventRepository.emit('AlbumInvite', { id: album.id, userId });
|
||||||
}
|
}
|
||||||
|
|
||||||
return mapAlbumWithAssets(album);
|
return mapAlbum(album);
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(auth: AuthDto, id: string, dto: UpdateAlbumDto): Promise<AlbumResponseDto> {
|
async update(auth: AuthDto, id: string, dto: UpdateAlbumDto): Promise<AlbumResponseDto> {
|
||||||
@@ -167,7 +163,7 @@ export class AlbumService extends BaseService {
|
|||||||
order: dto.order,
|
order: dto.order,
|
||||||
});
|
});
|
||||||
|
|
||||||
return mapAlbumWithoutAssets({ ...updatedAlbum, assets: album.assets });
|
return mapAlbum({ ...updatedAlbum, assets: album.assets });
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(auth: AuthDto, id: string): Promise<void> {
|
async delete(auth: AuthDto, id: string): Promise<void> {
|
||||||
@@ -305,7 +301,7 @@ export class AlbumService extends BaseService {
|
|||||||
await this.eventRepository.emit('AlbumInvite', { id, userId });
|
await this.eventRepository.emit('AlbumInvite', { id, userId });
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.findOrFail(id, { withAssets: true }).then(mapAlbumWithoutAssets);
|
return this.findOrFail(id, { withAssets: true }).then(mapAlbum);
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeUser(auth: AuthDto, id: string, userId: string | 'me'): Promise<void> {
|
async removeUser(auth: AuthDto, id: string, userId: string | 'me'): Promise<void> {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const refreshAlbum = async () => {
|
const refreshAlbum = async () => {
|
||||||
album = await getAlbumInfo({ id: album.id, withoutAssets: true });
|
album = await getAlbumInfo({ id: album.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
const onAlbumUserDelete = async ({ userId }: { userId: string }) => {
|
const onAlbumUserDelete = async ({ userId }: { userId: string }) => {
|
||||||
|
|||||||
+1
-1
@@ -133,7 +133,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const refreshAlbum = async () => {
|
const refreshAlbum = async () => {
|
||||||
album = await getAlbumInfo({ id: album.id, withoutAssets: true });
|
album = await getAlbumInfo({ id: album.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
const setModeToView = async () => {
|
const setModeToView = async () => {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { PageLoad } from './$types';
|
|||||||
|
|
||||||
export const load = (async ({ params, url }) => {
|
export const load = (async ({ params, url }) => {
|
||||||
await authenticate(url);
|
await authenticate(url);
|
||||||
const album = await getAlbumInfo({ id: params.albumId, withoutAssets: true });
|
const album = await getAlbumInfo({ id: params.albumId });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
album,
|
album,
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ export const albumFactory = Sync.makeFactory<AlbumResponseDto>({
|
|||||||
description: '',
|
description: '',
|
||||||
albumThumbnailAssetId: null,
|
albumThumbnailAssetId: null,
|
||||||
assetCount: Sync.each((index) => index % 5),
|
assetCount: Sync.each((index) => index % 5),
|
||||||
assets: [],
|
|
||||||
createdAt: Sync.each(() => faker.date.past().toISOString()),
|
createdAt: Sync.each(() => faker.date.past().toISOString()),
|
||||||
updatedAt: Sync.each(() => faker.date.past().toISOString()),
|
updatedAt: Sync.each(() => faker.date.past().toISOString()),
|
||||||
id: Sync.each(() => faker.string.uuid()),
|
id: Sync.each(() => faker.string.uuid()),
|
||||||
|
|||||||
Reference in New Issue
Block a user