Add monetization spine + Build Your Own Case (Min Sak)
- Stripe: StripeClient.php, checkout/portal/webhook endpoints, idempotent event handling - FreeTier: tier-aware credits (free/light/pro/pro_plus), bonus_balance, hourly caps per tier - pricing.php + billing.php: 4-tier cards, 3 topups, Customer Portal, balance breakdown - Min Sak: CaseStore.php, AzureDocIntelligence.php, AzureSearchAdmin.php — per-user hybrid RAG - api/case/: upload, list, delete, ingest-callback (HMAC-auth'd from n8n) - award-survey-credits: inter-site HMAC endpoint for dobetternorge.no survey bonus - dashboard.php: tier badge, balance breakdown card, Min Sak CTA, survey CTA - KorrespondAgent + all 3 other agents: use_my_case toggle wired to dbnToolsCaseContext() - bootstrap.php: dbnToolsCaseContext(), dbnToolsIntersiteSecret(), dbnToolsCurrentTier() Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../../includes/bootstrap.php';
|
||||
require_once __DIR__ . '/../../includes/CaseStore.php';
|
||||
|
||||
dbnToolsRequireMethod('POST');
|
||||
dbnToolsRequireAuth();
|
||||
|
||||
$userId = (int)($_SESSION['dbn_tools_sso_uid'] ?? 0);
|
||||
if ($userId <= 0) {
|
||||
dbnToolsError('Auth required.', 401, 'auth_required');
|
||||
}
|
||||
|
||||
$input = dbnToolsJsonInput(500);
|
||||
$docId = (int)($input['doc_id'] ?? 0);
|
||||
if ($docId <= 0) {
|
||||
dbnToolsError('doc_id required.', 400, 'bad_input');
|
||||
}
|
||||
|
||||
$ok = CaseStore::deleteDocument($userId, $docId);
|
||||
if (!$ok) {
|
||||
dbnToolsError('Dokumentet finnes ikke, eller er allerede slettet.', 404, 'not_found');
|
||||
}
|
||||
|
||||
dbnToolsRespond(['ok' => true, 'doc_id' => $docId]);
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Called by the n8n Case Ingest workflow after a document is OCR'd + indexed.
|
||||
* Auth: HMAC-SHA256 (same as award-survey-credits, shared INTERSITE_HMAC_SECRET).
|
||||
*
|
||||
* Body: { doc_id, status, page_count, doc_type, detected_date, parties, error_msg }
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../includes/bootstrap.php';
|
||||
|
||||
dbnToolsRequireMethod('POST');
|
||||
|
||||
$secret = dbnToolsIntersiteSecret();
|
||||
if ($secret === '') {
|
||||
dbnToolsError('Intersite secret not configured.', 500, 'misconfigured');
|
||||
}
|
||||
|
||||
$ts = (int)($_SERVER['HTTP_X_INTERSITE_TIMESTAMP'] ?? 0);
|
||||
$sig = (string)($_SERVER['HTTP_X_INTERSITE_SIGNATURE'] ?? '');
|
||||
if ($ts === 0 || $sig === '' || abs(time() - $ts) > 300) {
|
||||
dbnToolsError('Bad intersite auth.', 401, 'bad_auth');
|
||||
}
|
||||
|
||||
$raw = file_get_contents('php://input');
|
||||
if (!is_string($raw) || $raw === '') {
|
||||
dbnToolsError('Empty body.', 400, 'empty');
|
||||
}
|
||||
if (!hash_equals(hash_hmac('sha256', $ts . '.' . $raw, $secret), $sig)) {
|
||||
dbnToolsError('Bad signature.', 401, 'bad_sig');
|
||||
}
|
||||
|
||||
$data = json_decode($raw, true);
|
||||
if (!is_array($data)) {
|
||||
dbnToolsError('Invalid JSON.', 400, 'invalid_json');
|
||||
}
|
||||
|
||||
$docId = (int)($data['doc_id'] ?? 0);
|
||||
$status = (string)($data['status'] ?? 'failed');
|
||||
$pageCount = isset($data['page_count']) ? (int)$data['page_count'] : null;
|
||||
$docType = isset($data['doc_type']) ? (string)$data['doc_type'] : null;
|
||||
$detectedDate = isset($data['detected_date']) ? (string)$data['detected_date'] : null;
|
||||
$parties = $data['parties'] ?? null;
|
||||
$errorMsg = isset($data['error_msg']) ? (string)$data['error_msg'] : null;
|
||||
|
||||
if ($docId <= 0) {
|
||||
dbnToolsError('doc_id required.', 400, 'bad_input');
|
||||
}
|
||||
|
||||
$db = dbnmDb();
|
||||
$db->prepare(
|
||||
'UPDATE case_documents
|
||||
SET ocr_status = ?,
|
||||
page_count = COALESCE(?, page_count),
|
||||
doc_type = COALESCE(?, doc_type),
|
||||
detected_date = COALESCE(?, detected_date),
|
||||
parties = COALESCE(?, parties),
|
||||
ocr_error = COALESCE(?, ocr_error),
|
||||
indexed_at = CASE WHEN ? = "ready" THEN NOW() ELSE indexed_at END
|
||||
WHERE id = ?'
|
||||
)->execute([
|
||||
$status,
|
||||
$pageCount,
|
||||
$docType,
|
||||
$detectedDate,
|
||||
$parties !== null ? json_encode($parties, JSON_UNESCAPED_UNICODE) : null,
|
||||
$errorMsg,
|
||||
$status,
|
||||
$docId,
|
||||
]);
|
||||
|
||||
dbnToolsRespond(['ok' => true]);
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../../includes/bootstrap.php';
|
||||
require_once __DIR__ . '/../../includes/CaseStore.php';
|
||||
|
||||
dbnToolsRequireMethod('GET');
|
||||
dbnToolsRequireAuth();
|
||||
|
||||
$userId = (int)($_SESSION['dbn_tools_sso_uid'] ?? 0);
|
||||
if ($userId <= 0) {
|
||||
dbnToolsError('Auth required.', 401, 'auth_required');
|
||||
}
|
||||
|
||||
dbnToolsRespond([
|
||||
'ok' => true,
|
||||
'docs' => CaseStore::listDocs($userId),
|
||||
]);
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../../includes/bootstrap.php';
|
||||
require_once __DIR__ . '/../../includes/FreeTier.php';
|
||||
require_once __DIR__ . '/../../includes/CaseStore.php';
|
||||
|
||||
dbnToolsRequireMethod('POST');
|
||||
dbnToolsRequireAuth();
|
||||
|
||||
$userId = (int)($_SESSION['dbn_tools_sso_uid'] ?? 0);
|
||||
if ($userId <= 0) {
|
||||
dbnToolsError('Auth required.', 401, 'auth_required');
|
||||
}
|
||||
|
||||
if (empty($_FILES['file']) || ($_FILES['file']['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
|
||||
dbnToolsError('No file uploaded or upload error.', 400, 'no_file');
|
||||
}
|
||||
|
||||
$file = $_FILES['file'];
|
||||
$size = (int)$file['size'];
|
||||
$tmp = (string)$file['tmp_name'];
|
||||
$name = (string)$file['name'];
|
||||
|
||||
if ($size <= 0 || $size > 25 * 1024 * 1024) {
|
||||
dbnToolsError('Filen må være mellom 1 byte og 25 MB.', 413, 'bad_size');
|
||||
}
|
||||
|
||||
// Validate it's actually a PDF (magic number check)
|
||||
$fh = @fopen($tmp, 'rb');
|
||||
if ($fh === false) {
|
||||
dbnToolsError('Kunne ikke lese filen.', 500, 'read_fail');
|
||||
}
|
||||
$head = (string)fread($fh, 5);
|
||||
fclose($fh);
|
||||
if (strncmp($head, '%PDF-', 5) !== 0) {
|
||||
dbnToolsError('Filen er ikke en gyldig PDF.', 415, 'not_pdf');
|
||||
}
|
||||
|
||||
try {
|
||||
$doc = CaseStore::registerUpload($userId, $name, $tmp, $size);
|
||||
CaseStore::caseEnqueueIngest((int)$doc['doc_id'], $userId);
|
||||
dbnToolsRespond([
|
||||
'ok' => true,
|
||||
'doc_id' => $doc['doc_id'],
|
||||
'filename' => $doc['filename'],
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
dbnToolsError($e->getMessage(), 400, 'upload_failed');
|
||||
}
|
||||
Reference in New Issue
Block a user