From ad26062f4683a1eee80c40937c5f0d996bc2defa Mon Sep 17 00:00:00 2001 From: Kevin_Ramirez Date: Tue, 24 Feb 2026 14:00:28 -0600 Subject: [PATCH] Coreccion del cliente para descargar los archivos multimedia --- .../api/v1/modules/core/help_center/tasks.py | 4 +- .../api/v1/modules/core/help_center/utils.py | 7 +- backend/celerybeat-schedule | Bin 0 -> 16384 bytes docker-compose.yml | 9 +++ .../dashboard/help-center/[uuid]/+page.svelte | 76 ++++++++++++++---- .../help-center/editor/[uuid]/+page.svelte | 42 ++++++++-- 6 files changed, 114 insertions(+), 24 deletions(-) create mode 100644 backend/celerybeat-schedule diff --git a/backend/api/v1/modules/core/help_center/tasks.py b/backend/api/v1/modules/core/help_center/tasks.py index d069f94c..107df26d 100644 --- a/backend/api/v1/modules/core/help_center/tasks.py +++ b/backend/api/v1/modules/core/help_center/tasks.py @@ -244,8 +244,10 @@ def sync_from_hub_task(): # Download assets after bulk update (Polling) from .utils import download_file_from_hub, sync_assets_from_content for art_data in articles_data: - if art_data.get('file_url'): + # art_data contains the virtual fields because it was dumped via HelpArticleInDB + if "file_url" in art_data and art_data['file_url']: download_file_from_hub(art_data['file_url']) + sync_assets_from_content(art_data.get('content', '')) logger.info("Polling sync completed successfully.") diff --git a/backend/api/v1/modules/core/help_center/utils.py b/backend/api/v1/modules/core/help_center/utils.py index c6140e14..b5fe4c58 100644 --- a/backend/api/v1/modules/core/help_center/utils.py +++ b/backend/api/v1/modules/core/help_center/utils.py @@ -29,6 +29,7 @@ def download_file_from_hub(relative_path: str) -> bool: local_path = Path(clean_path) if local_path.exists(): + logger.info(f"File {clean_path} already exists, skipping download.") return True # Ensure directories exist @@ -38,7 +39,9 @@ def download_file_from_hub(relative_path: str) -> bool: # CENTRAL_SERVER_URL is usually http://hub:8000/api/v1/core/help-center/sync/ # We want http://hub:8000/api/ base_url = settings.CENTRAL_SERVER_URL.split("/v1/")[0] - hub_file_url = f"{base_url}/uploads/{clean_path.replace('uploads/', '')}" + # The file in backend is served usually under /api/uploads/... + # But clean_path is just "uploads/...". So the Hub route is base_url + "/" + clean_path + hub_file_url = f"{base_url}/{clean_path}" logger.info(f"Downloading asset from Hub: {hub_file_url} -> {local_path}") @@ -51,7 +54,7 @@ def download_file_from_hub(relative_path: str) -> bool: logger.info(f"Successfully downloaded {clean_path}") return True else: - logger.warning(f"Failed to download {clean_path}: Status {response.status_code}") + logger.warning(f"Failed to download {clean_path}: Status {response.status_code} URL: {hub_file_url}") return False except Exception as e: logger.error(f"Error downloading {clean_path}: {str(e)}") diff --git a/backend/celerybeat-schedule b/backend/celerybeat-schedule new file mode 100644 index 0000000000000000000000000000000000000000..6999c017b919b785b47a1bfbfd853c2952898ea0 GIT binary patch literal 16384 zcmeI(O=}ZD7zgl4jc#HSC`M>e5bAbtSP;urAj$q(R3(2Jm-K+tD8Tbo1iC1~;GADDff-ObESewlqp0(tf1R)ftj zw$UhS21E>S21E>S21OHeZ*k$i(Rx2VqVUzPE8<&dxM&0gzIJNo2pO0ng z@|hmn^~7iRN+8j(BHOe&l`~eSN<8Jz=QTfvg{g&E+)dHM@Dc$?2l~W>3Vq z$`b{YKd3ojl@~X_|e#rsumQwa@hTO@E*I`!dK@w5oIh$))Jg5L@Y* zZtZp6PtDaA9OrF|&a`t8NfBpSId`}Wq)cf{RT(5P7x8jJ>1|4{n3RrJ{XD)a!j*{Y z2l~3+{v~@QbIvr6shMK-Sut(8P-Qcx5xaX-)y%QOBN@=X$Gi(3dq{m(7tQwAP!F}F zBpZ`!{UW((.*?)<\/h\1>/g, (match, level, text) => { - const id = text.toLowerCase().replace(/[^\w]+/g, '-'); - return `${text}`; - }); - if (browser) { - return DOMPurify.sanitize(htmlWithIds); + if (!content) return ''; + try { + const rawHtml = marked.parse(content) as string; + // Add IDs to headers for TOC navigation + let htmlWithIds = rawHtml.replace(/(.*?)<\/h\1>/g, (match, level, text) => { + const id = text.toLowerCase().replace(/[^\w]+/g, '-'); + return `${text}`; + }); + + // Rewrite image src and link hrefs to point to the local backend + htmlWithIds = htmlWithIds.replace(/src="([^"]+)"/g, (match, src) => { + return `src="${resolveAssetUrl(src)}"`; + }); + htmlWithIds = htmlWithIds.replace(/href="(\/api\/uploads\/[^"]+)"/g, (match, href) => { + return `href="${resolveAssetUrl(href)}"`; + }); + + if (browser && typeof window !== 'undefined') { + // DOMPurify only works in the browser + return DOMPurify.sanitize(htmlWithIds); + } + return htmlWithIds; // SSR returns raw HTML (or could use isomorphic-dompurify) + } catch (e) { + console.error('Markdown parsing error:', e); + return ''; } - return htmlWithIds; } function scrollToHeader(id: string) { @@ -152,11 +187,20 @@
- - @@ -164,7 +208,7 @@
@@ -172,7 +216,7 @@ {:else if article.content_type === 'video'}
@@ -193,7 +237,11 @@ Este archivo no tiene vista previa directa.

