feat(server): better api error messages (for unhandled exceptions) (#4817)

* feat(server): better error messages

* chore: open api

* chore: remove debug log

* fix: syntax error

* fix: e2e test
This commit is contained in:
Jason Rasmussen 2023-11-03 21:33:15 -04:00 committed by GitHub
parent d4ef6f52bb
commit 2e424fe249
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
72 changed files with 1337 additions and 1315 deletions

File diff suppressed because it is too large Load diff

View file

@ -17,7 +17,7 @@ class OAuthService {
// Resolve API server endpoint from user provided serverUrl // Resolve API server endpoint from user provided serverUrl
await _apiService.resolveAndSetEndpoint(serverUrl); await _apiService.resolveAndSetEndpoint(serverUrl);
return await _apiService.oAuthApi.generateConfig( return await _apiService.oAuthApi.generateOAuthConfig(
OAuthConfigDto(redirectUri: '$callbackUrlScheme:/'), OAuthConfigDto(redirectUri: '$callbackUrlScheme:/'),
); );
} }
@ -29,7 +29,7 @@ class OAuthService {
callbackUrlScheme: callbackUrlScheme, callbackUrlScheme: callbackUrlScheme,
); );
return await _apiService.oAuthApi.callback( return await _apiService.oAuthApi.finishOAuth(
OAuthCallbackDto( OAuthCallbackDto(
url: result, url: result,
), ),

View file

@ -65,7 +65,7 @@ class LoginForm extends HookConsumerWidget {
isLoadingServer.value = true; isLoadingServer.value = true;
final endpoint = await apiService.resolveAndSetEndpoint(serverUrl); final endpoint = await apiService.resolveAndSetEndpoint(serverUrl);
final loginConfig = await apiService.oAuthApi.generateConfig( final loginConfig = await apiService.oAuthApi.generateOAuthConfig(
OAuthConfigDto(redirectUri: serverUrl), OAuthConfigDto(redirectUri: serverUrl),
); );

BIN
mobile/openapi/README.md generated

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -678,7 +678,7 @@
}, },
"/api-key": { "/api-key": {
"get": { "get": {
"operationId": "getKeys", "operationId": "getApiKeys",
"parameters": [], "parameters": [],
"responses": { "responses": {
"200": { "200": {
@ -711,7 +711,7 @@
] ]
}, },
"post": { "post": {
"operationId": "createKey", "operationId": "createApiKey",
"parameters": [], "parameters": [],
"requestBody": { "requestBody": {
"content": { "content": {
@ -753,7 +753,7 @@
}, },
"/api-key/{id}": { "/api-key/{id}": {
"delete": { "delete": {
"operationId": "deleteKey", "operationId": "deleteApiKey",
"parameters": [ "parameters": [
{ {
"name": "id", "name": "id",
@ -786,7 +786,7 @@
] ]
}, },
"get": { "get": {
"operationId": "getKey", "operationId": "getApiKey",
"parameters": [ "parameters": [
{ {
"name": "id", "name": "id",
@ -826,7 +826,7 @@
] ]
}, },
"put": { "put": {
"operationId": "updateKey", "operationId": "updateApiKey",
"parameters": [ "parameters": [
{ {
"name": "id", "name": "id",
@ -1084,7 +1084,7 @@
"/asset/bulk-upload-check": { "/asset/bulk-upload-check": {
"post": { "post": {
"description": "Checks if assets exist by checksums", "description": "Checks if assets exist by checksums",
"operationId": "bulkUploadCheck", "operationId": "checkBulkUpload",
"parameters": [], "parameters": [],
"requestBody": { "requestBody": {
"content": { "content": {
@ -1855,7 +1855,7 @@
}, },
"/asset/statistics": { "/asset/statistics": {
"get": { "get": {
"operationId": "getAssetStats", "operationId": "getAssetStatistics",
"parameters": [ "parameters": [
{ {
"name": "isArchived", "name": "isArchived",
@ -1977,7 +1977,7 @@
}, },
"/asset/time-bucket": { "/asset/time-bucket": {
"get": { "get": {
"operationId": "getByTimeBucket", "operationId": "getTimeBucket",
"parameters": [ "parameters": [
{ {
"name": "size", "name": "size",
@ -2596,7 +2596,7 @@
}, },
"/auth/admin-sign-up": { "/auth/admin-sign-up": {
"post": { "post": {
"operationId": "adminSignUp", "operationId": "signUpAdmin",
"parameters": [], "parameters": [],
"requestBody": { "requestBody": {
"content": { "content": {
@ -2943,7 +2943,7 @@
}, },
"/library": { "/library": {
"get": { "get": {
"operationId": "getAllForUser", "operationId": "getLibraries",
"parameters": [], "parameters": [],
"responses": { "responses": {
"200": { "200": {
@ -3265,7 +3265,7 @@
}, },
"/oauth/authorize": { "/oauth/authorize": {
"post": { "post": {
"operationId": "authorizeOAuth", "operationId": "startOAuth",
"parameters": [], "parameters": [],
"requestBody": { "requestBody": {
"content": { "content": {
@ -3296,7 +3296,7 @@
}, },
"/oauth/callback": { "/oauth/callback": {
"post": { "post": {
"operationId": "callback", "operationId": "finishOAuth",
"parameters": [], "parameters": [],
"requestBody": { "requestBody": {
"content": { "content": {
@ -3329,7 +3329,7 @@
"post": { "post": {
"deprecated": true, "deprecated": true,
"description": "@deprecated use feature flags and /oauth/authorize", "description": "@deprecated use feature flags and /oauth/authorize",
"operationId": "generateConfig", "operationId": "generateOAuthConfig",
"parameters": [], "parameters": [],
"requestBody": { "requestBody": {
"content": { "content": {
@ -3360,7 +3360,7 @@
}, },
"/oauth/link": { "/oauth/link": {
"post": { "post": {
"operationId": "link", "operationId": "linkOAuthAccount",
"parameters": [], "parameters": [],
"requestBody": { "requestBody": {
"content": { "content": {
@ -3402,7 +3402,7 @@
}, },
"/oauth/mobile-redirect": { "/oauth/mobile-redirect": {
"get": { "get": {
"operationId": "mobileRedirect", "operationId": "redirectOAuthToMobile",
"parameters": [], "parameters": [],
"responses": { "responses": {
"200": { "200": {
@ -3416,7 +3416,7 @@
}, },
"/oauth/unlink": { "/oauth/unlink": {
"post": { "post": {
"operationId": "unlink", "operationId": "unlinkOAuthAccount",
"parameters": [], "parameters": [],
"responses": { "responses": {
"201": { "201": {
@ -4307,9 +4307,9 @@
] ]
} }
}, },
"/server-info/stats": { "/server-info/statistics": {
"get": { "get": {
"operationId": "getStats", "operationId": "getServerStatistics",
"parameters": [], "parameters": [],
"responses": { "responses": {
"200": { "200": {
@ -4837,7 +4837,7 @@
}, },
"/system-config/defaults": { "/system-config/defaults": {
"get": { "get": {
"operationId": "getDefaults", "operationId": "getConfigDefaults",
"parameters": [], "parameters": [],
"responses": { "responses": {
"200": { "200": {

View file

@ -331,17 +331,17 @@ describe(AssetService.name, () => {
}); });
}); });
describe('getByTimeBucket', () => { describe('getTimeBucket', () => {
it('should return the assets for a album time bucket if user has album.read', async () => { it('should return the assets for a album time bucket if user has album.read', async () => {
accessMock.album.hasOwnerAccess.mockResolvedValue(true); accessMock.album.hasOwnerAccess.mockResolvedValue(true);
assetMock.getByTimeBucket.mockResolvedValue([assetStub.image]); assetMock.getTimeBucket.mockResolvedValue([assetStub.image]);
await expect( await expect(
sut.getByTimeBucket(authStub.admin, { size: TimeBucketSize.DAY, timeBucket: 'bucket', albumId: 'album-id' }), sut.getTimeBucket(authStub.admin, { size: TimeBucketSize.DAY, timeBucket: 'bucket', albumId: 'album-id' }),
).resolves.toEqual(expect.arrayContaining([expect.objectContaining({ id: 'asset-id' })])); ).resolves.toEqual(expect.arrayContaining([expect.objectContaining({ id: 'asset-id' })]));
expect(accessMock.album.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'album-id'); expect(accessMock.album.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'album-id');
expect(assetMock.getByTimeBucket).toBeCalledWith('bucket', { expect(assetMock.getTimeBucket).toBeCalledWith('bucket', {
size: TimeBucketSize.DAY, size: TimeBucketSize.DAY,
timeBucket: 'bucket', timeBucket: 'bucket',
albumId: 'album-id', albumId: 'album-id',
@ -349,17 +349,17 @@ describe(AssetService.name, () => {
}); });
it('should return the assets for a archive time bucket if user has archive.read', async () => { it('should return the assets for a archive time bucket if user has archive.read', async () => {
assetMock.getByTimeBucket.mockResolvedValue([assetStub.image]); assetMock.getTimeBucket.mockResolvedValue([assetStub.image]);
await expect( await expect(
sut.getByTimeBucket(authStub.admin, { sut.getTimeBucket(authStub.admin, {
size: TimeBucketSize.DAY, size: TimeBucketSize.DAY,
timeBucket: 'bucket', timeBucket: 'bucket',
isArchived: true, isArchived: true,
userId: authStub.admin.id, userId: authStub.admin.id,
}), }),
).resolves.toEqual(expect.arrayContaining([expect.objectContaining({ id: 'asset-id' })])); ).resolves.toEqual(expect.arrayContaining([expect.objectContaining({ id: 'asset-id' })]));
expect(assetMock.getByTimeBucket).toBeCalledWith('bucket', { expect(assetMock.getTimeBucket).toBeCalledWith('bucket', {
size: TimeBucketSize.DAY, size: TimeBucketSize.DAY,
timeBucket: 'bucket', timeBucket: 'bucket',
isArchived: true, isArchived: true,
@ -368,16 +368,16 @@ describe(AssetService.name, () => {
}); });
it('should return the assets for a library time bucket if user has library.read', async () => { it('should return the assets for a library time bucket if user has library.read', async () => {
assetMock.getByTimeBucket.mockResolvedValue([assetStub.image]); assetMock.getTimeBucket.mockResolvedValue([assetStub.image]);
await expect( await expect(
sut.getByTimeBucket(authStub.admin, { sut.getTimeBucket(authStub.admin, {
size: TimeBucketSize.DAY, size: TimeBucketSize.DAY,
timeBucket: 'bucket', timeBucket: 'bucket',
userId: authStub.admin.id, userId: authStub.admin.id,
}), }),
).resolves.toEqual(expect.arrayContaining([expect.objectContaining({ id: 'asset-id' })])); ).resolves.toEqual(expect.arrayContaining([expect.objectContaining({ id: 'asset-id' })]));
expect(assetMock.getByTimeBucket).toBeCalledWith('bucket', { expect(assetMock.getTimeBucket).toBeCalledWith('bucket', {
size: TimeBucketSize.DAY, size: TimeBucketSize.DAY,
timeBucket: 'bucket', timeBucket: 'bucket',
userId: authStub.admin.id, userId: authStub.admin.id,

View file

@ -194,12 +194,12 @@ export class AssetService {
return this.assetRepository.getTimeBuckets(dto); return this.assetRepository.getTimeBuckets(dto);
} }
async getByTimeBucket( async getTimeBucket(
authUser: AuthUserDto, authUser: AuthUserDto,
dto: TimeBucketAssetDto, dto: TimeBucketAssetDto,
): Promise<AssetResponseDto[] | SanitizedAssetResponseDto[]> { ): Promise<AssetResponseDto[] | SanitizedAssetResponseDto[]> {
await this.timeBucketChecks(authUser, dto); await this.timeBucketChecks(authUser, dto);
const assets = await this.assetRepository.getByTimeBucket(dto.timeBucket, dto); const assets = await this.assetRepository.getTimeBucket(dto.timeBucket, dto);
if (authUser.isShowMetadata) { if (authUser.isShowMetadata) {
return assets.map((asset) => mapAsset(asset, { withStack: true })); return assets.map((asset) => mapAsset(asset, { withStack: true }));
} else { } else {

View file

@ -315,13 +315,8 @@ export class AuthService {
const redirectUri = this.normalize(config, url.split('?')[0]); const redirectUri = this.normalize(config, url.split('?')[0]);
const client = await this.getOAuthClient(config); const client = await this.getOAuthClient(config);
const params = client.callbackParams(url); const params = client.callbackParams(url);
try { const tokens = await client.callback(redirectUri, params, { state: params.state });
const tokens = await client.callback(redirectUri, params, { state: params.state }); return client.userinfo<OAuthProfile>(tokens.access_token || '');
return client.userinfo<OAuthProfile>(tokens.access_token || '');
} catch (error: Error | any) {
this.logger.error(`Unable to complete OAuth login: ${error}`, error?.stack);
throw new InternalServerErrorException(`Unable to complete OAuth login: ${error}`, { cause: error });
}
} }
private async getOAuthClient(config: SystemConfig) { private async getOAuthClient(config: SystemConfig) {

View file

@ -123,6 +123,6 @@ export interface IAssetRepository {
getMapMarkers(ownerId: string, options?: MapMarkerSearchOptions): Promise<MapMarker[]>; getMapMarkers(ownerId: string, options?: MapMarkerSearchOptions): Promise<MapMarker[]>;
getStatistics(ownerId: string, options: AssetStatsOptions): Promise<AssetStats>; getStatistics(ownerId: string, options: AssetStatsOptions): Promise<AssetStats>;
getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]>; getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]>;
getByTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise<AssetEntity[]>; getTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise<AssetEntity[]>;
upsertExif(exif: Partial<ExifEntity>): Promise<void>; upsertExif(exif: Partial<ExifEntity>): Promise<void>;
} }

View file

@ -220,7 +220,7 @@ describe(ServerInfoService.name, () => {
}, },
]); ]);
await expect(sut.getStats()).resolves.toEqual({ await expect(sut.getStatistics()).resolves.toEqual({
photos: 120, photos: 120,
videos: 31, videos: 31,
usage: 1123455, usage: 1123455,

View file

@ -92,7 +92,7 @@ export class ServerInfoService {
}; };
} }
async getStats(): Promise<ServerStatsResponseDto> { async getStatistics(): Promise<ServerStatsResponseDto> {
const userStats: UserStatsQueryResponse[] = await this.userRepository.getUserStats(); const userStats: UserStatsQueryResponse[] = await this.userRepository.getUserStats();
const serverStats = new ServerStatsResponseDto(); const serverStats = new ServerStatsResponseDto();

View file

@ -1,5 +1,5 @@
import { LibraryType, UserEntity } from '@app/infra/entities'; import { LibraryType, UserEntity } from '@app/infra/entities';
import { BadRequestException, ForbiddenException, InternalServerErrorException, Logger } from '@nestjs/common'; import { BadRequestException, ForbiddenException } from '@nestjs/common';
import path from 'path'; import path from 'path';
import sanitize from 'sanitize-filename'; import sanitize from 'sanitize-filename';
import { AuthUserDto } from '../auth'; import { AuthUserDto } from '../auth';
@ -61,26 +61,21 @@ export class UserCore {
} }
} }
try { if (dto.password) {
if (dto.password) { dto.password = await this.cryptoRepository.hashBcrypt(dto.password, SALT_ROUNDS);
dto.password = await this.cryptoRepository.hashBcrypt(dto.password, SALT_ROUNDS);
}
if (dto.storageLabel === '') {
dto.storageLabel = null;
}
if (dto.externalPath === '') {
dto.externalPath = null;
} else if (dto.externalPath) {
dto.externalPath = path.normalize(dto.externalPath);
}
return this.userRepository.update(id, dto);
} catch (e) {
Logger.error(e, 'Failed to update user info');
throw new InternalServerErrorException('Failed to update user info');
} }
if (dto.storageLabel === '') {
dto.storageLabel = null;
}
if (dto.externalPath === '') {
dto.externalPath = null;
} else if (dto.externalPath) {
dto.externalPath = path.normalize(dto.externalPath);
}
return this.userRepository.update(id, dto);
} }
async createUser(dto: Partial<UserEntity> & { email: string }): Promise<UserEntity> { async createUser(dto: Partial<UserEntity> & { email: string }): Promise<UserEntity> {
@ -96,30 +91,25 @@ export class UserCore {
} }
} }
try { const payload: Partial<UserEntity> = { ...dto };
const payload: Partial<UserEntity> = { ...dto }; if (payload.password) {
if (payload.password) { payload.password = await this.cryptoRepository.hashBcrypt(payload.password, SALT_ROUNDS);
payload.password = await this.cryptoRepository.hashBcrypt(payload.password, SALT_ROUNDS);
}
if (payload.storageLabel) {
payload.storageLabel = sanitize(payload.storageLabel);
}
const userEntity = await this.userRepository.create(payload);
await this.libraryRepository.create({
owner: { id: userEntity.id } as UserEntity,
name: 'Default Library',
assets: [],
type: LibraryType.UPLOAD,
importPaths: [],
exclusionPatterns: [],
isVisible: true,
});
return userEntity;
} catch (e) {
Logger.error(e, 'Create new user');
throw new InternalServerErrorException('Failed to register new user');
} }
if (payload.storageLabel) {
payload.storageLabel = sanitize(payload.storageLabel);
}
const userEntity = await this.userRepository.create(payload);
await this.libraryRepository.create({
owner: { id: userEntity.id } as UserEntity,
name: 'Default Library',
assets: [],
type: LibraryType.UPLOAD,
importPaths: [],
exclusionPatterns: [],
isVisible: true,
});
return userEntity;
} }
} }

View file

@ -17,8 +17,8 @@ import {
import { ApiBody, ApiConsumes, ApiHeader, ApiOkResponse, ApiTags } from '@nestjs/swagger'; import { ApiBody, ApiConsumes, ApiHeader, ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { Response as Res } from 'express'; import { Response as Res } from 'express';
import { AuthUser, Authenticated, SharedLinkRoute } from '../../app.guard'; import { AuthUser, Authenticated, SharedLinkRoute } from '../../app.guard';
import { FileUploadInterceptor, ImmichFile, Route, mapToUploadFile } from '../../app.interceptor';
import { UUIDParamDto } from '../../controllers/dto/uuid-param.dto'; import { UUIDParamDto } from '../../controllers/dto/uuid-param.dto';
import { FileUploadInterceptor, ImmichFile, Route, mapToUploadFile } from '../../interceptors';
import FileNotEmptyValidator from '../validation/file-not-empty-validator'; import FileNotEmptyValidator from '../validation/file-not-empty-validator';
import { AssetService } from './asset.service'; import { AssetService } from './asset.service';
import { AssetBulkUploadCheckDto } from './dto/asset-check.dto'; import { AssetBulkUploadCheckDto } from './dto/asset-check.dto';
@ -204,7 +204,7 @@ export class AssetController {
*/ */
@Post('/bulk-upload-check') @Post('/bulk-upload-check')
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
bulkUploadCheck( checkBulkUpload(
@AuthUser() authUser: AuthUserDto, @AuthUser() authUser: AuthUserDto,
@Body(ValidationPipe) dto: AssetBulkUploadCheckDto, @Body(ValidationPipe) dto: AssetBulkUploadCheckDto,
): Promise<AssetBulkUploadCheckResponseDto> { ): Promise<AssetBulkUploadCheckResponseDto> {

View file

@ -2,14 +2,13 @@ import { DomainModule } from '@app/domain';
import { InfraModule } from '@app/infra'; import { InfraModule } from '@app/infra';
import { AssetEntity } from '@app/infra/entities'; import { AssetEntity } from '@app/infra/entities';
import { Module, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; import { Module, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core'; import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
import { ScheduleModule } from '@nestjs/schedule'; import { ScheduleModule } from '@nestjs/schedule';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { AssetRepository, IAssetRepository } from './api-v1/asset/asset-repository'; import { AssetRepository, IAssetRepository } from './api-v1/asset/asset-repository';
import { AssetController as AssetControllerV1 } from './api-v1/asset/asset.controller'; import { AssetController as AssetControllerV1 } from './api-v1/asset/asset.controller';
import { AssetService } from './api-v1/asset/asset.service'; import { AssetService } from './api-v1/asset/asset.service';
import { AppGuard } from './app.guard'; import { AppGuard } from './app.guard';
import { FileUploadInterceptor } from './app.interceptor';
import { AppService } from './app.service'; import { AppService } from './app.service';
import { import {
APIKeyController, APIKeyController,
@ -31,6 +30,7 @@ import {
TagController, TagController,
UserController, UserController,
} from './controllers'; } from './controllers';
import { ErrorInterceptor, FileUploadInterceptor } from './interceptors';
@Module({ @Module({
imports: [ imports: [
@ -61,10 +61,9 @@ import {
PersonController, PersonController,
], ],
providers: [ providers: [
// { provide: APP_INTERCEPTOR, useClass: ErrorInterceptor },
{ provide: APP_GUARD, useExisting: AppGuard }, { provide: APP_GUARD, useClass: AppGuard },
{ provide: IAssetRepository, useClass: AssetRepository }, { provide: IAssetRepository, useClass: AssetRepository },
AppGuard,
AppService, AppService,
AssetService, AssetService,
FileUploadInterceptor, FileUploadInterceptor,

View file

@ -47,6 +47,9 @@ function sortKeys<T>(obj: T): T {
return result as T; return result as T;
} }
export const routeToErrorMessage = (methodName: string) =>
'Failed to ' + methodName.replace(/[A-Z]+/g, (letter) => ` ${letter.toLowerCase()}`);
const patchOpenAPI = (document: OpenAPIObject) => { const patchOpenAPI = (document: OpenAPIObject) => {
document.paths = sortKeys(document.paths); document.paths = sortKeys(document.paths);
if (document.components?.schemas) { if (document.components?.schemas) {
@ -78,6 +81,10 @@ const patchOpenAPI = (document: OpenAPIObject) => {
delete operation.summary; delete operation.summary;
} }
if (operation.operationId) {
// console.log(`${routeToErrorMessage(operation.operationId).padEnd(40)} (${operation.operationId})`);
}
if (operation.description === '') { if (operation.description === '') {
delete operation.description; delete operation.description;
} }

View file

@ -20,22 +20,22 @@ export class APIKeyController {
constructor(private service: APIKeyService) {} constructor(private service: APIKeyService) {}
@Post() @Post()
createKey(@AuthUser() authUser: AuthUserDto, @Body() dto: APIKeyCreateDto): Promise<APIKeyCreateResponseDto> { createApiKey(@AuthUser() authUser: AuthUserDto, @Body() dto: APIKeyCreateDto): Promise<APIKeyCreateResponseDto> {
return this.service.create(authUser, dto); return this.service.create(authUser, dto);
} }
@Get() @Get()
getKeys(@AuthUser() authUser: AuthUserDto): Promise<APIKeyResponseDto[]> { getApiKeys(@AuthUser() authUser: AuthUserDto): Promise<APIKeyResponseDto[]> {
return this.service.getAll(authUser); return this.service.getAll(authUser);
} }
@Get(':id') @Get(':id')
getKey(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<APIKeyResponseDto> { getApiKey(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<APIKeyResponseDto> {
return this.service.getById(authUser, id); return this.service.getById(authUser, id);
} }
@Put(':id') @Put(':id')
updateKey( updateApiKey(
@AuthUser() authUser: AuthUserDto, @AuthUser() authUser: AuthUserDto,
@Param() { id }: UUIDParamDto, @Param() { id }: UUIDParamDto,
@Body() dto: APIKeyUpdateDto, @Body() dto: APIKeyUpdateDto,
@ -44,7 +44,7 @@ export class APIKeyController {
} }
@Delete(':id') @Delete(':id')
deleteKey(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<void> { deleteApiKey(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.delete(authUser, id); return this.service.delete(authUser, id);
} }
} }

View file

@ -39,10 +39,11 @@ import {
import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { AuthUser, Authenticated, SharedLinkRoute } from '../app.guard'; import { AuthUser, Authenticated, SharedLinkRoute } from '../app.guard';
import { UseValidation, asStreamableFile } from '../app.utils'; import { UseValidation, asStreamableFile } from '../app.utils';
import { Route } from '../interceptors';
import { UUIDParamDto } from './dto/uuid-param.dto'; import { UUIDParamDto } from './dto/uuid-param.dto';
@ApiTags('Asset') @ApiTags('Asset')
@Controller('asset') @Controller(Route.ASSET)
@Authenticated() @Authenticated()
@UseValidation() @UseValidation()
export class AssetController { export class AssetController {
@ -86,7 +87,7 @@ export class AssetController {
} }
@Get('statistics') @Get('statistics')
getAssetStats(@AuthUser() authUser: AuthUserDto, @Query() dto: AssetStatsDto): Promise<AssetStatsResponseDto> { getAssetStatistics(@AuthUser() authUser: AuthUserDto, @Query() dto: AssetStatsDto): Promise<AssetStatsResponseDto> {
return this.service.getStatistics(authUser, dto); return this.service.getStatistics(authUser, dto);
} }
@ -98,8 +99,8 @@ export class AssetController {
@Authenticated({ isShared: true }) @Authenticated({ isShared: true })
@Get('time-bucket') @Get('time-bucket')
getByTimeBucket(@AuthUser() authUser: AuthUserDto, @Query() dto: TimeBucketAssetDto): Promise<AssetResponseDto[]> { getTimeBucket(@AuthUser() authUser: AuthUserDto, @Query() dto: TimeBucketAssetDto): Promise<AssetResponseDto[]> {
return this.service.getByTimeBucket(authUser, dto) as Promise<AssetResponseDto[]>; return this.service.getTimeBucket(authUser, dto) as Promise<AssetResponseDto[]>;
} }
@Post('jobs') @Post('jobs')

View file

@ -43,7 +43,7 @@ export class AuthController {
@PublicRoute() @PublicRoute()
@Post('admin-sign-up') @Post('admin-sign-up')
@ApiBadRequestResponse({ description: 'The server already has an admin' }) @ApiBadRequestResponse({ description: 'The server already has an admin' })
adminSignUp(@Body() signUpCredential: SignUpDto): Promise<AdminSignupResponseDto> { signUpAdmin(@Body() signUpCredential: SignUpDto): Promise<AdminSignupResponseDto> {
return this.service.adminSignUp(signUpCredential); return this.service.adminSignUp(signUpCredential);
} }

View file

@ -21,7 +21,7 @@ export class LibraryController {
constructor(private service: LibraryService) {} constructor(private service: LibraryService) {}
@Get() @Get()
getAllForUser(@AuthUser() authUser: AuthUserDto): Promise<ResponseDto[]> { getLibraries(@AuthUser() authUser: AuthUserDto): Promise<ResponseDto[]> {
return this.service.getAllForUser(authUser); return this.service.getAllForUser(authUser);
} }

View file

@ -25,7 +25,7 @@ export class OAuthController {
@PublicRoute() @PublicRoute()
@Get('mobile-redirect') @Get('mobile-redirect')
@Redirect() @Redirect()
mobileRedirect(@Req() req: Request) { redirectOAuthToMobile(@Req() req: Request) {
return { return {
url: this.service.getMobileRedirect(req.url), url: this.service.getMobileRedirect(req.url),
statusCode: HttpStatus.TEMPORARY_REDIRECT, statusCode: HttpStatus.TEMPORARY_REDIRECT,
@ -35,19 +35,19 @@ export class OAuthController {
/** @deprecated use feature flags and /oauth/authorize */ /** @deprecated use feature flags and /oauth/authorize */
@PublicRoute() @PublicRoute()
@Post('config') @Post('config')
generateConfig(@Body() dto: OAuthConfigDto): Promise<OAuthConfigResponseDto> { generateOAuthConfig(@Body() dto: OAuthConfigDto): Promise<OAuthConfigResponseDto> {
return this.service.generateConfig(dto); return this.service.generateConfig(dto);
} }
@PublicRoute() @PublicRoute()
@Post('authorize') @Post('authorize')
authorizeOAuth(@Body() dto: OAuthConfigDto): Promise<OAuthAuthorizeResponseDto> { startOAuth(@Body() dto: OAuthConfigDto): Promise<OAuthAuthorizeResponseDto> {
return this.service.authorize(dto); return this.service.authorize(dto);
} }
@PublicRoute() @PublicRoute()
@Post('callback') @Post('callback')
async callback( async finishOAuth(
@Res({ passthrough: true }) res: Response, @Res({ passthrough: true }) res: Response,
@Body() dto: OAuthCallbackDto, @Body() dto: OAuthCallbackDto,
@GetLoginDetails() loginDetails: LoginDetails, @GetLoginDetails() loginDetails: LoginDetails,
@ -58,12 +58,12 @@ export class OAuthController {
} }
@Post('link') @Post('link')
link(@AuthUser() authUser: AuthUserDto, @Body() dto: OAuthCallbackDto): Promise<UserResponseDto> { linkOAuthAccount(@AuthUser() authUser: AuthUserDto, @Body() dto: OAuthCallbackDto): Promise<UserResponseDto> {
return this.service.link(authUser, dto); return this.service.link(authUser, dto);
} }
@Post('unlink') @Post('unlink')
unlink(@AuthUser() authUser: AuthUserDto): Promise<UserResponseDto> { unlinkOAuthAccount(@AuthUser() authUser: AuthUserDto): Promise<UserResponseDto> {
return this.service.unlink(authUser); return this.service.unlink(authUser);
} }
} }

View file

@ -57,9 +57,9 @@ export class ServerInfoController {
} }
@AdminRoute() @AdminRoute()
@Get('stats') @Get('statistics')
getStats(): Promise<ServerStatsResponseDto> { getServerStatistics(): Promise<ServerStatsResponseDto> {
return this.service.getStats(); return this.service.getStatistics();
} }
@PublicRoute() @PublicRoute()

View file

@ -17,7 +17,7 @@ export class SystemConfigController {
} }
@Get('defaults') @Get('defaults')
getDefaults(): SystemConfigDto { getConfigDefaults(): SystemConfigDto {
return this.service.getDefaults(); return this.service.getDefaults();
} }

View file

@ -22,8 +22,8 @@ import {
} from '@nestjs/common'; } from '@nestjs/common';
import { ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger'; import { ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger';
import { AdminRoute, AuthUser, Authenticated } from '../app.guard'; import { AdminRoute, AuthUser, Authenticated } from '../app.guard';
import { FileUploadInterceptor, Route } from '../app.interceptor';
import { UseValidation, asStreamableFile } from '../app.utils'; import { UseValidation, asStreamableFile } from '../app.utils';
import { FileUploadInterceptor, Route } from '../interceptors';
import { UUIDParamDto } from './dto/uuid-param.dto'; import { UUIDParamDto } from './dto/uuid-param.dto';
@ApiTags('User') @ApiTags('User')

View file

@ -0,0 +1,32 @@
import {
CallHandler,
ExecutionContext,
HttpException,
Injectable,
InternalServerErrorException,
Logger,
NestInterceptor,
} from '@nestjs/common';
import { Observable, catchError, throwError } from 'rxjs';
import { routeToErrorMessage } from '../app.utils';
@Injectable()
export class ErrorInterceptor implements NestInterceptor {
private logger = new Logger(ErrorInterceptor.name);
async intercept(context: ExecutionContext, next: CallHandler<any>): Promise<Observable<any>> {
return next.handle().pipe(
catchError((error) =>
throwError(() => {
if (error instanceof HttpException === false) {
const errorMessage = routeToErrorMessage(context.getHandler().name);
this.logger.error(errorMessage, error, error?.errors);
return new InternalServerErrorException(errorMessage);
} else {
return error;
}
}),
),
);
}
}

View file

@ -7,7 +7,7 @@ import { createHash } from 'crypto';
import { NextFunction, RequestHandler } from 'express'; import { NextFunction, RequestHandler } from 'express';
import multer, { StorageEngine, diskStorage } from 'multer'; import multer, { StorageEngine, diskStorage } from 'multer';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { AuthRequest } from './app.guard'; import { AuthRequest } from '../app.guard';
export enum Route { export enum Route {
ASSET = 'asset', ASSET = 'asset',

View file

@ -0,0 +1,2 @@
export * from './error.interceptor';
export * from './file.interceptor';

View file

@ -493,7 +493,7 @@ export class AssetRepository implements IAssetRepository {
.getRawMany(); .getRawMany();
} }
getByTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise<AssetEntity[]> { getTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise<AssetEntity[]> {
const truncated = dateTrunc(options); const truncated = dateTrunc(options);
return ( return (
this.getBuilder(options) this.getBuilder(options)

View file

@ -103,9 +103,9 @@ describe(`${ServerInfoController.name} (e2e)`, () => {
}); });
}); });
describe('GET /server-info/stats', () => { describe('GET /server-info/statistics', () => {
it('should require authentication', async () => { it('should require authentication', async () => {
const { status, body } = await request(server).get('/server-info/stats'); const { status, body } = await request(server).get('/server-info/statistics');
expect(status).toBe(401); expect(status).toBe(401);
expect(body).toEqual(errorStub.unauthorized); expect(body).toEqual(errorStub.unauthorized);
}); });
@ -115,7 +115,7 @@ describe(`${ServerInfoController.name} (e2e)`, () => {
await api.userApi.create(server, accessToken, { ...loginDto, firstName: 'test', lastName: 'test' }); await api.userApi.create(server, accessToken, { ...loginDto, firstName: 'test', lastName: 'test' });
const { accessToken: userAccessToken } = await api.authApi.login(server, loginDto); const { accessToken: userAccessToken } = await api.authApi.login(server, loginDto);
const { status, body } = await request(server) const { status, body } = await request(server)
.get('/server-info/stats') .get('/server-info/statistics')
.set('Authorization', `Bearer ${userAccessToken}`); .set('Authorization', `Bearer ${userAccessToken}`);
expect(status).toBe(403); expect(status).toBe(403);
expect(body).toEqual(errorStub.forbidden); expect(body).toEqual(errorStub.forbidden);
@ -123,7 +123,7 @@ describe(`${ServerInfoController.name} (e2e)`, () => {
it('should return the server stats', async () => { it('should return the server stats', async () => {
const { status, body } = await request(server) const { status, body } = await request(server)
.get('/server-info/stats') .get('/server-info/statistics')
.set('Authorization', `Bearer ${accessToken}`); .set('Authorization', `Bearer ${accessToken}`);
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toEqual({ expect(body).toEqual({

View file

@ -26,7 +26,7 @@ export const newAssetRepositoryMock = (): jest.Mocked<IAssetRepository> => {
findLivePhotoMatch: jest.fn(), findLivePhotoMatch: jest.fn(),
getMapMarkers: jest.fn(), getMapMarkers: jest.fn(),
getStatistics: jest.fn(), getStatistics: jest.fn(),
getByTimeBucket: jest.fn(), getTimeBucket: jest.fn(),
getTimeBuckets: jest.fn(), getTimeBuckets: jest.fn(),
restoreAll: jest.fn(), restoreAll: jest.fn(),
softDeleteAll: jest.fn(), softDeleteAll: jest.fn(),

File diff suppressed because it is too large Load diff

View file

@ -36,23 +36,19 @@ export const oauth = {
authorize: async (location: Location) => { authorize: async (location: Location) => {
try { try {
const redirectUri = location.href.split('?')[0]; const redirectUri = location.href.split('?')[0];
const { data } = await api.oauthApi.authorizeOAuth({ oAuthConfigDto: { redirectUri } }); const { data } = await api.oauthApi.startOAuth({ oAuthConfigDto: { redirectUri } });
goto(data.url); goto(data.url);
} catch (error) { } catch (error) {
handleError(error, 'Unable to login with OAuth'); handleError(error, 'Unable to login with OAuth');
} }
}, },
getConfig: (location: Location) => {
const redirectUri = location.href.split('?')[0];
return api.oauthApi.generateConfig({ oAuthConfigDto: { redirectUri } });
},
login: (location: Location) => { login: (location: Location) => {
return api.oauthApi.callback({ oAuthCallbackDto: { url: location.href } }); return api.oauthApi.finishOAuth({ oAuthCallbackDto: { url: location.href } });
}, },
link: (location: Location): AxiosPromise<UserResponseDto> => { link: (location: Location): AxiosPromise<UserResponseDto> => {
return api.oauthApi.link({ oAuthCallbackDto: { url: location.href } }); return api.oauthApi.linkOAuthAccount({ oAuthCallbackDto: { url: location.href } });
}, },
unlink: () => { unlink: () => {
return api.oauthApi.unlink(); return api.oauthApi.unlinkOAuthAccount();
}, },
}; };

View file

@ -32,7 +32,7 @@
async function getConfigs() { async function getConfigs() {
[savedConfig, defaultConfig] = await Promise.all([ [savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.ffmpeg), api.systemConfigApi.getConfig().then((res) => res.data.ffmpeg),
api.systemConfigApi.getDefaults().then((res) => res.data.ffmpeg), api.systemConfigApi.getConfigDefaults().then((res) => res.data.ffmpeg),
]); ]);
} }
@ -76,7 +76,7 @@
} }
async function resetToDefault() { async function resetToDefault() {
const { data: configs } = await api.systemConfigApi.getDefaults(); const { data: configs } = await api.systemConfigApi.getConfigDefaults();
ffmpegConfig = { ...configs.ffmpeg }; ffmpegConfig = { ...configs.ffmpeg };
defaultConfig = { ...configs.ffmpeg }; defaultConfig = { ...configs.ffmpeg };

View file

@ -22,7 +22,7 @@
async function getConfigs() { async function getConfigs() {
[savedConfig, defaultConfig] = await Promise.all([ [savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.job), api.systemConfigApi.getConfig().then((res) => res.data.job),
api.systemConfigApi.getDefaults().then((res) => res.data.job), api.systemConfigApi.getConfigDefaults().then((res) => res.data.job),
]); ]);
} }
@ -59,7 +59,7 @@
} }
async function resetToDefault() { async function resetToDefault() {
const { data: configs } = await api.systemConfigApi.getDefaults(); const { data: configs } = await api.systemConfigApi.getConfigDefaults();
jobConfig = { ...configs.job }; jobConfig = { ...configs.job };
defaultConfig = { ...configs.job }; defaultConfig = { ...configs.job };

View file

@ -28,7 +28,7 @@
async function getConfigs() { async function getConfigs() {
[savedConfig, defaultConfig] = await Promise.all([ [savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.library), api.systemConfigApi.getConfig().then((res) => res.data.library),
api.systemConfigApi.getDefaults().then((res) => res.data.library), api.systemConfigApi.getConfigDefaults().then((res) => res.data.library),
]); ]);
} }
@ -68,7 +68,7 @@
} }
async function resetToDefault() { async function resetToDefault() {
const { data: configs } = await api.systemConfigApi.getDefaults(); const { data: configs } = await api.systemConfigApi.getConfigDefaults();
libraryConfig = { ...configs.library }; libraryConfig = { ...configs.library };
defaultConfig = { ...configs.library }; defaultConfig = { ...configs.library };

View file

@ -22,7 +22,7 @@
async function refreshConfig() { async function refreshConfig() {
[savedConfig, defaultConfig] = await Promise.all([ [savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.machineLearning), api.systemConfigApi.getConfig().then((res) => res.data.machineLearning),
api.systemConfigApi.getDefaults().then((res) => res.data.machineLearning), api.systemConfigApi.getConfigDefaults().then((res) => res.data.machineLearning),
]); ]);
} }

View file

@ -22,7 +22,7 @@
async function refreshConfig() { async function refreshConfig() {
[savedConfig, defaultConfig] = await Promise.all([ [savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data), api.systemConfigApi.getConfig().then((res) => res.data),
api.systemConfigApi.getDefaults().then((res) => res.data), api.systemConfigApi.getConfigDefaults().then((res) => res.data),
]); ]);
} }
@ -65,7 +65,7 @@
} }
async function resetToDefault() { async function resetToDefault() {
const { data: configs } = await api.systemConfigApi.getDefaults(); const { data: configs } = await api.systemConfigApi.getConfigDefaults();
config = cloneDeep(configs); config = cloneDeep(configs);
defaultConfig = cloneDeep(configs); defaultConfig = cloneDeep(configs);

View file

@ -18,7 +18,7 @@
async function getConfigs() { async function getConfigs() {
[savedConfig, defaultConfig] = await Promise.all([ [savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.newVersionCheck), api.systemConfigApi.getConfig().then((res) => res.data.newVersionCheck),
api.systemConfigApi.getDefaults().then((res) => res.data.newVersionCheck), api.systemConfigApi.getConfigDefaults().then((res) => res.data.newVersionCheck),
]); ]);
} }
@ -55,7 +55,7 @@
} }
async function resetToDefault() { async function resetToDefault() {
const { data: configs } = await api.systemConfigApi.getDefaults(); const { data: configs } = await api.systemConfigApi.getConfigDefaults();
newVersionCheckConfig = { ...configs.newVersionCheck }; newVersionCheckConfig = { ...configs.newVersionCheck };
defaultConfig = { ...configs.newVersionCheck }; defaultConfig = { ...configs.newVersionCheck };

View file

@ -29,7 +29,7 @@
async function getConfigs() { async function getConfigs() {
[savedConfig, defaultConfig] = await Promise.all([ [savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.oauth), api.systemConfigApi.getConfig().then((res) => res.data.oauth),
api.systemConfigApi.getDefaults().then((res) => res.data.oauth), api.systemConfigApi.getConfigDefaults().then((res) => res.data.oauth),
]); ]);
} }
@ -90,7 +90,7 @@
} }
async function resetToDefault() { async function resetToDefault() {
const { data: defaultConfig } = await api.systemConfigApi.getDefaults(); const { data: defaultConfig } = await api.systemConfigApi.getConfigDefaults();
oauthConfig = { ...defaultConfig.oauth }; oauthConfig = { ...defaultConfig.oauth };

View file

@ -20,7 +20,7 @@
async function getConfigs() { async function getConfigs() {
[savedConfig, defaultConfig] = await Promise.all([ [savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.passwordLogin), api.systemConfigApi.getConfig().then((res) => res.data.passwordLogin),
api.systemConfigApi.getDefaults().then((res) => res.data.passwordLogin), api.systemConfigApi.getConfigDefaults().then((res) => res.data.passwordLogin),
]); ]);
} }
@ -77,7 +77,7 @@
} }
async function resetToDefault() { async function resetToDefault() {
const { data: configs } = await api.systemConfigApi.getDefaults(); const { data: configs } = await api.systemConfigApi.getConfigDefaults();
passwordLoginConfig = { ...configs.passwordLogin }; passwordLoginConfig = { ...configs.passwordLogin };
defaultConfig = { ...configs.passwordLogin }; defaultConfig = { ...configs.passwordLogin };

View file

@ -26,7 +26,7 @@
async function getConfigs() { async function getConfigs() {
[savedConfig, defaultConfig, templateOptions] = await Promise.all([ [savedConfig, defaultConfig, templateOptions] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.storageTemplate), api.systemConfigApi.getConfig().then((res) => res.data.storageTemplate),
api.systemConfigApi.getDefaults().then((res) => res.data.storageTemplate), api.systemConfigApi.getConfigDefaults().then((res) => res.data.storageTemplate),
api.systemConfigApi.getStorageTemplateOptions().then((res) => res.data), api.systemConfigApi.getStorageTemplateOptions().then((res) => res.data),
]); ]);
@ -119,7 +119,7 @@
} }
async function resetToDefault() { async function resetToDefault() {
const { data: defaultConfig } = await api.systemConfigApi.getDefaults(); const { data: defaultConfig } = await api.systemConfigApi.getConfigDefaults();
storageConfig.template = defaultConfig.storageTemplate.template; storageConfig.template = defaultConfig.storageTemplate.template;

View file

@ -19,7 +19,7 @@
async function getConfigs() { async function getConfigs() {
[savedConfig, defaultConfig] = await Promise.all([ [savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.theme), api.systemConfigApi.getConfig().then((res) => res.data.theme),
api.systemConfigApi.getDefaults().then((res) => res.data.theme), api.systemConfigApi.getConfigDefaults().then((res) => res.data.theme),
]); ]);
} }
@ -56,7 +56,7 @@
} }
async function resetToDefault() { async function resetToDefault() {
const { data: configs } = await api.systemConfigApi.getDefaults(); const { data: configs } = await api.systemConfigApi.getConfigDefaults();
themeConfig = { ...configs.theme }; themeConfig = { ...configs.theme };
defaultConfig = { ...configs.theme }; defaultConfig = { ...configs.theme };

View file

@ -20,7 +20,7 @@
async function getConfigs() { async function getConfigs() {
[savedConfig, defaultConfig] = await Promise.all([ [savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.thumbnail), api.systemConfigApi.getConfig().then((res) => res.data.thumbnail),
api.systemConfigApi.getDefaults().then((res) => res.data.thumbnail), api.systemConfigApi.getConfigDefaults().then((res) => res.data.thumbnail),
]); ]);
} }
@ -37,7 +37,7 @@
} }
async function resetToDefault() { async function resetToDefault() {
const { data: configs } = await api.systemConfigApi.getDefaults(); const { data: configs } = await api.systemConfigApi.getConfigDefaults();
thumbnailConfig = { ...configs.thumbnail }; thumbnailConfig = { ...configs.thumbnail };
defaultConfig = { ...configs.thumbnail }; defaultConfig = { ...configs.thumbnail };

View file

@ -20,7 +20,7 @@
async function getConfigs() { async function getConfigs() {
[savedConfig, defaultConfig] = await Promise.all([ [savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.trash), api.systemConfigApi.getConfig().then((res) => res.data.trash),
api.systemConfigApi.getDefaults().then((res) => res.data.trash), api.systemConfigApi.getConfigDefaults().then((res) => res.data.trash),
]); ]);
} }
@ -53,7 +53,7 @@
} }
async function resetToDefault() { async function resetToDefault() {
const { data: configs } = await api.systemConfigApi.getDefaults(); const { data: configs } = await api.systemConfigApi.getConfigDefaults();
trashConfig = { ...configs.trash }; trashConfig = { ...configs.trash };
defaultConfig = { ...configs.trash }; defaultConfig = { ...configs.trash };

View file

@ -30,7 +30,7 @@
const firstName = form.get('firstName'); const firstName = form.get('firstName');
const lastName = form.get('lastName'); const lastName = form.get('lastName');
const { status } = await api.authenticationApi.adminSignUp({ const { status } = await api.authenticationApi.signUpAdmin({
signUpDto: { signUpDto: {
email: String(email), email: String(email),
password: String(password), password: String(password),

View file

@ -2,7 +2,7 @@
import { page } from '$app/stores'; import { page } from '$app/stores';
import { locale, sidebarSettings } from '$lib/stores/preferences.store'; import { locale, sidebarSettings } from '$lib/stores/preferences.store';
import { featureFlags } from '$lib/stores/server-config.store'; import { featureFlags } from '$lib/stores/server-config.store';
import { AssetApiGetAssetStatsRequest, api } from '@api'; import { AssetApiGetAssetStatisticsRequest, api } from '@api';
import { import {
mdiAccount, mdiAccount,
mdiAccountMultiple, mdiAccountMultiple,
@ -23,8 +23,8 @@
import SideBarButton from './side-bar-button.svelte'; import SideBarButton from './side-bar-button.svelte';
import SideBarSection from './side-bar-section.svelte'; import SideBarSection from './side-bar-section.svelte';
const getStats = async (dto: AssetApiGetAssetStatsRequest) => { const getStats = async (dto: AssetApiGetAssetStatisticsRequest) => {
const { data: stats } = await api.assetApi.getAssetStats(dto); const { data: stats } = await api.assetApi.getAssetStatistics(dto);
return stats; return stats;
}; };

View file

@ -82,7 +82,7 @@
}; };
async function readLibraryList() { async function readLibraryList() {
const { data } = await api.libraryApi.getAllForUser(); const { data } = await api.libraryApi.getLibraries();
libraries = data; libraries = data;
dropdownOpen.length = libraries.length; dropdownOpen.length = libraries.length;

View file

@ -25,14 +25,14 @@
}; };
async function refreshKeys() { async function refreshKeys() {
const { data } = await api.keyApi.getKeys(); const { data } = await api.keyApi.getApiKeys();
keys = data; keys = data;
} }
const handleCreate = async (event: CustomEvent<APIKeyResponseDto>) => { const handleCreate = async (event: CustomEvent<APIKeyResponseDto>) => {
try { try {
const dto = event.detail; const dto = event.detail;
const { data } = await api.keyApi.createKey({ aPIKeyCreateDto: dto }); const { data } = await api.keyApi.createApiKey({ aPIKeyCreateDto: dto });
secret = data.secret; secret = data.secret;
} catch (error) { } catch (error) {
handleError(error, 'Unable to create a new API Key'); handleError(error, 'Unable to create a new API Key');
@ -50,7 +50,7 @@
const dto = event.detail; const dto = event.detail;
try { try {
await api.keyApi.updateKey({ id: editKey.id, aPIKeyUpdateDto: { name: dto.name } }); await api.keyApi.updateApiKey({ id: editKey.id, aPIKeyUpdateDto: { name: dto.name } });
notificationController.show({ notificationController.show({
message: `Saved API Key`, message: `Saved API Key`,
type: NotificationType.Info, type: NotificationType.Info,
@ -69,7 +69,7 @@
} }
try { try {
await api.keyApi.deleteKey({ id: deleteKey.id }); await api.keyApi.deleteApiKey({ id: deleteKey.id });
notificationController.show({ notificationController.show({
message: `Removed API Key: ${deleteKey.name}`, message: `Removed API Key: ${deleteKey.name}`,
type: NotificationType.Info, type: NotificationType.Info,

View file

@ -196,7 +196,7 @@ export class AssetStore {
bucket.cancelToken = new AbortController(); bucket.cancelToken = new AbortController();
const { data: assets } = await api.assetApi.getByTimeBucket( const { data: assets } = await api.assetApi.getTimeBucket(
{ {
...this.options, ...this.options,
timeBucket: bucketDate, timeBucket: bucketDate,
@ -206,7 +206,7 @@ export class AssetStore {
); );
if (this.albumId) { if (this.albumId) {
const { data: albumAssets } = await api.assetApi.getByTimeBucket( const { data: albumAssets } = await api.assetApi.getTimeBucket(
{ {
albumId: this.albumId, albumId: this.albumId,
timeBucket: bucketDate, timeBucket: bucketDate,

View file

@ -8,7 +8,7 @@ export const load = (async ({ parent, locals }) => {
throw redirect(302, AppRoute.AUTH_LOGIN); throw redirect(302, AppRoute.AUTH_LOGIN);
} }
const { data: keys } = await locals.api.keyApi.getKeys(); const { data: keys } = await locals.api.keyApi.getApiKeys();
const { data: devices } = await locals.api.authenticationApi.getAuthDevices(); const { data: devices } = await locals.api.authenticationApi.getAuthDevices();
const { data: partners } = await locals.api.partnerApi.getPartners({ direction: 'shared-by' }); const { data: partners } = await locals.api.partnerApi.getPartners({ direction: 'shared-by' });

View file

@ -11,7 +11,7 @@ export const load = (async ({ parent, locals: { api } }) => {
throw redirect(302, AppRoute.PHOTOS); throw redirect(302, AppRoute.PHOTOS);
} }
const { data: stats } = await api.serverInfoApi.getStats(); const { data: stats } = await api.serverInfoApi.getServerStatistics();
return { return {
user, user,

View file

@ -11,7 +11,7 @@
onMount(async () => { onMount(async () => {
setIntervalHandler = setInterval(async () => { setIntervalHandler = setInterval(async () => {
const { data: stats } = await api.serverInfoApi.getStats(); const { data: stats } = await api.serverInfoApi.getServerStatistics();
data.stats = stats; data.stats = stats;
}, 5000); }, 5000);
}); });