fix(server,web): correctly remove metadata from shared links (#4464)

* wip: strip metadata

* fix: authenticate time buckets

* hide detail panel

* fix tests

* fix lint

* add e2e tests

* chore: open api

* fix web compilation error

* feat: test with asset with gps position

* fix: only import fs.promises.cp

* fix: cleanup mapasset

* fix: format

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
Jonathan Jogenfors
2023-10-14 03:46:30 +02:00
committed by GitHub
parent 4a9f58bf9b
commit dadcf49eca
39 changed files with 332 additions and 150 deletions

View File

@@ -5770,6 +5770,9 @@
"format": "date-time",
"type": "string"
},
"hasMetadata": {
"type": "boolean"
},
"id": {
"type": "string"
},
@@ -5833,7 +5836,6 @@
"type": "array"
},
"thumbhash": {
"description": "base64 encoded thumbhash",
"nullable": true,
"type": "string"
},
@@ -5847,7 +5849,6 @@
},
"required": [
"type",
"id",
"deviceAssetId",
"deviceId",
"ownerId",
@@ -5855,19 +5856,21 @@
"originalPath",
"originalFileName",
"resized",
"thumbhash",
"fileCreatedAt",
"fileModifiedAt",
"updatedAt",
"isFavorite",
"isArchived",
"isTrashed",
"localDateTime",
"isOffline",
"isExternal",
"isReadOnly",
"checksum",
"id",
"thumbhash",
"localDateTime",
"duration",
"checksum"
"hasMetadata"
],
"type": "object"
},
@@ -7599,7 +7602,7 @@
"nullable": true,
"type": "string"
},
"showExif": {
"showMetadata": {
"default": true,
"type": "boolean"
},
@@ -7628,7 +7631,7 @@
"nullable": true,
"type": "string"
},
"showExif": {
"showMetadata": {
"type": "boolean"
}
},
@@ -7670,7 +7673,7 @@
"key": {
"type": "string"
},
"showExif": {
"showMetadata": {
"type": "boolean"
},
"type": {
@@ -7691,7 +7694,7 @@
"assets",
"allowUpload",
"allowDownload",
"showExif"
"showMetadata"
],
"type": "object"
},

View File

