Dashboard and error handling

This commit is contained in:
2025-07-15 13:22:11 +02:00
parent 5ac4c987d3
commit 6a5149fd4c
27 changed files with 572 additions and 204 deletions

View File

@ -0,0 +1,31 @@
// /src/utils/assertSdkResponseOk.ts
import type { AxiosResponse, AxiosError } from 'axios'
import { ApiError, ValidationError, NotFoundError } from '@/api/errors'
import { isHttpValidationError } from './typeguards';
export type SDKResponse<T> =
| (AxiosResponse<T> & { error: undefined })
| (AxiosError<any> & { data: undefined; error: any })
export function assertSdkResponseOk<T>(response: SDKResponse<T>): T {
if ('error' in response && response.error) {
const status = response.status ?? 500
if (status === 404) {
throw new NotFoundError("Not found")
}
if (status === 422 && isHttpValidationError(response.error)) {
throw new ValidationError(response.error.detail ?? [])
}
const errorMessage = response.error.detail ?? 'Unknown error'
throw new ApiError(`API error: ${errorMessage}`, status)
}
if ('data' in response && response.data) {
return response.data
}
throw new ApiError('Invalid response shape', 500)
}

View File

@ -0,0 +1,28 @@
export class ApiError extends Error {
public status: number
constructor(message: string, status: number) {
super(message)
this.name = 'ApiError'
this.status = status
}
}
export class NotFoundError extends ApiError {
constructor(message: string) {
super(message, 404)
this.name = "NotFoundError"
}
}
export class ValidationError extends ApiError {
public errors: any[]
constructor(errors: any[], message: string = 'Validation failed') {
super(message, 422)
this.name = 'ValidationError'
this.errors = errors
}
}

View File

@ -1,6 +1,8 @@
import { client } from '@/client/client.gen'
import { useAuthTokenStore } from '@/store/auth.ts'
import router from '@/router'
client.instance.interceptors.response.use(
response => response,
@ -11,6 +13,7 @@ client.instance.interceptors.response.use(
if (originalRequest.url.includes("/refresh")) {
const auth = useAuthTokenStore()
auth.logout()
router.push({ name: 'login' })
return Promise.reject("Refresh failed - logged out")
}
if (error.response?.status === 401 && !originalRequest._retry) {
@ -27,6 +30,6 @@ client.instance.interceptors.response.use(
}
return Promise.reject("Could not refresh token")
}
return Promise.reject("Could not refresh token")
return Promise.reject(error)
}
)

View File

@ -2,7 +2,7 @@
import { OPERATIONS } from '@/api/types'
import { SUBSYSTEM } from '@/api/types'
import type { Operation, SubSystem } from '@/client'
import type { HttpValidationError, Operation, SubSystem } from '@/client'
export function isOperation(value: string): value is Operation {
return (OPERATIONS as readonly string[]).includes(value)
@ -11,3 +11,7 @@ export function isOperation(value: string): value is Operation {
export function isSubSystem(value: string): value is SubSystem {
return (SUBSYSTEM as readonly string[]).includes(value)
}
export function isHttpValidationError(error: unknown): error is HttpValidationError {
return !!(error && typeof error === 'object' && 'detail' in error)
}