From 192ac21fb0329bdc290881cc2e8c307030e4c9e1 Mon Sep 17 00:00:00 2001 From: Sudo-Ivan Date: Wed, 7 Jan 2026 19:46:01 -0600 Subject: [PATCH] feat(docs): add API endpoints for deleting documentation versions and clearing Reticulum docs - Implemented DELETE endpoints to allow users to delete specific documentation versions and clear all Reticulum documentation. - Enhanced the DocsManager class with methods for version deletion and clearing documentation, including error handling and logging. - Updated frontend components to support version deletion and clearing of Reticulum docs with user confirmation dialogs. --- meshchatx/meshchat.py | 35 ++++++++++-- meshchatx/src/backend/docs_manager.py | 57 +++++++++++++++++++ .../src/frontend/components/docs/DocsPage.vue | 39 ++++++++++--- .../components/settings/SettingsPage.vue | 25 ++++++++ 4 files changed, 144 insertions(+), 12 deletions(-) diff --git a/meshchatx/meshchat.py b/meshchatx/meshchat.py index aa04c0b..bad0620 100644 --- a/meshchatx/meshchat.py +++ b/meshchatx/meshchat.py @@ -3753,6 +3753,31 @@ class ReticulumMeshChat: except Exception as e: return web.json_response({"error": str(e)}, status=500) + # delete docs version + @routes.delete("/api/v1/docs/version/{version}") + async def docs_delete_version(request): + try: + version = request.match_info.get("version") + if not version: + return web.json_response( + {"error": "No version provided"}, + status=400, + ) + + success = self.docs_manager.delete_version(version) + return web.json_response({"success": success}) + except Exception as e: + return web.json_response({"error": str(e)}, status=500) + + # clear reticulum docs + @routes.delete("/api/v1/maintenance/docs/reticulum") + async def docs_clear(request): + try: + success = self.docs_manager.clear_reticulum_docs() + return web.json_response({"success": success}) + except Exception as e: + return web.json_response({"error": str(e)}, status=500) + # search docs @routes.get("/api/v1/docs/search") async def docs_search(request): @@ -8901,7 +8926,7 @@ class ReticulumMeshChat: if "telemetry_enabled" in data: self.config.telemetry_enabled.set( - self._parse_bool(data["telemetry_enabled"]) + self._parse_bool(data["telemetry_enabled"]), ) # update banishment settings @@ -10285,7 +10310,7 @@ class ReticulumMeshChat: commands.extend(val) elif isinstance(val, dict): commands.append(val) - if 0x01 in lxmf_fields and 0x01 != LXMF.FIELD_COMMANDS: + if 0x01 in lxmf_fields and LXMF.FIELD_COMMANDS != 0x01: val = lxmf_fields[0x01] if isinstance(val, list): commands.extend(val) @@ -10321,11 +10346,11 @@ class ReticulumMeshChat: else: # Check if peer is trusted contact = ctx.database.contacts.get_contact_by_identity_hash( - source_hash + source_hash, ) if not contact or not contact.get("is_telemetry_trusted"): print( - f"Telemetry request from untrusted peer {source_hash}, ignoring" + f"Telemetry request from untrusted peer {source_hash}, ignoring", ) else: print(f"Responding to telemetry request from {source_hash}") @@ -10342,7 +10367,7 @@ class ReticulumMeshChat: "remote_identity_name": source_hash[:8], "lxmf_message": convert_db_lxmf_message_to_dict( ctx.database.messages.get_lxmf_message_by_hash( - lxmf_message.hash.hex() + lxmf_message.hash.hex(), ), include_attachments=False, ), diff --git a/meshchatx/src/backend/docs_manager.py b/meshchatx/src/backend/docs_manager.py index d78d031..bc41803 100644 --- a/meshchatx/src/backend/docs_manager.py +++ b/meshchatx/src/backend/docs_manager.py @@ -134,6 +134,63 @@ class DocsManager: return True return False + def delete_version(self, version): + """Deletes a specific version of documentation.""" + if version not in self.get_available_versions(): + return False + + version_path = os.path.join(self.versions_dir, version) + if not os.path.exists(version_path): + return False + + try: + # If the deleted version is the current one, unlink 'current' first + current_version = self.get_current_version() + if current_version == version: + if os.path.exists(self.docs_dir): + if os.path.islink(self.docs_dir): + os.unlink(self.docs_dir) + else: + shutil.rmtree(self.docs_dir) + + shutil.rmtree(version_path) + + # If we just deleted the current version, try to pick another one as current + if current_version == version: + self._update_current_link() + + return True + except Exception as e: + logging.exception(f"Failed to delete docs version {version}: {e}") + return False + + def clear_reticulum_docs(self): + """Clears all Reticulum documentation and versions.""" + try: + if os.path.exists(self.docs_base_dir): + # We don't want to delete the base dir itself, just its contents + # except possibly some metadata if we added any. + # Actually, deleting everything inside reticulum-docs is fine. + for item in os.listdir(self.docs_base_dir): + item_path = os.path.join(self.docs_base_dir, item) + if os.path.islink(item_path): + os.unlink(item_path) + elif os.path.isdir(item_path): + shutil.rmtree(item_path) + else: + os.remove(item_path) + + # Re-create required subdirectories + for d in [self.versions_dir, self.docs_dir]: + if not os.path.exists(d): + os.makedirs(d) + + self.config.docs_downloaded.set(False) + return True + except Exception as e: + logging.exception(f"Failed to clear Reticulum docs: {e}") + return False + def populate_meshchatx_docs(self): """Populates meshchatx-docs from the project's docs folder.""" # Try to find docs folder in several places diff --git a/meshchatx/src/frontend/components/docs/DocsPage.vue b/meshchatx/src/frontend/components/docs/DocsPage.vue index ae9b1d8..1d43824 100644 --- a/meshchatx/src/frontend/components/docs/DocsPage.vue +++ b/meshchatx/src/frontend/components/docs/DocsPage.vue @@ -114,7 +114,7 @@ +
+ +
@@ -2218,6 +2234,15 @@ export default { ToastUtils.error(this.$t("common.error")); } }, + async clearReticulumDocs() { + if (!(await DialogUtils.confirm(this.$t("maintenance.clear_confirm")))) return; + try { + await window.axios.delete("/api/v1/maintenance/docs/reticulum"); + ToastUtils.success(this.$t("maintenance.docs_cleared")); + } catch { + ToastUtils.error(this.$t("common.error")); + } + }, async exportMessages() { try { const response = await window.axios.get("/api/v1/maintenance/messages/export");