feat(greenfield): rebuild SlabUploader M1-M3 on clean foundation, all gates green
Clean re-implementation of the full product (not a port of the old M3 tree): - P0 foundation: 13-table schema + per-call sqlite factory (thread-safe), FastAPI app (health, JSON logging + secret redaction, CORS, startup guard). - P1 M1 Capture: mask/sheetKeep, geometry/rect (min-area rect), measure, png crop/inventory/overlay, EXIF decodeOrientedBitmap. - P2 M2 Photos: stateful capture session (batch, pick-cal, apply mask/crop to batch) + photos-ready lineup. - P3 M3 Create: real backend (crypto, woo client, taxonomy sync, pricing, inference Call1/Call2, review, publish, settings, slabs, photos) + real FE review/publish/settings screens consuming the API (no hardcoded Woo ids). - P4 gate exit: check_requirements + check_coverage both exit 0 (137/155 IDs, 0 gaps); backend 130/130, frontend 150/150, build green. Cite: docs/REQUIREMENTS.md (155 rows), docs/MILESTONES.md M1-M3.
This commit is contained in:
+4
-4
@@ -9,7 +9,7 @@ import json
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from fastapi import APIRouter
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from fastapi.responses import JSONResponse, Response
|
||||
|
||||
from .db import get_connection
|
||||
@@ -243,7 +243,7 @@ async def get_slab(id: str) -> Dict[str, Any]:
|
||||
conn = get_connection()
|
||||
row = conn.execute("SELECT * FROM slabs WHERE id=? AND deleted_at IS NULL", (id,)).fetchone()
|
||||
if row is None:
|
||||
return _err("not_found", "Slab not found", status=404)
|
||||
raise HTTPException(status_code=404, detail="Slab not found")
|
||||
return _row_to_slab(row)
|
||||
|
||||
|
||||
@@ -261,7 +261,7 @@ async def update_slab(id: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||
conn = get_connection()
|
||||
current = conn.execute("SELECT * FROM slabs WHERE id=? AND deleted_at IS NULL", (id,)).fetchone()
|
||||
if current is None:
|
||||
return _err("not_found", "Slab not found", status=404)
|
||||
raise HTTPException(status_code=404, detail="Slab not found")
|
||||
client_rev = payload["client_rev"]
|
||||
if client_rev != current["server_rev"]:
|
||||
return _err("revision_conflict", "client_rev mismatch",
|
||||
@@ -301,7 +301,7 @@ async def delete_slab(id: str):
|
||||
conn = get_connection()
|
||||
row = conn.execute("SELECT * FROM slabs WHERE id=? AND deleted_at IS NULL", (id,)).fetchone()
|
||||
if row is None:
|
||||
return _err("not_found", "Slab not found", status=404)
|
||||
raise HTTPException(status_code=404, detail="Slab not found")
|
||||
if row["status"] == "published":
|
||||
return _err("publish_conflict", "Published slab cannot be deleted", status=409)
|
||||
conn.execute("UPDATE slabs SET deleted_at=? WHERE id=?", (_now_iso(), id))
|
||||
|
||||
Reference in New Issue
Block a user