mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-24 18:56:32 -05:00
enhancement(auth): better error message handling
Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
parent
8e57188ab5
commit
ca9836d691
37 changed files with 199 additions and 207 deletions
|
@ -3,14 +3,10 @@
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import {
|
import { CanActivate, Inject, Injectable } from '@nestjs/common';
|
||||||
BadRequestException,
|
|
||||||
CanActivate,
|
|
||||||
Inject,
|
|
||||||
Injectable,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
|
|
||||||
import authConfiguration, { AuthConfig } from '../../config/auth.config';
|
import authConfiguration, { AuthConfig } from '../../config/auth.config';
|
||||||
|
import { FeatureDisabledError } from '../../errors/errors';
|
||||||
import { ConsoleLoggerService } from '../../logger/console-logger.service';
|
import { ConsoleLoggerService } from '../../logger/console-logger.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -26,7 +22,7 @@ export class LoginEnabledGuard implements CanActivate {
|
||||||
canActivate(): boolean {
|
canActivate(): boolean {
|
||||||
if (!this.authConfig.local.enableLogin) {
|
if (!this.authConfig.local.enableLogin) {
|
||||||
this.logger.debug('Local auth is disabled.', 'canActivate');
|
this.logger.debug('Local auth is disabled.', 'canActivate');
|
||||||
throw new BadRequestException('Local auth is disabled.');
|
throw new FeatureDisabledError('Local auth is disabled.');
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
import { CanActivate, Inject, Injectable } from '@nestjs/common';
|
import { CanActivate, Inject, Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import authConfiguration, { AuthConfig } from '../../config/auth.config';
|
import authConfiguration, { AuthConfig } from '../../config/auth.config';
|
||||||
import { RegistrationDisabledError } from '../../errors/errors';
|
import { FeatureDisabledError } from '../../errors/errors';
|
||||||
import { ConsoleLoggerService } from '../../logger/console-logger.service';
|
import { ConsoleLoggerService } from '../../logger/console-logger.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -22,7 +22,7 @@ export class RegistrationEnabledGuard implements CanActivate {
|
||||||
canActivate(): boolean {
|
canActivate(): boolean {
|
||||||
if (!this.authConfig.local.enableRegister) {
|
if (!this.authConfig.local.enableRegister) {
|
||||||
this.logger.debug('User registration is disabled.', 'canActivate');
|
this.logger.debug('User registration is disabled.', 'canActivate');
|
||||||
throw new RegistrationDisabledError();
|
throw new FeatureDisabledError('User registration is disabled');
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,7 @@ const mapOfHedgeDocErrorsToHttpErrors: Map<string, HttpExceptionConstructor> =
|
||||||
(object): HttpException => new PayloadTooLargeException(object),
|
(object): HttpException => new PayloadTooLargeException(object),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'RegistrationDisabledError',
|
'FeatureDisabledError',
|
||||||
(object): HttpException => new ForbiddenException(object),
|
(object): HttpException => new ForbiddenException(object),
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -60,6 +60,6 @@ export class MaximumDocumentLengthExceededError extends Error {
|
||||||
name = 'MaximumDocumentLengthExceededError';
|
name = 'MaximumDocumentLengthExceededError';
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RegistrationDisabledError extends Error {
|
export class FeatureDisabledError extends Error {
|
||||||
name = 'RegistrationDisabledError';
|
name = 'FeatureDisabledError';
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ describe('Auth', () => {
|
||||||
let displayName: string;
|
let displayName: string;
|
||||||
let password: string;
|
let password: string;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeEach(async () => {
|
||||||
testSetup = await TestSetupBuilder.create().build();
|
testSetup = await TestSetupBuilder.create().build();
|
||||||
await testSetup.app.init();
|
await testSetup.app.init();
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ describe('Auth', () => {
|
||||||
password = 'test_password';
|
password = 'test_password';
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterEach(async () => {
|
||||||
// Yes, this is a bad hack, but there is a race somewhere and I have
|
// Yes, this is a bad hack, but there is a race somewhere and I have
|
||||||
// no idea how to fix it.
|
// no idea how to fix it.
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
|
@ -193,7 +193,7 @@ describe('Auth', () => {
|
||||||
.set('Content-Type', 'application/json')
|
.set('Content-Type', 'application/json')
|
||||||
.set('Cookie', cookie)
|
.set('Cookie', cookie)
|
||||||
.send(JSON.stringify(changePasswordDto))
|
.send(JSON.stringify(changePasswordDto))
|
||||||
.expect(400);
|
.expect(403);
|
||||||
// enable login again
|
// enable login again
|
||||||
testSetup.configService.get('authConfig').local.enableLogin = true;
|
testSetup.configService.get('authConfig').local.enableLogin = true;
|
||||||
// new password doesn't work for login
|
// new password doesn't work for login
|
||||||
|
|
|
@ -43,10 +43,8 @@
|
||||||
"creating": "Creating note...",
|
"creating": "Creating note...",
|
||||||
"error": "Note couldn't be created.",
|
"error": "Note couldn't be created.",
|
||||||
"success": "Note has been created."
|
"success": "Note has been created."
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"api": {
|
"error": {
|
||||||
"note": {
|
|
||||||
"notFound": {
|
"notFound": {
|
||||||
"title": "Note not found",
|
"title": "Note not found",
|
||||||
"description": "The requested note doesn't exist."
|
"description": "The requested note doesn't exist."
|
||||||
|
@ -563,10 +561,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"register": {
|
"register": {
|
||||||
|
"question": "No account yet?",
|
||||||
"title": "Register",
|
"title": "Register",
|
||||||
"passwordAgain": "Password (again)",
|
"passwordAgain": "Password (again)",
|
||||||
"usernameInfo": "The username is your unique identifier for login.",
|
"usernameInfo": "The username is your unique identifier for login.",
|
||||||
"passwordInfo": "Choose a unique and secure password. It must contain at least 8 characters.",
|
"passwordInfo": "Choose a unique and secure password.",
|
||||||
"infoTermsPrivacy": "With the registration of my user account I agree to the following terms:",
|
"infoTermsPrivacy": "With the registration of my user account I agree to the following terms:",
|
||||||
"success": {
|
"success": {
|
||||||
"title": "Successfully registered",
|
"title": "Successfully registered",
|
||||||
|
|
|
@ -17,7 +17,7 @@ import type { Alias, NewAliasDto, PrimaryAliasDto } from './types'
|
||||||
* @throws {Error} when the api request wasn't successfull
|
* @throws {Error} when the api request wasn't successfull
|
||||||
*/
|
*/
|
||||||
export const addAlias = async (noteIdOrAlias: string, newAlias: string): Promise<Alias> => {
|
export const addAlias = async (noteIdOrAlias: string, newAlias: string): Promise<Alias> => {
|
||||||
const response = await new PostApiRequestBuilder<Alias, NewAliasDto>('alias', 'alias')
|
const response = await new PostApiRequestBuilder<Alias, NewAliasDto>('alias')
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
noteIdOrAlias,
|
noteIdOrAlias,
|
||||||
newAlias
|
newAlias
|
||||||
|
@ -35,7 +35,7 @@ export const addAlias = async (noteIdOrAlias: string, newAlias: string): Promise
|
||||||
* @throws {Error} when the api request wasn't successfull
|
* @throws {Error} when the api request wasn't successfull
|
||||||
*/
|
*/
|
||||||
export const markAliasAsPrimary = async (alias: string): Promise<Alias> => {
|
export const markAliasAsPrimary = async (alias: string): Promise<Alias> => {
|
||||||
const response = await new PutApiRequestBuilder<Alias, PrimaryAliasDto>('alias/' + alias, 'alias')
|
const response = await new PutApiRequestBuilder<Alias, PrimaryAliasDto>('alias/' + alias)
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
primaryAlias: true
|
primaryAlias: true
|
||||||
})
|
})
|
||||||
|
@ -50,5 +50,5 @@ export const markAliasAsPrimary = async (alias: string): Promise<Alias> => {
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const deleteAlias = async (alias: string): Promise<void> => {
|
export const deleteAlias = async (alias: string): Promise<void> => {
|
||||||
await new DeleteApiRequestBuilder('alias/' + alias, 'alias').sendRequest()
|
await new DeleteApiRequestBuilder('alias/' + alias).sendRequest()
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,5 +11,5 @@ import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-ap
|
||||||
* @throws {Error} if logout is not possible.
|
* @throws {Error} if logout is not possible.
|
||||||
*/
|
*/
|
||||||
export const doLogout = async (): Promise<void> => {
|
export const doLogout = async (): Promise<void> => {
|
||||||
await new DeleteApiRequestBuilder('auth/logout', 'auth').sendRequest()
|
await new DeleteApiRequestBuilder('auth/logout').sendRequest()
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import type { LoginDto } from './types'
|
||||||
* @throws {Error} when the api request wasn't successfull
|
* @throws {Error} when the api request wasn't successfull
|
||||||
*/
|
*/
|
||||||
export const doLdapLogin = async (provider: string, username: string, password: string): Promise<void> => {
|
export const doLdapLogin = async (provider: string, username: string, password: string): Promise<void> => {
|
||||||
await new PostApiRequestBuilder<void, LoginDto>('auth/ldap/' + provider, 'auth')
|
await new PostApiRequestBuilder<void, LoginDto>('auth/ldap/' + provider)
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
username: username,
|
username: username,
|
||||||
password: password
|
password: password
|
||||||
|
|
|
@ -17,7 +17,7 @@ import type { ChangePasswordDto, LoginDto, RegisterDto } from './types'
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const doLocalLogin = async (username: string, password: string): Promise<void> => {
|
export const doLocalLogin = async (username: string, password: string): Promise<void> => {
|
||||||
await new PostApiRequestBuilder<void, LoginDto>('auth/local/login', 'auth')
|
await new PostApiRequestBuilder<void, LoginDto>('auth/local/login')
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
username,
|
username,
|
||||||
password
|
password
|
||||||
|
@ -37,7 +37,7 @@ export const doLocalLogin = async (username: string, password: string): Promise<
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const doLocalRegister = async (username: string, displayName: string, password: string): Promise<void> => {
|
export const doLocalRegister = async (username: string, displayName: string, password: string): Promise<void> => {
|
||||||
await new PostApiRequestBuilder<void, RegisterDto>('auth/local', 'auth')
|
await new PostApiRequestBuilder<void, RegisterDto>('auth/local')
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
username,
|
username,
|
||||||
displayName,
|
displayName,
|
||||||
|
@ -54,7 +54,7 @@ export const doLocalRegister = async (username: string, displayName: string, pas
|
||||||
* @throws {AuthError.LOGIN_DISABLED} when local login is disabled on the backend.
|
* @throws {AuthError.LOGIN_DISABLED} when local login is disabled on the backend.
|
||||||
*/
|
*/
|
||||||
export const doLocalPasswordChange = async (currentPassword: string, newPassword: string): Promise<void> => {
|
export const doLocalPasswordChange = async (currentPassword: string, newPassword: string): Promise<void> => {
|
||||||
await new PutApiRequestBuilder<void, ChangePasswordDto>('auth/local', 'auth')
|
await new PutApiRequestBuilder<void, ChangePasswordDto>('auth/local')
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
currentPassword,
|
currentPassword,
|
||||||
newPassword
|
newPassword
|
||||||
|
|
|
@ -6,5 +6,5 @@
|
||||||
|
|
||||||
export interface ApiErrorResponse {
|
export interface ApiErrorResponse {
|
||||||
message: string
|
message: string
|
||||||
error: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,9 @@
|
||||||
export class ApiError extends Error {
|
export class ApiError extends Error {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly statusCode: number,
|
public readonly statusCode: number,
|
||||||
statusText: string,
|
public readonly backendErrorName: string | undefined,
|
||||||
i18nNamespace: string,
|
public readonly backendErrorMessage: string | undefined
|
||||||
public readonly apiErrorName: string | undefined
|
|
||||||
) {
|
) {
|
||||||
super(`api.error.${i18nNamespace}.${statusText}`)
|
super()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ export abstract class ApiRequestBuilder<ResponseType> {
|
||||||
*
|
*
|
||||||
* @param endpoint The target endpoint without a leading slash.
|
* @param endpoint The target endpoint without a leading slash.
|
||||||
*/
|
*/
|
||||||
constructor(endpoint: string, private apiI18nKey: string) {
|
constructor(endpoint: string) {
|
||||||
this.targetUrl = `api/private/${endpoint}`
|
this.targetUrl = `api/private/${endpoint}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,9 +38,8 @@ export abstract class ApiRequestBuilder<ResponseType> {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (response.status >= 400) {
|
if (response.status >= 400) {
|
||||||
const apiErrorResponse = await this.readApiErrorResponseFromBody(response)
|
const backendError = await this.readApiErrorResponseFromBody(response)
|
||||||
const statusText = response.status === 400 ? apiErrorResponse?.error ?? 'unknown' : response.statusText
|
throw new ApiError(response.status, backendError?.name, backendError?.message)
|
||||||
throw new ApiError(response.status, statusText, this.apiI18nKey, apiErrorResponse?.error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ApiResponse(response)
|
return new ApiResponse(response)
|
||||||
|
|
|
@ -21,7 +21,7 @@ describe('DeleteApiRequestBuilder', () => {
|
||||||
describe('sendRequest without body', () => {
|
describe('sendRequest without body', () => {
|
||||||
it('without headers', async () => {
|
it('without headers', async () => {
|
||||||
expectFetch('api/private/test', 204, { method: 'DELETE' })
|
expectFetch('api/private/test', 204, { method: 'DELETE' })
|
||||||
await new DeleteApiRequestBuilder<string, undefined>('test', 'test').sendRequest()
|
await new DeleteApiRequestBuilder<string, undefined>('test').sendRequest()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with single header', async () => {
|
it('with single header', async () => {
|
||||||
|
@ -31,7 +31,7 @@ describe('DeleteApiRequestBuilder', () => {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: expectedHeaders
|
headers: expectedHeaders
|
||||||
})
|
})
|
||||||
await new DeleteApiRequestBuilder<string, undefined>('test', 'test').withHeader('test', 'true').sendRequest()
|
await new DeleteApiRequestBuilder<string, undefined>('test').withHeader('test', 'true').sendRequest()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with overriding single header', async () => {
|
it('with overriding single header', async () => {
|
||||||
|
@ -41,7 +41,7 @@ describe('DeleteApiRequestBuilder', () => {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: expectedHeaders
|
headers: expectedHeaders
|
||||||
})
|
})
|
||||||
await new DeleteApiRequestBuilder<string, undefined>('test', 'test')
|
await new DeleteApiRequestBuilder<string, undefined>('test')
|
||||||
.withHeader('test', 'true')
|
.withHeader('test', 'true')
|
||||||
.withHeader('test', 'false')
|
.withHeader('test', 'false')
|
||||||
.sendRequest()
|
.sendRequest()
|
||||||
|
@ -55,7 +55,7 @@ describe('DeleteApiRequestBuilder', () => {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: expectedHeaders
|
headers: expectedHeaders
|
||||||
})
|
})
|
||||||
await new DeleteApiRequestBuilder<string, undefined>('test', 'test')
|
await new DeleteApiRequestBuilder<string, undefined>('test')
|
||||||
.withHeader('test', 'true')
|
.withHeader('test', 'true')
|
||||||
.withHeader('test2', 'false')
|
.withHeader('test2', 'false')
|
||||||
.sendRequest()
|
.sendRequest()
|
||||||
|
@ -71,7 +71,7 @@ describe('DeleteApiRequestBuilder', () => {
|
||||||
headers: expectedHeaders,
|
headers: expectedHeaders,
|
||||||
body: '{"test":true,"foo":"bar"}'
|
body: '{"test":true,"foo":"bar"}'
|
||||||
})
|
})
|
||||||
await new DeleteApiRequestBuilder('test', 'test')
|
await new DeleteApiRequestBuilder('test')
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
test: true,
|
test: true,
|
||||||
foo: 'bar'
|
foo: 'bar'
|
||||||
|
@ -84,7 +84,7 @@ describe('DeleteApiRequestBuilder', () => {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
body: 'HedgeDoc'
|
body: 'HedgeDoc'
|
||||||
})
|
})
|
||||||
await new DeleteApiRequestBuilder('test', 'test').withBody('HedgeDoc').sendRequest()
|
await new DeleteApiRequestBuilder('test').withBody('HedgeDoc').sendRequest()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('sendRequest with custom options', () => {
|
describe('sendRequest with custom options', () => {
|
||||||
|
@ -93,7 +93,7 @@ describe('DeleteApiRequestBuilder', () => {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
cache: 'force-cache'
|
cache: 'force-cache'
|
||||||
})
|
})
|
||||||
await new DeleteApiRequestBuilder<string, undefined>('test', 'test')
|
await new DeleteApiRequestBuilder<string, undefined>('test')
|
||||||
.withCustomOptions({
|
.withCustomOptions({
|
||||||
cache: 'force-cache'
|
cache: 'force-cache'
|
||||||
})
|
})
|
||||||
|
@ -105,7 +105,7 @@ describe('DeleteApiRequestBuilder', () => {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
cache: 'no-store'
|
cache: 'no-store'
|
||||||
})
|
})
|
||||||
await new DeleteApiRequestBuilder<string, undefined>('test', 'test')
|
await new DeleteApiRequestBuilder<string, undefined>('test')
|
||||||
.withCustomOptions({
|
.withCustomOptions({
|
||||||
cache: 'force-cache'
|
cache: 'force-cache'
|
||||||
})
|
})
|
||||||
|
@ -121,7 +121,7 @@ describe('DeleteApiRequestBuilder', () => {
|
||||||
cache: 'force-cache',
|
cache: 'force-cache',
|
||||||
integrity: 'test'
|
integrity: 'test'
|
||||||
})
|
})
|
||||||
await new DeleteApiRequestBuilder<string, undefined>('test', 'test')
|
await new DeleteApiRequestBuilder<string, undefined>('test')
|
||||||
.withCustomOptions({
|
.withCustomOptions({
|
||||||
cache: 'force-cache',
|
cache: 'force-cache',
|
||||||
integrity: 'test'
|
integrity: 'test'
|
||||||
|
@ -131,28 +131,28 @@ describe('DeleteApiRequestBuilder', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('failing sendRequest', () => {
|
describe('failing sendRequest', () => {
|
||||||
it('with bad request without api error name', async () => {
|
it('without backend provided error name or error message', async () => {
|
||||||
expectFetch('api/private/test', 400, { method: 'DELETE' })
|
expectFetch('api/private/test', 400, { method: 'DELETE' })
|
||||||
const request = new DeleteApiRequestBuilder<string>('test', 'test').sendRequest()
|
const request = new DeleteApiRequestBuilder<string>('test').sendRequest()
|
||||||
await expect(request).rejects.toEqual(new ApiError(400, 'unknown', 'test', 'testExplosion'))
|
await expect(request).rejects.toEqual(new ApiError(400, undefined, undefined))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with bad request with api error name', async () => {
|
it('with backend error name and error message', async () => {
|
||||||
expectFetch('api/private/test', 400, { method: 'DELETE' }, {
|
expectFetch('api/private/test', 400, { method: 'DELETE' }, {
|
||||||
message: 'The API has exploded!',
|
message: 'The API has exploded!',
|
||||||
error: 'testExplosion'
|
name: 'testExplosion'
|
||||||
} as ApiErrorResponse)
|
} as ApiErrorResponse)
|
||||||
const request = new DeleteApiRequestBuilder<string>('test', 'test').sendRequest()
|
const request = new DeleteApiRequestBuilder<string>('test').sendRequest()
|
||||||
await expect(request).rejects.toEqual(new ApiError(400, 'testExplosion', 'test', 'testExplosion'))
|
await expect(request).rejects.toEqual(new ApiError(400, 'testExplosion', 'The API has exploded!'))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with non bad request error', async () => {
|
it('with another status code than 400', async () => {
|
||||||
expectFetch('api/private/test', 401, { method: 'DELETE' }, {
|
expectFetch('api/private/test', 401, { method: 'DELETE' }, {
|
||||||
message: 'The API has exploded!',
|
message: 'The API has exploded!',
|
||||||
error: 'testExplosion'
|
name: 'testExplosion'
|
||||||
} as ApiErrorResponse)
|
} as ApiErrorResponse)
|
||||||
const request = new DeleteApiRequestBuilder<string>('test', 'test').sendRequest()
|
const request = new DeleteApiRequestBuilder<string>('test').sendRequest()
|
||||||
await expect(request).rejects.toEqual(new ApiError(401, 'forbidden', 'test', 'testExplosion'))
|
await expect(request).rejects.toEqual(new ApiError(401, 'testExplosion', 'The API has exploded!'))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -22,7 +22,7 @@ describe('GetApiRequestBuilder', () => {
|
||||||
describe('sendRequest', () => {
|
describe('sendRequest', () => {
|
||||||
it('without headers', async () => {
|
it('without headers', async () => {
|
||||||
expectFetch('api/private/test', 200, { method: 'GET' })
|
expectFetch('api/private/test', 200, { method: 'GET' })
|
||||||
await new GetApiRequestBuilder<string>('test', 'test').sendRequest()
|
await new GetApiRequestBuilder<string>('test').sendRequest()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with single header', async () => {
|
it('with single header', async () => {
|
||||||
|
@ -32,7 +32,7 @@ describe('GetApiRequestBuilder', () => {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: expectedHeaders
|
headers: expectedHeaders
|
||||||
})
|
})
|
||||||
await new GetApiRequestBuilder<string>('test', 'test').withHeader('test', 'true').sendRequest()
|
await new GetApiRequestBuilder<string>('test').withHeader('test', 'true').sendRequest()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with overriding single header', async () => {
|
it('with overriding single header', async () => {
|
||||||
|
@ -42,7 +42,7 @@ describe('GetApiRequestBuilder', () => {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: expectedHeaders
|
headers: expectedHeaders
|
||||||
})
|
})
|
||||||
await new GetApiRequestBuilder<string>('test', 'test')
|
await new GetApiRequestBuilder<string>('test')
|
||||||
.withHeader('test', 'true')
|
.withHeader('test', 'true')
|
||||||
.withHeader('test', 'false')
|
.withHeader('test', 'false')
|
||||||
.sendRequest()
|
.sendRequest()
|
||||||
|
@ -56,7 +56,7 @@ describe('GetApiRequestBuilder', () => {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: expectedHeaders
|
headers: expectedHeaders
|
||||||
})
|
})
|
||||||
await new GetApiRequestBuilder<string>('test', 'test')
|
await new GetApiRequestBuilder<string>('test')
|
||||||
.withHeader('test', 'true')
|
.withHeader('test', 'true')
|
||||||
.withHeader('test2', 'false')
|
.withHeader('test2', 'false')
|
||||||
.sendRequest()
|
.sendRequest()
|
||||||
|
@ -69,7 +69,7 @@ describe('GetApiRequestBuilder', () => {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
cache: 'force-cache'
|
cache: 'force-cache'
|
||||||
})
|
})
|
||||||
await new GetApiRequestBuilder<string>('test', 'test')
|
await new GetApiRequestBuilder<string>('test')
|
||||||
.withCustomOptions({
|
.withCustomOptions({
|
||||||
cache: 'force-cache'
|
cache: 'force-cache'
|
||||||
})
|
})
|
||||||
|
@ -81,7 +81,7 @@ describe('GetApiRequestBuilder', () => {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
cache: 'no-store'
|
cache: 'no-store'
|
||||||
})
|
})
|
||||||
await new GetApiRequestBuilder<string>('test', 'test')
|
await new GetApiRequestBuilder<string>('test')
|
||||||
.withCustomOptions({
|
.withCustomOptions({
|
||||||
cache: 'force-cache'
|
cache: 'force-cache'
|
||||||
})
|
})
|
||||||
|
@ -97,7 +97,7 @@ describe('GetApiRequestBuilder', () => {
|
||||||
cache: 'force-cache',
|
cache: 'force-cache',
|
||||||
integrity: 'test'
|
integrity: 'test'
|
||||||
})
|
})
|
||||||
await new GetApiRequestBuilder<string>('test', 'test')
|
await new GetApiRequestBuilder<string>('test')
|
||||||
.withCustomOptions({
|
.withCustomOptions({
|
||||||
cache: 'force-cache',
|
cache: 'force-cache',
|
||||||
integrity: 'test'
|
integrity: 'test'
|
||||||
|
@ -107,28 +107,28 @@ describe('GetApiRequestBuilder', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('failing sendRequest', () => {
|
describe('failing sendRequest', () => {
|
||||||
it('with bad request without api error name', async () => {
|
it('without backend provided error name or error message', async () => {
|
||||||
expectFetch('api/private/test', 400, { method: 'GET' })
|
expectFetch('api/private/test', 400, { method: 'GET' })
|
||||||
const request = new GetApiRequestBuilder<string>('test', 'test').sendRequest()
|
const request = new GetApiRequestBuilder<string>('test').sendRequest()
|
||||||
await expect(request).rejects.toEqual(new ApiError(400, 'unknown', 'test', 'testExplosion'))
|
await expect(request).rejects.toEqual(new ApiError(400, undefined, undefined))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with bad request with api error name', async () => {
|
it('with backend error name and error message', async () => {
|
||||||
expectFetch('api/private/test', 400, { method: 'GET' }, {
|
expectFetch('api/private/test', 400, { method: 'GET' }, {
|
||||||
message: 'The API has exploded!',
|
message: 'The API has exploded!',
|
||||||
error: 'testExplosion'
|
name: 'testExplosion'
|
||||||
} as ApiErrorResponse)
|
} as ApiErrorResponse)
|
||||||
const request = new GetApiRequestBuilder<string>('test', 'test').sendRequest()
|
const request = new GetApiRequestBuilder<string>('test').sendRequest()
|
||||||
await expect(request).rejects.toEqual(new ApiError(400, 'testExplosion', 'test', 'testExplosion'))
|
await expect(request).rejects.toEqual(new ApiError(400, 'testExplosion', 'The API has exploded!'))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with non bad request error', async () => {
|
it('with another status code than 400', async () => {
|
||||||
expectFetch('api/private/test', 401, { method: 'GET' }, {
|
expectFetch('api/private/test', 401, { method: 'GET' }, {
|
||||||
message: 'The API has exploded!',
|
message: 'The API has exploded!',
|
||||||
error: 'testExplosion'
|
name: 'testExplosion'
|
||||||
} as ApiErrorResponse)
|
} as ApiErrorResponse)
|
||||||
const request = new GetApiRequestBuilder<string>('test', 'test').sendRequest()
|
const request = new GetApiRequestBuilder<string>('test').sendRequest()
|
||||||
await expect(request).rejects.toEqual(new ApiError(401, 'forbidden', 'test', 'testExplosion'))
|
await expect(request).rejects.toEqual(new ApiError(401, 'testExplosion', 'The API has exploded!'))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -22,7 +22,7 @@ describe('PostApiRequestBuilder', () => {
|
||||||
describe('sendRequest without body', () => {
|
describe('sendRequest without body', () => {
|
||||||
it('without headers', async () => {
|
it('without headers', async () => {
|
||||||
expectFetch('api/private/test', 201, { method: 'POST' })
|
expectFetch('api/private/test', 201, { method: 'POST' })
|
||||||
await new PostApiRequestBuilder<string, undefined>('test', 'test').sendRequest()
|
await new PostApiRequestBuilder<string, undefined>('test').sendRequest()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with single header', async () => {
|
it('with single header', async () => {
|
||||||
|
@ -32,7 +32,7 @@ describe('PostApiRequestBuilder', () => {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: expectedHeaders
|
headers: expectedHeaders
|
||||||
})
|
})
|
||||||
await new PostApiRequestBuilder<string, undefined>('test', 'test').withHeader('test', 'true').sendRequest()
|
await new PostApiRequestBuilder<string, undefined>('test').withHeader('test', 'true').sendRequest()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with overriding single header', async () => {
|
it('with overriding single header', async () => {
|
||||||
|
@ -42,7 +42,7 @@ describe('PostApiRequestBuilder', () => {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: expectedHeaders
|
headers: expectedHeaders
|
||||||
})
|
})
|
||||||
await new PostApiRequestBuilder<string, undefined>('test', 'test')
|
await new PostApiRequestBuilder<string, undefined>('test')
|
||||||
.withHeader('test', 'true')
|
.withHeader('test', 'true')
|
||||||
.withHeader('test', 'false')
|
.withHeader('test', 'false')
|
||||||
.sendRequest()
|
.sendRequest()
|
||||||
|
@ -56,7 +56,7 @@ describe('PostApiRequestBuilder', () => {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: expectedHeaders
|
headers: expectedHeaders
|
||||||
})
|
})
|
||||||
await new PostApiRequestBuilder<string, undefined>('test', 'test')
|
await new PostApiRequestBuilder<string, undefined>('test')
|
||||||
.withHeader('test', 'true')
|
.withHeader('test', 'true')
|
||||||
.withHeader('test2', 'false')
|
.withHeader('test2', 'false')
|
||||||
.sendRequest()
|
.sendRequest()
|
||||||
|
@ -72,7 +72,7 @@ describe('PostApiRequestBuilder', () => {
|
||||||
headers: expectedHeaders,
|
headers: expectedHeaders,
|
||||||
body: '{"test":true,"foo":"bar"}'
|
body: '{"test":true,"foo":"bar"}'
|
||||||
})
|
})
|
||||||
await new PostApiRequestBuilder('test', 'test')
|
await new PostApiRequestBuilder('test')
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
test: true,
|
test: true,
|
||||||
foo: 'bar'
|
foo: 'bar'
|
||||||
|
@ -85,7 +85,7 @@ describe('PostApiRequestBuilder', () => {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: 'HedgeDoc'
|
body: 'HedgeDoc'
|
||||||
})
|
})
|
||||||
await new PostApiRequestBuilder('test', 'test').withBody('HedgeDoc').sendRequest()
|
await new PostApiRequestBuilder('test').withBody('HedgeDoc').sendRequest()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('sendRequest with custom options', () => {
|
describe('sendRequest with custom options', () => {
|
||||||
|
@ -94,7 +94,7 @@ describe('PostApiRequestBuilder', () => {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
cache: 'force-cache'
|
cache: 'force-cache'
|
||||||
})
|
})
|
||||||
await new PostApiRequestBuilder<string, undefined>('test', 'test')
|
await new PostApiRequestBuilder<string, undefined>('test')
|
||||||
.withCustomOptions({
|
.withCustomOptions({
|
||||||
cache: 'force-cache'
|
cache: 'force-cache'
|
||||||
})
|
})
|
||||||
|
@ -106,7 +106,7 @@ describe('PostApiRequestBuilder', () => {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
cache: 'no-store'
|
cache: 'no-store'
|
||||||
})
|
})
|
||||||
await new PostApiRequestBuilder<string, undefined>('test', 'test')
|
await new PostApiRequestBuilder<string, undefined>('test')
|
||||||
.withCustomOptions({
|
.withCustomOptions({
|
||||||
cache: 'force-cache'
|
cache: 'force-cache'
|
||||||
})
|
})
|
||||||
|
@ -122,7 +122,7 @@ describe('PostApiRequestBuilder', () => {
|
||||||
cache: 'force-cache',
|
cache: 'force-cache',
|
||||||
integrity: 'test'
|
integrity: 'test'
|
||||||
})
|
})
|
||||||
await new PostApiRequestBuilder<string, undefined>('test', 'test')
|
await new PostApiRequestBuilder<string, undefined>('test')
|
||||||
.withCustomOptions({
|
.withCustomOptions({
|
||||||
cache: 'force-cache',
|
cache: 'force-cache',
|
||||||
integrity: 'test'
|
integrity: 'test'
|
||||||
|
@ -132,28 +132,28 @@ describe('PostApiRequestBuilder', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('failing sendRequest', () => {
|
describe('failing sendRequest', () => {
|
||||||
it('with bad request without api error name', async () => {
|
it('without backend provided error name or error message', async () => {
|
||||||
expectFetch('api/private/test', 400, { method: 'POST' })
|
expectFetch('api/private/test', 400, { method: 'POST' })
|
||||||
const request = new PostApiRequestBuilder<string, string>('test', 'test').sendRequest()
|
const request = new PostApiRequestBuilder<string, string>('test').sendRequest()
|
||||||
await expect(request).rejects.toEqual(new ApiError(400, 'unknown', 'test', 'testExplosion'))
|
await expect(request).rejects.toEqual(new ApiError(400, undefined, undefined))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with bad request with api error name', async () => {
|
it('with backend error name and error message', async () => {
|
||||||
expectFetch('api/private/test', 400, { method: 'POST' }, {
|
expectFetch('api/private/test', 400, { method: 'POST' }, {
|
||||||
message: 'The API has exploded!',
|
message: 'The API has exploded!',
|
||||||
error: 'testExplosion'
|
name: 'testExplosion'
|
||||||
} as ApiErrorResponse)
|
} as ApiErrorResponse)
|
||||||
const request = new PostApiRequestBuilder<string, string>('test', 'test').sendRequest()
|
const request = new PostApiRequestBuilder<string, string>('test').sendRequest()
|
||||||
await expect(request).rejects.toEqual(new ApiError(400, 'testExplosion', 'test', 'testExplosion'))
|
await expect(request).rejects.toEqual(new ApiError(400, 'testExplosion', 'The API has exploded!'))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with non bad request error', async () => {
|
it('with another status code than 400', async () => {
|
||||||
expectFetch('api/private/test', 401, { method: 'POST' }, {
|
expectFetch('api/private/test', 401, { method: 'POST' }, {
|
||||||
message: 'The API has exploded!',
|
message: 'The API has exploded!',
|
||||||
error: 'testExplosion'
|
name: 'testExplosion'
|
||||||
} as ApiErrorResponse)
|
} as ApiErrorResponse)
|
||||||
const request = new PostApiRequestBuilder<string, string>('test', 'test').sendRequest()
|
const request = new PostApiRequestBuilder<string, string>('test').sendRequest()
|
||||||
await expect(request).rejects.toEqual(new ApiError(401, 'forbidden', 'test', 'testExplosion'))
|
await expect(request).rejects.toEqual(new ApiError(401, 'testExplosion', 'The API has exploded!'))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -22,7 +22,7 @@ describe('PutApiRequestBuilder', () => {
|
||||||
describe('sendRequest without body', () => {
|
describe('sendRequest without body', () => {
|
||||||
it('without headers', async () => {
|
it('without headers', async () => {
|
||||||
expectFetch('api/private/test', 200, { method: 'PUT' })
|
expectFetch('api/private/test', 200, { method: 'PUT' })
|
||||||
await new PutApiRequestBuilder<string, undefined>('test', 'test').sendRequest()
|
await new PutApiRequestBuilder<string, undefined>('test').sendRequest()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with single header', async () => {
|
it('with single header', async () => {
|
||||||
|
@ -32,7 +32,7 @@ describe('PutApiRequestBuilder', () => {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: expectedHeaders
|
headers: expectedHeaders
|
||||||
})
|
})
|
||||||
await new PutApiRequestBuilder<string, undefined>('test', 'test').withHeader('test', 'true').sendRequest()
|
await new PutApiRequestBuilder<string, undefined>('test').withHeader('test', 'true').sendRequest()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with overriding single header', async () => {
|
it('with overriding single header', async () => {
|
||||||
|
@ -42,7 +42,7 @@ describe('PutApiRequestBuilder', () => {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: expectedHeaders
|
headers: expectedHeaders
|
||||||
})
|
})
|
||||||
await new PutApiRequestBuilder<string, undefined>('test', 'test')
|
await new PutApiRequestBuilder<string, undefined>('test')
|
||||||
.withHeader('test', 'true')
|
.withHeader('test', 'true')
|
||||||
.withHeader('test', 'false')
|
.withHeader('test', 'false')
|
||||||
.sendRequest()
|
.sendRequest()
|
||||||
|
@ -56,7 +56,7 @@ describe('PutApiRequestBuilder', () => {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: expectedHeaders
|
headers: expectedHeaders
|
||||||
})
|
})
|
||||||
await new PutApiRequestBuilder<string, undefined>('test', 'test')
|
await new PutApiRequestBuilder<string, undefined>('test')
|
||||||
.withHeader('test', 'true')
|
.withHeader('test', 'true')
|
||||||
.withHeader('test2', 'false')
|
.withHeader('test2', 'false')
|
||||||
.sendRequest()
|
.sendRequest()
|
||||||
|
@ -72,7 +72,7 @@ describe('PutApiRequestBuilder', () => {
|
||||||
headers: expectedHeaders,
|
headers: expectedHeaders,
|
||||||
body: '{"test":true,"foo":"bar"}'
|
body: '{"test":true,"foo":"bar"}'
|
||||||
})
|
})
|
||||||
await new PutApiRequestBuilder('test', 'test')
|
await new PutApiRequestBuilder('test')
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
test: true,
|
test: true,
|
||||||
foo: 'bar'
|
foo: 'bar'
|
||||||
|
@ -85,7 +85,7 @@ describe('PutApiRequestBuilder', () => {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: 'HedgeDoc'
|
body: 'HedgeDoc'
|
||||||
})
|
})
|
||||||
await new PutApiRequestBuilder('test', 'test').withBody('HedgeDoc').sendRequest()
|
await new PutApiRequestBuilder('test').withBody('HedgeDoc').sendRequest()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('sendRequest with custom options', () => {
|
describe('sendRequest with custom options', () => {
|
||||||
|
@ -94,7 +94,7 @@ describe('PutApiRequestBuilder', () => {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
cache: 'force-cache'
|
cache: 'force-cache'
|
||||||
})
|
})
|
||||||
await new PutApiRequestBuilder<string, undefined>('test', 'test')
|
await new PutApiRequestBuilder<string, undefined>('test')
|
||||||
.withCustomOptions({
|
.withCustomOptions({
|
||||||
cache: 'force-cache'
|
cache: 'force-cache'
|
||||||
})
|
})
|
||||||
|
@ -106,7 +106,7 @@ describe('PutApiRequestBuilder', () => {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
cache: 'no-store'
|
cache: 'no-store'
|
||||||
})
|
})
|
||||||
await new PutApiRequestBuilder<string, undefined>('test', 'test')
|
await new PutApiRequestBuilder<string, undefined>('test')
|
||||||
.withCustomOptions({
|
.withCustomOptions({
|
||||||
cache: 'force-cache'
|
cache: 'force-cache'
|
||||||
})
|
})
|
||||||
|
@ -122,7 +122,7 @@ describe('PutApiRequestBuilder', () => {
|
||||||
cache: 'force-cache',
|
cache: 'force-cache',
|
||||||
integrity: 'test'
|
integrity: 'test'
|
||||||
})
|
})
|
||||||
await new PutApiRequestBuilder<string, undefined>('test', 'test')
|
await new PutApiRequestBuilder<string, undefined>('test')
|
||||||
.withCustomOptions({
|
.withCustomOptions({
|
||||||
cache: 'force-cache',
|
cache: 'force-cache',
|
||||||
integrity: 'test'
|
integrity: 'test'
|
||||||
|
@ -132,28 +132,28 @@ describe('PutApiRequestBuilder', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('failing sendRequest', () => {
|
describe('failing sendRequest', () => {
|
||||||
it('with bad request without api error name', async () => {
|
it('without backend provided error name or error message', async () => {
|
||||||
expectFetch('api/private/test', 400, { method: 'PUT' })
|
expectFetch('api/private/test', 400, { method: 'PUT' })
|
||||||
const request = new PutApiRequestBuilder<string, string>('test', 'test').sendRequest()
|
const request = new PutApiRequestBuilder<string, undefined>('test').sendRequest()
|
||||||
await expect(request).rejects.toEqual(new ApiError(400, 'unknown', 'test', 'testExplosion'))
|
await expect(request).rejects.toEqual(new ApiError(400, undefined, undefined))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with bad request with api error name', async () => {
|
it('with backend error name and error message', async () => {
|
||||||
expectFetch('api/private/test', 400, { method: 'PUT' }, {
|
expectFetch('api/private/test', 400, { method: 'PUT' }, {
|
||||||
message: 'The API has exploded!',
|
message: 'The API has exploded!',
|
||||||
error: 'testExplosion'
|
name: 'testExplosion'
|
||||||
} as ApiErrorResponse)
|
} as ApiErrorResponse)
|
||||||
const request = new PutApiRequestBuilder<string, string>('test', 'test').sendRequest()
|
const request = new PutApiRequestBuilder<string, undefined>('test').sendRequest()
|
||||||
await expect(request).rejects.toEqual(new ApiError(400, 'testExplosion', 'test', 'testExplosion'))
|
await expect(request).rejects.toEqual(new ApiError(400, 'testExplosion', 'The API has exploded!'))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with non bad request error', async () => {
|
it('with another status code than 400', async () => {
|
||||||
expectFetch('api/private/test', 401, { method: 'PUT' }, {
|
expectFetch('api/private/test', 401, { method: 'PUT' }, {
|
||||||
message: 'The API has exploded!',
|
message: 'The API has exploded!',
|
||||||
error: 'testExplosion'
|
name: 'testExplosion'
|
||||||
} as ApiErrorResponse)
|
} as ApiErrorResponse)
|
||||||
const request = new PutApiRequestBuilder<string, string>('test', 'test').sendRequest()
|
const request = new PutApiRequestBuilder<string, undefined>('test').sendRequest()
|
||||||
await expect(request).rejects.toEqual(new ApiError(401, 'forbidden', 'test', 'testExplosion'))
|
await expect(request).rejects.toEqual(new ApiError(401, 'testExplosion', 'The API has exploded!'))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
*/
|
*/
|
||||||
import { ApiError } from './api-error'
|
import { ApiError } from './api-error'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps an error to an i18n key.
|
||||||
|
*/
|
||||||
export class ErrorToI18nKeyMapper {
|
export class ErrorToI18nKeyMapper {
|
||||||
private foundI18nKey: string | undefined = undefined
|
private foundI18nKey: string | undefined = undefined
|
||||||
|
|
||||||
|
@ -21,7 +24,7 @@ export class ErrorToI18nKeyMapper {
|
||||||
if (
|
if (
|
||||||
this.foundI18nKey === undefined &&
|
this.foundI18nKey === undefined &&
|
||||||
this.apiError instanceof ApiError &&
|
this.apiError instanceof ApiError &&
|
||||||
this.apiError.apiErrorName === errorName
|
this.apiError.backendErrorName === errorName
|
||||||
) {
|
) {
|
||||||
this.foundI18nKey = i18nKey
|
this.foundI18nKey = i18nKey
|
||||||
}
|
}
|
||||||
|
@ -35,12 +38,10 @@ export class ErrorToI18nKeyMapper {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
public orFallbackI18nKey(fallback?: string): typeof fallback {
|
public orFallbackI18nKey<T extends string | undefined = string>(fallback: T): string | T {
|
||||||
const foundValue = this.foundI18nKey ?? fallback
|
if (!this.foundI18nKey) {
|
||||||
if (foundValue !== undefined && this.i18nNamespace !== undefined) {
|
return fallback
|
||||||
return `${this.i18nNamespace}.${foundValue}`
|
|
||||||
} else {
|
|
||||||
return foundValue
|
|
||||||
}
|
}
|
||||||
|
return this.i18nNamespace ? `${this.i18nNamespace}.${this.foundI18nKey}` : this.foundI18nKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,6 @@ import type { Config } from './types'
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const getConfig = async (): Promise<Config> => {
|
export const getConfig = async (): Promise<Config> => {
|
||||||
const response = await new GetApiRequestBuilder<Config>('config', 'config').sendRequest()
|
const response = await new GetApiRequestBuilder<Config>('config').sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,6 @@ import type { GroupInfo } from './types'
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const getGroup = async (groupName: string): Promise<GroupInfo> => {
|
export const getGroup = async (groupName: string): Promise<GroupInfo> => {
|
||||||
const response = await new GetApiRequestBuilder<GroupInfo>('groups/' + groupName, 'group').sendRequest()
|
const response = await new GetApiRequestBuilder<GroupInfo>('groups/' + groupName).sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ import type { ChangePinStatusDto, HistoryEntry, HistoryEntryPutDto } from './typ
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const getRemoteHistory = async (): Promise<HistoryEntry[]> => {
|
export const getRemoteHistory = async (): Promise<HistoryEntry[]> => {
|
||||||
const response = await new GetApiRequestBuilder<HistoryEntry[]>('me/history', 'history').sendRequest()
|
const response = await new GetApiRequestBuilder<HistoryEntry[]>('me/history').sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,9 +27,7 @@ export const getRemoteHistory = async (): Promise<HistoryEntry[]> => {
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const setRemoteHistoryEntries = async (entries: HistoryEntryPutDto[]): Promise<void> => {
|
export const setRemoteHistoryEntries = async (entries: HistoryEntryPutDto[]): Promise<void> => {
|
||||||
await new PostApiRequestBuilder<void, HistoryEntryPutDto[]>('me/history', 'history')
|
await new PostApiRequestBuilder<void, HistoryEntryPutDto[]>('me/history').withJsonBody(entries).sendRequest()
|
||||||
.withJsonBody(entries)
|
|
||||||
.sendRequest()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,10 +41,7 @@ export const updateRemoteHistoryEntryPinStatus = async (
|
||||||
noteIdOrAlias: string,
|
noteIdOrAlias: string,
|
||||||
pinStatus: boolean
|
pinStatus: boolean
|
||||||
): Promise<HistoryEntry> => {
|
): Promise<HistoryEntry> => {
|
||||||
const response = await new PutApiRequestBuilder<HistoryEntry, ChangePinStatusDto>(
|
const response = await new PutApiRequestBuilder<HistoryEntry, ChangePinStatusDto>('me/history/' + noteIdOrAlias)
|
||||||
'me/history/' + noteIdOrAlias,
|
|
||||||
'history'
|
|
||||||
)
|
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
pinStatus
|
pinStatus
|
||||||
})
|
})
|
||||||
|
@ -61,7 +56,7 @@ export const updateRemoteHistoryEntryPinStatus = async (
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const deleteRemoteHistoryEntry = async (noteIdOrAlias: string): Promise<void> => {
|
export const deleteRemoteHistoryEntry = async (noteIdOrAlias: string): Promise<void> => {
|
||||||
await new DeleteApiRequestBuilder('me/history/' + noteIdOrAlias, 'history').sendRequest()
|
await new DeleteApiRequestBuilder('me/history/' + noteIdOrAlias).sendRequest()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -70,5 +65,5 @@ export const deleteRemoteHistoryEntry = async (noteIdOrAlias: string): Promise<v
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const deleteRemoteHistory = async (): Promise<void> => {
|
export const deleteRemoteHistory = async (): Promise<void> => {
|
||||||
await new DeleteApiRequestBuilder('me/history', 'history').sendRequest()
|
await new DeleteApiRequestBuilder('me/history').sendRequest()
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ import type { ChangeDisplayNameDto, LoginUserInfo } from './types'
|
||||||
* @throws {Error} when the user is not signed-in.
|
* @throws {Error} when the user is not signed-in.
|
||||||
*/
|
*/
|
||||||
export const getMe = async (): Promise<LoginUserInfo> => {
|
export const getMe = async (): Promise<LoginUserInfo> => {
|
||||||
const response = await new GetApiRequestBuilder<LoginUserInfo>('me', 'me').sendRequest()
|
const response = await new GetApiRequestBuilder<LoginUserInfo>('me').sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ export const getMe = async (): Promise<LoginUserInfo> => {
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const deleteUser = async (): Promise<void> => {
|
export const deleteUser = async (): Promise<void> => {
|
||||||
await new DeleteApiRequestBuilder('me', 'me').sendRequest()
|
await new DeleteApiRequestBuilder('me').sendRequest()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,7 +36,7 @@ export const deleteUser = async (): Promise<void> => {
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const updateDisplayName = async (displayName: string): Promise<void> => {
|
export const updateDisplayName = async (displayName: string): Promise<void> => {
|
||||||
await new PostApiRequestBuilder<void, ChangeDisplayNameDto>('me/profile', 'me')
|
await new PostApiRequestBuilder<void, ChangeDisplayNameDto>('me/profile')
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
displayName
|
displayName
|
||||||
})
|
})
|
||||||
|
@ -50,6 +50,6 @@ export const updateDisplayName = async (displayName: string): Promise<void> => {
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const getMyMedia = async (): Promise<MediaUpload[]> => {
|
export const getMyMedia = async (): Promise<MediaUpload[]> => {
|
||||||
const response = await new GetApiRequestBuilder<MediaUpload[]>('me/media', 'me').sendRequest()
|
const response = await new GetApiRequestBuilder<MediaUpload[]>('me/media').sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import type { ImageProxyRequestDto, ImageProxyResponse, MediaUpload } from './ty
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const getProxiedUrl = async (imageUrl: string): Promise<ImageProxyResponse> => {
|
export const getProxiedUrl = async (imageUrl: string): Promise<ImageProxyResponse> => {
|
||||||
const response = await new PostApiRequestBuilder<ImageProxyResponse, ImageProxyRequestDto>('media/proxy', 'media')
|
const response = await new PostApiRequestBuilder<ImageProxyResponse, ImageProxyRequestDto>('media/proxy')
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
url: imageUrl
|
url: imageUrl
|
||||||
})
|
})
|
||||||
|
@ -34,7 +34,7 @@ export const getProxiedUrl = async (imageUrl: string): Promise<ImageProxyRespons
|
||||||
export const uploadFile = async (noteIdOrAlias: string, media: Blob): Promise<MediaUpload> => {
|
export const uploadFile = async (noteIdOrAlias: string, media: Blob): Promise<MediaUpload> => {
|
||||||
const postData = new FormData()
|
const postData = new FormData()
|
||||||
postData.append('file', media)
|
postData.append('file', media)
|
||||||
const response = await new PostApiRequestBuilder<MediaUpload, void>('media', 'media')
|
const response = await new PostApiRequestBuilder<MediaUpload, void>('media')
|
||||||
.withHeader('HedgeDoc-Note', noteIdOrAlias)
|
.withHeader('HedgeDoc-Note', noteIdOrAlias)
|
||||||
.withBody(postData)
|
.withBody(postData)
|
||||||
.sendRequest()
|
.sendRequest()
|
||||||
|
@ -48,5 +48,5 @@ export const uploadFile = async (noteIdOrAlias: string, media: Blob): Promise<Me
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const deleteUploadedMedia = async (mediaId: string): Promise<void> => {
|
export const deleteUploadedMedia = async (mediaId: string): Promise<void> => {
|
||||||
await new DeleteApiRequestBuilder('media/' + mediaId, 'media').sendRequest()
|
await new DeleteApiRequestBuilder('media/' + mediaId).sendRequest()
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ import type { Note, NoteDeletionOptions, NoteMetadata } from './types'
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const getNote = async (noteIdOrAlias: string): Promise<Note> => {
|
export const getNote = async (noteIdOrAlias: string): Promise<Note> => {
|
||||||
const response = await new GetApiRequestBuilder<Note>('notes/' + noteIdOrAlias, 'note').sendRequest()
|
const response = await new GetApiRequestBuilder<Note>('notes/' + noteIdOrAlias).sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ export const getNote = async (noteIdOrAlias: string): Promise<Note> => {
|
||||||
* @return Metadata of the specified note.
|
* @return Metadata of the specified note.
|
||||||
*/
|
*/
|
||||||
export const getNoteMetadata = async (noteIdOrAlias: string): Promise<NoteMetadata> => {
|
export const getNoteMetadata = async (noteIdOrAlias: string): Promise<NoteMetadata> => {
|
||||||
const response = await new GetApiRequestBuilder<NoteMetadata>(`notes/${noteIdOrAlias}/metadata`, 'note').sendRequest()
|
const response = await new GetApiRequestBuilder<NoteMetadata>(`notes/${noteIdOrAlias}/metadata`).sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ export const getNoteMetadata = async (noteIdOrAlias: string): Promise<NoteMetada
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const getMediaForNote = async (noteIdOrAlias: string): Promise<MediaUpload[]> => {
|
export const getMediaForNote = async (noteIdOrAlias: string): Promise<MediaUpload[]> => {
|
||||||
const response = await new GetApiRequestBuilder<MediaUpload[]>(`notes/${noteIdOrAlias}/media`, 'note').sendRequest()
|
const response = await new GetApiRequestBuilder<MediaUpload[]>(`notes/${noteIdOrAlias}/media`).sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ export const getMediaForNote = async (noteIdOrAlias: string): Promise<MediaUploa
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const createNote = async (markdown: string): Promise<Note> => {
|
export const createNote = async (markdown: string): Promise<Note> => {
|
||||||
const response = await new PostApiRequestBuilder<Note, void>('notes', 'note')
|
const response = await new PostApiRequestBuilder<Note, void>('notes')
|
||||||
.withHeader('Content-Type', 'text/markdown')
|
.withHeader('Content-Type', 'text/markdown')
|
||||||
.withBody(markdown)
|
.withBody(markdown)
|
||||||
.sendRequest()
|
.sendRequest()
|
||||||
|
@ -68,7 +68,7 @@ export const createNote = async (markdown: string): Promise<Note> => {
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const createNoteWithPrimaryAlias = async (markdown: string, primaryAlias: string): Promise<Note> => {
|
export const createNoteWithPrimaryAlias = async (markdown: string, primaryAlias: string): Promise<Note> => {
|
||||||
const response = await new PostApiRequestBuilder<Note, void>('notes/' + primaryAlias, 'note')
|
const response = await new PostApiRequestBuilder<Note, void>('notes/' + primaryAlias)
|
||||||
.withHeader('Content-Type', 'text/markdown')
|
.withHeader('Content-Type', 'text/markdown')
|
||||||
.withBody(markdown)
|
.withBody(markdown)
|
||||||
.sendRequest()
|
.sendRequest()
|
||||||
|
@ -82,7 +82,7 @@ export const createNoteWithPrimaryAlias = async (markdown: string, primaryAlias:
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const deleteNote = async (noteIdOrAlias: string): Promise<void> => {
|
export const deleteNote = async (noteIdOrAlias: string): Promise<void> => {
|
||||||
await new DeleteApiRequestBuilder<void, NoteDeletionOptions>('notes/' + noteIdOrAlias, 'note')
|
await new DeleteApiRequestBuilder<void, NoteDeletionOptions>('notes/' + noteIdOrAlias)
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
keepMedia: false
|
keepMedia: false
|
||||||
// TODO Ask whether the user wants to keep the media uploaded to the note.
|
// TODO Ask whether the user wants to keep the media uploaded to the note.
|
||||||
|
|
|
@ -18,8 +18,7 @@ import type { OwnerChangeDto, PermissionSetDto } from './types'
|
||||||
*/
|
*/
|
||||||
export const setNoteOwner = async (noteId: string, owner: string): Promise<NotePermissions> => {
|
export const setNoteOwner = async (noteId: string, owner: string): Promise<NotePermissions> => {
|
||||||
const response = await new PutApiRequestBuilder<NotePermissions, OwnerChangeDto>(
|
const response = await new PutApiRequestBuilder<NotePermissions, OwnerChangeDto>(
|
||||||
`notes/${noteId}/metadata/permissions/owner`,
|
`notes/${noteId}/metadata/permissions/owner`
|
||||||
'permission'
|
|
||||||
)
|
)
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
owner
|
owner
|
||||||
|
@ -43,8 +42,7 @@ export const setUserPermission = async (
|
||||||
canEdit: boolean
|
canEdit: boolean
|
||||||
): Promise<NotePermissions> => {
|
): Promise<NotePermissions> => {
|
||||||
const response = await new PutApiRequestBuilder<NotePermissions, PermissionSetDto>(
|
const response = await new PutApiRequestBuilder<NotePermissions, PermissionSetDto>(
|
||||||
`notes/${noteId}/metadata/permissions/users/${username}`,
|
`notes/${noteId}/metadata/permissions/users/${username}`
|
||||||
'permission'
|
|
||||||
)
|
)
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
canEdit
|
canEdit
|
||||||
|
@ -68,8 +66,7 @@ export const setGroupPermission = async (
|
||||||
canEdit: boolean
|
canEdit: boolean
|
||||||
): Promise<NotePermissions> => {
|
): Promise<NotePermissions> => {
|
||||||
const response = await new PutApiRequestBuilder<NotePermissions, PermissionSetDto>(
|
const response = await new PutApiRequestBuilder<NotePermissions, PermissionSetDto>(
|
||||||
`notes/${noteId}/metadata/permissions/groups/${groupName}`,
|
`notes/${noteId}/metadata/permissions/groups/${groupName}`
|
||||||
'permission'
|
|
||||||
)
|
)
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
canEdit
|
canEdit
|
||||||
|
@ -88,8 +85,7 @@ export const setGroupPermission = async (
|
||||||
*/
|
*/
|
||||||
export const removeUserPermission = async (noteId: string, username: string): Promise<NotePermissions> => {
|
export const removeUserPermission = async (noteId: string, username: string): Promise<NotePermissions> => {
|
||||||
const response = await new DeleteApiRequestBuilder<NotePermissions>(
|
const response = await new DeleteApiRequestBuilder<NotePermissions>(
|
||||||
`notes/${noteId}/metadata/permissions/users/${username}`,
|
`notes/${noteId}/metadata/permissions/users/${username}`
|
||||||
'permission'
|
|
||||||
).sendRequest()
|
).sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
@ -104,8 +100,7 @@ export const removeUserPermission = async (noteId: string, username: string): Pr
|
||||||
*/
|
*/
|
||||||
export const removeGroupPermission = async (noteId: string, groupName: string): Promise<NotePermissions> => {
|
export const removeGroupPermission = async (noteId: string, groupName: string): Promise<NotePermissions> => {
|
||||||
const response = await new DeleteApiRequestBuilder<NotePermissions>(
|
const response = await new DeleteApiRequestBuilder<NotePermissions>(
|
||||||
`notes/${noteId}/metadata/permissions/groups/${groupName}`,
|
`notes/${noteId}/metadata/permissions/groups/${groupName}`
|
||||||
'permission'
|
|
||||||
).sendRequest()
|
).sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,7 @@ import type { RevisionDetails, RevisionMetadata } from './types'
|
||||||
*/
|
*/
|
||||||
export const getRevision = async (noteId: string, revisionId: number): Promise<RevisionDetails> => {
|
export const getRevision = async (noteId: string, revisionId: number): Promise<RevisionDetails> => {
|
||||||
const response = await new GetApiRequestBuilder<RevisionDetails>(
|
const response = await new GetApiRequestBuilder<RevisionDetails>(
|
||||||
`notes/${noteId}/revisions/${revisionId}`,
|
`notes/${noteId}/revisions/${revisionId}`
|
||||||
'revisions'
|
|
||||||
).sendRequest()
|
).sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
@ -31,10 +30,7 @@ export const getRevision = async (noteId: string, revisionId: number): Promise<R
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const getAllRevisions = async (noteId: string): Promise<RevisionMetadata[]> => {
|
export const getAllRevisions = async (noteId: string): Promise<RevisionMetadata[]> => {
|
||||||
const response = await new GetApiRequestBuilder<RevisionMetadata[]>(
|
const response = await new GetApiRequestBuilder<RevisionMetadata[]>(`notes/${noteId}/revisions`).sendRequest()
|
||||||
`notes/${noteId}/revisions`,
|
|
||||||
'revisions'
|
|
||||||
).sendRequest()
|
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,5 +41,5 @@ export const getAllRevisions = async (noteId: string): Promise<RevisionMetadata[
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const deleteRevisionsForNote = async (noteIdOrAlias: string): Promise<void> => {
|
export const deleteRevisionsForNote = async (noteIdOrAlias: string): Promise<void> => {
|
||||||
await new DeleteApiRequestBuilder(`notes/${noteIdOrAlias}/revisions`, 'revisions').sendRequest()
|
await new DeleteApiRequestBuilder(`notes/${noteIdOrAlias}/revisions`).sendRequest()
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import type { AccessToken, AccessTokenWithSecret, CreateAccessTokenDto } from '.
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const getAccessTokenList = async (): Promise<AccessToken[]> => {
|
export const getAccessTokenList = async (): Promise<AccessToken[]> => {
|
||||||
const response = await new GetApiRequestBuilder<AccessToken[]>('tokens', 'tokens').sendRequest()
|
const response = await new GetApiRequestBuilder<AccessToken[]>('tokens').sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ export const getAccessTokenList = async (): Promise<AccessToken[]> => {
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const postNewAccessToken = async (label: string, validUntil: number): Promise<AccessTokenWithSecret> => {
|
export const postNewAccessToken = async (label: string, validUntil: number): Promise<AccessTokenWithSecret> => {
|
||||||
const response = await new PostApiRequestBuilder<AccessTokenWithSecret, CreateAccessTokenDto>('tokens', 'tokens')
|
const response = await new PostApiRequestBuilder<AccessTokenWithSecret, CreateAccessTokenDto>('tokens')
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
label,
|
label,
|
||||||
validUntil
|
validUntil
|
||||||
|
@ -44,5 +44,5 @@ export const postNewAccessToken = async (label: string, validUntil: number): Pro
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const deleteAccessToken = async (keyId: string): Promise<void> => {
|
export const deleteAccessToken = async (keyId: string): Promise<void> => {
|
||||||
await new DeleteApiRequestBuilder('tokens/' + keyId, 'tokens').sendRequest()
|
await new DeleteApiRequestBuilder('tokens/' + keyId).sendRequest()
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,6 @@ import type { UserInfo } from './types'
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const getUser = async (username: string): Promise<UserInfo> => {
|
export const getUser = async (username: string): Promise<UserInfo> => {
|
||||||
const response = await new GetApiRequestBuilder<UserInfo>('users/' + username, 'users').sendRequest()
|
const response = await new GetApiRequestBuilder<UserInfo>('users/' + username).sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ export const NewPasswordField: React.FC<CommonFieldProps> = ({ onChange, value,
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const isValid = useMemo(() => {
|
const isValid = useMemo(() => {
|
||||||
return value.trim() !== '' && value.length >= 8
|
return value.trim() !== ''
|
||||||
}, [value])
|
}, [value])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -34,7 +34,6 @@ export const NewPasswordField: React.FC<CommonFieldProps> = ({ onChange, value,
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
placeholder={t('login.auth.password') ?? undefined}
|
placeholder={t('login.auth.password') ?? undefined}
|
||||||
className='bg-dark text-light'
|
className='bg-dark text-light'
|
||||||
minLength={8}
|
|
||||||
autoComplete='new-password'
|
autoComplete='new-password'
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -19,14 +19,17 @@ exports[`Note loading boundary shows an error 1`] = `
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
titleI18nKey:
|
titleI18nKey:
|
||||||
api.note.notFound.title
|
noteLoadingBoundary.error.notFound.title
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
descriptionI18nKey:
|
descriptionI18nKey:
|
||||||
api.note.notFound.description
|
noteLoadingBoundary.error.notFound.description
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
children:
|
children:
|
||||||
|
<span>
|
||||||
|
This is a mock for CreateNonExistingNoteHint
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
import { ApiError } from '../../../api/common/api-error'
|
||||||
import * as getNoteModule from '../../../api/notes'
|
import * as getNoteModule from '../../../api/notes'
|
||||||
import type { Note } from '../../../api/notes/types'
|
import type { Note } from '../../../api/notes/types'
|
||||||
import * as LoadingScreenModule from '../../../components/application-loader/loading-screen/loading-screen'
|
import * as LoadingScreenModule from '../../../components/application-loader/loading-screen/loading-screen'
|
||||||
|
@ -87,7 +88,7 @@ describe('Note loading boundary', () => {
|
||||||
jest.spyOn(getNoteModule, 'getNote').mockImplementation((id) => {
|
jest.spyOn(getNoteModule, 'getNote').mockImplementation((id) => {
|
||||||
expect(id).toBe(mockedNoteId)
|
expect(id).toBe(mockedNoteId)
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
setTimeout(() => reject(new Error('api.note.notFound')), 0)
|
setTimeout(() => reject(new ApiError(404, undefined, undefined)), 0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
import { ApiError } from '../../../api/common/api-error'
|
||||||
|
import { ErrorToI18nKeyMapper } from '../../../api/common/error-to-i18n-key-mapper'
|
||||||
import { LoadingScreen } from '../../application-loader/loading-screen/loading-screen'
|
import { LoadingScreen } from '../../application-loader/loading-screen/loading-screen'
|
||||||
import { CommonErrorPage } from '../../error-pages/common-error-page'
|
import { CommonErrorPage } from '../../error-pages/common-error-page'
|
||||||
import { CustomAsyncLoadingBoundary } from '../async-loading-boundary/custom-async-loading-boundary'
|
import { CustomAsyncLoadingBoundary } from '../async-loading-boundary/custom-async-loading-boundary'
|
||||||
|
@ -28,11 +30,18 @@ export const NoteLoadingBoundary: React.FC<PropsWithChildren> = ({ children }) =
|
||||||
|
|
||||||
const errorComponent = useMemo(() => {
|
const errorComponent = useMemo(() => {
|
||||||
if (error === undefined) {
|
if (error === undefined) {
|
||||||
return <></>
|
return null
|
||||||
}
|
}
|
||||||
|
const errorI18nKeyPrefix = new ErrorToI18nKeyMapper(error, 'noteLoadingBoundary.error')
|
||||||
|
.withHttpCode(404, 'notFound')
|
||||||
|
.withHttpCode(403, 'forbidden')
|
||||||
|
.withHttpCode(401, 'forbidden')
|
||||||
|
.orFallbackI18nKey('other')
|
||||||
return (
|
return (
|
||||||
<CommonErrorPage titleI18nKey={`${error.message}.title`} descriptionI18nKey={`${error.message}.description`}>
|
<CommonErrorPage
|
||||||
<ShowIf condition={error.message === 'api.error.note.not_found'}>
|
titleI18nKey={`${errorI18nKeyPrefix}.title`}
|
||||||
|
descriptionI18nKey={`${errorI18nKeyPrefix}.description`}>
|
||||||
|
<ShowIf condition={error instanceof ApiError && error.statusCode === 404}>
|
||||||
<CreateNonExistingNoteHint onNoteCreated={loadNoteFromServer} />
|
<CreateNonExistingNoteHint onNoteCreated={loadNoteFromServer} />
|
||||||
</ShowIf>
|
</ShowIf>
|
||||||
</CommonErrorPage>
|
</CommonErrorPage>
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { doLocalLogin } from '../../../api/auth/local'
|
import { doLocalLogin } from '../../../api/auth/local'
|
||||||
|
import { ErrorToI18nKeyMapper } from '../../../api/common/error-to-i18n-key-mapper'
|
||||||
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||||
import { useOnInputChange } from '../../../hooks/common/use-on-input-change'
|
import { useOnInputChange } from '../../../hooks/common/use-on-input-change'
|
||||||
import { ShowIf } from '../../common/show-if/show-if'
|
import { ShowIf } from '../../common/show-if/show-if'
|
||||||
|
@ -30,7 +31,14 @@ export const ViaLocal: React.FC = () => {
|
||||||
(event: FormEvent) => {
|
(event: FormEvent) => {
|
||||||
doLocalLogin(username, password)
|
doLocalLogin(username, password)
|
||||||
.then(() => fetchAndSetUser())
|
.then(() => fetchAndSetUser())
|
||||||
.catch((error: Error) => setError(error.message))
|
.catch((error: Error) => {
|
||||||
|
const errorI18nKey = new ErrorToI18nKeyMapper(error, 'login.auth.error')
|
||||||
|
.withHttpCode(404, 'usernamePassword')
|
||||||
|
.withHttpCode(401, 'usernamePassword')
|
||||||
|
.withBackendErrorName('FeatureDisabledError', 'loginDisabled')
|
||||||
|
.orFallbackI18nKey('other')
|
||||||
|
setError(errorI18nKey)
|
||||||
|
})
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
},
|
},
|
||||||
[username, password]
|
[username, password]
|
||||||
|
@ -45,25 +53,23 @@ export const ViaLocal: React.FC = () => {
|
||||||
<Card.Title>
|
<Card.Title>
|
||||||
<Trans i18nKey='login.signInVia' values={{ service: t('login.auth.username') }} />
|
<Trans i18nKey='login.signInVia' values={{ service: t('login.auth.username') }} />
|
||||||
</Card.Title>
|
</Card.Title>
|
||||||
<Form onSubmit={onLoginSubmit}>
|
<Form onSubmit={onLoginSubmit} className={'d-flex gap-3 flex-column'}>
|
||||||
<UsernameField onChange={onUsernameChange} invalid={!!error} />
|
<UsernameField onChange={onUsernameChange} invalid={!!error} />
|
||||||
<PasswordField onChange={onPasswordChange} invalid={!!error} />
|
<PasswordField onChange={onPasswordChange} invalid={!!error} />
|
||||||
<Alert className='small' show={!!error} variant='danger'>
|
<Alert className='small' show={!!error} variant='danger'>
|
||||||
<Trans i18nKey={error} />
|
<Trans i18nKey={error} />
|
||||||
</Alert>
|
</Alert>
|
||||||
|
<Button type='submit' variant='primary'>
|
||||||
<div className='flex flex-row' dir='auto'>
|
|
||||||
<Button type='submit' variant='primary' className='mx-2'>
|
|
||||||
<Trans i18nKey='login.signIn' />
|
<Trans i18nKey='login.signIn' />
|
||||||
</Button>
|
</Button>
|
||||||
<ShowIf condition={allowRegister}>
|
<ShowIf condition={allowRegister}>
|
||||||
|
<Trans i18nKey={'login.register.question'} />
|
||||||
<Link href={'/register'} passHref={true}>
|
<Link href={'/register'} passHref={true}>
|
||||||
<Button type='button' variant='secondary' className='mx-2'>
|
<Button type='button' variant='secondary' className={'d-block w-100'}>
|
||||||
<Trans i18nKey='login.register.title' />
|
<Trans i18nKey='login.register.title' />
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</ShowIf>
|
</ShowIf>
|
||||||
</div>
|
|
||||||
</Form>
|
</Form>
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
@ -37,8 +37,8 @@ export const ProfileChangePassword: React.FC = () => {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const foundI18nKey = new ErrorToI18nKeyMapper(error as Error, 'login.auth.error')
|
const foundI18nKey = new ErrorToI18nKeyMapper(error as Error, 'login.auth.error')
|
||||||
.withHttpCode(401, 'invalidCredentials')
|
.withHttpCode(401, 'invalidCredentials')
|
||||||
.withBackendErrorName('loginDisabled', 'loginDisabled')
|
.withBackendErrorName('FeatureDisabledError', 'loginDisabled')
|
||||||
.withBackendErrorName('passwordTooWeak', 'passwordTooWeak')
|
.withBackendErrorName('PasswordTooWeakError', 'passwordTooWeak')
|
||||||
.orFallbackI18nKey('other')
|
.orFallbackI18nKey('other')
|
||||||
return Promise.reject(foundI18nKey)
|
return Promise.reject(foundI18nKey)
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -21,8 +21,8 @@ export const RegisterError: React.FC<RegisterErrorProps> = ({ error }) => {
|
||||||
}
|
}
|
||||||
return new ErrorToI18nKeyMapper(error, 'login.register.error')
|
return new ErrorToI18nKeyMapper(error, 'login.register.error')
|
||||||
.withHttpCode(409, 'usernameExisting')
|
.withHttpCode(409, 'usernameExisting')
|
||||||
.withBackendErrorName('registrationDisabled', 'registrationDisabled')
|
.withBackendErrorName('FeatureDisabledError', 'registrationDisabled')
|
||||||
.withBackendErrorName('passwordTooWeak', 'passwordTooWeak')
|
.withBackendErrorName('PasswordTooWeakError', 'passwordTooWeak')
|
||||||
.orFallbackI18nKey('other')
|
.orFallbackI18nKey('other')
|
||||||
}, [error])
|
}, [error])
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ export const LoginPage: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<LandingLayout>
|
<LandingLayout>
|
||||||
<div className='my-3'>
|
<div className='my-3'>
|
||||||
<Row className='h-100 flex justify-content-center'>
|
<Row className='h-100 d-flex justify-content-center'>
|
||||||
<ShowIf condition={ldapProviders.length > 0 || localLoginEnabled}>
|
<ShowIf condition={ldapProviders.length > 0 || localLoginEnabled}>
|
||||||
<Col xs={12} sm={10} lg={4}>
|
<Col xs={12} sm={10} lg={4}>
|
||||||
<ShowIf condition={localLoginEnabled}>
|
<ShowIf condition={localLoginEnabled}>
|
||||||
|
|
|
@ -54,17 +54,11 @@ export const RegisterPage: NextPage = () => {
|
||||||
)
|
)
|
||||||
|
|
||||||
const ready = useMemo(() => {
|
const ready = useMemo(() => {
|
||||||
return (
|
return username.trim() !== '' && displayName.trim() !== '' && password.trim() !== '' && password === passwordAgain
|
||||||
username.trim() !== '' &&
|
|
||||||
displayName.trim() !== '' &&
|
|
||||||
password.trim() !== '' &&
|
|
||||||
password.length >= 8 &&
|
|
||||||
password === passwordAgain
|
|
||||||
)
|
|
||||||
}, [username, password, displayName, passwordAgain])
|
}, [username, password, displayName, passwordAgain])
|
||||||
|
|
||||||
const isWeakPassword = useMemo(() => {
|
const isWeakPassword = useMemo(() => {
|
||||||
return error?.apiErrorName === 'passwordTooWeak'
|
return error?.backendErrorName === 'PasswordTooWeakError'
|
||||||
}, [error])
|
}, [error])
|
||||||
|
|
||||||
const onUsernameChange = useOnInputChange(setUsername)
|
const onUsernameChange = useOnInputChange(setUsername)
|
||||||
|
@ -90,7 +84,7 @@ export const RegisterPage: NextPage = () => {
|
||||||
<Col lg={6}>
|
<Col lg={6}>
|
||||||
<Card className='bg-dark mb-4 text-start'>
|
<Card className='bg-dark mb-4 text-start'>
|
||||||
<Card.Body>
|
<Card.Body>
|
||||||
<Form onSubmit={doRegisterSubmit}>
|
<Form onSubmit={doRegisterSubmit} className={'d-flex flex-column gap-3'}>
|
||||||
<UsernameField onChange={onUsernameChange} value={username} />
|
<UsernameField onChange={onUsernameChange} value={username} />
|
||||||
<DisplayNameField onChange={onDisplayNameChange} value={displayName} />
|
<DisplayNameField onChange={onDisplayNameChange} value={displayName} />
|
||||||
<NewPasswordField onChange={onPasswordChange} value={password} hasError={isWeakPassword} />
|
<NewPasswordField onChange={onPasswordChange} value={password} hasError={isWeakPassword} />
|
||||||
|
@ -106,9 +100,9 @@ export const RegisterPage: NextPage = () => {
|
||||||
<Button variant='primary' type='submit' disabled={!ready}>
|
<Button variant='primary' type='submit' disabled={!ready}>
|
||||||
<Trans i18nKey='login.register.title' />
|
<Trans i18nKey='login.register.title' />
|
||||||
</Button>
|
</Button>
|
||||||
</Form>
|
|
||||||
|
|
||||||
<RegisterError error={error} />
|
<RegisterError error={error} />
|
||||||
|
</Form>
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
Loading…
Reference in a new issue