Skip to content

Commit 16ea765

Browse files
committed
feat(api): add import job REST API and service layer
Add REST API endpoints and business logic for GEDCOM import job management: API Endpoints (6): - POST /api/import-jobs - Create job with file upload - GET /api/import-jobs - List user's jobs (paginated, filterable) - GET /api/import-jobs/{job_id} - Get job detail with stages - POST /api/import-jobs/{job_id}/pause - Pause running job - POST /api/import-jobs/{job_id}/resume - Resume paused job - DELETE /api/import-jobs/{job_id} - Cancel job and delete files Service Layer: - create_import_job() - Upload GEDCOM, transition UPLOADED → QUEUED - get_job_with_stages() - Fetch job with pipeline stages - list_user_jobs() - Paginated job list with filtering - pause_job() / resume_job() - Job lifecycle management - cancel_job() - Delete job and cleanup storage - claim_next_job() - Worker job claiming (lease-based) - complete_stage() / fail_job() - Stage execution updates Database Changes: - Add order field to ImportJobStage for pipeline ordering - Change ImportJob.user_id from UUID to string (for Google JWT subject IDs) Tests: - 17 comprehensive endpoint tests (100% passing) - Coverage: routes 72%, service layer 26% (worker functions untested) File Storage: - Pattern: /data/gedcom/{user_id}/{job_id}/original.ged - user_id is string (Google subject), job_id is UUID State Machine: - Jobs: UPLOADED → QUEUED → IN_PROGRESS → PAUSED/COMPLETED/FAILED/CANCELLED - Stages: PENDING → IN_PROGRESS → COMPLETED/FAILED/RETRYING
1 parent 1ee23c4 commit 16ea765

7 files changed

Lines changed: 1473 additions & 3 deletions

File tree

apps/api/src/api/app.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import api.database # noqa: F401 - Import to register models with SQLModel metadata
1414
from api.enums import Environment
1515
from api.logging import setup_logging
16-
from api.routes import auth, health, user, wikitree
16+
from api.routes import auth, health, import_jobs, user, wikitree
1717
from api.settings import Settings, settings
1818

1919
logger = logging.getLogger(__name__)
@@ -148,5 +148,6 @@ async def lifespan(application: FastAPI):
148148

149149
app.include_router(auth.router, prefix='/auth')
150150
app.include_router(health.router, prefix='/health')
151+
app.include_router(import_jobs.router, prefix='/api')
151152
app.include_router(user.router, prefix='/user')
152153
app.include_router(wikitree.router, prefix='/api')

apps/api/src/api/database.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ class ImportJob(SQLModel, table=True):
6868
__tablename__ = 'import_jobs' # pyrefly: ignore[bad-override]
6969

7070
id: UUID = Field(default_factory=uuid4, primary_key=True)
71-
user_id: UUID = Field(foreign_key='app_users.id', index=True)
71+
# user_id stores Google subject ID directly (not FK to app_users)
72+
# because auth uses JWT validation, not database user lookups
73+
user_id: str = Field(index=True)
7274
source_type: str # gedcom | wikitree-export | manual
7375
original_filename: str
7476
stored_path: str
@@ -93,7 +95,8 @@ class ImportJobStage(SQLModel, table=True):
9395

9496
id: UUID = Field(default_factory=uuid4, primary_key=True)
9597
import_job_id: UUID = Field(foreign_key='import_jobs.id', index=True)
96-
stage_name: str # parse | normalize | search | match | review
98+
stage_name: str # validate | parse | normalize | search | match | review
99+
order: int # Pipeline execution order (0=validate, 1=parse, etc.)
97100
status: ImportJobStageStatus = Field(
98101
sa_column=Column(
99102
SQLEnum(ImportJobStageStatus), index=True, nullable=False

0 commit comments

Comments
 (0)