@@ -47,6 +47,7 @@ import {
BulkIdsDto,
MapMarkerResponseDto,
MemoryLaneResponseDto,
SanitizedAssetResponseDto,
TimeBucketResponseDto,
mapAsset,
} from './response-dto';
@@ -198,10 +199,17 @@ export class AssetService {
return this.assetRepository.getTimeBuckets(dto);
}
async getByTimeBucket(authUser: AuthUserDto, dto: TimeBucketAssetDto): Promise<AssetResponseDto[]> {
async getByTimeBucket(
authUser: AuthUserDto,
dto: TimeBucketAssetDto,
): Promise<AssetResponseDto[] | SanitizedAssetResponseDto[]> {
await this.timeBucketChecks(authUser, dto);
const assets = await this.assetRepository.getByTimeBucket(dto.timeBucket, dto);
return assets.map(mapAsset);
if (authUser.isShowMetadata) {
return assets.map((asset) => mapAsset(asset));
} else {
return assets.map((asset) => mapAsset(asset, true));
}
}
async downloadFile(authUser: AuthUserDto, id: string): Promise<ImmichReadStream> {

View File

@@ -6,43 +6,62 @@ import { UserResponseDto, mapUser } from '../../user/response-dto/user-response.
import { ExifResponseDto, mapExif } from './exif-response.dto';
import { SmartInfoResponseDto, mapSmartInfo } from './smart-info-response.dto';
export class AssetResponseDto {
export class SanitizedAssetResponseDto {
id!: string;
@ApiProperty({ enumName: 'AssetTypeEnum', enum: AssetType })
type!: AssetType;
thumbhash!: string | null;
resized!: boolean;
localDateTime!: Date;
duration!: string;
livePhotoVideoId?: string | null;
hasMetadata!: boolean;
}
export class AssetResponseDto extends SanitizedAssetResponseDto {
deviceAssetId!: string;
deviceId!: string;
ownerId!: string;
owner?: UserResponseDto;
libraryId!: string;
@ApiProperty({ enumName: 'AssetTypeEnum', enum: AssetType })
type!: AssetType;
originalPath!: string;
originalFileName!: string;
resized!: boolean;
/**base64 encoded thumbhash */
thumbhash!: string | null;
fileCreatedAt!: Date;
fileModifiedAt!: Date;
updatedAt!: Date;
isFavorite!: boolean;
isArchived!: boolean;
isTrashed!: boolean;
localDateTime!: Date;
isOffline!: boolean;
isExternal!: boolean;
isReadOnly!: boolean;
duration!: string;
exifInfo?: ExifResponseDto;
smartInfo?: SmartInfoResponseDto;
livePhotoVideoId?: string | null;
tags?: TagResponseDto[];
people?: PersonResponseDto[];
/**base64 encoded sha1 hash */
checksum!: string;
}
function _map(entity: AssetEntity, withExif: boolean): AssetResponseDto {
export function mapAsset(entity: AssetEntity, stripMetadata = false): AssetResponseDto {
const sanitizedAssetResponse: SanitizedAssetResponseDto = {
id: entity.id,
type: entity.type,
thumbhash: entity.thumbhash?.toString('base64') ?? null,
localDateTime: entity.localDateTime,
resized: !!entity.resizePath,
duration: entity.duration ?? '0:00:00.00000',
livePhotoVideoId: entity.livePhotoVideoId,
hasMetadata: false,
};
if (stripMetadata) {
return sanitizedAssetResponse as AssetResponseDto;
}
return {
...sanitizedAssetResponse,
id: entity.id,
deviceAssetId: entity.deviceAssetId,
ownerId: entity.ownerId,
@@ -62,7 +81,7 @@ function _map(entity: AssetEntity, withExif: boolean): AssetResponseDto {
isArchived: entity.isArchived,
isTrashed: !!entity.deletedAt,
duration: entity.duration ?? '0:00:00.00000',
exifInfo: withExif ? (entity.exifInfo ? mapExif(entity.exifInfo) : undefined) : undefined,
exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined,
smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined,
livePhotoVideoId: entity.livePhotoVideoId,
tags: entity.tags?.map(mapTag),
@@ -71,17 +90,10 @@ function _map(entity: AssetEntity, withExif: boolean): AssetResponseDto {
isExternal: entity.isExternal,
isOffline: entity.isOffline,
isReadOnly: entity.isReadOnly,
hasMetadata: true,
};
}
export function mapAsset(entity: AssetEntity): AssetResponseDto {
return _map(entity, true);
}
export function mapAssetWithoutExif(entity: AssetEntity): AssetResponseDto {
return _map(entity, false);
}
export class MemoryLaneResponseDto {
title!: string;
assets!: AssetResponseDto[];

View File

@@ -52,3 +52,15 @@ export function mapExif(entity: ExifEntity): ExifResponseDto {
projectionType: entity.projectionType,
};
}
export function mapSanitizedExif(entity: ExifEntity): ExifResponseDto {
return {
fileSizeInByte: entity.fileSizeInByte ? parseInt(entity.fileSizeInByte.toString()) : null,
orientation: entity.orientation,
dateTimeOriginal: entity.dateTimeOriginal,
timeZone: entity.timeZone,
projectionType: entity.projectionType,
exifImageWidth: entity.exifImageWidth,
exifImageHeight: entity.exifImageHeight,
};
}

View File

@@ -380,7 +380,7 @@ export class AuthService {
sharedLinkId: link.id,
isAllowUpload: link.allowUpload,
isAllowDownload: link.allowDownload,
isShowExif: link.showExif,
isShowMetadata: link.showExif,
};
}
}
@@ -431,7 +431,7 @@ export class AuthService {
isPublicUser: false,
isAllowUpload: true,
isAllowDownload: true,
isShowExif: true,
isShowMetadata: true,
accessTokenId: token.id,
};
}

View File

@@ -6,7 +6,7 @@ export class AuthUserDto {
sharedLinkId?: string;
isAllowUpload?: boolean;
isAllowDownload?: boolean;
isShowExif?: boolean;
isShowMetadata?: boolean;
accessTokenId?: string;
externalPath?: string | null;
}

View File

@@ -97,7 +97,7 @@ export class PersonService {
async getAssets(authUser: AuthUserDto, id: string): Promise<AssetResponseDto[]> {
await this.access.requirePermission(authUser, Permission.PERSON_READ, id);
const assets = await this.repository.getAssets(id);
return assets.map(mapAsset);
return assets.map((asset) => mapAsset(asset));
}
async update(authUser: AuthUserDto, id: string, dto: PersonUpdateDto): Promise<PersonResponseDto> {

View File

@@ -154,7 +154,7 @@ export class SearchService {
items: assets.items
.map((item) => lookup[item.id])
.filter((item) => !!item)
.map(mapAsset),
.map((asset) => mapAsset(asset)),
},
};
}

View File

@@ -2,7 +2,7 @@ import { SharedLinkEntity, SharedLinkType } from '@app/infra/entities';
import { ApiProperty } from '@nestjs/swagger';
import _ from 'lodash';
import { AlbumResponseDto, mapAlbumWithoutAssets } from '../album';
import { AssetResponseDto, mapAsset, mapAssetWithoutExif } from '../asset';
import { AssetResponseDto, mapAsset } from '../asset';
export class SharedLinkResponseDto {
id!: string;
@@ -17,8 +17,9 @@ export class SharedLinkResponseDto {
assets!: AssetResponseDto[];
album?: AlbumResponseDto;
allowUpload!: boolean;
allowDownload!: boolean;
showExif!: boolean;
showMetadata!: boolean;
}
export function mapSharedLink(sharedLink: SharedLinkEntity): SharedLinkResponseDto {
@@ -35,15 +36,15 @@ export function mapSharedLink(sharedLink: SharedLinkEntity): SharedLinkResponseD
type: sharedLink.type,
createdAt: sharedLink.createdAt,
expiresAt: sharedLink.expiresAt,
assets: assets.map(mapAsset),
assets: assets.map((asset) => mapAsset(asset)),
album: sharedLink.album ? mapAlbumWithoutAssets(sharedLink.album) : undefined,
allowUpload: sharedLink.allowUpload,
allowDownload: sharedLink.allowDownload,
showExif: sharedLink.showExif,
showMetadata: sharedLink.showExif,
};
}
export function mapSharedLinkWithNoExif(sharedLink: SharedLinkEntity): SharedLinkResponseDto {
export function mapSharedLinkWithoutMetadata(sharedLink: SharedLinkEntity): SharedLinkResponseDto {
const linkAssets = sharedLink.assets || [];
const albumAssets = (sharedLink?.album?.assets || []).map((asset) => asset);
@@ -57,10 +58,10 @@ export function mapSharedLinkWithNoExif(sharedLink: SharedLinkEntity): SharedLin
type: sharedLink.type,
createdAt: sharedLink.createdAt,
expiresAt: sharedLink.expiresAt,
assets: assets.map(mapAssetWithoutExif),
assets: assets.map((asset) => mapAsset(asset, true)) as AssetResponseDto[],
album: sharedLink.album ? mapAlbumWithoutAssets(sharedLink.album) : undefined,
allowUpload: sharedLink.allowUpload,
allowDownload: sharedLink.allowDownload,
showExif: sharedLink.showExif,
showMetadata: sharedLink.showExif,
};
}

View File

@@ -34,7 +34,7 @@ export class SharedLinkCreateDto {
@Optional()
@IsBoolean()
showExif?: boolean = true;
showMetadata?: boolean = true;
}
export class SharedLinkEditDto {
@@ -51,5 +51,5 @@ export class SharedLinkEditDto {
allowDownload?: boolean;
@Optional()
showExif?: boolean;
showMetadata?: boolean;
}

View File

@@ -59,10 +59,10 @@ describe(SharedLinkService.name, () => {
expect(shareMock.get).toHaveBeenCalledWith(authDto.id, authDto.sharedLinkId);
});
it('should return not return exif', async () => {
it('should not return metadata', async () => {
const authDto = authStub.adminSharedLinkNoExif;
shareMock.get.mockResolvedValue(sharedLinkStub.readonlyNoExif);
await expect(sut.getMine(authDto)).resolves.toEqual(sharedLinkResponseStub.readonlyNoExif);
await expect(sut.getMine(authDto)).resolves.toEqual(sharedLinkResponseStub.readonlyNoMetadata);
expect(shareMock.get).toHaveBeenCalledWith(authDto.id, authDto.sharedLinkId);
});
});
@@ -137,7 +137,7 @@ describe(SharedLinkService.name, () => {
await sut.create(authStub.admin, {
type: SharedLinkType.INDIVIDUAL,
assetIds: [assetStub.image.id],
showExif: true,
showMetadata: true,
allowDownload: true,
allowUpload: true,
});

View File

@@ -4,7 +4,7 @@ import { AccessCore, Permission } from '../access';
import { AssetIdErrorReason, AssetIdsDto, AssetIdsResponseDto } from '../asset';
import { AuthUserDto } from '../auth';
import { IAccessRepository, ICryptoRepository, ISharedLinkRepository } from '../repositories';
import { SharedLinkResponseDto, mapSharedLink, mapSharedLinkWithNoExif } from './shared-link-response.dto';
import { SharedLinkResponseDto, mapSharedLink, mapSharedLinkWithoutMetadata } from './shared-link-response.dto';
import { SharedLinkCreateDto, SharedLinkEditDto } from './shared-link.dto';
@Injectable()
@@ -24,7 +24,7 @@ export class SharedLinkService {
}
async getMine(authUser: AuthUserDto): Promise<SharedLinkResponseDto> {
const { sharedLinkId: id, isPublicUser, isShowExif } = authUser;
const { sharedLinkId: id, isPublicUser, isShowMetadata: isShowExif } = authUser;
if (!isPublicUser || !id) {
throw new ForbiddenException();
@@ -69,7 +69,7 @@ export class SharedLinkService {
expiresAt: dto.expiresAt || null,
allowUpload: dto.allowUpload ?? true,
allowDownload: dto.allowDownload ?? true,
showExif: dto.showExif ?? true,
showExif: dto.showMetadata ?? true,
});
return this.map(sharedLink, { withExif: true });
@@ -84,7 +84,7 @@ export class SharedLinkService {
expiresAt: dto.expiresAt,
allowUpload: dto.allowUpload,
allowDownload: dto.allowDownload,
showExif: dto.showExif,
showExif: dto.showMetadata,
});
return this.map(sharedLink, { withExif: true });
}
@@ -157,6 +157,6 @@ export class SharedLinkService {
}
private map(sharedLink: SharedLinkEntity, { withExif }: { withExif: boolean }) {
return withExif ? mapSharedLink(sharedLink) : mapSharedLinkWithNoExif(sharedLink);
return withExif ? mapSharedLink(sharedLink) : mapSharedLinkWithoutMetadata(sharedLink);
}
}

View File

@@ -47,7 +47,7 @@ export class TagService {
async getAssets(authUser: AuthUserDto, id: string): Promise<AssetResponseDto[]> {
await this.findOrFail(authUser, id);
const assets = await this.repository.getAssets(authUser.id, id);
return assets.map(mapAsset);
return assets.map((asset) => mapAsset(asset));
}
async addAssets(authUser: AuthUserDto, id: string, dto: AssetIdsDto): Promise<AssetIdsResponseDto[]> {

View File

@@ -186,7 +186,7 @@ export class AssetController {
@SharedLinkRoute()
@Get('/assetById/:id')
getAssetById(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<AssetResponseDto> {
return this.assetService.getAssetById(authUser, id);
return this.assetService.getAssetById(authUser, id) as Promise<AssetResponseDto>;
}
/**

View File

@@ -10,9 +10,9 @@ import {
IStorageRepository,
JobName,
mapAsset,
mapAssetWithoutExif,
mimeTypes,
Permission,
SanitizedAssetResponseDto,
UploadFile,
} from '@app/domain';
import { ASSET_CHECKSUM_CONSTRAINT, AssetEntity, AssetType, LibraryType } from '@app/infra/entities';
@@ -187,22 +187,29 @@ export class AssetService {
return assets.map((asset) => mapAsset(asset));
}
public async getAssetById(authUser: AuthUserDto, assetId: string): Promise<AssetResponseDto> {
public async getAssetById(
authUser: AuthUserDto,
assetId: string,
): Promise<AssetResponseDto | SanitizedAssetResponseDto> {
await this.access.requirePermission(authUser, Permission.ASSET_READ, assetId);
const allowExif = this.getExifPermission(authUser);
const includeMetadata = this.getExifPermission(authUser);
const asset = await this._assetRepository.getById(assetId);
const data = allowExif ? mapAsset(asset) : mapAssetWithoutExif(asset);
if (includeMetadata) {
const data = mapAsset(asset);
if (data.ownerId !== authUser.id) {
data.people = [];
if (data.ownerId !== authUser.id) {
data.people = [];
}
if (authUser.isPublicUser) {
delete data.owner;
}
return data;
} else {
return mapAsset(asset, true);
}
if (authUser.isPublicUser) {
delete data.owner;
}
return data;
}
async serveThumbnail(authUser: AuthUserDto, assetId: string, query: GetAssetThumbnailDto, res: Res) {
@@ -374,7 +381,7 @@ export class AssetService {
}
getExifPermission(authUser: AuthUserDto) {
return !authUser.isPublicUser || authUser.isShowExif;
return !authUser.isPublicUser || authUser.isShowMetadata;
}
private getThumbnailPath(asset: AssetEntity, format: GetAssetThumbnailFormatEnum) {

View File

@@ -98,7 +98,7 @@ export class AssetController {
@Authenticated({ isShared: true })
@Get('time-bucket')
getByTimeBucket(@AuthUser() authUser: AuthUserDto, @Query() dto: TimeBucketAssetDto): Promise<AssetResponseDto[]> {
return this.service.getByTimeBucket(authUser, dto);
return this.service.getByTimeBucket(authUser, dto) as Promise<AssetResponseDto[]>;
}
@Post('jobs')

View File

@@ -10,4 +10,11 @@ export const sharedLinkApi = {
expect(status).toBe(201);
return body as SharedLinkResponseDto;
},
getMySharedLink: async (server: any, key: string) => {
const { status, body } = await request(server).get('/shared-link/me').query({ key });
expect(status).toBe(200);
return body as SharedLinkResponseDto;
},
};

View File

@@ -1,11 +1,17 @@
import { AlbumResponseDto, LoginResponseDto, SharedLinkResponseDto } from '@app/domain';
import { PartnerController } from '@app/immich';
import { SharedLinkType } from '@app/infra/entities';
import { LibraryType, SharedLinkType } from '@app/infra/entities';
import { INestApplication } from '@nestjs/common';
import { api } from '@test/api';
import { db } from '@test/db';
import { errorStub, uuidStub } from '@test/fixtures';
import { createTestApp } from '@test/test-utils';
import {
IMMICH_TEST_ASSET_PATH,
IMMICH_TEST_ASSET_TEMP_PATH,
createTestApp,
restoreTempFolder,
} from '@test/test-utils';
import { cp } from 'fs/promises';
import request from 'supertest';
const user1Dto = {
@@ -18,24 +24,22 @@ const user1Dto = {
describe(`${PartnerController.name} (e2e)`, () => {
let app: INestApplication;
let server: any;
let loginResponse: LoginResponseDto;
let accessToken: string;
let admin: LoginResponseDto;
let user1: LoginResponseDto;
let album: AlbumResponseDto;
let sharedLink: SharedLinkResponseDto;
beforeAll(async () => {
app = await createTestApp();
app = await createTestApp(true);
server = app.getHttpServer();
});
beforeEach(async () => {
await db.reset();
await api.authApi.adminSignUp(server);
loginResponse = await api.authApi.adminLogin(server);
accessToken = loginResponse.accessToken;
admin = await api.authApi.adminLogin(server);
await api.userApi.create(server, accessToken, user1Dto);
await api.userApi.create(server, admin.accessToken, user1Dto);
user1 = await api.authApi.login(server, { email: user1Dto.email, password: user1Dto.password });
album = await api.albumApi.create(server, user1.accessToken, { albumName: 'shared with link' });
@@ -48,6 +52,7 @@ describe(`${PartnerController.name} (e2e)`, () => {
afterAll(async () => {
await db.disconnect();
await app.close();
await restoreTempFolder();
});
describe('GET /shared-link', () => {
@@ -68,7 +73,9 @@ describe(`${PartnerController.name} (e2e)`, () => {
});
it('should not get shared links created by other users', async () => {
const { status, body } = await request(server).get('/shared-link').set('Authorization', `Bearer ${accessToken}`);
const { status, body } = await request(server)
.get('/shared-link')
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual([]);
@@ -77,7 +84,9 @@ describe(`${PartnerController.name} (e2e)`, () => {
describe('GET /shared-link/me', () => {
it('should not require admin authentication', async () => {
const { status } = await request(server).get('/shared-link/me').set('Authorization', `Bearer ${accessToken}`);
const { status } = await request(server)
.get('/shared-link/me')
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(403);
});
@@ -104,7 +113,7 @@ describe(`${PartnerController.name} (e2e)`, () => {
type: SharedLinkType.ALBUM,
albumId: softDeletedAlbum.id,
});
await api.userApi.delete(server, accessToken, user1.userId);
await api.userApi.delete(server, admin.accessToken, user1.userId);
const { status, body } = await request(server).get('/shared-link/me').query({ key: softDeletedAlbumLink.key });
@@ -133,7 +142,7 @@ describe(`${PartnerController.name} (e2e)`, () => {
it('should not get shared link by id if user has not created the link or it does not exist', async () => {
const { status, body } = await request(server)
.get(`/shared-link/${sharedLink.id}`)
.set('Authorization', `Bearer ${accessToken}`);
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400);
expect(body).toEqual(expect.objectContaining({ message: 'Shared link not found' }));
@@ -248,4 +257,81 @@ describe(`${PartnerController.name} (e2e)`, () => {
expect(status).toBe(200);
});
});
describe('Shared link metadata', () => {
beforeEach(async () => {
await restoreTempFolder();
await cp(
`${IMMICH_TEST_ASSET_PATH}/metadata/gps-position/thompson-springs.jpg`,
`${IMMICH_TEST_ASSET_TEMP_PATH}/thompson-springs.jpg`,
);
await api.userApi.setExternalPath(server, admin.accessToken, admin.userId, '/');
const library = await api.libraryApi.create(server, admin.accessToken, {
type: LibraryType.EXTERNAL,
importPaths: [`${IMMICH_TEST_ASSET_TEMP_PATH}`],
});
await api.libraryApi.scanLibrary(server, admin.accessToken, library.id);
const assets = await api.assetApi.getAllAssets(server, admin.accessToken);
expect(assets).toHaveLength(1);
album = await api.albumApi.create(server, admin.accessToken, { albumName: 'New album' });
await api.albumApi.addAssets(server, admin.accessToken, album.id, { ids: [assets[0].id] });
});
it('should return metadata for album shared link', async () => {
const sharedLink = await api.sharedLinkApi.create(server, admin.accessToken, {
type: SharedLinkType.ALBUM,
albumId: album.id,
});
const returnedLink = await api.sharedLinkApi.getMySharedLink(server, sharedLink.key);
expect(returnedLink.assets).toHaveLength(1);
expect(returnedLink.album).toBeDefined();
const returnedAsset = returnedLink.assets[0];
expect(returnedAsset).toEqual(
expect.objectContaining({
originalFileName: 'thompson-springs',
resized: true,
localDateTime: '2022-01-10T15:15:44.310Z',
fileCreatedAt: '2022-01-10T19:15:44.310Z',
exifInfo: expect.objectContaining({
longitude: -108.400968333333,
latitude: 39.115,
orientation: '1',
dateTimeOriginal: '2022-01-10T19:15:44.310Z',
timeZone: 'UTC-4',
state: 'Mesa County, Colorado',
country: 'United States of America',
}),
}),
);
});
it('should not return metadata for album shared link without metadata', async () => {
const sharedLink = await api.sharedLinkApi.create(server, admin.accessToken, {
type: SharedLinkType.ALBUM,
albumId: album.id,
showMetadata: false,
});
const returnedLink = await api.sharedLinkApi.getMySharedLink(server, sharedLink.key);
expect(returnedLink.assets).toHaveLength(1);
expect(returnedLink.album).toBeDefined();
const returnedAsset = returnedLink.assets[0];
expect(returnedAsset).not.toHaveProperty('exifInfo');
expect(returnedAsset).not.toHaveProperty('fileCreatedAt');
expect(returnedAsset).not.toHaveProperty('originalFilename');
expect(returnedAsset).not.toHaveProperty('originalPath');
});
});
});

View File

@@ -48,7 +48,7 @@ export const authStub = {
isPublicUser: false,
isAllowUpload: true,
isAllowDownload: true,
isShowExif: true,
isShowMetadata: true,
accessTokenId: 'token-id',
externalPath: null,
}),
@@ -59,7 +59,7 @@ export const authStub = {
isPublicUser: false,
isAllowUpload: true,
isAllowDownload: true,
isShowExif: true,
isShowMetadata: true,
accessTokenId: 'token-id',
externalPath: null,
}),
@@ -70,7 +70,7 @@ export const authStub = {
isPublicUser: false,
isAllowUpload: true,
isAllowDownload: true,
isShowExif: true,
isShowMetadata: true,
accessTokenId: 'token-id',
externalPath: '/data/user1',
}),
@@ -81,7 +81,7 @@ export const authStub = {
isAllowUpload: true,
isAllowDownload: true,
isPublicUser: true,
isShowExif: true,
isShowMetadata: true,
sharedLinkId: '123',
}),
adminSharedLinkNoExif: Object.freeze<AuthUserDto>({
@@ -91,7 +91,7 @@ export const authStub = {
isAllowUpload: true,
isAllowDownload: true,
isPublicUser: true,
isShowExif: false,
isShowMetadata: false,
sharedLinkId: '123',
}),
readonlySharedLink: Object.freeze<AuthUserDto>({
@@ -101,7 +101,7 @@ export const authStub = {
isAllowUpload: false,
isAllowDownload: false,
isPublicUser: true,
isShowExif: true,
isShowMetadata: true,
sharedLinkId: '123',
accessTokenId: 'token-id',
}),

View File

@@ -71,8 +71,20 @@ const assetResponse: AssetResponseDto = {
checksum: 'ZmlsZSBoYXNo',
isTrashed: false,
libraryId: 'library-id',
hasMetadata: true,
};
const assetResponseWithoutMetadata = {
id: 'id_1',
type: AssetType.VIDEO,
resized: false,
thumbhash: null,
localDateTime: today,
duration: '0:00:00.00000',
livePhotoVideoId: null,
hasMetadata: false,
} as AssetResponseDto;
const albumResponse: AlbumResponseDto = {
albumName: 'Test Album',
description: '',
@@ -253,7 +265,7 @@ export const sharedLinkResponseStub = {
expiresAt: tomorrow,
id: '123',
key: sharedLinkBytes.toString('base64url'),
showExif: true,
showMetadata: true,
type: SharedLinkType.ALBUM,
userId: 'admin_id',
}),
@@ -267,7 +279,7 @@ export const sharedLinkResponseStub = {
expiresAt: yesterday,
id: '123',
key: sharedLinkBytes.toString('base64url'),
showExif: true,
showMetadata: true,
type: SharedLinkType.ALBUM,
userId: 'admin_id',
}),
@@ -281,11 +293,11 @@ export const sharedLinkResponseStub = {
description: null,
allowUpload: false,
allowDownload: false,
showExif: true,
showMetadata: true,
album: albumResponse,
assets: [assetResponse],
}),
readonlyNoExif: Object.freeze<SharedLinkResponseDto>({
readonlyNoMetadata: Object.freeze<SharedLinkResponseDto>({
id: '123',
userId: 'admin_id',
key: sharedLinkBytes.toString('base64url'),
@@ -295,8 +307,8 @@ export const sharedLinkResponseStub = {
description: null,
allowUpload: false,
allowDownload: false,
showExif: false,
showMetadata: false,
album: { ...albumResponse, startDate: assetResponse.fileCreatedAt, endDate: assetResponse.fileCreatedAt },
assets: [{ ...assetResponse, exifInfo: undefined }],
assets: [{ ...assetResponseWithoutMetadata, exifInfo: undefined }],
}),
};