From e0ad428a9fe71761c75e774ad88bca6e7973eeae Mon Sep 17 00:00:00 2001 From: Bruno Bernardino Date: Mon, 23 Jun 2025 08:57:02 +0100 Subject: [PATCH] Improve error messages This improves error messages throughout. It might sometimes be too verbose, but that's better than being opaque (#74). Also upgrades Deno's patch version. Fixes #74 --- .dvmrc | 2 +- Dockerfile | 2 +- components/expenses/MainExpenses.tsx | 7 ++- components/files/MainFiles.tsx | 55 +++++++++++++++++++ components/files/ManageShareModal.tsx | 5 ++ components/files/MoveDirectoryOrFileModal.tsx | 5 ++ components/files/SearchFiles.tsx | 5 ++ components/notes/MainNotes.tsx | 20 +++++++ components/photos/MainPhotos.tsx | 10 ++++ docker-compose.yml | 2 +- islands/auth/MultiFactorAuthSettings.tsx | 41 +++++++++++--- islands/dashboard/Links.tsx | 7 ++- islands/dashboard/Notes.tsx | 5 ++ islands/news/Articles.tsx | 15 +++++ islands/news/Feeds.tsx | 15 +++++ islands/notes/Note.tsx | 5 ++ 16 files changed, 188 insertions(+), 13 deletions(-) diff --git a/.dvmrc b/.dvmrc index cc6c9a4..e75da3e 100644 --- a/.dvmrc +++ b/.dvmrc @@ -1 +1 @@ -2.3.5 +2.3.6 diff --git a/Dockerfile b/Dockerfile index 1cf5ad3..15aebca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM denoland/deno:ubuntu-2.3.5 +FROM denoland/deno:ubuntu-2.3.6 EXPOSE 8000 diff --git a/components/expenses/MainExpenses.tsx b/components/expenses/MainExpenses.tsx index 44c4f5f..7e369e2 100644 --- a/components/expenses/MainExpenses.tsx +++ b/components/expenses/MainExpenses.tsx @@ -165,10 +165,15 @@ export default function MainExpenses({ initialBudgets, initialExpenses, initialM method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to export expenses. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as ExportResponseBody; if (!result.success) { - throw new Error('Failed to get contact!'); + throw new Error('Failed to export expenses!'); } const exportContents = JSON.stringify(result.jsonContents, null, 2); diff --git a/components/files/MainFiles.tsx b/components/files/MainFiles.tsx index 477ac8d..d9dcd59 100644 --- a/components/files/MainFiles.tsx +++ b/components/files/MainFiles.tsx @@ -123,6 +123,11 @@ export default function MainFiles( method: 'POST', body: requestBody, }); + + if (!response.ok) { + throw new Error(`Failed to upload file. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as UploadResponseBody; if (!result.success) { @@ -170,6 +175,11 @@ export default function MainFiles( method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to create directory. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as CreateDirectoryResponseBody; if (!result.success) { @@ -239,6 +249,11 @@ export default function MainFiles( method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to rename directory. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as RenameDirectoryResponseBody; if (!result.success) { @@ -273,6 +288,11 @@ export default function MainFiles( method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to rename file. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as RenameResponseBody; if (!result.success) { @@ -327,6 +347,11 @@ export default function MainFiles( method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to move directory. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as MoveDirectoryResponseBody; if (!result.success) { @@ -359,6 +384,11 @@ export default function MainFiles( method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to move file. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as MoveResponseBody; if (!result.success) { @@ -391,6 +421,11 @@ export default function MainFiles( method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to delete directory. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as DeleteDirectoryResponseBody; if (!result.success) { @@ -423,6 +458,11 @@ export default function MainFiles( method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to delete file. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as DeleteResponseBody; if (!result.success) { @@ -536,6 +576,11 @@ export default function MainFiles( method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to create share. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as CreateShareResponseBody; if (!result.success) { @@ -587,6 +632,11 @@ export default function MainFiles( method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to update share. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as UpdateShareResponseBody; if (!result.success) { @@ -624,6 +674,11 @@ export default function MainFiles( method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to delete file share. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as DeleteShareResponseBody; if (!result.success) { diff --git a/components/files/ManageShareModal.tsx b/components/files/ManageShareModal.tsx index 9ddab6e..9e67e49 100644 --- a/components/files/ManageShareModal.tsx +++ b/components/files/ManageShareModal.tsx @@ -40,6 +40,11 @@ export default function ManageShareModal( method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to get file share. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as ResponseBody; if (!result.success) { diff --git a/components/files/MoveDirectoryOrFileModal.tsx b/components/files/MoveDirectoryOrFileModal.tsx index c7f6469..4afdbce 100644 --- a/components/files/MoveDirectoryOrFileModal.tsx +++ b/components/files/MoveDirectoryOrFileModal.tsx @@ -42,6 +42,11 @@ export default function MoveDirectoryOrFileModal( method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to get directories. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as ResponseBody; if (!result.success) { diff --git a/components/files/SearchFiles.tsx b/components/files/SearchFiles.tsx index e0a2fc5..195044c 100644 --- a/components/files/SearchFiles.tsx +++ b/components/files/SearchFiles.tsx @@ -41,6 +41,11 @@ export default function SearchFiles({}: SearchFilesProps) { method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to search files. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as ResponseBody; if (!result.success) { diff --git a/components/notes/MainNotes.tsx b/components/notes/MainNotes.tsx index 2c1ebcc..1637104 100644 --- a/components/notes/MainNotes.tsx +++ b/components/notes/MainNotes.tsx @@ -64,6 +64,11 @@ export default function MainNotes({ initialDirectories, initialFiles, initialPat method: 'POST', body: requestBody, }); + + if (!response.ok) { + throw new Error(`Failed to create note. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as UploadResponseBody; if (!result.success) { @@ -114,6 +119,11 @@ export default function MainNotes({ initialDirectories, initialFiles, initialPat method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to create directory. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as CreateDirectoryResponseBody; if (!result.success) { @@ -155,6 +165,11 @@ export default function MainNotes({ initialDirectories, initialFiles, initialPat method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to delete directory. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as DeleteDirectoryResponseBody; if (!result.success) { @@ -187,6 +202,11 @@ export default function MainNotes({ initialDirectories, initialFiles, initialPat method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to delete note. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as DeleteResponseBody; if (!result.success) { diff --git a/components/photos/MainPhotos.tsx b/components/photos/MainPhotos.tsx index 241f09a..1c88493 100644 --- a/components/photos/MainPhotos.tsx +++ b/components/photos/MainPhotos.tsx @@ -58,6 +58,11 @@ export default function MainPhotos({ initialDirectories, initialFiles, initialPa method: 'POST', body: requestBody, }); + + if (!response.ok) { + throw new Error(`Failed to upload photo. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as UploadResponseBody; if (!result.success) { @@ -104,6 +109,11 @@ export default function MainPhotos({ initialDirectories, initialFiles, initialPa method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to create directory. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as CreateDirectoryResponseBody; if (!result.success) { diff --git a/docker-compose.yml b/docker-compose.yml index 4586fee..1ff0c32 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: website: - image: ghcr.io/bewcloud/bewcloud:v2.2.0 + image: ghcr.io/bewcloud/bewcloud:v2.2.2 restart: always ports: - 127.0.0.1:8000:8000 diff --git a/islands/auth/MultiFactorAuthSettings.tsx b/islands/auth/MultiFactorAuthSettings.tsx index 6f383e9..fe19084 100644 --- a/islands/auth/MultiFactorAuthSettings.tsx +++ b/islands/auth/MultiFactorAuthSettings.tsx @@ -100,7 +100,7 @@ export default function MultiFactorAuthSettings({ methods }: MultiFactorAuthSett const beginData = await beginResponse.json() as PasskeySetupBeginResponseBody; if (!beginData.success) { - throw new Error(beginData.error || 'Failed to begin passkey registration'); + throw new Error(beginData.error || 'Failed to begin passkey registration.'); } const registrationResponse = await startRegistration({ optionsJSON: beginData.options! }); @@ -125,7 +125,7 @@ export default function MultiFactorAuthSettings({ methods }: MultiFactorAuthSett const completeData = await completeResponse.json() as PasskeySetupCompleteResponseBody; if (!completeData.success) { - throw new Error(completeData.error || 'Failed to complete passkey registration'); + throw new Error(completeData.error || 'Failed to complete passkey registration.'); } setupData.value = { @@ -142,10 +142,16 @@ export default function MultiFactorAuthSettings({ methods }: MultiFactorAuthSett body: JSON.stringify(requestBody), }); + if (!response.ok) { + throw new Error( + `Failed to setup TOTP multi-factor authentication. ${response.statusText} ${await response.text()}`, + ); + } + const data = await response.json() as TOTPSetupResponseBody; if (!data.success || !data.data) { - throw new Error(data.error || 'Failed to setup TOTP multi-factor authentication'); + throw new Error(data.error || 'Failed to setup TOTP multi-factor authentication.'); } setupData.value = { @@ -165,10 +171,17 @@ export default function MultiFactorAuthSettings({ methods }: MultiFactorAuthSett body: JSON.stringify(requestBody), }); + if (!response.ok) { + throw new Error( + `Failed to setup email multi-factor authentication. Please check your SMTP settings are valid and try again. ${response.statusText} ${await response + .text()}`, + ); + } + const data = await response.json() as EmailSetupResponseBody; if (!data.success || !data.data) { - throw new Error(data.error || 'Failed to setup email multi-factor authentication'); + throw new Error(data.error || 'Failed to setup email multi-factor authentication.'); } setupData.value = { @@ -221,13 +234,19 @@ export default function MultiFactorAuthSettings({ methods }: MultiFactorAuthSett body: JSON.stringify(requestBody), }); + if (!response.ok) { + throw new Error( + `Failed to enable multi-factor authentication method. ${response.statusText} ${await response.text()}`, + ); + } + const data = await response.json() as MultiFactorAuthEnableResponseBody; if (!data.success) { - throw new Error(data.error || 'Failed to enable multi-factor authentication'); + throw new Error(data.error || 'Failed to enable multi-factor authentication method.'); } - success.value = 'Multi-factor authentication has been enabled successfully! Reloading...'; + success.value = 'Multi-factor authentication method has been enabled successfully! Reloading...'; setupData.value = null; verificationToken.value = ''; @@ -262,13 +281,19 @@ export default function MultiFactorAuthSettings({ methods }: MultiFactorAuthSett body: JSON.stringify(requestBody), }); + if (!response.ok) { + throw new Error( + `Failed to disable multi-factor authentication method. ${response.statusText} ${await response.text()}`, + ); + } + const data = await response.json() as MultiFactorAuthDisableResponseBody; if (!data.success) { - throw new Error(data.error || 'Failed to disable multi-factor authentication'); + throw new Error(data.error || 'Failed to disable multi-factor authentication method.'); } - success.value = 'Multi-factor authentication has been disabled successfully! Reloading...'; + success.value = 'Multi-factor authentication method has been disabled successfully! Reloading...'; showDisableForm.value = null; disablePassword.value = ''; diff --git a/islands/dashboard/Links.tsx b/islands/dashboard/Links.tsx index 7b03318..93a558f 100644 --- a/islands/dashboard/Links.tsx +++ b/islands/dashboard/Links.tsx @@ -33,10 +33,15 @@ export default function Links({ initialLinks }: LinksProps) { method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to save link. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as ResponseBody; if (!result.success) { - throw new Error('Failed to save notes!'); + throw new Error('Failed to save link!'); } } catch (error) { console.error(error); diff --git a/islands/dashboard/Notes.tsx b/islands/dashboard/Notes.tsx index 822f60c..bfad74f 100644 --- a/islands/dashboard/Notes.tsx +++ b/islands/dashboard/Notes.tsx @@ -28,6 +28,11 @@ export default function Notes({ initialNotes }: NotesProps) { method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to save notes. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as ResponseBody; if (!result.success) { diff --git a/islands/news/Articles.tsx b/islands/news/Articles.tsx index c2e1972..17482ed 100644 --- a/islands/news/Articles.tsx +++ b/islands/news/Articles.tsx @@ -37,6 +37,11 @@ export default function Articles({ initialArticles }: ArticlesProps) { method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to refresh articles. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as RefreshResponseBody; if (!result.success) { @@ -87,6 +92,11 @@ export default function Articles({ initialArticles }: ArticlesProps) { method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to mark article as read. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as ReadResponseBody; if (!result.success) { @@ -114,6 +124,11 @@ export default function Articles({ initialArticles }: ArticlesProps) { method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to mark all articles as read. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as ReadResponseBody; if (!result.success) { diff --git a/islands/news/Feeds.tsx b/islands/news/Feeds.tsx index 2c57eee..883b405 100644 --- a/islands/news/Feeds.tsx +++ b/islands/news/Feeds.tsx @@ -84,6 +84,11 @@ export default function Feeds({ initialFeeds }: FeedsProps) { method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to add feed. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as AddResponseBody; if (!result.success) { @@ -116,6 +121,11 @@ export default function Feeds({ initialFeeds }: FeedsProps) { method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to delete feed. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as DeleteResponseBody; if (!result.success) { @@ -168,6 +178,11 @@ export default function Feeds({ initialFeeds }: FeedsProps) { method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to import feeds. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as ImportResponseBody; if (!result.success) { diff --git a/islands/notes/Note.tsx b/islands/notes/Note.tsx index 3d5fce8..52ef7ca 100644 --- a/islands/notes/Note.tsx +++ b/islands/notes/Note.tsx @@ -31,6 +31,11 @@ export default function Note({ fileName, currentPath, contents }: NoteProps) { method: 'POST', body: JSON.stringify(requestBody), }); + + if (!response.ok) { + throw new Error(`Failed to save note. ${response.statusText} ${await response.text()}`); + } + const result = await response.json() as ResponseBody; if (!result.success) {