From d4fb2a8cccb3c770e61339e8952f290ae5b31f33 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Wed, 28 Jan 2026 13:34:42 +0100 Subject: [PATCH] Enhance health activity error handling: Implement detailed error responses in FalukantController for 'tooClose' scenarios, including retry timing. Update localization files for improved user feedback on health measures and errors. Refactor error handling in HealthView to display appropriate messages based on error responses. --- backend/controllers/falukantController.js | 11 ++++++++- backend/services/falukantService.js | 6 ++++- frontend/src/i18n/locales/de/falukant.json | 7 +++++- frontend/src/i18n/locales/en/falukant.json | 27 +++++++++++++++++++++- frontend/src/views/falukant/HealthView.vue | 20 +++++++++++++--- 5 files changed, 64 insertions(+), 7 deletions(-) diff --git a/backend/controllers/falukantController.js b/backend/controllers/falukantController.js index d1df872..a158980 100644 --- a/backend/controllers/falukantController.js +++ b/backend/controllers/falukantController.js @@ -155,7 +155,16 @@ class FalukantController { this.advanceNobility = this._wrapWithUser((userId) => this.service.advanceNobility(userId)); this.getHealth = this._wrapWithUser((userId) => this.service.getHealth(userId)); - this.healthActivity = this._wrapWithUser((userId, req) => this.service.healthActivity(userId, req.body.measureTr)); + this.healthActivity = this._wrapWithUser(async (userId, req) => { + try { + return await this.service.healthActivity(userId, req.body.measureTr); + } catch (e) { + if (e && e.name === 'PreconditionError' && e.message === 'tooClose') { + throw { status: 412, message: 'tooClose', retryAt: e.meta?.retryAt }; + } + throw e; + } + }); this.getPoliticsOverview = this._wrapWithUser((userId) => this.service.getPoliticsOverview(userId)); this.getOpenPolitics = this._wrapWithUser((userId) => this.service.getOpenPolitics(userId)); diff --git a/backend/services/falukantService.js b/backend/services/falukantService.js index 5bcb743..9b07b63 100644 --- a/backend/services/falukantService.js +++ b/backend/services/falukantService.js @@ -3662,7 +3662,11 @@ class FalukantService extends BaseService { limit: 1 }); if (lastHealthActivity) { - throw new Error('too close'); + // Berechne, wann die nächste Maßnahme möglich ist (24 Stunden nach der letzten) + const retryAt = new Date(lastHealthActivity.createdAt.getTime() + 24 * 60 * 60 * 1000); + const err = new PreconditionError('tooClose'); + err.meta = { retryAt: retryAt.toISOString() }; + throw err; } const activityObject = FalukantService.HEALTH_ACTIVITIES.find((a) => a.tr === activity); if (!activityObject) { diff --git a/frontend/src/i18n/locales/de/falukant.json b/frontend/src/i18n/locales/de/falukant.json index bf8606b..6d1d99d 100644 --- a/frontend/src/i18n/locales/de/falukant.json +++ b/frontend/src/i18n/locales/de/falukant.json @@ -895,7 +895,12 @@ "drunkOfLife": "Trunk des Lebens", "barber": "Barbier" }, - "choose": "Bitte auswählen" + "choose": "Bitte auswählen", + "errors": { + "tooClose": "Du kannst nicht so oft Maßnahmen durchführen.", + "generic": "Ein Fehler ist aufgetreten." + }, + "nextMeasureAt": "Nächste Maßnahme ab" }, "politics": { "title": "Politik", diff --git a/frontend/src/i18n/locales/en/falukant.json b/frontend/src/i18n/locales/en/falukant.json index 9a2d59f..823d430 100644 --- a/frontend/src/i18n/locales/en/falukant.json +++ b/frontend/src/i18n/locales/en/falukant.json @@ -197,7 +197,32 @@ }, "nobility": { "cooldown": "You can only advance again on {date}." - }, + }, + "healthview": { + "title": "Health", + "age": "Age", + "status": "Health Status", + "measuresTaken": "Measures Taken", + "measure": "Measure", + "date": "Date", + "cost": "Cost", + "success": "Success", + "selectMeasure": "Select Measure", + "perform": "Perform", + "measures": { + "pill": "Pill", + "doctor": "Doctor Visit", + "witch": "Witch", + "drunkOfLife": "Elixir of Life", + "barber": "Barber" + }, + "choose": "Please select", + "errors": { + "tooClose": "You cannot perform measures so often.", + "generic": "An error occurred." + }, + "nextMeasureAt": "Next measure from" + }, "branchProduction": { "storageAvailable": "Free storage" }, diff --git a/frontend/src/views/falukant/HealthView.vue b/frontend/src/views/falukant/HealthView.vue index 3cb7e2d..d756a18 100644 --- a/frontend/src/views/falukant/HealthView.vue +++ b/frontend/src/views/falukant/HealthView.vue @@ -142,9 +142,23 @@ export default { this.selectedTr = ''; } catch (err) { console.error('Error performing measure', err); - const title = this.$t('falukant.healthview.title'); - const remoteMsg = err?.response?.data?.error || err?.message || String(err); - this.$root.$refs.messageDialog?.open(remoteMsg, title); + if (err?.response?.status === 412) { + const retryAtIso = err.response?.data?.retryAt; + const code = err.response?.data?.error || err.response?.data?.message; + if (retryAtIso) { + const retryStr = new Date(retryAtIso).toLocaleString(navigator.language, { + year: 'numeric', month: '2-digit', day: '2-digit', + hour: '2-digit', minute: '2-digit' + }); + const baseMsg = this.$t(`falukant.healthview.errors.${code}`); + this.$root.$refs.errorDialog?.open(`${baseMsg} — ${this.$t('falukant.healthview.nextMeasureAt')}: ${retryStr}`); + } else { + this.$root.$refs.errorDialog?.open(this.$t(`falukant.healthview.errors.${code}`)); + } + } else { + const code = err?.response?.data?.error || err?.message || 'generic'; + this.$root.$refs.errorDialog?.open(this.$t(`falukant.healthview.errors.${code}`) || this.$t('falukant.healthview.errors.generic')); + } } }, handleDaemonMessage(evt) {