Files
Dokumenten-Check/Scripts/DC_BACKEND_PKG.sql

440 lines
15 KiB
MySQL
Raw Normal View History

2026-04-27 14:57:33 +02:00
-- =============================================================================
-- DC_BACKEND_PKG
-- PL/SQL-Schnittstelle zum Quarkus dc-backend (Kubernetes Service dc-backend:8090)
--
-- Voraussetzungen:
-- - APEX_WEB_SERVICE-Berechtigung für das Schema:
-- GRANT EXECUTE ON APEX_240100.APEX_WEB_SERVICE TO <schema>; -- APEX-Version anpassen
-- - ACL für ausgehende HTTP-Verbindung im selben Namespace:
-- DBMS_NETWORK_ACL_ADMIN.APPEND_HOST_ACE(
-- host => 'dc-backend', lower_port => 8090, upper_port => 8090,
-- ace => xs$ace_type(privilege_list => xs$name_list('connect','resolve'),
-- principal_name => '<schema>', principal_type => xs_acl.ptype_db));
--
-- Deployment:
-- sqlplus user/pass@db @"Scripts/DC_BACKEND_PKG.sql"
-- =============================================================================
-- =============================================================================
-- Package Spec
-- =============================================================================
CREATE OR REPLACE PACKAGE DC_BACKEND_PKG AS
-- Basis-URL des dc-backend Kubernetes Service
c_base_url CONSTANT VARCHAR2(200) := 'http://dc-backend:8090';
-- API-Key (X-API-KEY Header).
-- Leer = kein Auth (Dev-Modus).
-- Zur Laufzeit setzen: DC_BACKEND_PKG.g_api_key := 'mein-key';
g_api_key VARCHAR2(500) := '';
-- -------------------------------------------------------------------------
-- Startet die KI-gestützte Dokumentenprüfung für ein Projekt (asynchron).
-- Das Projekt muss im Status PENDING sein.
-- POST /check/{p_project_id} → 202 Accepted
--
-- p_project_id ID des dc_projects-Eintrags
-- p_status_code HTTP-Statuscode der Antwort (202 = OK, 409 = nicht PENDING)
-- p_response JSON-Antwort des Backend
-- -------------------------------------------------------------------------
PROCEDURE start_check(
p_project_id IN NUMBER,
p_status_code OUT NUMBER,
p_response OUT VARCHAR2
);
-- -------------------------------------------------------------------------
-- Health-Check: prüft ob der dc-backend Service erreichbar ist.
-- GET /check/health → 200 UP
--
-- Rückgabe: TRUE = Service läuft, FALSE = nicht erreichbar
-- -------------------------------------------------------------------------
FUNCTION health_check RETURN BOOLEAN;
-- -------------------------------------------------------------------------
-- Startet die Prüfung und wirft eine Exception wenn nicht 202 zurückkommt.
-- Convenience-Wrapper für direkte APEX-Button-Aufrufe.
-- -------------------------------------------------------------------------
PROCEDURE start_check_or_raise(
p_project_id IN NUMBER
);
-- -------------------------------------------------------------------------
-- Fortschritts-Record
-- -------------------------------------------------------------------------
TYPE t_project_status IS RECORD (
status dc_projects.status%TYPE,
processing_progress dc_projects.progress%TYPE,
is_completed BOOLEAN,
is_running BOOLEAN,
is_pending BOOLEAN
);
-- -------------------------------------------------------------------------
-- Liest Status und Fortschritt eines Projekts direkt aus der DB.
-- Kein REST-Aufruf sofort und transaktionssicher.
--
-- Beispiel:
-- l_s := DC_BACKEND_PKG.get_status(1);
-- DBMS_OUTPUT.PUT_LINE(l_s.status || ' ' || l_s.processing_progress || '%');
-- -------------------------------------------------------------------------
FUNCTION get_status(
p_project_id IN NUMBER
) RETURN t_project_status;
-- -------------------------------------------------------------------------
-- Wartet (polling) bis das Projekt COMPLETED oder ein Timeout erreicht ist.
-- Wirft eine Exception bei Timeout oder wenn das Projekt nicht gefunden wird.
--
-- p_timeout_sec Max. Wartezeit in Sekunden (Default: 1800 = 30 Min.)
-- p_interval_sec Polling-Intervall in Sekunden (Default: 5)
-- -------------------------------------------------------------------------
PROCEDURE wait_for_completion(
p_project_id IN NUMBER,
p_timeout_sec IN NUMBER DEFAULT 1800,
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
);
2026-04-27 14:57:33 +02:00
END DC_BACKEND_PKG;
/
-- =============================================================================
-- Package Body
-- =============================================================================
CREATE OR REPLACE PACKAGE BODY DC_BACKEND_PKG AS
-- -------------------------------------------------------------------------
-- Setzt gemeinsame Request-Header (Content-Type + optional API-Key)
-- -------------------------------------------------------------------------
PROCEDURE set_headers IS
l_idx PLS_INTEGER := 1;
BEGIN
APEX_WEB_SERVICE.G_REQUEST_HEADERS.DELETE;
APEX_WEB_SERVICE.G_REQUEST_HEADERS(l_idx).NAME := 'Content-Type';
APEX_WEB_SERVICE.G_REQUEST_HEADERS(l_idx).VALUE := 'application/json';
l_idx := l_idx + 1;
IF g_api_key IS NOT NULL AND LENGTH(TRIM(g_api_key)) > 0 THEN
APEX_WEB_SERVICE.G_REQUEST_HEADERS(l_idx).NAME := 'X-API-KEY';
APEX_WEB_SERVICE.G_REQUEST_HEADERS(l_idx).VALUE := g_api_key;
END IF;
END set_headers;
-- -------------------------------------------------------------------------
PROCEDURE start_check(
p_project_id IN NUMBER,
p_status_code OUT NUMBER,
p_response OUT VARCHAR2
) IS
l_url VARCHAR2(500);
l_response CLOB;
BEGIN
l_url := c_base_url || '/check/' || TO_CHAR(p_project_id);
set_headers();
l_response := APEX_WEB_SERVICE.MAKE_REST_REQUEST(
p_url => l_url,
p_http_method => 'POST',
p_body => '{}' -- leerer Body, projectId ist im Pfad
);
p_status_code := APEX_WEB_SERVICE.G_STATUS_CODE;
p_response := SUBSTR(l_response, 1, 4000);
EXCEPTION
WHEN OTHERS THEN
p_status_code := -1;
p_response := 'Verbindungsfehler: ' || SQLERRM;
END start_check;
-- -------------------------------------------------------------------------
FUNCTION health_check RETURN BOOLEAN IS
l_url VARCHAR2(500);
l_response CLOB;
l_status NUMBER;
BEGIN
l_url := c_base_url || '/check/health';
set_headers();
l_response := APEX_WEB_SERVICE.MAKE_REST_REQUEST(
p_url => l_url,
p_http_method => 'GET'
);
l_status := APEX_WEB_SERVICE.G_STATUS_CODE;
RETURN l_status = 200;
EXCEPTION
WHEN OTHERS THEN
RETURN FALSE;
END health_check;
-- -------------------------------------------------------------------------
PROCEDURE start_check_or_raise(
p_project_id IN NUMBER
) IS
l_status NUMBER;
l_response VARCHAR2(4000);
BEGIN
start_check(
p_project_id => p_project_id,
p_status_code => l_status,
p_response => l_response
);
IF l_status = 202 THEN
-- OK: Verarbeitung wurde gestartet
NULL;
ELSIF l_status = 409 THEN
RAISE_APPLICATION_ERROR(-20100,
'Projekt ' || p_project_id || ' ist nicht im Status PENDING.');
ELSIF l_status = 401 THEN
RAISE_APPLICATION_ERROR(-20101,
'Ungültiger API-Key für dc-backend.');
ELSIF l_status = -1 THEN
RAISE_APPLICATION_ERROR(-20102,
'dc-backend nicht erreichbar: ' || l_response);
ELSE
RAISE_APPLICATION_ERROR(-20103,
'Unerwarteter HTTP-Status ' || l_status || ': ' || l_response);
END IF;
END start_check_or_raise;
-- -------------------------------------------------------------------------
FUNCTION get_status(
p_project_id IN NUMBER
) RETURN t_project_status IS
l_rec t_project_status;
BEGIN
SELECT status, progress
INTO l_rec.status, l_rec.processing_progress
FROM dc_projects
WHERE id = p_project_id;
l_rec.is_completed := l_rec.status = 'COMPLETED';
l_rec.is_running := l_rec.status = 'IN_PROGRESS';
l_rec.is_pending := l_rec.status = 'PENDING';
RETURN l_rec;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20110,
'Projekt ' || p_project_id || ' nicht gefunden.');
END get_status;
-- -------------------------------------------------------------------------
PROCEDURE wait_for_completion(
p_project_id IN NUMBER,
p_timeout_sec IN NUMBER DEFAULT 1800,
p_interval_sec IN NUMBER DEFAULT 5
) IS
l_start TIMESTAMP := SYSTIMESTAMP;
l_status t_project_status;
BEGIN
LOOP
l_status := get_status(p_project_id);
EXIT WHEN l_status.is_completed;
IF EXTRACT(SECOND FROM (SYSTIMESTAMP - l_start))
+ EXTRACT(MINUTE FROM (SYSTIMESTAMP - l_start)) * 60
+ EXTRACT(HOUR FROM (SYSTIMESTAMP - l_start)) * 3600
> p_timeout_sec
THEN
RAISE_APPLICATION_ERROR(-20111,
'Timeout nach ' || p_timeout_sec || 's Projekt ' ||
p_project_id || ' noch im Status ' || l_status.status ||
' (' || l_status.processing_progress || '%).');
END IF;
DBMS_SESSION.SLEEP(p_interval_sec);
END LOOP;
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;
2026-04-27 14:57:33 +02:00
END DC_BACKEND_PKG;
/
-- =============================================================================
-- Schnelltest (optional, auskommentiert)
-- =============================================================================
/*
-- Health-Check
BEGIN
IF DC_BACKEND_PKG.health_check() THEN
DBMS_OUTPUT.PUT_LINE('dc-backend: UP');
ELSE
DBMS_OUTPUT.PUT_LINE('dc-backend: NICHT ERREICHBAR');
END IF;
END;
/
-- Prüfung starten (Projekt-ID anpassen)
DECLARE
l_status NUMBER;
l_response VARCHAR2(4000);
BEGIN
DC_BACKEND_PKG.start_check(
p_project_id => 1,
p_status_code => l_status,
p_response => l_response
);
DBMS_OUTPUT.PUT_LINE('Status: ' || l_status);
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;
/
2026-04-27 14:57:33 +02:00
*/