Refactor/error and api handling (#82)

* DB migrations

* Format errors in migrations

* Implemented new error and permission handling.

* Updated and fixed tests

* Update src/routes/api/tenants/[id]/appointments/[appointmentId]/cancel/+server.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/routes/api/tenants/[id]/agents/[agentId]/absences/[absenceId]/+server.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/routes/api/tenants/[id]/channels/+server.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fixed test

* Added error constants

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Hendrik
2025-09-18 15:04:06 +02:00
committed by GitHub
co-authored by Copilot
parent ae0a54a4c4
commit e486072ffa
42 changed files with 1747 additions and 574 deletions
+35 -3
View File
@@ -1,5 +1,17 @@
import { ERRORS } from "$lib/errors";
import type { UniversalLogger } from "$lib/logger";
import { json } from "@sveltejs/kit";
export const logError =
(logger: UniversalLogger) =>
(message: string, error: unknown, requestedBy?: string, tenantId?: string) => {
logger.error(message, {
tenantId,
requestedBy,
error: JSON.stringify(error || "?"),
});
};
export class BackendError extends Error {
constructor(
message: string,
@@ -8,13 +20,23 @@ export class BackendError extends Error {
super(message);
}
public toJson = () => json({ message: this.message }, { status: this.code });
public toJson = () => json({ error: this.message, message: this.message }, { status: this.code });
}
export class AuthorizationError extends BackendError {
constructor(
message: string = ERRORS.SECURITY.AUTHORIZATION_FAILED,
public code = 403,
) {
super(message, code);
this.name = "AuthorizationError";
}
}
export class AuthenticationError extends BackendError {
constructor(
message: string,
public code = 403,
message: string = ERRORS.SECURITY.AUTHENTICATION_REQUIRED,
public code = 401,
) {
super(message, code);
this.name = "AuthenticationError";
@@ -50,3 +72,13 @@ export class ConflictError extends BackendError {
this.name = "ConflictError";
}
}
export class InternalError extends BackendError {
constructor(
message: string = ERRORS.BACKEND.OBFUSCATED,
public code = 500,
) {
super(message, code);
this.name = "InternalError";
}
}
+10 -7
View File
@@ -1,4 +1,4 @@
import { json } from "@sveltejs/kit";
import { AuthenticationError, AuthorizationError } from "./errors";
/**
* Check whether the user can access the data within a route.
@@ -10,29 +10,32 @@ export const checkPermission = (
locals: App.Locals,
tenantId: string | null,
administrative: boolean = false,
): Response | null => {
global: boolean = false,
): void => {
if (!locals.user) {
return json({ error: "Authentication required" }, { status: 401 });
throw new AuthenticationError();
}
if (locals.user.role === "GLOBAL_ADMIN") {
// Global admin can view absences for any tenant
return null;
return;
} else if (
!global &&
locals.user.role === "TENANT_ADMIN" &&
tenantId != null &&
locals.user.tenantId === tenantId
) {
// Tenant admin and staff can view absences for their own tenant
return null;
return;
} else if (
!global &&
!administrative &&
locals.user.role === "STAFF" &&
tenantId != null &&
locals.user.tenantId === tenantId
) {
// Tenant admin and staff can view absences for their own tenant
return null;
return;
} else {
return json({ error: "Insufficient permissions" }, { status: 403 });
throw new AuthorizationError();
}
};