fix(server): apply shared link metadata visibility in asset mapper (#32561)
This commit is contained in:
@@ -384,6 +384,27 @@ describe('/search', () => {
|
|||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body.assets.items).toEqual([expect.objectContaining({ id: asset.id })]);
|
expect(body.assets.items).toEqual([expect.objectContaining({ id: asset.id })]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should strip metadata for a shared link without metadata', async () => {
|
||||||
|
const asset = await utils.createAsset(admin.accessToken);
|
||||||
|
const album = await utils.createAlbum(admin.accessToken, { albumName: 'foo', assetIds: [asset.id] });
|
||||||
|
const sharedLink = await utils.createSharedLink(admin.accessToken, {
|
||||||
|
type: SharedLinkType.Album,
|
||||||
|
albumId: album.id,
|
||||||
|
showMetadata: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post(`/search/metadata?key=${sharedLink.key}`)
|
||||||
|
.send({ albumIds: [album.id], withExif: true });
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body.assets.items).toHaveLength(1);
|
||||||
|
expect(body.assets.items[0]).toEqual(expect.objectContaining({ id: asset.id, hasMetadata: false }));
|
||||||
|
expect(body.assets.items[0]).not.toHaveProperty('exifInfo');
|
||||||
|
expect(body.assets.items[0]).not.toHaveProperty('originalPath');
|
||||||
|
expect(body.assets.items[0]).not.toHaveProperty('originalFileName');
|
||||||
|
expect(body.assets.items[0]).not.toHaveProperty('ownerId');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('POST /search/random', () => {
|
describe('POST /search/random', () => {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import z from 'zod';
|
|||||||
import type { MaybeDehydrated } from 'src/types.js';
|
import type { MaybeDehydrated } from 'src/types.js';
|
||||||
import { AssetFace, AssetFile, Exif, Stack, Tag, User } from 'src/database.js';
|
import { AssetFace, AssetFile, Exif, Stack, Tag, User } from 'src/database.js';
|
||||||
import { HistoryBuilder } from 'src/decorators.js';
|
import { HistoryBuilder } from 'src/decorators.js';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto.js';
|
import { AuthDto, isMetadataHidden } from 'src/dtos/auth.dto.js';
|
||||||
import { AssetEditActionItem } from 'src/dtos/editing.dto.js';
|
import { AssetEditActionItem } from 'src/dtos/editing.dto.js';
|
||||||
import { ExifResponseSchema, mapExif } from 'src/dtos/exif.dto.js';
|
import { ExifResponseSchema, mapExif } from 'src/dtos/exif.dto.js';
|
||||||
import { PersonResponseDto, PersonResponseSchema, mapPerson } from 'src/dtos/person.dto.js';
|
import { PersonResponseDto, PersonResponseSchema, mapPerson } from 'src/dtos/person.dto.js';
|
||||||
@@ -157,7 +157,6 @@ export type MapAsset = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type AssetMapOptions = {
|
export type AssetMapOptions = {
|
||||||
stripMetadata?: boolean;
|
|
||||||
withStack?: boolean;
|
withStack?: boolean;
|
||||||
auth?: AuthDto;
|
auth?: AuthDto;
|
||||||
};
|
};
|
||||||
@@ -191,9 +190,9 @@ const mapStack = (entity: { stack?: Stack | null }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function mapAsset(entity: MaybeDehydrated<MapAsset>, options: AssetMapOptions = {}): AssetResponseDto {
|
export function mapAsset(entity: MaybeDehydrated<MapAsset>, options: AssetMapOptions = {}): AssetResponseDto {
|
||||||
const { stripMetadata = false, withStack = false } = options;
|
const { withStack = false, auth } = options;
|
||||||
|
|
||||||
if (stripMetadata) {
|
if (isMetadataHidden(auth)) {
|
||||||
const sanitizedAssetResponse: SanitizedAssetResponseDto = {
|
const sanitizedAssetResponse: SanitizedAssetResponseDto = {
|
||||||
id: entity.id,
|
id: entity.id,
|
||||||
type: entity.type,
|
type: entity.type,
|
||||||
@@ -213,7 +212,7 @@ export function mapAsset(entity: MaybeDehydrated<MapAsset>, options: AssetMapOpt
|
|||||||
id: entity.id,
|
id: entity.id,
|
||||||
createdAt: asDateTimeString(entity.createdAt),
|
createdAt: asDateTimeString(entity.createdAt),
|
||||||
ownerId: entity.ownerId,
|
ownerId: entity.ownerId,
|
||||||
owner: entity.owner ? mapUser(entity.owner) : undefined,
|
owner: entity.owner && !auth?.sharedLink ? mapUser(entity.owner) : undefined,
|
||||||
libraryId: entity.libraryId,
|
libraryId: entity.libraryId,
|
||||||
type: entity.type,
|
type: entity.type,
|
||||||
originalPath: entity.originalPath,
|
originalPath: entity.originalPath,
|
||||||
@@ -232,7 +231,7 @@ export function mapAsset(entity: MaybeDehydrated<MapAsset>, options: AssetMapOpt
|
|||||||
exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined,
|
exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined,
|
||||||
livePhotoVideoId: entity.livePhotoVideoId,
|
livePhotoVideoId: entity.livePhotoVideoId,
|
||||||
tags: entity.tags?.map((tag) => mapTag(tag)),
|
tags: entity.tags?.map((tag) => mapTag(tag)),
|
||||||
people: peopleFromFaces(entity.faces),
|
people: auth?.sharedLink ? [] : peopleFromFaces(entity.faces),
|
||||||
checksum: hexOrBufferToBase64(entity.checksum)!,
|
checksum: hexOrBufferToBase64(entity.checksum)!,
|
||||||
stack: withStack ? mapStack(entity) : undefined,
|
stack: withStack ? mapStack(entity) : undefined,
|
||||||
isOffline: entity.isOffline,
|
isOffline: entity.isOffline,
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ export type AuthDto = {
|
|||||||
session?: AuthSession;
|
session?: AuthSession;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isMetadataHidden = (auth?: AuthDto) => !!auth?.sharedLink && !auth.sharedLink.showExif;
|
||||||
|
|
||||||
const LoginCredentialSchema = z
|
const LoginCredentialSchema = z
|
||||||
.object({
|
.object({
|
||||||
email: toEmail.describe('User email').meta({ example: 'testuser@email.com' }),
|
email: toEmail.describe('User email').meta({ example: 'testuser@email.com' }),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { SharedLink } from 'src/database.js';
|
|||||||
import { HistoryBuilder } from 'src/decorators.js';
|
import { HistoryBuilder } from 'src/decorators.js';
|
||||||
import { AlbumResponseSchema, mapAlbum } from 'src/dtos/album.dto.js';
|
import { AlbumResponseSchema, mapAlbum } from 'src/dtos/album.dto.js';
|
||||||
import { AssetResponseSchema, mapAsset } from 'src/dtos/asset-response.dto.js';
|
import { AssetResponseSchema, mapAsset } from 'src/dtos/asset-response.dto.js';
|
||||||
|
import { AuthDto } from 'src/dtos/auth.dto.js';
|
||||||
import { SharedLinkType, SharedLinkTypeSchema } from 'src/enum.js';
|
import { SharedLinkType, SharedLinkTypeSchema } from 'src/enum.js';
|
||||||
import { isoDatetimeToDate } from 'src/validation.js';
|
import { isoDatetimeToDate } from 'src/validation.js';
|
||||||
|
|
||||||
@@ -100,7 +101,7 @@ export class SharedLinkEditDto extends createZodDto(SharedLinkEditSchema) {}
|
|||||||
export class SharedLinkLoginDto extends createZodDto(SharedLinkLoginSchema) {}
|
export class SharedLinkLoginDto extends createZodDto(SharedLinkLoginSchema) {}
|
||||||
export class SharedLinkResponseDto extends createZodDto(SharedLinkResponseSchema) {}
|
export class SharedLinkResponseDto extends createZodDto(SharedLinkResponseSchema) {}
|
||||||
|
|
||||||
export function mapSharedLink(sharedLink: SharedLink, options: { stripAssetMetadata: boolean }): SharedLinkResponseDto {
|
export function mapSharedLink(sharedLink: SharedLink, { auth }: { auth: AuthDto }): SharedLinkResponseDto {
|
||||||
const assets = sharedLink.assets || [];
|
const assets = sharedLink.assets || [];
|
||||||
|
|
||||||
const response = {
|
const response = {
|
||||||
@@ -112,7 +113,7 @@ export function mapSharedLink(sharedLink: SharedLink, options: { stripAssetMetad
|
|||||||
type: sharedLink.type,
|
type: sharedLink.type,
|
||||||
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, { auth })),
|
||||||
album: sharedLink.album ? mapAlbum(sharedLink.album) : undefined,
|
album: sharedLink.album ? mapAlbum(sharedLink.album) : undefined,
|
||||||
allowUpload: sharedLink.allowUpload,
|
allowUpload: sharedLink.allowUpload,
|
||||||
allowDownload: sharedLink.allowDownload,
|
allowDownload: sharedLink.allowDownload,
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ import {
|
|||||||
import { jsonArrayFrom } from 'kysely/helpers/postgres';
|
import { jsonArrayFrom } from 'kysely/helpers/postgres';
|
||||||
import { isEmpty, isUndefined, omitBy } from 'lodash-es';
|
import { isEmpty, isUndefined, omitBy } from 'lodash-es';
|
||||||
import { InjectKysely } from 'nestjs-kysely';
|
import { InjectKysely } from 'nestjs-kysely';
|
||||||
import type { AuthDto } from 'src/dtos/auth.dto.js';
|
|
||||||
import { LockableProperty, Stack } from 'src/database.js';
|
import { LockableProperty, Stack } from 'src/database.js';
|
||||||
import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators.js';
|
import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators.js';
|
||||||
|
import { type AuthDto, isMetadataHidden } from 'src/dtos/auth.dto.js';
|
||||||
import {
|
import {
|
||||||
AssetFileType,
|
AssetFileType,
|
||||||
AssetOrder,
|
AssetOrder,
|
||||||
@@ -852,9 +852,7 @@ export class AssetRepository {
|
|||||||
)
|
)
|
||||||
.as('ratio'),
|
.as('ratio'),
|
||||||
])
|
])
|
||||||
.$if(!auth.sharedLink || auth.sharedLink.showExif, (qb) =>
|
.$if(!isMetadataHidden(auth), (qb) => qb.select(['asset_exif.city', 'asset_exif.country']))
|
||||||
qb.select(['asset_exif.city', 'asset_exif.country']),
|
|
||||||
)
|
|
||||||
.$if(!!options.withCoordinates, (qb) => qb.select(['asset_exif.latitude', 'asset_exif.longitude']))
|
.$if(!!options.withCoordinates, (qb) => qb.select(['asset_exif.latitude', 'asset_exif.longitude']))
|
||||||
.where('asset.deletedAt', options.isTrashed ? 'is not' : 'is', null)
|
.where('asset.deletedAt', options.isTrashed ? 'is not' : 'is', null)
|
||||||
.$if(options.visibility === undefined, withDefaultVisibility)
|
.$if(options.visibility === undefined, withDefaultVisibility)
|
||||||
@@ -952,7 +950,7 @@ export class AssetRepository {
|
|||||||
eb.fn.coalesce(eb.fn('array_agg', ['status']), sql.lit('{}')).as('status'),
|
eb.fn.coalesce(eb.fn('array_agg', ['status']), sql.lit('{}')).as('status'),
|
||||||
eb.fn.coalesce(eb.fn('array_agg', ['thumbhash']), sql.lit('{}')).as('thumbhash'),
|
eb.fn.coalesce(eb.fn('array_agg', ['thumbhash']), sql.lit('{}')).as('thumbhash'),
|
||||||
])
|
])
|
||||||
.$if(!auth.sharedLink || auth.sharedLink.showExif, (qb) =>
|
.$if(!isMetadataHidden(auth), (qb) =>
|
||||||
qb.select((eb) => [
|
qb.select((eb) => [
|
||||||
eb.fn.coalesce(eb.fn('array_agg', ['city']), sql.lit('{}')).as('city'),
|
eb.fn.coalesce(eb.fn('array_agg', ['city']), sql.lit('{}')).as('city'),
|
||||||
eb.fn.coalesce(eb.fn('array_agg', ['country']), sql.lit('{}')).as('country'),
|
eb.fn.coalesce(eb.fn('array_agg', ['country']), sql.lit('{}')).as('country'),
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
mapAlbum,
|
mapAlbum,
|
||||||
} from 'src/dtos/album.dto.js';
|
} from 'src/dtos/album.dto.js';
|
||||||
import { BulkIdErrorReason, BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto.js';
|
import { BulkIdErrorReason, BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto.js';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto.js';
|
import { AuthDto, isMetadataHidden } from 'src/dtos/auth.dto.js';
|
||||||
import { MapMarkerResponseDto } from 'src/dtos/map.dto.js';
|
import { MapMarkerResponseDto } from 'src/dtos/map.dto.js';
|
||||||
import { AlbumUserRole, Permission } from 'src/enum.js';
|
import { AlbumUserRole, Permission } from 'src/enum.js';
|
||||||
import { AlbumAssetCount } from 'src/repositories/album.repository.js';
|
import { AlbumAssetCount } from 'src/repositories/album.repository.js';
|
||||||
@@ -91,7 +91,7 @@ export class AlbumService extends BaseService {
|
|||||||
async getMapMarkers(auth: AuthDto, id: string): Promise<MapMarkerResponseDto[]> {
|
async getMapMarkers(auth: AuthDto, id: string): Promise<MapMarkerResponseDto[]> {
|
||||||
await this.requireAccess({ auth, permission: Permission.AlbumRead, ids: [id] });
|
await this.requireAccess({ auth, permission: Permission.AlbumRead, ids: [id] });
|
||||||
|
|
||||||
if (auth.sharedLink && !auth.sharedLink.showExif) {
|
if (isMetadataHidden(auth)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
UploadFieldName,
|
UploadFieldName,
|
||||||
} from 'src/dtos/asset-media.dto.js';
|
} from 'src/dtos/asset-media.dto.js';
|
||||||
import { AssetDownloadOriginalDto } from 'src/dtos/asset.dto.js';
|
import { AssetDownloadOriginalDto } from 'src/dtos/asset.dto.js';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto.js';
|
import { AuthDto, isMetadataHidden } from 'src/dtos/auth.dto.js';
|
||||||
import {
|
import {
|
||||||
AssetFileType,
|
AssetFileType,
|
||||||
AssetVisibility,
|
AssetVisibility,
|
||||||
@@ -285,8 +285,7 @@ export class AssetMediaService extends BaseService {
|
|||||||
throw new NotFoundException('Asset media not found');
|
throw new NotFoundException('Asset media not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileNameBase =
|
const fileNameBase = isMetadataHidden(auth) ? id : getFileNameWithoutExtension(originalFileName);
|
||||||
auth.sharedLink && !auth.sharedLink.showExif ? id : getFileNameWithoutExtension(originalFileName);
|
|
||||||
const fileName = `${fileNameBase}_${size}${getFilenameExtension(path)}`;
|
const fileName = `${fileNameBase}_${size}${getFilenameExtension(path)}`;
|
||||||
|
|
||||||
return new ImmichFileResponse({
|
return new ImmichFileResponse({
|
||||||
|
|||||||
@@ -93,12 +93,16 @@ describe(AssetService.name, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should allow shared link access', async () => {
|
it('should allow shared link access', async () => {
|
||||||
const asset = AssetFactory.create();
|
const asset = AssetFactory.from()
|
||||||
|
.owner()
|
||||||
|
.face({}, (face) => face.person())
|
||||||
|
.build();
|
||||||
mocks.access.asset.checkSharedLinkAccess.mockResolvedValue(new Set([asset.id]));
|
mocks.access.asset.checkSharedLinkAccess.mockResolvedValue(new Set([asset.id]));
|
||||||
mocks.asset.getById.mockResolvedValue(getForAsset(asset));
|
mocks.asset.getById.mockResolvedValue(getForAsset(asset));
|
||||||
|
|
||||||
await sut.get(authStub.adminSharedLink, asset.id);
|
const result = await sut.get(authStub.adminSharedLink, asset.id);
|
||||||
|
|
||||||
|
expect(result).toMatchObject({ id: asset.id, hasMetadata: true, people: [], owner: undefined });
|
||||||
expect(mocks.access.asset.checkSharedLinkAccess).toHaveBeenCalledWith(
|
expect(mocks.access.asset.checkSharedLinkAccess).toHaveBeenCalledWith(
|
||||||
authStub.adminSharedLink.sharedLink?.id,
|
authStub.adminSharedLink.sharedLink?.id,
|
||||||
new Set([asset.id]),
|
new Set([asset.id]),
|
||||||
|
|||||||
@@ -80,21 +80,7 @@ export class AssetService extends BaseService {
|
|||||||
throw new BadRequestException('Asset not found');
|
throw new BadRequestException('Asset not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auth.sharedLink && !auth.sharedLink.showExif) {
|
return mapAsset(asset, { withStack: true, auth });
|
||||||
return mapAsset(asset, { stripMetadata: true, withStack: true, auth });
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = mapAsset(asset, { withStack: true, auth });
|
|
||||||
|
|
||||||
if (auth.sharedLink) {
|
|
||||||
delete data.owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auth.sharedLink) {
|
|
||||||
data.people = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(auth: AuthDto, id: string, dto: UpdateAssetDto): Promise<AssetResponseDto> {
|
async update(auth: AuthDto, id: string, dto: UpdateAssetDto): Promise<AssetResponseDto> {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ describe(SharedLinkService.name, () => {
|
|||||||
mocks.sharedLink.getAll.mockResolvedValue([getForSharedLink(sharedLink1), getForSharedLink(sharedLink2)]);
|
mocks.sharedLink.getAll.mockResolvedValue([getForSharedLink(sharedLink1), getForSharedLink(sharedLink2)]);
|
||||||
await expect(sut.getAll(authStub.user1, {})).resolves.toEqual(
|
await expect(sut.getAll(authStub.user1, {})).resolves.toEqual(
|
||||||
[getForSharedLink(sharedLink1), getForSharedLink(sharedLink2)].map((link) =>
|
[getForSharedLink(sharedLink1), getForSharedLink(sharedLink2)].map((link) =>
|
||||||
mapSharedLink(link, { stripAssetMetadata: false }),
|
mapSharedLink(link, { auth: authStub.user1 }),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
expect(mocks.sharedLink.getAll).toHaveBeenCalledWith({ userId: authStub.user1.user.id });
|
expect(mocks.sharedLink.getAll).toHaveBeenCalledWith({ userId: authStub.user1.user.id });
|
||||||
@@ -48,7 +48,7 @@ describe(SharedLinkService.name, () => {
|
|||||||
const sharedLink = SharedLinkFactory.create();
|
const sharedLink = SharedLinkFactory.create();
|
||||||
mocks.sharedLink.get.mockResolvedValue(getForSharedLink(sharedLink));
|
mocks.sharedLink.get.mockResolvedValue(getForSharedLink(sharedLink));
|
||||||
await expect(sut.getMine(authDto, [])).resolves.toEqual(
|
await expect(sut.getMine(authDto, [])).resolves.toEqual(
|
||||||
mapSharedLink(getForSharedLink(sharedLink), { stripAssetMetadata: false }),
|
mapSharedLink(getForSharedLink(sharedLink), { auth: authDto }),
|
||||||
);
|
);
|
||||||
expect(mocks.sharedLink.get).toHaveBeenCalledWith(authDto.user.id, authDto.sharedLink?.id);
|
expect(mocks.sharedLink.get).toHaveBeenCalledWith(authDto.user.id, authDto.sharedLink?.id);
|
||||||
});
|
});
|
||||||
@@ -107,7 +107,7 @@ describe(SharedLinkService.name, () => {
|
|||||||
const sharedLink = SharedLinkFactory.create();
|
const sharedLink = SharedLinkFactory.create();
|
||||||
mocks.sharedLink.get.mockResolvedValue(getForSharedLink(sharedLink));
|
mocks.sharedLink.get.mockResolvedValue(getForSharedLink(sharedLink));
|
||||||
await expect(sut.get(authStub.user1, sharedLink.id)).resolves.toEqual(
|
await expect(sut.get(authStub.user1, sharedLink.id)).resolves.toEqual(
|
||||||
mapSharedLink(getForSharedLink(sharedLink), { stripAssetMetadata: true }),
|
mapSharedLink(getForSharedLink(sharedLink), { auth: authStub.user1 }),
|
||||||
);
|
);
|
||||||
expect(mocks.sharedLink.get).toHaveBeenCalledWith(authStub.user1.user.id, sharedLink.id);
|
expect(mocks.sharedLink.get).toHaveBeenCalledWith(authStub.user1.user.id, sharedLink.id);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export class SharedLinkService extends BaseService {
|
|||||||
return this.sharedLinkRepository
|
return this.sharedLinkRepository
|
||||||
.getAll({ userId: auth.user.id, id, albumId })
|
.getAll({ userId: auth.user.id, id, albumId })
|
||||||
|
|
||||||
.then((links) => links.map((link) => mapSharedLink(link, { stripAssetMetadata: false })));
|
.then((links) => links.map((link) => mapSharedLink(link, { auth })));
|
||||||
}
|
}
|
||||||
|
|
||||||
async login(auth: AuthDto, dto: SharedLinkLoginDto) {
|
async login(auth: AuthDto, dto: SharedLinkLoginDto) {
|
||||||
@@ -43,7 +43,7 @@ export class SharedLinkService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sharedLink: await this.mapWithAlbumMetadata(sharedLink, { stripAssetMetadata: !sharedLink.showExif }),
|
sharedLink: await this.mapWithAlbumMetadata(sharedLink, { auth }),
|
||||||
token: this.asToken({ id, password }),
|
token: this.asToken({ id, password }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -60,12 +60,12 @@ export class SharedLinkService extends BaseService {
|
|||||||
throw new UnauthorizedException('Password required');
|
throw new UnauthorizedException('Password required');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.mapWithAlbumMetadata(sharedLink, { stripAssetMetadata: !sharedLink.showExif });
|
return this.mapWithAlbumMetadata(sharedLink, { auth });
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(auth: AuthDto, id: string): Promise<SharedLinkResponseDto> {
|
async get(auth: AuthDto, id: string): Promise<SharedLinkResponseDto> {
|
||||||
const sharedLink = await this.findOrFail(auth.user.id, id);
|
const sharedLink = await this.findOrFail(auth.user.id, id);
|
||||||
return this.mapWithAlbumMetadata(sharedLink, { stripAssetMetadata: false });
|
return this.mapWithAlbumMetadata(sharedLink, { auth });
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(auth: AuthDto, dto: SharedLinkCreateDto): Promise<SharedLinkResponseDto> {
|
async create(auth: AuthDto, dto: SharedLinkCreateDto): Promise<SharedLinkResponseDto> {
|
||||||
@@ -105,7 +105,7 @@ export class SharedLinkService extends BaseService {
|
|||||||
slug: dto.slug || null,
|
slug: dto.slug || null,
|
||||||
});
|
});
|
||||||
|
|
||||||
return mapSharedLink(sharedLink, { stripAssetMetadata: false });
|
return mapSharedLink(sharedLink, { auth });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handleError(error);
|
this.handleError(error);
|
||||||
}
|
}
|
||||||
@@ -133,7 +133,7 @@ export class SharedLinkService extends BaseService {
|
|||||||
showExif: dto.showMetadata,
|
showExif: dto.showMetadata,
|
||||||
slug: dto.slug || null,
|
slug: dto.slug || null,
|
||||||
});
|
});
|
||||||
return mapSharedLink(sharedLink, { stripAssetMetadata: false });
|
return mapSharedLink(sharedLink, { auth });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handleError(error);
|
this.handleError(error);
|
||||||
}
|
}
|
||||||
@@ -235,8 +235,8 @@ export class SharedLinkService extends BaseService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async mapWithAlbumMetadata(sharedLink: SharedLink, options: { stripAssetMetadata: boolean }) {
|
private async mapWithAlbumMetadata(sharedLink: SharedLink, { auth }: { auth: AuthDto }) {
|
||||||
const response = mapSharedLink(sharedLink, options);
|
const response = mapSharedLink(sharedLink, { auth });
|
||||||
if (response.album) {
|
if (response.album) {
|
||||||
const [metadata] = await this.albumRepository.getMetadataForIds([response.album.id]);
|
const [metadata] = await this.albumRepository.getMetadataForIds([response.album.id]);
|
||||||
response.album.startDate = asDateTimeString(metadata?.startDate ?? undefined);
|
response.album.startDate = asDateTimeString(metadata?.startDate ?? undefined);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto.js';
|
import { AuthDto, isMetadataHidden } from 'src/dtos/auth.dto.js';
|
||||||
import { TimeBucketAssetDto, TimeBucketDto, TimeBucketsResponseDto } from 'src/dtos/time-bucket.dto.js';
|
import { TimeBucketAssetDto, TimeBucketDto, TimeBucketsResponseDto } from 'src/dtos/time-bucket.dto.js';
|
||||||
import { AssetVisibility, Permission } from 'src/enum.js';
|
import { AssetVisibility, Permission } from 'src/enum.js';
|
||||||
import { TimeBucketOptions } from 'src/repositories/asset.repository.js';
|
import { TimeBucketOptions } from 'src/repositories/asset.repository.js';
|
||||||
@@ -69,7 +69,7 @@ export class TimelineService extends BaseService {
|
|||||||
await this.requireAccess({ auth, permission: Permission.TagRead, ids: [dto.tagId] });
|
await this.requireAccess({ auth, permission: Permission.TagRead, ids: [dto.tagId] });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auth.sharedLink && !auth.sharedLink.showExif) {
|
if (isMetadataHidden(auth)) {
|
||||||
dto.withCoordinates = false;
|
dto.withCoordinates = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user