- Add PostgreSQL service to docker-compose with health check and postgres_data volume
- Mount ./ocr_images as bind volume for persistent image storage
- Add backend/database.py with schema init and get_db() context manager
- Add 5 new API endpoints: POST /api/jobs, GET /api/jobs (search), GET /api/jobs/{id},
GET /api/jobs/{id}/image, PUT /api/jobs/{id}/review
- Jobs are saved with author/book/chapter/page metadata, auto UUID, and submitted_at timestamp
- Jobs start as 'unreviewed'; review captures edited text, reviewer name, and reviewed_at
- Add MetadataForm.jsx (author/book/chapter/page inputs) to the New Job panel
- Add JobsPanel.jsx with search/filter, paginated list, and detail pane with review form
- Add "Commit Job" button to ResultPanel (plain_ocr mode only) with success/error feedback
- Add "New Job" / "Browse Jobs" navigation to the app header
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
72 lines
2.1 KiB
Python
72 lines
2.1 KiB
Python
import os
|
|
import psycopg2
|
|
import psycopg2.extras
|
|
from contextlib import contextmanager
|
|
from decouple import config as env_config
|
|
|
|
DATABASE_URL = env_config(
|
|
"DATABASE_URL",
|
|
default="postgresql://ocr_user:ocr_password@postgres:5432/ocr_db"
|
|
)
|
|
|
|
|
|
def _get_conn():
|
|
return psycopg2.connect(DATABASE_URL, cursor_factory=psycopg2.extras.RealDictCursor)
|
|
|
|
|
|
def init_db():
|
|
"""Create tables if they don't exist. Called once at startup."""
|
|
conn = None
|
|
try:
|
|
conn = _get_conn()
|
|
with conn.cursor() as cur:
|
|
cur.execute("""
|
|
CREATE TABLE IF NOT EXISTS ocr_jobs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
author TEXT,
|
|
book TEXT,
|
|
chapter TEXT,
|
|
page TEXT,
|
|
submitted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
image_path TEXT NOT NULL,
|
|
original_filename TEXT,
|
|
ocr_text TEXT,
|
|
status TEXT NOT NULL DEFAULT 'unreviewed',
|
|
reviewed_text TEXT,
|
|
reviewer_name TEXT,
|
|
reviewed_at TIMESTAMPTZ,
|
|
mode TEXT
|
|
)
|
|
""")
|
|
# Index for fast full-text-style searches on common fields
|
|
cur.execute("""
|
|
CREATE INDEX IF NOT EXISTS ocr_jobs_status_idx ON ocr_jobs(status)
|
|
""")
|
|
cur.execute("""
|
|
CREATE INDEX IF NOT EXISTS ocr_jobs_submitted_at_idx ON ocr_jobs(submitted_at DESC)
|
|
""")
|
|
conn.commit()
|
|
print("Database initialized.")
|
|
except Exception as exc:
|
|
print(f"Database init failed: {exc}")
|
|
if conn:
|
|
conn.rollback()
|
|
raise
|
|
finally:
|
|
if conn:
|
|
conn.close()
|
|
|
|
|
|
@contextmanager
|
|
def get_db():
|
|
"""Yield a connection and auto-commit/rollback."""
|
|
conn = _get_conn()
|
|
try:
|
|
yield conn
|
|
conn.commit()
|
|
except Exception:
|
|
conn.rollback()
|
|
raise
|
|
finally:
|
|
conn.close()
|