Some checks failed
CI / Unit & Component Tests (push) Failing after 2m37s
CI / OCR Service Tests (push) Successful in 32s
CI / OCR Service Tests (pull_request) Successful in 30s
CI / Backend Unit Tests (push) Failing after 2m47s
CI / Unit & Component Tests (pull_request) Failing after 2m29s
CI / Backend Unit Tests (pull_request) Failing after 2m46s
- Add /register route with invite code prefill, password show/hide - Add /login?registered=1 success banner - Add /admin/invites page: list, create, revoke, copy link - Add Einladungen nav section to admin sidebar (ADMIN_USER perm) - Add invite error codes to errors.ts - Add 48 i18n keys across de/en/es - Update hooks.server.ts to allow public access to invite/register API Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
146 lines
4.3 KiB
TypeScript
146 lines
4.3 KiB
TypeScript
import * as m from '$lib/paraglide/messages.js';
|
|
|
|
/**
|
|
* Mirror of the backend ErrorCode enum.
|
|
* Keep in sync with backend/src/main/java/org/raddatz/familienarchiv/exception/ErrorCode.java
|
|
*/
|
|
export type ErrorCode =
|
|
| 'PERSON_NOT_FOUND'
|
|
| 'ALIAS_NOT_FOUND'
|
|
| 'DOCUMENT_NOT_FOUND'
|
|
| 'DOCUMENT_NO_FILE'
|
|
| 'FILE_NOT_FOUND'
|
|
| 'FILE_UPLOAD_FAILED'
|
|
| 'UNSUPPORTED_FILE_TYPE'
|
|
| 'USER_NOT_FOUND'
|
|
| 'EMAIL_ALREADY_IN_USE'
|
|
| 'WRONG_CURRENT_PASSWORD'
|
|
| 'IMPORT_ALREADY_RUNNING'
|
|
| 'INVALID_RESET_TOKEN'
|
|
| 'INVITE_NOT_FOUND'
|
|
| 'INVITE_EXHAUSTED'
|
|
| 'INVITE_REVOKED'
|
|
| 'INVITE_EXPIRED'
|
|
| 'ANNOTATION_NOT_FOUND'
|
|
| 'ANNOTATION_UPDATE_FAILED'
|
|
| 'TRANSCRIPTION_BLOCK_NOT_FOUND'
|
|
| 'TRANSCRIPTION_BLOCK_CONFLICT'
|
|
| 'COMMENT_NOT_FOUND'
|
|
| 'OCR_SERVICE_UNAVAILABLE'
|
|
| 'OCR_JOB_NOT_FOUND'
|
|
| 'OCR_DOCUMENT_NOT_UPLOADED'
|
|
| 'OCR_PROCESSING_FAILED'
|
|
| 'TRAINING_ALREADY_RUNNING'
|
|
| 'OCR_TRAINING_CONFLICT'
|
|
| 'INVALID_TAG_COLOR'
|
|
| 'TAG_CYCLE_DETECTED'
|
|
| 'TAG_NOT_FOUND'
|
|
| 'TAG_MERGE_SELF'
|
|
| 'TAG_MERGE_INVALID_TARGET'
|
|
| 'MISSING_CREDENTIALS'
|
|
| 'UNAUTHORIZED'
|
|
| 'FORBIDDEN'
|
|
| 'VALIDATION_ERROR'
|
|
| 'INTERNAL_ERROR';
|
|
|
|
export interface BackendError {
|
|
code: ErrorCode;
|
|
message: string; // English developer message — not shown to users
|
|
}
|
|
|
|
/**
|
|
* Attempts to parse a backend ErrorResponse from a failed fetch response.
|
|
* Returns null if the body is not valid JSON or does not contain a code field.
|
|
*/
|
|
export async function parseBackendError(res: Response): Promise<BackendError | null> {
|
|
try {
|
|
const body = await res.json();
|
|
if (body && typeof body.code === 'string') {
|
|
return body as BackendError;
|
|
}
|
|
} catch {
|
|
// Body was not JSON
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** Returns a localised message for the given error code via Paraglide. Falls back to INTERNAL_ERROR. */
|
|
export function getErrorMessage(code: ErrorCode | string | undefined): string {
|
|
switch (code) {
|
|
case 'PERSON_NOT_FOUND':
|
|
return m.error_person_not_found();
|
|
case 'ALIAS_NOT_FOUND':
|
|
return m.error_alias_not_found();
|
|
case 'DOCUMENT_NOT_FOUND':
|
|
return m.error_document_not_found();
|
|
case 'DOCUMENT_NO_FILE':
|
|
return m.error_document_no_file();
|
|
case 'FILE_NOT_FOUND':
|
|
return m.error_file_not_found();
|
|
case 'FILE_UPLOAD_FAILED':
|
|
return m.error_file_upload_failed();
|
|
case 'UNSUPPORTED_FILE_TYPE':
|
|
return m.error_unsupported_file_type();
|
|
case 'USER_NOT_FOUND':
|
|
return m.error_user_not_found();
|
|
case 'EMAIL_ALREADY_IN_USE':
|
|
return m.error_email_already_in_use();
|
|
case 'WRONG_CURRENT_PASSWORD':
|
|
return m.error_wrong_current_password();
|
|
case 'IMPORT_ALREADY_RUNNING':
|
|
return m.error_import_already_running();
|
|
case 'INVALID_RESET_TOKEN':
|
|
return m.error_invalid_reset_token();
|
|
case 'INVITE_NOT_FOUND':
|
|
return m.error_invite_not_found();
|
|
case 'INVITE_EXHAUSTED':
|
|
return m.error_invite_exhausted();
|
|
case 'INVITE_REVOKED':
|
|
return m.error_invite_revoked();
|
|
case 'INVITE_EXPIRED':
|
|
return m.error_invite_expired();
|
|
case 'ANNOTATION_NOT_FOUND':
|
|
return m.error_annotation_not_found();
|
|
case 'ANNOTATION_UPDATE_FAILED':
|
|
return m.error_annotation_update_failed();
|
|
case 'TRANSCRIPTION_BLOCK_NOT_FOUND':
|
|
return m.error_transcription_block_not_found();
|
|
case 'TRANSCRIPTION_BLOCK_CONFLICT':
|
|
return m.error_transcription_block_conflict();
|
|
case 'COMMENT_NOT_FOUND':
|
|
return m.error_comment_not_found();
|
|
case 'OCR_SERVICE_UNAVAILABLE':
|
|
return m.error_ocr_service_unavailable();
|
|
case 'OCR_JOB_NOT_FOUND':
|
|
return m.error_ocr_job_not_found();
|
|
case 'OCR_DOCUMENT_NOT_UPLOADED':
|
|
return m.error_ocr_document_not_uploaded();
|
|
case 'OCR_PROCESSING_FAILED':
|
|
return m.error_ocr_processing_failed();
|
|
case 'TRAINING_ALREADY_RUNNING':
|
|
return m.error_training_already_running();
|
|
case 'OCR_TRAINING_CONFLICT':
|
|
return m.error_internal_error();
|
|
case 'INVALID_TAG_COLOR':
|
|
return m.error_invalid_tag_color();
|
|
case 'TAG_CYCLE_DETECTED':
|
|
return m.error_tag_cycle_detected();
|
|
case 'TAG_NOT_FOUND':
|
|
return m.error_tag_not_found();
|
|
case 'TAG_MERGE_SELF':
|
|
return m.error_tag_merge_self();
|
|
case 'TAG_MERGE_INVALID_TARGET':
|
|
return m.error_tag_merge_invalid_target();
|
|
case 'MISSING_CREDENTIALS':
|
|
return m.login_error_missing_credentials();
|
|
case 'UNAUTHORIZED':
|
|
return m.error_unauthorized();
|
|
case 'FORBIDDEN':
|
|
return m.error_forbidden();
|
|
case 'VALIDATION_ERROR':
|
|
return m.error_validation_error();
|
|
default:
|
|
return m.error_internal_error();
|
|
}
|
|
}
|