mirror of
https://github.com/immich-app/immich.git
synced 2024-12-25 10:43:13 +02:00
feat(server): improve API specification (#1853)
This commit is contained in:
parent
da9b9c8c69
commit
9323cc76d9
BIN
mobile/openapi/README.md
generated
BIN
mobile/openapi/README.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/APIKeyApi.md
generated
BIN
mobile/openapi/doc/APIKeyApi.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/AlbumApi.md
generated
BIN
mobile/openapi/doc/AlbumApi.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/AssetApi.md
generated
BIN
mobile/openapi/doc/AssetApi.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/AuthenticationApi.md
generated
BIN
mobile/openapi/doc/AuthenticationApi.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/DeviceInfoApi.md
generated
BIN
mobile/openapi/doc/DeviceInfoApi.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/JobApi.md
generated
BIN
mobile/openapi/doc/JobApi.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/OAuthApi.md
generated
BIN
mobile/openapi/doc/OAuthApi.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/ServerInfoApi.md
generated
BIN
mobile/openapi/doc/ServerInfoApi.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/ShareApi.md
generated
BIN
mobile/openapi/doc/ShareApi.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/SystemConfigApi.md
generated
BIN
mobile/openapi/doc/SystemConfigApi.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/TagApi.md
generated
BIN
mobile/openapi/doc/TagApi.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/UserApi.md
generated
BIN
mobile/openapi/doc/UserApi.md
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/album_api.dart
generated
BIN
mobile/openapi/lib/api/album_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/asset_api.dart
generated
BIN
mobile/openapi/lib/api/asset_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/share_api.dart
generated
BIN
mobile/openapi/lib/api/share_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/test/album_api_test.dart
generated
BIN
mobile/openapi/test/album_api_test.dart
generated
Binary file not shown.
BIN
mobile/openapi/test/asset_api_test.dart
generated
BIN
mobile/openapi/test/asset_api_test.dart
generated
Binary file not shown.
BIN
mobile/openapi/test/share_api_test.dart
generated
BIN
mobile/openapi/test/share_api_test.dart
generated
Binary file not shown.
@ -22,7 +22,7 @@ import { AddUsersDto } from './dto/add-users.dto';
|
||||
import { RemoveAssetsDto } from './dto/remove-assets.dto';
|
||||
import { UpdateAlbumDto } from './dto/update-album.dto';
|
||||
import { GetAlbumsDto } from './dto/get-albums.dto';
|
||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { AlbumResponseDto } from '@app/domain';
|
||||
import { AlbumCountResponseDto } from './response-dto/album-count-response.dto';
|
||||
import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto';
|
||||
@ -37,7 +37,6 @@ import { CreateAlbumShareLinkDto as CreateAlbumSharedLinkDto } from './dto/creat
|
||||
|
||||
// TODO might be worth creating a AlbumParamsDto that validates `albumId` instead of using the pipe.
|
||||
|
||||
@ApiBearerAuth()
|
||||
@ApiTags('Album')
|
||||
@Controller('album')
|
||||
export class AlbumController {
|
||||
@ -134,12 +133,13 @@ export class AlbumController {
|
||||
|
||||
@Authenticated({ isShared: true })
|
||||
@Get('/:albumId/download')
|
||||
@ApiOkResponse({ content: { 'application/zip': { schema: { type: 'string', format: 'binary' } } } })
|
||||
async downloadArchive(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string,
|
||||
@Query(new ValidationPipe({ transform: true })) dto: DownloadDto,
|
||||
@Response({ passthrough: true }) res: Res,
|
||||
): Promise<any> {
|
||||
) {
|
||||
this.albumService.checkDownloadAccess(authUser);
|
||||
|
||||
const { stream, fileName, fileSize, fileCount, complete } = await this.albumService.downloadArchive(
|
||||
|
@ -28,7 +28,7 @@ import { Response as Res } from 'express';
|
||||
import { DeleteAssetDto } from './dto/delete-asset.dto';
|
||||
import { SearchAssetDto } from './dto/search-asset.dto';
|
||||
import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
|
||||
import { ApiBearerAuth, ApiBody, ApiConsumes, ApiHeader, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiBody, ApiConsumes, ApiHeader, ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto';
|
||||
import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto';
|
||||
import { AssetResponseDto, ImmichReadStream } from '@app/domain';
|
||||
@ -62,7 +62,6 @@ function asStreamableFile({ stream, type, length }: ImmichReadStream) {
|
||||
return new StreamableFile(stream, { type, length });
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@ApiTags('Asset')
|
||||
@Controller('asset')
|
||||
export class AssetController {
|
||||
@ -108,21 +107,23 @@ export class AssetController {
|
||||
|
||||
@Authenticated({ isShared: true })
|
||||
@Get('/download/:assetId')
|
||||
@ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } })
|
||||
async downloadFile(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Response({ passthrough: true }) res: Res,
|
||||
@Param('assetId') assetId: string,
|
||||
): Promise<any> {
|
||||
) {
|
||||
return this.assetService.downloadFile(authUser, assetId).then(asStreamableFile);
|
||||
}
|
||||
|
||||
@Authenticated({ isShared: true })
|
||||
@Post('/download-files')
|
||||
@ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } })
|
||||
async downloadFiles(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Response({ passthrough: true }) res: Res,
|
||||
@Body(new ValidationPipe()) dto: DownloadFilesDto,
|
||||
): Promise<any> {
|
||||
) {
|
||||
this.assetService.checkDownloadAccess(authUser);
|
||||
await this.assetService.checkAssetsAccess(authUser, [...dto.assetIds]);
|
||||
const { stream, fileName, fileSize, fileCount, complete } = await this.assetService.downloadFiles(dto);
|
||||
@ -138,11 +139,12 @@ export class AssetController {
|
||||
*/
|
||||
@Authenticated({ isShared: true })
|
||||
@Get('/download-library')
|
||||
@ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } })
|
||||
async downloadLibrary(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Query(new ValidationPipe({ transform: true })) dto: DownloadDto,
|
||||
@Response({ passthrough: true }) res: Res,
|
||||
): Promise<any> {
|
||||
) {
|
||||
this.assetService.checkDownloadAccess(authUser);
|
||||
const { stream, fileName, fileSize, fileCount, complete } = await this.assetService.downloadLibrary(authUser, dto);
|
||||
res.attachment(fileName);
|
||||
@ -155,13 +157,14 @@ export class AssetController {
|
||||
@Authenticated({ isShared: true })
|
||||
@Get('/file/:assetId')
|
||||
@Header('Cache-Control', 'max-age=31536000')
|
||||
@ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } })
|
||||
async serveFile(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Headers() headers: Record<string, string>,
|
||||
@Response({ passthrough: true }) res: Res,
|
||||
@Query(new ValidationPipe({ transform: true })) query: ServeFileDto,
|
||||
@Param('assetId') assetId: string,
|
||||
): Promise<any> {
|
||||
) {
|
||||
await this.assetService.checkAssetsAccess(authUser, [assetId]);
|
||||
return this.assetService.serveFile(authUser, assetId, query, res, headers);
|
||||
}
|
||||
@ -169,13 +172,14 @@ export class AssetController {
|
||||
@Authenticated({ isShared: true })
|
||||
@Get('/thumbnail/:assetId')
|
||||
@Header('Cache-Control', 'max-age=31536000')
|
||||
@ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } })
|
||||
async getAssetThumbnail(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Headers() headers: Record<string, string>,
|
||||
@Response({ passthrough: true }) res: Res,
|
||||
@Param('assetId') assetId: string,
|
||||
@Query(new ValidationPipe({ transform: true })) query: GetAssetThumbnailDto,
|
||||
): Promise<any> {
|
||||
) {
|
||||
await this.assetService.checkAssetsAccess(authUser, [assetId]);
|
||||
return this.assetService.getAssetThumbnail(assetId, query, res, headers);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Body, Controller, Get, Param, Put, ValidationPipe } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Authenticated } from '../../decorators/authenticated.decorator';
|
||||
import { AllJobStatusResponseDto } from './response-dto/all-job-status-response.dto';
|
||||
import { GetJobDto } from './dto/get-job.dto';
|
||||
@ -8,7 +8,6 @@ import { JobCommandDto } from './dto/job-command.dto';
|
||||
|
||||
@Authenticated({ admin: true })
|
||||
@ApiTags('Job')
|
||||
@ApiBearerAuth()
|
||||
@Controller('jobs')
|
||||
export class JobController {
|
||||
constructor(private readonly jobService: JobService) {}
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
ValidateAccessTokenResponseDto,
|
||||
} from '@app/domain';
|
||||
import { Body, Controller, Ip, Post, Req, Res, ValidationPipe } from '@nestjs/common';
|
||||
import { ApiBadRequestResponse, ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiBadRequestResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { Request, Response } from 'express';
|
||||
import { GetAuthUser } from '../decorators/auth-user.decorator';
|
||||
import { Authenticated } from '../decorators/authenticated.decorator';
|
||||
@ -45,7 +45,6 @@ export class AuthController {
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@ApiBearerAuth()
|
||||
@Post('validateToken')
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
validateAccessToken(@GetAuthUser() authUser: AuthUserDto): ValidateAccessTokenResponseDto {
|
||||
@ -53,7 +52,6 @@ export class AuthController {
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@ApiBearerAuth()
|
||||
@Post('change-password')
|
||||
async changePassword(@GetAuthUser() authUser: AuthUserDto, @Body() dto: ChangePasswordDto): Promise<UserResponseDto> {
|
||||
return this.authService.changePassword(authUser, dto);
|
||||
|
@ -5,12 +5,11 @@ import {
|
||||
UpsertDeviceInfoDto as UpsertDto,
|
||||
} from '@app/domain';
|
||||
import { Body, Controller, Put, ValidationPipe } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { GetAuthUser } from '../decorators/auth-user.decorator';
|
||||
import { Authenticated } from '../decorators/authenticated.decorator';
|
||||
|
||||
@Authenticated()
|
||||
@ApiBearerAuth()
|
||||
@ApiTags('Device Info')
|
||||
@Controller('device-info')
|
||||
export class DeviceInfoController {
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { SystemConfigDto, SystemConfigService, SystemConfigTemplateStorageOptionDto } from '@app/domain';
|
||||
import { Body, Controller, Get, Put, ValidationPipe } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Authenticated } from '../decorators/authenticated.decorator';
|
||||
|
||||
@ApiTags('System Config')
|
||||
@ApiBearerAuth()
|
||||
@Authenticated({ admin: true })
|
||||
@Controller('system-config')
|
||||
export class SystemConfigController {
|
||||
|
@ -23,7 +23,7 @@ import { UpdateUserDto } from '@app/domain';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { profileImageUploadOption } from '../config/profile-image-upload.config';
|
||||
import { Response as Res } from 'express';
|
||||
import { ApiBearerAuth, ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger';
|
||||
import { UserResponseDto } from '@app/domain';
|
||||
import { UserCountResponseDto } from '@app/domain';
|
||||
import { CreateProfileImageDto } from '@app/domain';
|
||||
@ -36,7 +36,6 @@ export class UserController {
|
||||
constructor(private readonly userService: UserService) {}
|
||||
|
||||
@Authenticated()
|
||||
@ApiBearerAuth()
|
||||
@Get()
|
||||
async getAllUsers(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@ -51,14 +50,12 @@ export class UserController {
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@ApiBearerAuth()
|
||||
@Get('me')
|
||||
async getMyUserInfo(@GetAuthUser() authUser: AuthUserDto): Promise<UserResponseDto> {
|
||||
return await this.userService.getUserInfo(authUser);
|
||||
}
|
||||
|
||||
@Authenticated({ admin: true })
|
||||
@ApiBearerAuth()
|
||||
@Post()
|
||||
async createUser(
|
||||
@Body(new ValidationPipe({ transform: true })) createUserDto: CreateUserDto,
|
||||
@ -72,21 +69,18 @@ export class UserController {
|
||||
}
|
||||
|
||||
@Authenticated({ admin: true })
|
||||
@ApiBearerAuth()
|
||||
@Delete('/:userId')
|
||||
async deleteUser(@GetAuthUser() authUser: AuthUserDto, @Param('userId') userId: string): Promise<UserResponseDto> {
|
||||
return await this.userService.deleteUser(authUser, userId);
|
||||
}
|
||||
|
||||
@Authenticated({ admin: true })
|
||||
@ApiBearerAuth()
|
||||
@Post('/:userId/restore')
|
||||
async restoreUser(@GetAuthUser() authUser: AuthUserDto, @Param('userId') userId: string): Promise<UserResponseDto> {
|
||||
return await this.userService.restoreUser(authUser, userId);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@ApiBearerAuth()
|
||||
@Put()
|
||||
async updateUser(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@ -97,7 +91,6 @@ export class UserController {
|
||||
|
||||
@UseInterceptors(FileInterceptor('file', profileImageUploadOption))
|
||||
@Authenticated()
|
||||
@ApiBearerAuth()
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@ApiBody({
|
||||
description: 'A new avatar for the user',
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { applyDecorators, SetMetadata } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiCookieAuth, ApiQuery } from '@nestjs/swagger';
|
||||
|
||||
interface AuthenticatedOptions {
|
||||
admin?: boolean;
|
||||
@ -12,7 +13,7 @@ export enum Metadata {
|
||||
}
|
||||
|
||||
export const Authenticated = (options?: AuthenticatedOptions) => {
|
||||
const decorators = [SetMetadata(Metadata.AUTH_ROUTE, true)];
|
||||
const decorators: MethodDecorator[] = [ApiBearerAuth(), ApiCookieAuth(), SetMetadata(Metadata.AUTH_ROUTE, true)];
|
||||
|
||||
options = options || {};
|
||||
|
||||
@ -22,6 +23,7 @@ export const Authenticated = (options?: AuthenticatedOptions) => {
|
||||
|
||||
if (options.isShared) {
|
||||
decorators.push(SetMetadata(Metadata.SHARED_ROUTE, true));
|
||||
decorators.push(ApiQuery({ name: 'key', type: String, required: false }));
|
||||
}
|
||||
|
||||
return applyDecorators(...decorators);
|
||||
|
@ -11,6 +11,7 @@ import { RedisIoAdapter } from './middlewares/redis-io.adapter.middleware';
|
||||
import { json } from 'body-parser';
|
||||
import { patchOpenAPI } from './utils/patch-open-api.util';
|
||||
import { getLogLevels, MACHINE_LEARNING_ENABLED } from '@app/common';
|
||||
import { IMMICH_ACCESS_COOKIE } from '@app/domain';
|
||||
|
||||
const logger = new Logger('ImmichServer');
|
||||
|
||||
@ -42,6 +43,7 @@ async function bootstrap() {
|
||||
scheme: 'Bearer',
|
||||
in: 'header',
|
||||
})
|
||||
.addCookieAuth(IMMICH_ACCESS_COOKIE)
|
||||
.addServer('/api')
|
||||
.build();
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
524
web/src/api/open-api/api.ts
generated
524
web/src/api/open-api/api.ts
generated
File diff suppressed because it is too large
Load Diff
@ -93,6 +93,7 @@ describe('AlbumCard component', () => {
|
||||
expect(apiMock.assetApi.getAssetThumbnail).toHaveBeenCalledWith(
|
||||
'thumbnailIdOne',
|
||||
ThumbnailFormat.Jpeg,
|
||||
undefined,
|
||||
{ responseType: 'blob' }
|
||||
);
|
||||
expect(createObjectURLMock).toHaveBeenCalledWith(thumbnailBlob);
|
||||
|
@ -34,9 +34,14 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const { data } = await api.assetApi.getAssetThumbnail(thubmnailId, ThumbnailFormat.Jpeg, {
|
||||
responseType: 'blob'
|
||||
});
|
||||
const { data } = await api.assetApi.getAssetThumbnail(
|
||||
thubmnailId,
|
||||
ThumbnailFormat.Jpeg,
|
||||
undefined,
|
||||
{
|
||||
responseType: 'blob'
|
||||
}
|
||||
);
|
||||
|
||||
if (data instanceof Blob) {
|
||||
return URL.createObjectURL(data);
|
||||
|
@ -170,11 +170,7 @@
|
||||
{
|
||||
assetIds: assets.map((a) => a.id)
|
||||
},
|
||||
{
|
||||
params: {
|
||||
key: sharedLink?.key
|
||||
}
|
||||
}
|
||||
sharedLink?.key
|
||||
);
|
||||
|
||||
if (data.album) {
|
||||
@ -269,10 +265,8 @@
|
||||
const { data, status, headers } = await api.albumApi.downloadArchive(
|
||||
album.id,
|
||||
skip || undefined,
|
||||
sharedLink?.key,
|
||||
{
|
||||
params: {
|
||||
key: sharedLink?.key
|
||||
},
|
||||
responseType: 'blob',
|
||||
onDownloadProgress: function (progressEvent) {
|
||||
const request = this as XMLHttpRequest;
|
||||
|
@ -145,8 +145,7 @@
|
||||
|
||||
$downloadAssets[imageFileName] = 0;
|
||||
|
||||
const { data, status } = await api.assetApi.downloadFile(assetId, {
|
||||
params: { key },
|
||||
const { data, status } = await api.assetApi.downloadFile(assetId, key, {
|
||||
responseType: 'blob',
|
||||
onDownloadProgress: (progressEvent) => {
|
||||
if (progressEvent.lengthComputable) {
|
||||
|
@ -26,10 +26,7 @@
|
||||
|
||||
const loadAssetData = async () => {
|
||||
try {
|
||||
const { data } = await api.assetApi.serveFile(asset.id, false, true, {
|
||||
params: {
|
||||
key: publicSharedKey
|
||||
},
|
||||
const { data } = await api.assetApi.serveFile(asset.id, false, true, publicSharedKey, {
|
||||
responseType: 'blob'
|
||||
});
|
||||
|
||||
|
@ -54,11 +54,7 @@
|
||||
{
|
||||
assetIds
|
||||
},
|
||||
{
|
||||
params: {
|
||||
key: sharedLink?.key
|
||||
}
|
||||
}
|
||||
sharedLink?.key
|
||||
);
|
||||
|
||||
notificationController.show({
|
||||
@ -76,11 +72,7 @@
|
||||
{
|
||||
assetIds: assets.filter((a) => !selectedAssets.has(a)).map((a) => a.id)
|
||||
},
|
||||
{
|
||||
params: {
|
||||
key: sharedLink?.key
|
||||
}
|
||||
}
|
||||
sharedLink?.key
|
||||
);
|
||||
|
||||
assets = assets.filter((a) => !selectedAssets.has(a));
|
||||
|
@ -11,9 +11,14 @@
|
||||
return noThumbnailUrl;
|
||||
}
|
||||
|
||||
const { data } = await api.assetApi.getAssetThumbnail(thubmnailId, ThumbnailFormat.Webp, {
|
||||
responseType: 'blob'
|
||||
});
|
||||
const { data } = await api.assetApi.getAssetThumbnail(
|
||||
thubmnailId,
|
||||
ThumbnailFormat.Webp,
|
||||
undefined,
|
||||
{
|
||||
responseType: 'blob'
|
||||
}
|
||||
);
|
||||
if (data instanceof Blob) {
|
||||
return URL.createObjectURL(data);
|
||||
}
|
||||
|
@ -18,19 +18,17 @@ export const addAssetsToAlbum = async (
|
||||
assetIds: Array<string>,
|
||||
key: string | undefined = undefined
|
||||
): Promise<AddAssetsResponseDto> =>
|
||||
api.albumApi
|
||||
.addAssetsToAlbum(albumId, { assetIds }, { params: { key } })
|
||||
.then(({ data: dto }) => {
|
||||
if (dto.successfullyAdded > 0) {
|
||||
// This might be 0 if the user tries to add an asset that is already in the album
|
||||
notificationController.show({
|
||||
message: `Added ${dto.successfullyAdded} to ${dto.album?.albumName}`,
|
||||
type: NotificationType.Info
|
||||
});
|
||||
}
|
||||
api.albumApi.addAssetsToAlbum(albumId, { assetIds }, key).then(({ data: dto }) => {
|
||||
if (dto.successfullyAdded > 0) {
|
||||
// This might be 0 if the user tries to add an asset that is already in the album
|
||||
notificationController.show({
|
||||
message: `Added ${dto.successfullyAdded} to ${dto.album?.albumName}`,
|
||||
type: NotificationType.Info
|
||||
});
|
||||
}
|
||||
|
||||
return dto;
|
||||
});
|
||||
return dto;
|
||||
});
|
||||
|
||||
export async function bulkDownload(
|
||||
fileName: string,
|
||||
@ -53,24 +51,20 @@ export async function bulkDownload(
|
||||
|
||||
let total = 0;
|
||||
|
||||
const { data, status, headers } = await api.assetApi.downloadFiles(
|
||||
{ assetIds },
|
||||
{
|
||||
params: { key },
|
||||
responseType: 'blob',
|
||||
onDownloadProgress: function (progressEvent) {
|
||||
const request = this as XMLHttpRequest;
|
||||
if (!total) {
|
||||
total = Number(request.getResponseHeader('X-Immich-Content-Length-Hint')) || 0;
|
||||
}
|
||||
const { data, status, headers } = await api.assetApi.downloadFiles({ assetIds }, key, {
|
||||
responseType: 'blob',
|
||||
onDownloadProgress: function (progressEvent) {
|
||||
const request = this as XMLHttpRequest;
|
||||
if (!total) {
|
||||
total = Number(request.getResponseHeader('X-Immich-Content-Length-Hint')) || 0;
|
||||
}
|
||||
|
||||
if (total) {
|
||||
const current = progressEvent.loaded;
|
||||
downloadAssets.set({ [downloadFileName]: Math.floor((current / total) * 100) });
|
||||
}
|
||||
if (total) {
|
||||
const current = progressEvent.loaded;
|
||||
downloadAssets.set({ [downloadFileName]: Math.floor((current / total) * 100) });
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const isNotComplete = headers['x-immich-archive-complete'] === 'false';
|
||||
const fileCount = Number(headers['x-immich-archive-file-count']) || 0;
|
||||
|
@ -108,11 +108,7 @@ async function fileUploader(
|
||||
deviceAssetId: String(deviceAssetId),
|
||||
deviceId: 'WEB'
|
||||
},
|
||||
{
|
||||
params: {
|
||||
key: sharedKey
|
||||
}
|
||||
}
|
||||
sharedKey
|
||||
);
|
||||
|
||||
if (status === 200 && data.isExist && data.id) {
|
||||
|
@ -12,7 +12,7 @@ export const load: PageServerLoad = async ({ params, parent }) => {
|
||||
const { key } = params;
|
||||
|
||||
try {
|
||||
const { data: sharedLink } = await api.shareApi.getMySharedLink({ params: { key } });
|
||||
const { data: sharedLink } = await api.shareApi.getMySharedLink(key);
|
||||
|
||||
const assetCount = sharedLink.assets.length;
|
||||
const assetId = sharedLink.album?.albumThumbnailAssetId || sharedLink.assets[0]?.id;
|
||||
|
@ -7,9 +7,7 @@ import type { PageServerLoad } from './$types';
|
||||
export const load: PageServerLoad = async ({ params }) => {
|
||||
try {
|
||||
const { key, assetId } = params;
|
||||
const { data: asset } = await api.assetApi.getAssetById(assetId, {
|
||||
params: { key }
|
||||
});
|
||||
const { data: asset } = await api.assetApi.getAssetById(assetId, key);
|
||||
|
||||
if (!asset) {
|
||||
return error(404, 'Asset not found');
|
||||
|
Loading…
Reference in New Issue
Block a user