fix: use RealDictCursor for PostgreSQL row access
All conn.cursor() calls replaced with get_cursor(conn) to enable dict-like row access (prof['pin_hash'] instead of prof[column_index]). This fixes KeyError when accessing PostgreSQL query results. Fixes: 'tuple' object has no attribute '__getitem__' with string keys Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
124df01983
commit
9fbedb6c4b
114
backend/main.py
114
backend/main.py
|
|
@ -14,7 +14,7 @@ from slowapi.util import get_remote_address
|
|||
from slowapi.errors import RateLimitExceeded
|
||||
from starlette.requests import Request
|
||||
|
||||
from db import get_db, r2d
|
||||
from db import get_db, get_cursor, r2d
|
||||
|
||||
DATA_DIR = Path(os.getenv("DATA_DIR", "./data"))
|
||||
PHOTOS_DIR = Path(os.getenv("PHOTOS_DIR", "./photos"))
|
||||
|
|
@ -51,7 +51,7 @@ def get_pid(x_profile_id: Optional[str] = Header(default=None)) -> str:
|
|||
if x_profile_id:
|
||||
return x_profile_id
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT id FROM profiles ORDER BY created LIMIT 1")
|
||||
row = cur.fetchone()
|
||||
if row: return row['id']
|
||||
|
|
@ -134,7 +134,7 @@ def make_token() -> str:
|
|||
def get_session(token: str):
|
||||
if not token: return None
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute(
|
||||
"SELECT s.*, p.role, p.name, p.ai_enabled, p.ai_limit_day, p.export_enabled "
|
||||
"FROM sessions s JOIN profiles p ON s.profile_id=p.id "
|
||||
|
|
@ -157,7 +157,7 @@ def require_admin(x_auth_token: Optional[str]=Header(default=None)):
|
|||
@app.get("/api/profiles")
|
||||
def list_profiles(session=Depends(require_auth)):
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT * FROM profiles ORDER BY created")
|
||||
rows = cur.fetchall()
|
||||
return [r2d(r) for r in rows]
|
||||
|
|
@ -166,19 +166,19 @@ def list_profiles(session=Depends(require_auth)):
|
|||
def create_profile(p: ProfileCreate, session=Depends(require_auth)):
|
||||
pid = str(uuid.uuid4())
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("""INSERT INTO profiles (id,name,avatar_color,sex,dob,height,goal_weight,goal_bf_pct,created,updated)
|
||||
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP)""",
|
||||
(pid,p.name,p.avatar_color,p.sex,p.dob,p.height,p.goal_weight,p.goal_bf_pct))
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT * FROM profiles WHERE id=%s", (pid,))
|
||||
return r2d(cur.fetchone())
|
||||
|
||||
@app.get("/api/profiles/{pid}")
|
||||
def get_profile(pid: str, session=Depends(require_auth)):
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT * FROM profiles WHERE id=%s", (pid,))
|
||||
row = cur.fetchone()
|
||||
if not row: raise HTTPException(404, "Profil nicht gefunden")
|
||||
|
|
@ -189,7 +189,7 @@ def update_profile(pid: str, p: ProfileUpdate, session=Depends(require_auth)):
|
|||
with get_db() as conn:
|
||||
data = {k:v for k,v in p.model_dump().items() if v is not None}
|
||||
data['updated'] = datetime.now().isoformat()
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute(f"UPDATE profiles SET {', '.join(f'{k}=%s' for k in data)} WHERE id=%s",
|
||||
list(data.values())+[pid])
|
||||
return get_profile(pid, session)
|
||||
|
|
@ -197,7 +197,7 @@ def update_profile(pid: str, p: ProfileUpdate, session=Depends(require_auth)):
|
|||
@app.delete("/api/profiles/{pid}")
|
||||
def delete_profile(pid: str, session=Depends(require_auth)):
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT COUNT(*) FROM profiles")
|
||||
count = cur.fetchone()[0]
|
||||
if count <= 1: raise HTTPException(400, "Letztes Profil kann nicht gelöscht werden")
|
||||
|
|
@ -222,7 +222,7 @@ def update_active_profile(p: ProfileUpdate, x_profile_id: Optional[str] = Header
|
|||
def list_weight(limit: int=365, x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)):
|
||||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute(
|
||||
"SELECT * FROM weight_log WHERE profile_id=%s ORDER BY date DESC LIMIT %s", (pid,limit))
|
||||
return [r2d(r) for r in cur.fetchall()]
|
||||
|
|
@ -231,7 +231,7 @@ def list_weight(limit: int=365, x_profile_id: Optional[str]=Header(default=None)
|
|||
def upsert_weight(e: WeightEntry, x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)):
|
||||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT id FROM weight_log WHERE profile_id=%s AND date=%s", (pid,e.date))
|
||||
ex = cur.fetchone()
|
||||
if ex:
|
||||
|
|
@ -247,7 +247,7 @@ def upsert_weight(e: WeightEntry, x_profile_id: Optional[str]=Header(default=Non
|
|||
def update_weight(wid: str, e: WeightEntry, x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)):
|
||||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("UPDATE weight_log SET date=%s,weight=%s,note=%s WHERE id=%s AND profile_id=%s",
|
||||
(e.date,e.weight,e.note,wid,pid))
|
||||
return {"id":wid}
|
||||
|
|
@ -256,7 +256,7 @@ def update_weight(wid: str, e: WeightEntry, x_profile_id: Optional[str]=Header(d
|
|||
def delete_weight(wid: str, x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)):
|
||||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("DELETE FROM weight_log WHERE id=%s AND profile_id=%s", (wid,pid))
|
||||
return {"ok":True}
|
||||
|
||||
|
|
@ -264,7 +264,7 @@ def delete_weight(wid: str, x_profile_id: Optional[str]=Header(default=None), se
|
|||
def weight_stats(x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)):
|
||||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT date,weight FROM weight_log WHERE profile_id=%s ORDER BY date DESC LIMIT 90", (pid,))
|
||||
rows = cur.fetchall()
|
||||
if not rows: return {"count":0,"latest":None,"prev":None,"min":None,"max":None,"avg_7d":None}
|
||||
|
|
@ -278,7 +278,7 @@ def weight_stats(x_profile_id: Optional[str]=Header(default=None), session: dict
|
|||
def list_circs(limit: int=100, x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)):
|
||||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute(
|
||||
"SELECT * FROM circumference_log WHERE profile_id=%s ORDER BY date DESC LIMIT %s", (pid,limit))
|
||||
return [r2d(r) for r in cur.fetchall()]
|
||||
|
|
@ -287,7 +287,7 @@ def list_circs(limit: int=100, x_profile_id: Optional[str]=Header(default=None),
|
|||
def upsert_circ(e: CircumferenceEntry, x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)):
|
||||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT id FROM circumference_log WHERE profile_id=%s AND date=%s", (pid,e.date))
|
||||
ex = cur.fetchone()
|
||||
d = e.model_dump()
|
||||
|
|
@ -310,7 +310,7 @@ def update_circ(eid: str, e: CircumferenceEntry, x_profile_id: Optional[str]=Hea
|
|||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
d = e.model_dump()
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute(f"UPDATE circumference_log SET {', '.join(f'{k}=%s' for k in d)} WHERE id=%s AND profile_id=%s",
|
||||
list(d.values())+[eid,pid])
|
||||
return {"id":eid}
|
||||
|
|
@ -319,7 +319,7 @@ def update_circ(eid: str, e: CircumferenceEntry, x_profile_id: Optional[str]=Hea
|
|||
def delete_circ(eid: str, x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)):
|
||||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("DELETE FROM circumference_log WHERE id=%s AND profile_id=%s", (eid,pid))
|
||||
return {"ok":True}
|
||||
|
||||
|
|
@ -328,7 +328,7 @@ def delete_circ(eid: str, x_profile_id: Optional[str]=Header(default=None), sess
|
|||
def list_caliper(limit: int=100, x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)):
|
||||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute(
|
||||
"SELECT * FROM caliper_log WHERE profile_id=%s ORDER BY date DESC LIMIT %s", (pid,limit))
|
||||
return [r2d(r) for r in cur.fetchall()]
|
||||
|
|
@ -337,7 +337,7 @@ def list_caliper(limit: int=100, x_profile_id: Optional[str]=Header(default=None
|
|||
def upsert_caliper(e: CaliperEntry, x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)):
|
||||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT id FROM caliper_log WHERE profile_id=%s AND date=%s", (pid,e.date))
|
||||
ex = cur.fetchone()
|
||||
d = e.model_dump()
|
||||
|
|
@ -362,7 +362,7 @@ def update_caliper(eid: str, e: CaliperEntry, x_profile_id: Optional[str]=Header
|
|||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
d = e.model_dump()
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute(f"UPDATE caliper_log SET {', '.join(f'{k}=%s' for k in d)} WHERE id=%s AND profile_id=%s",
|
||||
list(d.values())+[eid,pid])
|
||||
return {"id":eid}
|
||||
|
|
@ -371,7 +371,7 @@ def update_caliper(eid: str, e: CaliperEntry, x_profile_id: Optional[str]=Header
|
|||
def delete_caliper(eid: str, x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)):
|
||||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("DELETE FROM caliper_log WHERE id=%s AND profile_id=%s", (eid,pid))
|
||||
return {"ok":True}
|
||||
|
||||
|
|
@ -380,7 +380,7 @@ def delete_caliper(eid: str, x_profile_id: Optional[str]=Header(default=None), s
|
|||
def list_activity(limit: int=200, x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)):
|
||||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute(
|
||||
"SELECT * FROM activity_log WHERE profile_id=%s ORDER BY date DESC, start_time DESC LIMIT %s", (pid,limit))
|
||||
return [r2d(r) for r in cur.fetchall()]
|
||||
|
|
@ -391,7 +391,7 @@ def create_activity(e: ActivityEntry, x_profile_id: Optional[str]=Header(default
|
|||
eid = str(uuid.uuid4())
|
||||
d = e.model_dump()
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("""INSERT INTO activity_log
|
||||
(id,profile_id,date,start_time,end_time,activity_type,duration_min,kcal_active,kcal_resting,
|
||||
hr_avg,hr_max,distance_km,rpe,source,notes,created)
|
||||
|
|
@ -406,7 +406,7 @@ def update_activity(eid: str, e: ActivityEntry, x_profile_id: Optional[str]=Head
|
|||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
d = e.model_dump()
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute(f"UPDATE activity_log SET {', '.join(f'{k}=%s' for k in d)} WHERE id=%s AND profile_id=%s",
|
||||
list(d.values())+[eid,pid])
|
||||
return {"id":eid}
|
||||
|
|
@ -415,7 +415,7 @@ def update_activity(eid: str, e: ActivityEntry, x_profile_id: Optional[str]=Head
|
|||
def delete_activity(eid: str, x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)):
|
||||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("DELETE FROM activity_log WHERE id=%s AND profile_id=%s", (eid,pid))
|
||||
return {"ok":True}
|
||||
|
||||
|
|
@ -423,7 +423,7 @@ def delete_activity(eid: str, x_profile_id: Optional[str]=Header(default=None),
|
|||
def activity_stats(x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)):
|
||||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute(
|
||||
"SELECT * FROM activity_log WHERE profile_id=%s ORDER BY date DESC LIMIT 30", (pid,))
|
||||
rows = [r2d(r) for r in cur.fetchall()]
|
||||
|
|
@ -448,7 +448,7 @@ async def import_activity_csv(file: UploadFile=File(...), x_profile_id: Optional
|
|||
reader = csv.DictReader(io.StringIO(text))
|
||||
inserted = skipped = 0
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
for row in reader:
|
||||
wtype = row.get('Workout Type','').strip()
|
||||
start = row.get('Start','').strip()
|
||||
|
|
@ -492,7 +492,7 @@ async def upload_photo(file: UploadFile=File(...), date: str="",
|
|||
path = PHOTOS_DIR / f"{fid}{ext}"
|
||||
async with aiofiles.open(path,'wb') as f: await f.write(await file.read())
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("INSERT INTO photos (id,profile_id,date,path,created) VALUES (%s,%s,%s,%s,CURRENT_TIMESTAMP)",
|
||||
(fid,pid,date,str(path)))
|
||||
return {"id":fid,"date":date}
|
||||
|
|
@ -500,7 +500,7 @@ async def upload_photo(file: UploadFile=File(...), date: str="",
|
|||
@app.get("/api/photos/{fid}")
|
||||
def get_photo(fid: str, session: dict=Depends(require_auth)):
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT path FROM photos WHERE id=%s", (fid,))
|
||||
row = cur.fetchone()
|
||||
if not row: raise HTTPException(404)
|
||||
|
|
@ -510,7 +510,7 @@ def get_photo(fid: str, session: dict=Depends(require_auth)):
|
|||
def list_photos(x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)):
|
||||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute(
|
||||
"SELECT * FROM photos WHERE profile_id=%s ORDER BY created DESC LIMIT 100", (pid,))
|
||||
return [r2d(r) for r in cur.fetchall()]
|
||||
|
|
@ -546,7 +546,7 @@ async def import_nutrition_csv(file: UploadFile=File(...), x_profile_id: Optiona
|
|||
count+=1
|
||||
inserted=0
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
for iso,vals in days.items():
|
||||
kcal=round(vals['kcal'],1); fat=round(vals['fat_g'],1)
|
||||
carbs=round(vals['carbs_g'],1); prot=round(vals['protein_g'],1)
|
||||
|
|
@ -565,7 +565,7 @@ async def import_nutrition_csv(file: UploadFile=File(...), x_profile_id: Optiona
|
|||
def list_nutrition(limit: int=365, x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)):
|
||||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute(
|
||||
"SELECT * FROM nutrition_log WHERE profile_id=%s ORDER BY date DESC LIMIT %s", (pid,limit))
|
||||
return [r2d(r) for r in cur.fetchall()]
|
||||
|
|
@ -574,7 +574,7 @@ def list_nutrition(limit: int=365, x_profile_id: Optional[str]=Header(default=No
|
|||
def nutrition_correlations(x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)):
|
||||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT * FROM nutrition_log WHERE profile_id=%s ORDER BY date",(pid,))
|
||||
nutr={r['date']:r2d(r) for r in cur.fetchall()}
|
||||
cur.execute("SELECT date,weight FROM weight_log WHERE profile_id=%s ORDER BY date",(pid,))
|
||||
|
|
@ -602,7 +602,7 @@ def nutrition_correlations(x_profile_id: Optional[str]=Header(default=None), ses
|
|||
def nutrition_weekly(weeks: int=16, x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)):
|
||||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT * FROM nutrition_log WHERE profile_id=%s ORDER BY date DESC LIMIT %s",(pid,weeks*7))
|
||||
rows=[r2d(r) for r in cur.fetchall()]
|
||||
if not rows: return []
|
||||
|
|
@ -622,7 +622,7 @@ def nutrition_weekly(weeks: int=16, x_profile_id: Optional[str]=Header(default=N
|
|||
def get_stats(x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)):
|
||||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT COUNT(*) FROM weight_log WHERE profile_id=%s",(pid,))
|
||||
weight_count = cur.fetchone()[0]
|
||||
cur.execute("SELECT COUNT(*) FROM circumference_log WHERE profile_id=%s",(pid,))
|
||||
|
|
@ -648,7 +648,7 @@ import httpx, json
|
|||
def get_ai_insight(scope: str, x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)):
|
||||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT * FROM ai_insights WHERE profile_id=%s AND scope=%s ORDER BY created DESC LIMIT 1", (pid,scope))
|
||||
row = cur.fetchone()
|
||||
if not row: return None
|
||||
|
|
@ -658,14 +658,14 @@ def get_ai_insight(scope: str, x_profile_id: Optional[str]=Header(default=None),
|
|||
def delete_ai_insight(scope: str, x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)):
|
||||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("DELETE FROM ai_insights WHERE profile_id=%s AND scope=%s", (pid,scope))
|
||||
return {"ok":True}
|
||||
|
||||
def check_ai_limit(pid: str):
|
||||
"""Check if profile has reached daily AI limit. Returns (allowed, limit, used)."""
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT ai_enabled, ai_limit_day FROM profiles WHERE id=%s", (pid,))
|
||||
prof = cur.fetchone()
|
||||
if not prof or not prof['ai_enabled']:
|
||||
|
|
@ -685,7 +685,7 @@ def inc_ai_usage(pid: str):
|
|||
"""Increment AI usage counter for today."""
|
||||
today = datetime.now().date().isoformat()
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT id, call_count FROM ai_usage WHERE profile_id=%s AND date=%s", (pid, today))
|
||||
row = cur.fetchone()
|
||||
if row:
|
||||
|
|
@ -697,7 +697,7 @@ def inc_ai_usage(pid: str):
|
|||
def _get_profile_data(pid: str):
|
||||
"""Fetch all relevant data for AI analysis."""
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT * FROM profiles WHERE id=%s", (pid,))
|
||||
prof = r2d(cur.fetchone())
|
||||
cur.execute("SELECT * FROM weight_log WHERE profile_id=%s ORDER BY date DESC LIMIT 90", (pid,))
|
||||
|
|
@ -831,7 +831,7 @@ async def analyze_with_prompt(slug: str, x_profile_id: Optional[str]=Header(defa
|
|||
|
||||
# Get prompt template
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT * FROM ai_prompts WHERE slug=%s AND active=1", (slug,))
|
||||
prompt_row = cur.fetchone()
|
||||
if not prompt_row:
|
||||
|
|
@ -872,7 +872,7 @@ async def analyze_with_prompt(slug: str, x_profile_id: Optional[str]=Header(defa
|
|||
|
||||
# Save insight
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("DELETE FROM ai_insights WHERE profile_id=%s AND scope=%s", (pid, slug))
|
||||
cur.execute("INSERT INTO ai_insights (id, profile_id, scope, content, created) VALUES (%s,%s,%s,%s,CURRENT_TIMESTAMP)",
|
||||
(str(uuid.uuid4()), pid, slug, content))
|
||||
|
|
@ -891,7 +891,7 @@ async def analyze_pipeline(x_profile_id: Optional[str]=Header(default=None), ses
|
|||
|
||||
# Stage 1: Parallel JSON analyses
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT slug, template FROM ai_prompts WHERE slug LIKE 'pipeline_%' AND slug NOT IN ('pipeline_synthesis','pipeline_goals') AND active=1")
|
||||
stage1_prompts = [r2d(r) for r in cur.fetchall()]
|
||||
|
||||
|
|
@ -936,7 +936,7 @@ async def analyze_pipeline(x_profile_id: Optional[str]=Header(default=None), ses
|
|||
vars['stage1_activity'] = json.dumps(stage1_results.get('pipeline_activity', {}), ensure_ascii=False)
|
||||
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT template FROM ai_prompts WHERE slug='pipeline_synthesis' AND active=1")
|
||||
synth_row = cur.fetchone()
|
||||
if not synth_row:
|
||||
|
|
@ -973,7 +973,7 @@ async def analyze_pipeline(x_profile_id: Optional[str]=Header(default=None), ses
|
|||
prof = data['profile']
|
||||
if prof.get('goal_weight') or prof.get('goal_bf_pct'):
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT template FROM ai_prompts WHERE slug='pipeline_goals' AND active=1")
|
||||
goals_row = cur.fetchone()
|
||||
if goals_row:
|
||||
|
|
@ -1008,7 +1008,7 @@ async def analyze_pipeline(x_profile_id: Optional[str]=Header(default=None), ses
|
|||
|
||||
# Save as 'gesamt' scope
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("DELETE FROM ai_insights WHERE profile_id=%s AND scope='gesamt'", (pid,))
|
||||
cur.execute("INSERT INTO ai_insights (id, profile_id, scope, content, created) VALUES (%s,%s,'gesamt',%s,CURRENT_TIMESTAMP)",
|
||||
(str(uuid.uuid4()), pid, final_content))
|
||||
|
|
@ -1020,7 +1020,7 @@ async def analyze_pipeline(x_profile_id: Optional[str]=Header(default=None), ses
|
|||
def list_prompts(session: dict=Depends(require_auth)):
|
||||
"""List all available AI prompts."""
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT * FROM ai_prompts WHERE active=1 AND slug NOT LIKE 'pipeline_%' ORDER BY sort_order")
|
||||
return [r2d(r) for r in cur.fetchall()]
|
||||
|
||||
|
|
@ -1029,7 +1029,7 @@ def get_ai_usage(x_profile_id: Optional[str]=Header(default=None), session: dict
|
|||
"""Get AI usage stats for current profile."""
|
||||
pid = get_pid(x_profile_id)
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT ai_limit_day FROM profiles WHERE id=%s", (pid,))
|
||||
prof = cur.fetchone()
|
||||
limit = prof['ai_limit_day'] if prof else None
|
||||
|
|
@ -1066,7 +1066,7 @@ class PasswordResetConfirm(BaseModel):
|
|||
async def login(req: LoginRequest, request: Request):
|
||||
"""Login with email + password."""
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT * FROM profiles WHERE email=%s", (req.email.lower().strip(),))
|
||||
prof = cur.fetchone()
|
||||
if not prof:
|
||||
|
|
@ -1101,7 +1101,7 @@ def logout(x_auth_token: Optional[str]=Header(default=None)):
|
|||
"""Logout (delete session)."""
|
||||
if x_auth_token:
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("DELETE FROM sessions WHERE token=%s", (x_auth_token,))
|
||||
return {"ok": True}
|
||||
|
||||
|
|
@ -1117,7 +1117,7 @@ async def password_reset_request(req: PasswordResetRequest, request: Request):
|
|||
"""Request password reset email."""
|
||||
email = req.email.lower().strip()
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT id, name FROM profiles WHERE email=%s", (email,))
|
||||
prof = cur.fetchone()
|
||||
if not prof:
|
||||
|
|
@ -1173,7 +1173,7 @@ Dein Mitai Jinkendo Team
|
|||
def password_reset_confirm(req: PasswordResetConfirm):
|
||||
"""Confirm password reset with token."""
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT profile_id FROM sessions WHERE token=%s AND expires_at > CURRENT_TIMESTAMP",
|
||||
(f"reset_{req.token}",))
|
||||
sess = cur.fetchone()
|
||||
|
|
@ -1198,7 +1198,7 @@ class AdminProfileUpdate(BaseModel):
|
|||
def admin_list_profiles(session: dict=Depends(require_admin)):
|
||||
"""Admin: List all profiles with stats."""
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT * FROM profiles ORDER BY created")
|
||||
profs = [r2d(r) for r in cur.fetchall()]
|
||||
|
||||
|
|
@ -1224,7 +1224,7 @@ def admin_update_profile(pid: str, data: AdminProfileUpdate, session: dict=Depen
|
|||
if not updates:
|
||||
return {"ok": True}
|
||||
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute(f"UPDATE profiles SET {', '.join(f'{k}=%s' for k in updates)} WHERE id=%s",
|
||||
list(updates.values()) + [pid])
|
||||
|
||||
|
|
@ -1267,7 +1267,7 @@ def export_csv(x_profile_id: Optional[str]=Header(default=None), session: dict=D
|
|||
|
||||
# Check export permission
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT export_enabled FROM profiles WHERE id=%s", (pid,))
|
||||
prof = cur.fetchone()
|
||||
if not prof or not prof['export_enabled']:
|
||||
|
|
@ -1282,7 +1282,7 @@ def export_csv(x_profile_id: Optional[str]=Header(default=None), session: dict=D
|
|||
|
||||
# Weight
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur = get_cursor(conn)
|
||||
cur.execute("SELECT date, weight, note FROM weight_log WHERE profile_id=%s ORDER BY date", (pid,))
|
||||
for r in cur.fetchall():
|
||||
writer.writerow(["Gewicht", r['date'], f"{r['weight']}kg", r['note'] or ""])
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user