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