mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-10-29 17:40:28 +00:00
feat(web) Individual assets shared mechanism (#1317)
* Create shared link modal for individual asset * Added API to create asset shared link * Added viewer for individual shared link * Added multiselection app bar * Refactor gallery viewer to its own component * Refactor * Refactor * Add and remove asset from shared link * Fixed test * Fixed notification card doesn't wrap * Add check asset access when created asset shared link * pr feedback
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
Header,
|
||||
Put,
|
||||
UploadedFiles,
|
||||
Patch,
|
||||
} from '@nestjs/common';
|
||||
import { Authenticated } from '../../decorators/authenticated.decorator';
|
||||
import { AssetService } from './asset.service';
|
||||
@@ -50,6 +51,9 @@ import {
|
||||
IMMICH_CONTENT_LENGTH_HINT,
|
||||
} from '../../constants/download.constant';
|
||||
import { DownloadFilesDto } from './dto/download-files.dto';
|
||||
import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto';
|
||||
import { SharedLinkResponseDto } from '../share/response-dto/shared-link-response.dto';
|
||||
import { UpdateAssetsToSharedLinkDto } from './dto/add-assets-to-shared-link.dto';
|
||||
|
||||
@ApiBearerAuth()
|
||||
@ApiTags('Asset')
|
||||
@@ -321,4 +325,22 @@ export class AssetController {
|
||||
): Promise<CheckExistingAssetsResponseDto> {
|
||||
return await this.assetService.checkExistingAssets(authUser, checkExistingAssetsDto);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Post('/shared-link')
|
||||
async createAssetsSharedLink(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Body(ValidationPipe) dto: CreateAssetsShareLinkDto,
|
||||
): Promise<SharedLinkResponseDto> {
|
||||
return await this.assetService.createAssetsSharedLink(authUser, dto);
|
||||
}
|
||||
|
||||
@Authenticated({ isShared: true })
|
||||
@Patch('/shared-link')
|
||||
async updateAssetsInSharedLink(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Body(ValidationPipe) dto: UpdateAssetsToSharedLinkDto,
|
||||
): Promise<SharedLinkResponseDto> {
|
||||
return await this.assetService.updateAssetsInSharedLink(authUser, dto);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { createHash, randomUUID } from 'node:crypto';
|
||||
import { QueryFailedError, Repository } from 'typeorm';
|
||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||
import { AssetEntity, AssetType } from '@app/infra';
|
||||
import { AssetEntity, AssetType, SharedLinkType } from '@app/infra';
|
||||
import { constants, createReadStream, ReadStream, stat } from 'fs';
|
||||
import { ServeFileDto } from './dto/serve-file.dto';
|
||||
import { Response as Res } from 'express';
|
||||
@@ -59,6 +59,9 @@ import { StorageService } from '@app/storage';
|
||||
import { ShareCore } from '../share/share.core';
|
||||
import { ISharedLinkRepository } from '../share/shared-link.repository';
|
||||
import { DownloadFilesDto } from './dto/download-files.dto';
|
||||
import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto';
|
||||
import { mapSharedLinkToResponseDto, SharedLinkResponseDto } from '../share/response-dto/shared-link-response.dto';
|
||||
import { UpdateAssetsToSharedLinkDto } from './dto/add-assets-to-shared-link.dto';
|
||||
|
||||
const fileInfo = promisify(stat);
|
||||
|
||||
@@ -699,6 +702,42 @@ export class AssetService {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
}
|
||||
|
||||
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.createSharedLink(authUser.id, {
|
||||
sharedType: SharedLinkType.INDIVIDUAL,
|
||||
expiredAt: dto.expiredAt,
|
||||
allowUpload: dto.allowUpload,
|
||||
assets: assets,
|
||||
description: dto.description,
|
||||
});
|
||||
|
||||
return mapSharedLinkToResponseDto(sharedLink);
|
||||
}
|
||||
|
||||
async updateAssetsInSharedLink(
|
||||
authUser: AuthUserDto,
|
||||
dto: UpdateAssetsToSharedLinkDto,
|
||||
): 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.updateAssetsInSharedLink(authUser.sharedLinkId, assets);
|
||||
return mapSharedLinkToResponseDto(updatedLink);
|
||||
}
|
||||
}
|
||||
|
||||
async function processETag(path: string, res: Res, headers: Record<string, string>): Promise<boolean> {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { IsNotEmpty } from 'class-validator';
|
||||
|
||||
export class UpdateAssetsToSharedLinkDto {
|
||||
@IsNotEmpty()
|
||||
assetIds!: string[];
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsArray, IsBoolean, 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[];
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
expiredAt?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
allowUpload?: boolean;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user