Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/application/i18n/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,16 @@
"title": "Recents",
"authText": "You are not logged in, log in to see your recent notes"
},
"error": {
"401": "Unauthorized",
"404": "Not found",
"403": "Forbidden",
"500": "Internal server error"
},
"errors": {
"401": "You must be authenticated to access this resource",
"404": "Page not found",
"403": "Access Denied",
"500": "Unknown error happened",
"default": "Something went wrong"
},
Expand Down Expand Up @@ -176,6 +184,7 @@
"noteSettings": "Note settings",
"marketplace": "Marketplace",
"addTool": "Add tool",
"error": "Oops! Something went wrong",
"notFound": "Not found",
"joinTeam": "Join",
"authorization": "Authorize",
Expand Down
16 changes: 15 additions & 1 deletion src/application/router/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,6 @@ const routes: RouteRecordRaw[] = [
discardTabOnLeave: true,
},
},

/**
* 404 page
*/
Expand All @@ -195,6 +194,21 @@ const routes: RouteRecordRaw[] = [
code: 404,
},
},
/**
* error page
*/
{
path: '/error/:code',
component: ErrorPage,
meta: {
layout: 'fullpage',
pageTitleI18n: 'pages.error',
discardTabOnLeave: true,
},
props: route => ({
code: route.params.code,
}),
},
];

export default routes;
27 changes: 17 additions & 10 deletions src/application/services/useNote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { NoteTool } from '@/domain/entities/Note';
import { useRouter, useRoute } from 'vue-router';
import type { NoteDraft } from '@/domain/entities/NoteDraft';
import type EditorTool from '@/domain/entities/EditorTool';
import DomainError from '@/domain/entities/errors/Base';
import useNavbar from './useNavbar';
import { getTitle } from '@/infrastructure/utils/note';

Expand Down Expand Up @@ -98,7 +99,7 @@ interface UseNoteComposableOptions {
* @param options - note service options
*/
export default function (options: UseNoteComposableOptions): UseNoteComposableState {
const { patchOpenedPageByUrl } = useNavbar();
const { patchOpenedPageByUrl, deleteOpenedPageByUrl } = useNavbar();
/**
* Current note identifier
*/
Expand Down Expand Up @@ -163,15 +164,21 @@ export default function (options: UseNoteComposableOptions): UseNoteComposableSt
* @param id - Note identifier got from composable argument
*/
async function load(id: NoteId): Promise<void> {
/**
* @todo try-catch domain errors
*/
const response = await noteService.getNoteById(id);

note.value = response.note;
canEdit.value = response.accessRights.canEdit;
noteTools.value = response.tools;
parentNote.value = response.parentNote;
try {
const response = await noteService.getNoteById(id);

note.value = response.note;
canEdit.value = response.accessRights.canEdit;
noteTools.value = response.tools;
parentNote.value = response.parentNote;
} catch (error) {
deleteOpenedPageByUrl(route.path);
if (error instanceof DomainError) {
void router.push(`/error/${error.statusCode}`);
} else {
void router.push('/error/500');
}
}
}

/**
Expand Down
14 changes: 13 additions & 1 deletion src/domain/entities/errors/Base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,16 @@
* Domain Error — own class describing exceptions in our business-logic.
* For example, when user is not authorized to perform some action.
*/
export default class DomainError extends Error {}
export default class DomainError extends Error {
public statusCode?: number;
Comment thread
neSpecc marked this conversation as resolved.
/**
* Constructor for Domain error
* @param message - Error message
* @param statusCode - Status code
*/
constructor(message: string, statusCode?: number) {
super(message);
this.name = 'DomainError';
this.statusCode = statusCode;
}
}
16 changes: 16 additions & 0 deletions src/domain/entities/errors/Forbidden.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import DomainError from './Base';

/**
* Domain error thrown when user does not have permission
*/
export default class ForbiddenError extends DomainError {
/**
* Constructor for NotFound error
* @param message - Error message
* @param statusCode - Error status code
*/
constructor(message: string = 'Permission denied', statusCode: number = 403) {
super(message, statusCode);
this.name = 'ForbiddenError';
}
};
5 changes: 3 additions & 2 deletions src/domain/entities/errors/NotFound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ export default class NotFoundError extends DomainError {
/**
* Constructor for NotFound error
* @param message - Error message
* @param statusCode - Error status code
*/
constructor(message: string = 'NotFound') {
super(message);
constructor(message: string = 'NotFound', statusCode: number = 404) {
super(message, statusCode);
this.name = 'NotFoundError';
}
}
16 changes: 16 additions & 0 deletions src/domain/entities/errors/ResourceUnavailableError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import DomainError from './Base';

/**
* Domain error thrown when unknown error occurs
*/
export default class ResourceUnavailableError extends DomainError {
/**
* Constructor for ResourceUnavailableError error
* @param message - Error message
* @param statusCode - Error status code
*/
constructor(message: string = 'Internal server error', statusCode: number = 500) {
super(message, statusCode);
this.name = 'ResourceUnavailableError';
}
}
7 changes: 4 additions & 3 deletions src/domain/entities/errors/Unauthorized.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import DomainError from './Base';

/**
* Domain error thrown when user is not authorized to perform some action
* Domain error thrown when user is not authenticated
*/
export default class UnauthorizedError extends DomainError {
/**
* Constructor for unauthorized error
* @param message - Error message
* @param statusCode - Error status code
*/
constructor(message: string = 'Unauthorized') {
super(message);
constructor(message: string = 'Unauthorized', statusCode: number = 401) {
super(message, statusCode);
this.name = 'UnauthorizedError';
}
}
11 changes: 8 additions & 3 deletions src/infrastructure/transport/notes-api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import AuthorizableTransport from '@/infrastructure/transport/authorizable.trans
import type JSONValue from '../types/JSONValue';
import UnauthorizedError from '@/domain/entities/errors/Unauthorized';
import NotFoundError from '@/domain/entities/errors/NotFound';
import ResourceUnavailableError from '@/domain/entities/errors/ResourceUnavailableError';
import ForbiddenError from '@/domain/entities/errors/Forbidden';
import type { FilesDto } from '../types/FileDto';

/**
Expand Down Expand Up @@ -48,12 +50,15 @@ export default class NotesApiTransport extends AuthorizableTransport {
*/
switch (status) {
case 401:
return new UnauthorizedError(errorText, status);
case 403:
return new UnauthorizedError(errorText);
return new ForbiddenError(errorText, status);
case 404:
return new NotFoundError(errorText);
return new NotFoundError(errorText, status);
case 406:
return new NotFoundError(errorText, status);
default:
return new Error(errorText);
return new ResourceUnavailableError(errorText, 500);
}
},
});
Expand Down
17 changes: 17 additions & 0 deletions src/presentation/pages/Error.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
<script setup lang="ts">
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useHead } from 'unhead';
import { useRoute } from 'vue-router';
import useNavbar from '@/application/services/useNavbar';

const route = useRoute();
const { patchOpenedPageByUrl } = useNavbar();

const { t, te } = useI18n();

Expand Down Expand Up @@ -44,6 +50,17 @@ const message = computed(() => {

return t('errors.default');
});

useHead({
title: t(`pages.error`),
});

const openPageInfo = {
title: te(`error.${props.code}`) ? t(`error.${props.code}`) : t(`pages.error`),
Comment thread
e11sy marked this conversation as resolved.
url: route.path,
};

patchOpenedPageByUrl(route.path, openPageInfo);
</script>

<style lang="postcss" module>
Expand Down