refactor(server, web): create shared link (#2879)
* refactor: shared links * chore: open api * fix: tsc error
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { AlbumResponseDto } from '@app/domain';
|
||||
import { Body, Controller, Delete, Get, Param, Post, Put, Query, Response } from '@nestjs/common';
|
||||
import { Body, Controller, Delete, Get, Param, Put, Query, Response } from '@nestjs/common';
|
||||
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { Response as Res } from 'express';
|
||||
import { handleDownload } from '../../app.utils';
|
||||
@@ -10,7 +10,6 @@ import { UseValidation } from '../../decorators/use-validation.decorator';
|
||||
import { DownloadDto } from '../asset/dto/download-library.dto';
|
||||
import { AlbumService } from './album.service';
|
||||
import { AddAssetsDto } from './dto/add-assets.dto';
|
||||
import { CreateAlbumShareLinkDto as CreateAlbumSharedLinkDto } from './dto/create-album-shared-link.dto';
|
||||
import { RemoveAssetsDto } from './dto/remove-assets.dto';
|
||||
import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto';
|
||||
|
||||
@@ -59,9 +58,4 @@ export class AlbumController {
|
||||
) {
|
||||
return this.service.downloadArchive(authUser, id, dto).then((download) => handleDownload(download, res));
|
||||
}
|
||||
|
||||
@Post('create-shared-link')
|
||||
createAlbumSharedLink(@AuthUser() authUser: AuthUserDto, @Body() dto: CreateAlbumSharedLinkDto) {
|
||||
return this.service.createSharedLink(authUser, dto);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AlbumResponseDto, ICryptoRepository, ISharedLinkRepository, mapUser } from '@app/domain';
|
||||
import { AlbumResponseDto, mapUser } from '@app/domain';
|
||||
import { AlbumEntity, UserEntity } from '@app/infra/entities';
|
||||
import { ForbiddenException, NotFoundException } from '@nestjs/common';
|
||||
import { newCryptoRepositoryMock, newSharedLinkRepositoryMock, userEntityStub } from '@test';
|
||||
import { userEntityStub } from '@test';
|
||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||
import { DownloadService } from '../../modules/download/download.service';
|
||||
import { IAlbumRepository } from './album-repository';
|
||||
@@ -11,9 +11,7 @@ import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto';
|
||||
describe('Album service', () => {
|
||||
let sut: AlbumService;
|
||||
let albumRepositoryMock: jest.Mocked<IAlbumRepository>;
|
||||
let sharedLinkRepositoryMock: jest.Mocked<ISharedLinkRepository>;
|
||||
let downloadServiceMock: jest.Mocked<Partial<DownloadService>>;
|
||||
let cryptoMock: jest.Mocked<ICryptoRepository>;
|
||||
|
||||
const authUser: AuthUserDto = Object.freeze({
|
||||
id: '1111',
|
||||
@@ -99,20 +97,11 @@ describe('Album service', () => {
|
||||
updateThumbnails: jest.fn(),
|
||||
};
|
||||
|
||||
sharedLinkRepositoryMock = newSharedLinkRepositoryMock();
|
||||
|
||||
downloadServiceMock = {
|
||||
downloadArchive: jest.fn(),
|
||||
};
|
||||
|
||||
cryptoMock = newCryptoRepositoryMock();
|
||||
|
||||
sut = new AlbumService(
|
||||
albumRepositoryMock,
|
||||
sharedLinkRepositoryMock,
|
||||
downloadServiceMock as DownloadService,
|
||||
cryptoMock,
|
||||
);
|
||||
sut = new AlbumService(albumRepositoryMock, downloadServiceMock as DownloadService);
|
||||
});
|
||||
|
||||
it('gets an owned album', async () => {
|
||||
|
||||
@@ -1,36 +1,22 @@
|
||||
import {
|
||||
AlbumResponseDto,
|
||||
ICryptoRepository,
|
||||
ISharedLinkRepository,
|
||||
mapAlbum,
|
||||
mapSharedLink,
|
||||
SharedLinkCore,
|
||||
SharedLinkResponseDto,
|
||||
} from '@app/domain';
|
||||
import { AlbumEntity, SharedLinkType } from '@app/infra/entities';
|
||||
import { AlbumResponseDto, mapAlbum } from '@app/domain';
|
||||
import { AlbumEntity } from '@app/infra/entities';
|
||||
import { BadRequestException, ForbiddenException, Inject, Injectable, Logger, NotFoundException } from '@nestjs/common';
|
||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||
import { DownloadService } from '../../modules/download/download.service';
|
||||
import { DownloadDto } from '../asset/dto/download-library.dto';
|
||||
import { IAlbumRepository } from './album-repository';
|
||||
import { AddAssetsDto } from './dto/add-assets.dto';
|
||||
import { CreateAlbumShareLinkDto } from './dto/create-album-shared-link.dto';
|
||||
import { RemoveAssetsDto } from './dto/remove-assets.dto';
|
||||
import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto';
|
||||
|
||||
@Injectable()
|
||||
export class AlbumService {
|
||||
readonly logger = new Logger(AlbumService.name);
|
||||
private shareCore: SharedLinkCore;
|
||||
private logger = new Logger(AlbumService.name);
|
||||
|
||||
constructor(
|
||||
@Inject(IAlbumRepository) private albumRepository: IAlbumRepository,
|
||||
@Inject(ISharedLinkRepository) sharedLinkRepository: ISharedLinkRepository,
|
||||
private downloadService: DownloadService,
|
||||
@Inject(ICryptoRepository) cryptoRepository: ICryptoRepository,
|
||||
) {
|
||||
this.shareCore = new SharedLinkCore(sharedLinkRepository, cryptoRepository);
|
||||
}
|
||||
) {}
|
||||
|
||||
private async _getAlbum({
|
||||
authUser,
|
||||
@@ -91,7 +77,7 @@ export class AlbumService {
|
||||
}
|
||||
|
||||
async downloadArchive(authUser: AuthUserDto, albumId: string, dto: DownloadDto) {
|
||||
this.shareCore.checkDownloadAccess(authUser);
|
||||
this.checkDownloadAccess(authUser);
|
||||
|
||||
const album = await this._getAlbum({ authUser, albumId, validateIsOwner: false });
|
||||
const assets = (album.assets || []).map((asset) => asset).slice(dto.skip || 0);
|
||||
@@ -99,20 +85,9 @@ export class AlbumService {
|
||||
return this.downloadService.downloadArchive(album.albumName, assets);
|
||||
}
|
||||
|
||||
async createSharedLink(authUser: AuthUserDto, dto: CreateAlbumShareLinkDto): Promise<SharedLinkResponseDto> {
|
||||
const album = await this._getAlbum({ authUser, albumId: dto.albumId });
|
||||
|
||||
const sharedLink = await this.shareCore.create(authUser.id, {
|
||||
type: SharedLinkType.ALBUM,
|
||||
expiresAt: dto.expiresAt,
|
||||
allowUpload: dto.allowUpload,
|
||||
album,
|
||||
assets: [],
|
||||
description: dto.description,
|
||||
allowDownload: dto.allowDownload,
|
||||
showExif: dto.showExif,
|
||||
});
|
||||
|
||||
return mapSharedLink(sharedLink);
|
||||
private checkDownloadAccess(authUser: AuthUserDto) {
|
||||
if (authUser.isPublicUser && !authUser.isAllowDownload) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import { ValidateUUID } from '@app/immich/decorators/validate-uuid.decorator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsBoolean, IsDate, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class CreateAlbumShareLinkDto {
|
||||
@ValidateUUID()
|
||||
albumId!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
@ApiProperty()
|
||||
expiresAt?: Date;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@ApiProperty()
|
||||
allowUpload?: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@ApiProperty()
|
||||
allowDownload?: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@ApiProperty()
|
||||
showExif?: boolean;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty()
|
||||
description?: string;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AssetResponseDto, ImmichReadStream, SharedLinkResponseDto } from '@app/domain';
|
||||
import { AssetResponseDto, ImmichReadStream } from '@app/domain';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
HttpStatus,
|
||||
Param,
|
||||
ParseFilePipe,
|
||||
Patch,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
@@ -28,15 +27,12 @@ import { assetUploadOption, ImmichFile } from '../../config/asset-upload.config'
|
||||
import { UUIDParamDto } from '../../controllers/dto/uuid-param.dto';
|
||||
import { AuthUser, AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||
import { Authenticated, SharedLinkRoute } from '../../decorators/authenticated.decorator';
|
||||
import { AddAssetsDto } from '../album/dto/add-assets.dto';
|
||||
import { RemoveAssetsDto } from '../album/dto/remove-assets.dto';
|
||||
import FileNotEmptyValidator from '../validation/file-not-empty-validator';
|
||||
import { AssetService } from './asset.service';
|
||||
import { AssetBulkUploadCheckDto } from './dto/asset-check.dto';
|
||||
import { AssetSearchDto } from './dto/asset-search.dto';
|
||||
import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
|
||||
import { CheckExistingAssetsDto } from './dto/check-existing-assets.dto';
|
||||
import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto';
|
||||
import { CreateAssetDto, mapToUploadFile } from './dto/create-asset.dto';
|
||||
import { DeleteAssetDto } from './dto/delete-asset.dto';
|
||||
import { DeviceIdDto } from './dto/device-id.dto';
|
||||
@@ -319,30 +315,4 @@ export class AssetController {
|
||||
): Promise<AssetBulkUploadCheckResponseDto> {
|
||||
return this.assetService.bulkUploadCheck(authUser, dto);
|
||||
}
|
||||
|
||||
@Post('/shared-link')
|
||||
createAssetsSharedLink(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Body(ValidationPipe) dto: CreateAssetsShareLinkDto,
|
||||
): Promise<SharedLinkResponseDto> {
|
||||
return this.assetService.createAssetsSharedLink(authUser, dto);
|
||||
}
|
||||
|
||||
@SharedLinkRoute()
|
||||
@Patch('/shared-link/add')
|
||||
addAssetsToSharedLink(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Body(ValidationPipe) dto: AddAssetsDto,
|
||||
): Promise<SharedLinkResponseDto> {
|
||||
return this.assetService.addAssetsToSharedLink(authUser, dto);
|
||||
}
|
||||
|
||||
@SharedLinkRoute()
|
||||
@Patch('/shared-link/remove')
|
||||
removeAssetsFromSharedLink(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Body(ValidationPipe) dto: RemoveAssetsDto,
|
||||
): Promise<SharedLinkResponseDto> {
|
||||
return this.assetService.removeAssetsFromSharedLink(authUser, dto);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,19 @@
|
||||
import {
|
||||
IAccessRepository,
|
||||
ICryptoRepository,
|
||||
IJobRepository,
|
||||
ISharedLinkRepository,
|
||||
IStorageRepository,
|
||||
JobName,
|
||||
} from '@app/domain';
|
||||
import { IAccessRepository, IJobRepository, IStorageRepository, JobName } from '@app/domain';
|
||||
import { AssetEntity, AssetType, ExifEntity } from '@app/infra/entities';
|
||||
import { BadRequestException, ForbiddenException } from '@nestjs/common';
|
||||
import { ForbiddenException } from '@nestjs/common';
|
||||
import {
|
||||
assetEntityStub,
|
||||
authStub,
|
||||
fileStub,
|
||||
newAccessRepositoryMock,
|
||||
newCryptoRepositoryMock,
|
||||
newJobRepositoryMock,
|
||||
newSharedLinkRepositoryMock,
|
||||
newStorageRepositoryMock,
|
||||
sharedLinkResponseStub,
|
||||
sharedLinkStub,
|
||||
} from '@test';
|
||||
import { when } from 'jest-when';
|
||||
import { QueryFailedError, Repository } from 'typeorm';
|
||||
import { DownloadService } from '../../modules/download/download.service';
|
||||
import { IAssetRepository } from './asset-repository';
|
||||
import { AssetService } from './asset.service';
|
||||
import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto';
|
||||
import { CreateAssetDto } from './dto/create-asset.dto';
|
||||
import { TimeGroupEnum } from './dto/get-asset-count-by-time-bucket.dto';
|
||||
import { AssetRejectReason, AssetUploadAction } from './response-dto/asset-check-response.dto';
|
||||
@@ -134,8 +122,6 @@ describe('AssetService', () => {
|
||||
let accessMock: jest.Mocked<IAccessRepository>;
|
||||
let assetRepositoryMock: jest.Mocked<IAssetRepository>;
|
||||
let downloadServiceMock: jest.Mocked<Partial<DownloadService>>;
|
||||
let sharedLinkRepositoryMock: jest.Mocked<ISharedLinkRepository>;
|
||||
let cryptoMock: jest.Mocked<ICryptoRepository>;
|
||||
let jobMock: jest.Mocked<IJobRepository>;
|
||||
let storageMock: jest.Mocked<IStorageRepository>;
|
||||
|
||||
@@ -165,9 +151,7 @@ describe('AssetService', () => {
|
||||
};
|
||||
|
||||
accessMock = newAccessRepositoryMock();
|
||||
sharedLinkRepositoryMock = newSharedLinkRepositoryMock();
|
||||
jobMock = newJobRepositoryMock();
|
||||
cryptoMock = newCryptoRepositoryMock();
|
||||
storageMock = newStorageRepositoryMock();
|
||||
|
||||
sut = new AssetService(
|
||||
@@ -175,9 +159,7 @@ describe('AssetService', () => {
|
||||
assetRepositoryMock,
|
||||
a,
|
||||
downloadServiceMock as DownloadService,
|
||||
sharedLinkRepositoryMock,
|
||||
jobMock,
|
||||
cryptoMock,
|
||||
storageMock,
|
||||
);
|
||||
|
||||
@@ -189,77 +171,6 @@ describe('AssetService', () => {
|
||||
.mockResolvedValue(assetEntityStub.livePhotoMotionAsset);
|
||||
});
|
||||
|
||||
describe('createAssetsSharedLink', () => {
|
||||
it('should create an individual share link', async () => {
|
||||
const asset1 = _getAsset_1();
|
||||
const dto: CreateAssetsShareLinkDto = { assetIds: [asset1.id] };
|
||||
|
||||
assetRepositoryMock.getById.mockResolvedValue(asset1);
|
||||
accessMock.hasOwnerAssetAccess.mockResolvedValue(true);
|
||||
sharedLinkRepositoryMock.create.mockResolvedValue(sharedLinkStub.valid);
|
||||
|
||||
await expect(sut.createAssetsSharedLink(authStub.user1, dto)).resolves.toEqual(sharedLinkResponseStub.valid);
|
||||
|
||||
expect(assetRepositoryMock.getById).toHaveBeenCalledWith(asset1.id);
|
||||
expect(accessMock.hasOwnerAssetAccess).toHaveBeenCalledWith(authStub.user1.id, asset1.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateAssetsInSharedLink', () => {
|
||||
it('should require a valid shared link', async () => {
|
||||
const asset1 = _getAsset_1();
|
||||
|
||||
const authDto = authStub.adminSharedLink;
|
||||
const dto = { assetIds: [asset1.id] };
|
||||
|
||||
assetRepositoryMock.getById.mockResolvedValue(asset1);
|
||||
sharedLinkRepositoryMock.get.mockResolvedValue(null);
|
||||
accessMock.hasSharedLinkAssetAccess.mockResolvedValue(true);
|
||||
|
||||
await expect(sut.addAssetsToSharedLink(authDto, dto)).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
expect(assetRepositoryMock.getById).toHaveBeenCalledWith(asset1.id);
|
||||
expect(sharedLinkRepositoryMock.get).toHaveBeenCalledWith(authDto.id, authDto.sharedLinkId);
|
||||
expect(sharedLinkRepositoryMock.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should add assets to a shared link', async () => {
|
||||
const asset1 = _getAsset_1();
|
||||
|
||||
const authDto = authStub.adminSharedLink;
|
||||
const dto = { assetIds: [asset1.id] };
|
||||
|
||||
assetRepositoryMock.getById.mockResolvedValue(asset1);
|
||||
sharedLinkRepositoryMock.get.mockResolvedValue(sharedLinkStub.valid);
|
||||
accessMock.hasSharedLinkAssetAccess.mockResolvedValue(true);
|
||||
sharedLinkRepositoryMock.update.mockResolvedValue(sharedLinkStub.valid);
|
||||
|
||||
await expect(sut.addAssetsToSharedLink(authDto, dto)).resolves.toEqual(sharedLinkResponseStub.valid);
|
||||
|
||||
expect(assetRepositoryMock.getById).toHaveBeenCalledWith(asset1.id);
|
||||
expect(sharedLinkRepositoryMock.get).toHaveBeenCalledWith(authDto.id, authDto.sharedLinkId);
|
||||
expect(sharedLinkRepositoryMock.update).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should remove assets from a shared link', async () => {
|
||||
const asset1 = _getAsset_1();
|
||||
|
||||
const authDto = authStub.adminSharedLink;
|
||||
const dto = { assetIds: [asset1.id] };
|
||||
|
||||
assetRepositoryMock.getById.mockResolvedValue(asset1);
|
||||
sharedLinkRepositoryMock.get.mockResolvedValue(sharedLinkStub.valid);
|
||||
accessMock.hasSharedLinkAssetAccess.mockResolvedValue(true);
|
||||
sharedLinkRepositoryMock.update.mockResolvedValue(sharedLinkStub.valid);
|
||||
|
||||
await expect(sut.removeAssetsFromSharedLink(authDto, dto)).resolves.toEqual(sharedLinkResponseStub.valid);
|
||||
|
||||
expect(assetRepositoryMock.getById).toHaveBeenCalledWith(asset1.id);
|
||||
expect(sharedLinkRepositoryMock.get).toHaveBeenCalledWith(authDto.id, authDto.sharedLinkId);
|
||||
expect(sharedLinkRepositoryMock.update).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('uploadFile', () => {
|
||||
it('should handle a file upload', async () => {
|
||||
const assetEntity = _getAsset_1();
|
||||
|
||||
@@ -2,19 +2,14 @@ import {
|
||||
AssetResponseDto,
|
||||
getLivePhotoMotionFilename,
|
||||
IAccessRepository,
|
||||
ICryptoRepository,
|
||||
IJobRepository,
|
||||
ImmichReadStream,
|
||||
ISharedLinkRepository,
|
||||
IStorageRepository,
|
||||
JobName,
|
||||
mapAsset,
|
||||
mapAssetWithoutExif,
|
||||
mapSharedLink,
|
||||
SharedLinkCore,
|
||||
SharedLinkResponseDto,
|
||||
} from '@app/domain';
|
||||
import { AssetEntity, AssetType, SharedLinkType } from '@app/infra/entities';
|
||||
import { AssetEntity, AssetType } from '@app/infra/entities';
|
||||
import {
|
||||
BadRequestException,
|
||||
ForbiddenException,
|
||||
@@ -33,15 +28,12 @@ import { QueryFailedError, Repository } from 'typeorm';
|
||||
import { promisify } from 'util';
|
||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||
import { DownloadService } from '../../modules/download/download.service';
|
||||
import { AddAssetsDto } from '../album/dto/add-assets.dto';
|
||||
import { RemoveAssetsDto } from '../album/dto/remove-assets.dto';
|
||||
import { IAssetRepository } from './asset-repository';
|
||||
import { AssetCore } from './asset.core';
|
||||
import { AssetBulkUploadCheckDto } from './dto/asset-check.dto';
|
||||
import { AssetSearchDto } from './dto/asset-search.dto';
|
||||
import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
|
||||
import { CheckExistingAssetsDto } from './dto/check-existing-assets.dto';
|
||||
import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto';
|
||||
import { CreateAssetDto, UploadFile } from './dto/create-asset.dto';
|
||||
import { DeleteAssetDto } from './dto/delete-asset.dto';
|
||||
import { DownloadFilesDto } from './dto/download-files.dto';
|
||||
@@ -80,22 +72,17 @@ interface ServableFile {
|
||||
@Injectable()
|
||||
export class AssetService {
|
||||
readonly logger = new Logger(AssetService.name);
|
||||
private shareCore: SharedLinkCore;
|
||||
private assetCore: AssetCore;
|
||||
|
||||
constructor(
|
||||
@Inject(IAccessRepository) private accessRepository: IAccessRepository,
|
||||
@Inject(IAssetRepository) private _assetRepository: IAssetRepository,
|
||||
@InjectRepository(AssetEntity)
|
||||
private assetRepository: Repository<AssetEntity>,
|
||||
@InjectRepository(AssetEntity) private assetRepository: Repository<AssetEntity>,
|
||||
private downloadService: DownloadService,
|
||||
@Inject(ISharedLinkRepository) sharedLinkRepository: ISharedLinkRepository,
|
||||
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
||||
@Inject(ICryptoRepository) cryptoRepository: ICryptoRepository,
|
||||
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
|
||||
) {
|
||||
this.assetCore = new AssetCore(_assetRepository, jobRepository);
|
||||
this.shareCore = new SharedLinkCore(sharedLinkRepository, cryptoRepository);
|
||||
}
|
||||
|
||||
public async uploadFile(
|
||||
@@ -608,61 +595,9 @@ export class AssetService {
|
||||
}
|
||||
|
||||
private checkDownloadAccess(authUser: AuthUserDto) {
|
||||
this.shareCore.checkDownloadAccess(authUser);
|
||||
}
|
||||
|
||||
async createAssetsSharedLink(authUser: AuthUserDto, dto: CreateAssetsShareLinkDto): Promise<SharedLinkResponseDto> {
|
||||
const assets = [];
|
||||
|
||||
await this.checkAssetsAccess(authUser, dto.assetIds);
|
||||
for (const assetId of dto.assetIds) {
|
||||
const asset = await this._assetRepository.getById(assetId);
|
||||
assets.push(asset);
|
||||
}
|
||||
|
||||
const sharedLink = await this.shareCore.create(authUser.id, {
|
||||
type: SharedLinkType.INDIVIDUAL,
|
||||
expiresAt: dto.expiresAt,
|
||||
allowUpload: dto.allowUpload,
|
||||
assets,
|
||||
description: dto.description,
|
||||
allowDownload: dto.allowDownload,
|
||||
showExif: dto.showExif,
|
||||
});
|
||||
|
||||
return mapSharedLink(sharedLink);
|
||||
}
|
||||
|
||||
async addAssetsToSharedLink(authUser: AuthUserDto, dto: AddAssetsDto): Promise<SharedLinkResponseDto> {
|
||||
if (!authUser.sharedLinkId) {
|
||||
if (authUser.isPublicUser && !authUser.isAllowDownload) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
const assets = [];
|
||||
|
||||
for (const assetId of dto.assetIds) {
|
||||
const asset = await this._assetRepository.getById(assetId);
|
||||
assets.push(asset);
|
||||
}
|
||||
|
||||
const updatedLink = await this.shareCore.addAssets(authUser.id, authUser.sharedLinkId, assets);
|
||||
return mapSharedLink(updatedLink);
|
||||
}
|
||||
|
||||
async removeAssetsFromSharedLink(authUser: AuthUserDto, dto: RemoveAssetsDto): Promise<SharedLinkResponseDto> {
|
||||
if (!authUser.sharedLinkId) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
const assets = [];
|
||||
|
||||
for (const assetId of dto.assetIds) {
|
||||
const asset = await this._assetRepository.getById(assetId);
|
||||
assets.push(asset);
|
||||
}
|
||||
|
||||
const updatedLink = await this.shareCore.removeAssets(authUser.id, authUser.sharedLinkId, assets);
|
||||
return mapSharedLink(updatedLink);
|
||||
}
|
||||
|
||||
getExifPermission(authUser: AuthUserDto) {
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsArray, IsBoolean, IsDate, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class CreateAssetsShareLinkDto {
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
@IsNotEmpty({ each: true })
|
||||
@ApiProperty({
|
||||
isArray: true,
|
||||
type: String,
|
||||
title: 'Array asset IDs to be shared',
|
||||
example: [
|
||||
'bf973405-3f2a-48d2-a687-2ed4167164be',
|
||||
'dd41870b-5d00-46d2-924e-1d8489a0aa0f',
|
||||
'fad77c3f-deef-4e7e-9608-14c1aa4e559a',
|
||||
],
|
||||
})
|
||||
assetIds!: string[];
|
||||
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
@IsOptional()
|
||||
expiresAt?: Date;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
allowUpload?: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
allowDownload?: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
showExif?: boolean;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string;
|
||||
}
|
||||
@@ -1,13 +1,21 @@
|
||||
import { AuthUserDto, EditSharedLinkDto, SharedLinkResponseDto, SharedLinkService } from '@app/domain';
|
||||
import { Body, Controller, Delete, Get, Param, Patch } from '@nestjs/common';
|
||||
import {
|
||||
AssetIdsDto,
|
||||
AssetIdsResponseDto,
|
||||
AuthUserDto,
|
||||
SharedLinkCreateDto,
|
||||
SharedLinkEditDto,
|
||||
SharedLinkResponseDto,
|
||||
SharedLinkService,
|
||||
} from '@app/domain';
|
||||
import { Body, Controller, Delete, Get, Param, Patch, Post, Put } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { AuthUser } from '../decorators/auth-user.decorator';
|
||||
import { Authenticated, SharedLinkRoute } from '../decorators/authenticated.decorator';
|
||||
import { UseValidation } from '../decorators/use-validation.decorator';
|
||||
import { UUIDParamDto } from './dto/uuid-param.dto';
|
||||
|
||||
@ApiTags('share')
|
||||
@Controller('share')
|
||||
@ApiTags('Shared Link')
|
||||
@Controller('shared-link')
|
||||
@Authenticated()
|
||||
@UseValidation()
|
||||
export class SharedLinkController {
|
||||
@@ -29,11 +37,16 @@ export class SharedLinkController {
|
||||
return this.service.get(authUser, id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
createSharedLink(@AuthUser() authUser: AuthUserDto, @Body() dto: SharedLinkCreateDto) {
|
||||
return this.service.create(authUser, dto);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
updateSharedLink(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: EditSharedLinkDto,
|
||||
@Body() dto: SharedLinkEditDto,
|
||||
): Promise<SharedLinkResponseDto> {
|
||||
return this.service.update(authUser, id, dto);
|
||||
}
|
||||
@@ -42,4 +55,24 @@ export class SharedLinkController {
|
||||
removeSharedLink(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.remove(authUser, id);
|
||||
}
|
||||
|
||||
@SharedLinkRoute()
|
||||
@Put(':id/assets')
|
||||
addSharedLinkAssets(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: AssetIdsDto,
|
||||
): Promise<AssetIdsResponseDto[]> {
|
||||
return this.service.addAssets(authUser, id, dto);
|
||||
}
|
||||
|
||||
@SharedLinkRoute()
|
||||
@Delete(':id/assets')
|
||||
removeSharedLinkAssets(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: AssetIdsDto,
|
||||
): Promise<AssetIdsResponseDto[]> {
|
||||
return this.service.removeAssets(authUser, id, dto);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user