feat: Add Markdown export service for DOCX and PDF formats

- Implemented MarkdownExportService to convert Markdown text into DOCX and PDF formats.
- Supported Markdown elements include headings, bold, italic, blockquotes, tables, and specific emojis.
- Added methods for generating DOCX and PDF documents with proper formatting and rendering.
- Enhanced application properties to enable logging for production environment.
- Introduced a test-run script to reset project state and trigger processing via API.

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Wolf G. Beckmann
2026-04-28 14:32:45 +02:00
parent 1816168d1f
commit 522bc21439
24 changed files with 2011 additions and 127 deletions

View File

@@ -96,6 +96,41 @@ CREATE OR REPLACE PACKAGE DC_BACKEND_PKG AS
p_interval_sec IN NUMBER DEFAULT 5
);
-- -------------------------------------------------------------------------
-- Konvertiert einen Markdown-CLOB zu DOCX oder PDF.
-- POST /export/markdown → BLOB (application/pdf oder .docx)
--
-- p_markdown Markdown-Text (z.B. dc_projects.report_markdown)
-- p_format 'PDF' (Standard) oder 'DOCX'
-- Rückgabe BLOB mit dem fertigen Dokument
-- -------------------------------------------------------------------------
FUNCTION convert_markdown(
p_markdown IN CLOB,
p_format IN VARCHAR2 DEFAULT 'PDF'
) RETURN BLOB;
-- -------------------------------------------------------------------------
-- Convenience: liest report_markdown des Projekts und konvertiert zu PDF/DOCX.
--
-- Beispiel:
-- v_blob := DC_BACKEND_PKG.project_report(1, 'PDF');
-- -------------------------------------------------------------------------
FUNCTION project_report(
p_project_id IN NUMBER,
p_format IN VARCHAR2 DEFAULT 'PDF'
) RETURN BLOB;
-- -------------------------------------------------------------------------
-- Generiert den Markdown-Prüfbericht für ein Projekt neu und speichert ihn
-- in dc_projects.report_markdown. Ruft intern dc_generate_report auf.
--
-- Beispiel:
-- DC_BACKEND_PKG.regenerate_report(1);
-- -------------------------------------------------------------------------
PROCEDURE regenerate_report(
p_project_id IN NUMBER
);
END DC_BACKEND_PKG;
/
@@ -262,6 +297,93 @@ CREATE OR REPLACE PACKAGE BODY DC_BACKEND_PKG AS
END wait_for_completion;
-- -------------------------------------------------------------------------
FUNCTION convert_markdown(
p_markdown IN CLOB,
p_format IN VARCHAR2 DEFAULT 'PDF'
) RETURN BLOB IS
l_url VARCHAR2(500);
l_body CLOB;
l_result BLOB;
l_status NUMBER;
BEGIN
l_url := c_base_url || '/export/markdown';
-- JSON-Body mit CLOB-Inhalt: APEX_JSON unterstützt CLOBs beliebiger Größe
APEX_JSON.INITIALIZE_CLOB_OUTPUT;
APEX_JSON.OPEN_OBJECT;
APEX_JSON.WRITE('content', p_markdown);
APEX_JSON.WRITE('format', UPPER(p_format));
APEX_JSON.CLOSE_OBJECT;
l_body := APEX_JSON.GET_CLOB_OUTPUT;
APEX_JSON.FREE_OUTPUT;
set_headers();
l_result := APEX_WEB_SERVICE.MAKE_REST_REQUEST_B(
p_url => l_url,
p_http_method => 'POST',
p_body => l_body
);
l_status := APEX_WEB_SERVICE.G_STATUS_CODE;
IF l_status NOT IN (200, 201) THEN
RAISE_APPLICATION_ERROR(-20200,
'Export-Service Fehler: HTTP ' || l_status ||
' (format=' || p_format || ')');
END IF;
RETURN l_result;
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE = -20200 THEN RAISE; END IF;
RAISE_APPLICATION_ERROR(-20201,
'Verbindungsfehler beim Export: ' || SQLERRM);
END convert_markdown;
-- -------------------------------------------------------------------------
FUNCTION project_report(
p_project_id IN NUMBER,
p_format IN VARCHAR2 DEFAULT 'PDF'
) RETURN BLOB IS
l_markdown CLOB;
BEGIN
SELECT report_markdown
INTO l_markdown
FROM dc_projects
WHERE id = p_project_id;
IF l_markdown IS NULL OR DBMS_LOB.GETLENGTH(l_markdown) = 0 THEN
RAISE_APPLICATION_ERROR(-20202,
'Projekt ' || p_project_id || ' hat noch keinen Markdown-Bericht. '
|| 'Bitte zuerst POST /api/dc/projects/' || p_project_id || '/report aufrufen.');
END IF;
RETURN convert_markdown(l_markdown, p_format);
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20110,
'Projekt ' || p_project_id || ' nicht gefunden.');
END project_report;
-- -------------------------------------------------------------------------
PROCEDURE regenerate_report(
p_project_id IN NUMBER
) IS
BEGIN
dc_generate_report(p_project_id => p_project_id);
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20110,
'Projekt ' || p_project_id || ' nicht gefunden.');
END regenerate_report;
END DC_BACKEND_PKG;
/
@@ -294,4 +416,24 @@ BEGIN
DBMS_OUTPUT.PUT_LINE('Response: ' || l_response);
END;
/
-- Bericht als PDF exportieren
DECLARE
l_blob BLOB;
BEGIN
DC_BACKEND_PKG.g_api_key := 'mein-api-key';
l_blob := DC_BACKEND_PKG.project_report(p_project_id => 1, p_format => 'PDF');
DBMS_OUTPUT.PUT_LINE('PDF-Größe: ' || DBMS_LOB.GETLENGTH(l_blob) || ' Bytes');
END;
/
-- Bericht als DOCX exportieren
DECLARE
l_blob BLOB;
BEGIN
DC_BACKEND_PKG.g_api_key := 'mein-api-key';
l_blob := DC_BACKEND_PKG.project_report(p_project_id => 1, p_format => 'DOCX');
DBMS_OUTPUT.PUT_LINE('DOCX-Größe: ' || DBMS_LOB.GETLENGTH(l_blob) || ' Bytes');
END;
/
*/