b014638f39
- POST /api/save-to-corpus.php — saves tool output text to user's default CaveauAI corpus via ClientRagPipeline
- api/case/upload.php — dual-writes uploaded PDFs to CaveauAI client_documents (best-effort)
- assets/js/corpus-save.js — shared <dialog> handler for .js-save-corpus buttons on all tool pages
- includes/layout_footer.php — injects corpus-save.js + shared save dialog markup
- korrespond/deep-research/barnevernet/discrepancy JS — save-to-corpus buttons on output sections
- api/search.php + LegalTools::search() — corpus_scope param ('shared'|'private'|'both'), merges personal CaveauAI corpus with shared legal library when 'both'
- includes/tool_form.php + assets/js/tools.js — corpus scope radio toggle shown on search tab
- api/user-docs.php — add POST upload method for non-SSO authenticated users
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
166 lines
6.1 KiB
PHP
166 lines
6.1 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* GET /api/user-docs.php — list uploaded documents for current user
|
|
* DELETE /api/user-docs.php?id=X — remove a document
|
|
* POST /api/user-docs.php — upload a document (file field = 'file')
|
|
*
|
|
* SSO users (dbn_tools_sso_uid) are keyed by their SSO uid.
|
|
* Other authenticated users are keyed by session_id() as a fallback.
|
|
* Reads/writes the shared dobetternorge.dbn_user_docs table.
|
|
* Requires DBN_DB_* env vars pointing at the dobetternorge database.
|
|
*/
|
|
require_once __DIR__ . '/../includes/bootstrap.php';
|
|
|
|
dbnToolsRequireMethod('GET', 'DELETE', 'POST');
|
|
|
|
if (!dbnToolsIsAuthenticated()) {
|
|
http_response_code(401);
|
|
header('Content-Type: application/json');
|
|
echo json_encode(['ok' => false, 'error' => 'Not authenticated.']);
|
|
exit;
|
|
}
|
|
|
|
// SSO uid for SSO users; session id as stable key for client sessions
|
|
$ssoUid = (string)($_SESSION['dbn_tools_sso_uid'] ?? '');
|
|
$userKey = $ssoUid !== '' ? $ssoUid : 'sess_' . session_id();
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
// ── Connect to the shared dobetternorge DB (dbn_user_docs lives here) ─────────
|
|
function dbnSharedDb(): ?PDO
|
|
{
|
|
static $pdo = null;
|
|
if ($pdo !== null) return $pdo;
|
|
$host = dbnToolsEnv('DBN_DB_HOST', dbnToolsEnv('DBNM_DB_HOST', 'localhost'));
|
|
$name = dbnToolsEnv('DBN_DB_NAME', dbnToolsEnv('DBNM_DB_NAME', 'dobetternorge'));
|
|
$user = dbnToolsEnv('DBN_DB_USER', dbnToolsEnv('DBNM_DB_USER', 'root'));
|
|
$pass = dbnToolsEnv('DBN_DB_PASS', dbnToolsEnv('DBNM_DB_PASS', ''));
|
|
try {
|
|
$pdo = new PDO("mysql:host={$host};dbname={$name};charset=utf8mb4", $user, $pass, [
|
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
PDO::ATTR_TIMEOUT => 5,
|
|
]);
|
|
return $pdo;
|
|
} catch (Throwable) { return null; }
|
|
}
|
|
|
|
$method = $_SERVER['REQUEST_METHOD'];
|
|
|
|
// ── POST — upload a document ──────────────────────────────────────────────────
|
|
if ($method === 'POST') {
|
|
if (empty($_FILES['file']) || !is_array($_FILES['file'])) {
|
|
http_response_code(422);
|
|
echo json_encode(['ok' => false, 'error' => 'No file uploaded.']);
|
|
exit;
|
|
}
|
|
|
|
try {
|
|
$extracted = dbnToolsExtractUploadedFile($_FILES['file']);
|
|
} catch (Throwable $e) {
|
|
http_response_code(422);
|
|
echo json_encode(['ok' => false, 'error' => $e->getMessage()]);
|
|
exit;
|
|
}
|
|
|
|
$docId = uniqid('wbd_', true);
|
|
$filename = basename((string)($_FILES['file']['name'] ?? 'document'));
|
|
$fileType = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
|
$chunks = isset($extracted['text']) ? max(1, (int)ceil(mb_strlen($extracted['text']) / 1000)) : 0;
|
|
$now = gmdate('Y-m-d H:i:s');
|
|
|
|
$db = dbnSharedDb();
|
|
if ($db) {
|
|
$db->prepare(
|
|
'INSERT INTO dbn_user_docs (id, user_id, filename, file_type, chunk_count, source, status, created_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)'
|
|
)->execute([$docId, $userKey, $filename, $fileType, $chunks, 'workbench', 'ready', $now]);
|
|
}
|
|
|
|
echo json_encode([
|
|
'ok' => true,
|
|
'doc' => [
|
|
'doc_id' => $docId,
|
|
'filename' => $filename,
|
|
'file_type' => $fileType,
|
|
'chunk_count' => $chunks,
|
|
'source' => 'workbench',
|
|
'created_at' => $now,
|
|
],
|
|
]);
|
|
exit;
|
|
}
|
|
|
|
// ── DELETE ────────────────────────────────────────────────────────────────────
|
|
if ($method === 'DELETE') {
|
|
$docId = trim($_GET['id'] ?? '');
|
|
if ($docId === '') {
|
|
http_response_code(400);
|
|
echo json_encode(['ok' => false, 'error' => 'Missing id parameter.']);
|
|
exit;
|
|
}
|
|
|
|
$db = dbnSharedDb();
|
|
if ($db) {
|
|
$stmt = $db->prepare('SELECT id FROM dbn_user_docs WHERE id = ? AND user_id = ?');
|
|
$stmt->execute([$docId, $userKey]);
|
|
if ($stmt->fetch()) {
|
|
$db->prepare('DELETE FROM dbn_user_docs WHERE id = ? AND user_id = ?')
|
|
->execute([$docId, $userKey]);
|
|
|
|
// Delete Qdrant points for this doc
|
|
$qdrantUrl = 'http://10.0.2.10:6333';
|
|
$body = [
|
|
'filter' => [
|
|
'must' => [
|
|
['key' => 'doc_id', 'match' => ['value' => $docId]],
|
|
['key' => 'user_id', 'match' => ['value' => $userKey]],
|
|
],
|
|
],
|
|
];
|
|
$ch = curl_init("$qdrantUrl/collections/dbn_user_docs/points/delete");
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_POST => true,
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_TIMEOUT => 10,
|
|
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
|
|
CURLOPT_POSTFIELDS => json_encode($body),
|
|
]);
|
|
curl_exec($ch);
|
|
curl_close($ch);
|
|
}
|
|
}
|
|
echo json_encode(['ok' => true]);
|
|
exit;
|
|
}
|
|
|
|
// ── GET ───────────────────────────────────────────────────────────────────────
|
|
$db = dbnSharedDb();
|
|
if (!$db) {
|
|
echo json_encode(['ok' => true, 'docs' => []]);
|
|
exit;
|
|
}
|
|
|
|
$stmt = $db->prepare(
|
|
'SELECT id, filename, file_type, chunk_count, source, created_at
|
|
FROM dbn_user_docs
|
|
WHERE user_id = ? AND status = ?
|
|
ORDER BY created_at DESC
|
|
LIMIT 50'
|
|
);
|
|
$stmt->execute([$userKey, 'ready']);
|
|
$rows = $stmt->fetchAll();
|
|
|
|
$docs = array_map(static fn($r) => [
|
|
'doc_id' => $r['id'],
|
|
'filename' => $r['filename'],
|
|
'file_type' => $r['file_type'],
|
|
'chunk_count' => (int)$r['chunk_count'],
|
|
'source' => $r['source'],
|
|
'created_at' => $r['created_at'],
|
|
], $rows);
|
|
|
|
echo json_encode(['ok' => true, 'docs' => $docs]);
|