Add job tracking with PostgreSQL, image storage, and review workflow
- 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>
This commit is contained in:
71
backend/database.py
Normal file
71
backend/database.py
Normal file
@@ -0,0 +1,71 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user