MVP+: add image, price, URL, comments, status, category, rating, archive, stats
This commit is contained in:
1 parent
f8609d7b56
commit
52f2d54768
5 files changed
+272
-48
No files matched your search
@@ -33,8 +33,21 @@ def init():
|
||||
CREATE TABLE IF NOT EXISTS cards (
|
||||
id TEXT PRIMARY KEY, list_id TEXT REFERENCES lists(id) ON DELETE CASCADE,
|
||||
board_id TEXT REFERENCES boards(id) ON DELETE CASCADE,
|
||||
title TEXT, description TEXT DEFAULT '', position INTEGER DEFAULT 0
|
||||
title TEXT, description TEXT DEFAULT '', position INTEGER DEFAULT 0,
|
||||
image_url TEXT DEFAULT '',
|
||||
price REAL DEFAULT 0,
|
||||
url TEXT DEFAULT '',
|
||||
status TEXT DEFAULT 'want',
|
||||
category TEXT DEFAULT '',
|
||||
rating INTEGER DEFAULT 3,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
bought_at TIMESTAMP,
|
||||
comments TEXT DEFAULT '',
|
||||
archived INTEGER DEFAULT 0
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_cards_status ON cards(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_cards_category ON cards(category);
|
||||
CREATE INDEX IF NOT EXISTS idx_cards_board ON cards(board_id);
|
||||
""")
|
||||
|
||||
init()
|
||||
@@ -49,11 +62,29 @@ class ListIn(BaseModel):
|
||||
class CardIn(BaseModel):
|
||||
title: str
|
||||
description: str = ""
|
||||
image_url: str = ""
|
||||
price: float = 0
|
||||
url: str = ""
|
||||
status: str = "want"
|
||||
category: str = ""
|
||||
rating: int = 3
|
||||
comments: str = ""
|
||||
|
||||
@app.get("/")
|
||||
def index():
|
||||
return FileResponse("index.html")
|
||||
class CardUpdate(BaseModel):
|
||||
title: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
image_url: Optional[str] = None
|
||||
price: Optional[float] = None
|
||||
url: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
category: Optional[str] = None
|
||||
rating: Optional[int] = None
|
||||
comments: Optional[str] = None
|
||||
archived: Optional[int] = None
|
||||
position: Optional[int] = None
|
||||
list_id: Optional[str] = None
|
||||
|
||||
# Boards
|
||||
@app.get("/api/boards")
|
||||
def boards():
|
||||
with db() as c:
|
||||
@@ -74,6 +105,7 @@ def delete_board(bid: str):
|
||||
broadcast({"type": "board:delete", "data": {"id": bid}})
|
||||
return {"ok": True}
|
||||
|
||||
# Lists
|
||||
@app.get("/api/boards/{bid}/lists")
|
||||
def lists(bid: str):
|
||||
with db() as c:
|
||||
@@ -108,6 +140,7 @@ def delete_list(lid: str):
|
||||
broadcast({"type": "list:delete", "data": {"id": lid}})
|
||||
return {"ok": True}
|
||||
|
||||
# Cards
|
||||
@app.get("/api/lists/{lid}/cards")
|
||||
def cards(lid: str):
|
||||
with db() as c:
|
||||
@@ -119,21 +152,25 @@ def create_card(lid: str, data: CardIn):
|
||||
with db() as c:
|
||||
bid = c.execute("SELECT board_id FROM lists WHERE id=?", (lid,)).fetchone()["board_id"]
|
||||
p = c.execute("SELECT COALESCE(MAX(position),-1) m FROM cards WHERE list_id=?", (lid,)).fetchone()["m"]
|
||||
c.execute("INSERT INTO cards VALUES (?,?,?,?,?,?)", (cid, lid, bid, data.title, data.description, p+1))
|
||||
broadcast({"type": "card:create", "data": {"id": cid, "list_id": lid, "board_id": bid, "title": data.title, "description": data.description, "position": p+1}})
|
||||
return {"id": cid, "list_id": lid, "board_id": bid, "title": data.title, "description": data.description, "position": p+1}
|
||||
c.execute("INSERT INTO cards VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,0)",
|
||||
(cid, lid, bid, data.title, data.description, p+1,
|
||||
data.image_url, data.price, data.url, data.status,
|
||||
data.category, data.rating, data.comments))
|
||||
broadcast({"type": "card:create", "data": dict(c.execute("SELECT * FROM cards WHERE id=?", (cid,)).fetchone())})
|
||||
return dict(c.execute("SELECT * FROM cards WHERE id=?", (cid,)).fetchone())
|
||||
|
||||
@app.patch("/api/cards/{cid}")
|
||||
def update_card(cid: str, data: dict):
|
||||
def update_card(cid: str, data: CardUpdate):
|
||||
with db() as c:
|
||||
u, ps = [], []
|
||||
for k in ("title","description","position","list_id"):
|
||||
if k in data: u.append(f"{k}=?"); ps.append(data[k])
|
||||
for k in ("title","description","image_url","price","url","status","category","rating","comments","archived","position","list_id"):
|
||||
if getattr(data, k, None) is not None:
|
||||
u.append(f"{k}=?"); ps.append(getattr(data, k))
|
||||
if u:
|
||||
ps.append(cid)
|
||||
c.execute(f"UPDATE cards SET {', '.join(u)} WHERE id=?", ps)
|
||||
row = c.execute("SELECT * FROM cards WHERE id=?", (cid,)).fetchone()
|
||||
broadcast({"type": "card:update", "data": dict(row)})
|
||||
row = dict(c.execute("SELECT * FROM cards WHERE id=?", (cid,)).fetchone())
|
||||
broadcast({"type": "card:update", "data": row})
|
||||
return {"ok": True}
|
||||
|
||||
@app.delete("/api/cards/{cid}")
|
||||
@@ -143,6 +180,25 @@ def delete_card(cid: str):
|
||||
broadcast({"type": "card:delete", "data": {"id": cid}})
|
||||
return {"ok": True}
|
||||
|
||||
# Stats
|
||||
@app.get("/api/stats/{bid}")
|
||||
def stats(bid: str):
|
||||
with db() as c:
|
||||
total = c.execute("SELECT COALESCE(SUM(price),0) s FROM cards WHERE board_id=? AND archived=0", (bid,)).fetchone()["s"]
|
||||
bought = c.execute("SELECT COUNT(*) c FROM cards WHERE board_id=? AND status='bought'", (bid,)).fetchone()["c"]
|
||||
want = c.execute("SELECT COUNT(*) c FROM cards WHERE board_id=? AND status='want'", (bid,)).fetchone()["c"]
|
||||
deferred = c.execute("SELECT COUNT(*) c FROM cards WHERE board_id=? AND status='deferred'", (bid,)).fetchone()["c"]
|
||||
in_progress = c.execute("SELECT COUNT(*) c FROM cards WHERE board_id=? AND status='in_progress'", (bid,)).fetchone()["c"]
|
||||
return {"total": round(total, 2), "bought": bought, "want": want, "deferred": deferred, "in_progress": in_progress}
|
||||
|
||||
# Categories
|
||||
@app.get("/api/categories/{bid}")
|
||||
def categories(bid: str):
|
||||
with db() as c:
|
||||
rows = c.execute("SELECT DISTINCT category FROM cards WHERE board_id=? AND category != '' AND archived=0", (bid,)).fetchall()
|
||||
cats = [r["category"] for r in rows]
|
||||
return {"categories": cats}
|
||||
|
||||
# WebSocket broadcast
|
||||
clients = set()
|
||||
|
||||
|
||||
Reference in new issue
Block a user