- diff --git a/frontend/src/routes/dashboard/help-center/editor/[uuid]/+page.svelte b/frontend/src/routes/dashboard/help-center/editor/[uuid]/+page.svelte index 103d8404..2455fea8 100644 --- a/frontend/src/routes/dashboard/help-center/editor/[uuid]/+page.svelte +++ b/frontend/src/routes/dashboard/help-center/editor/[uuid]/+page.svelte @@ -27,6 +27,19 @@ import { marked } from 'marked'; import DOMPurify from 'dompurify'; + // Helper to resolve an API path from the DB (e.g., /api/uploads/...) to a full URL pointing to the local backend. + function resolveAssetUrl(url: string | undefined): string { + if (!url) return ''; + if (url.startsWith('http://') || url.startsWith('https://')) return url; + + // Fallback to empty string if VITE_API_URL is missing + // @ts-ignore + const apiUrl = import.meta.env.VITE_API_URL || ''; + const base = apiUrl.replace(/\/$/, ''); + const cleanUrl = url.replace(/^\/api\//, '/'); + return `${base}${cleanUrl}`; + } + let article = $state(null); let loading = $state(true); let processing = $state(false); @@ -206,11 +219,26 @@ } function renderMarkdown(text: string) { - const html = marked.parse(text || '') as string; - if (browser) { - return DOMPurify.sanitize(html); + if (!text) return ''; + try { + const html = marked.parse(text) as string; + + // Rewrite image src and link hrefs to point to the local backend + let resolvedHtml = html.replace(/src="([^"]+)"/g, (match, src) => { + return `src="${resolveAssetUrl(src)}"`; + }); + resolvedHtml = resolvedHtml.replace(/href="(\/api\/uploads\/[^"]+)"/g, (match, href) => { + return `href="${resolveAssetUrl(href)}"`; + }); + + if (browser && typeof window !== 'undefined') { + return DOMPurify.sanitize(resolvedHtml); + } + return resolvedHtml; + } catch (e) { + console.error("Markdown parsing error in editor:", e); + return ''; } - return html; } @@ -359,8 +387,8 @@ bind:value={content} class="w-full flex-1 resize-none bg-background p-8 font-mono text-sm leading-relaxed outline-none" placeholder="# Empieza a escribir aquí..." - ondrop={handleDrop} - ondragover={(e) => e.preventDefault()} + ondrop={(e: DragEvent) => handleDrop(e)} + ondragover={(e: DragEvent) => e.preventDefault()} > @@ -418,7 +446,7 @@
-