feat: 权限细化、商业认证跳转、UI改进 #19
@@ -863,11 +863,48 @@ def adopt_recipe(recipe_id: int, user=Depends(require_role("admin"))):
|
||||
if row["owner_id"] == user["id"]:
|
||||
conn.close()
|
||||
return {"ok": True, "msg": "already owned"}
|
||||
old_owner = conn.execute("SELECT display_name, username FROM users WHERE id = ?", (row["owner_id"],)).fetchone()
|
||||
old_owner = conn.execute("SELECT id, role, display_name, username FROM users WHERE id = ?", (row["owner_id"],)).fetchone()
|
||||
old_name = (old_owner["display_name"] or old_owner["username"]) if old_owner else "unknown"
|
||||
conn.execute("UPDATE recipes SET owner_id = ?, updated_by = ? WHERE id = ?", (user["id"], user["id"], recipe_id))
|
||||
log_audit(conn, user["id"], "adopt_recipe", "recipe", recipe_id, row["name"],
|
||||
json.dumps({"from_user": old_name}))
|
||||
# Notify submitter that recipe was approved
|
||||
if old_owner and old_owner["id"] != user["id"]:
|
||||
conn.execute(
|
||||
"INSERT INTO notifications (target_role, title, body, target_user_id) VALUES (?, ?, ?, ?)",
|
||||
(old_owner["role"], "🎉 配方已采纳",
|
||||
f"你共享的配方「{row['name']}」已被采纳到公共配方库!", old_owner["id"])
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@app.post("/api/recipes/{recipe_id}/reject")
|
||||
def reject_recipe(recipe_id: int, body: dict = None, user=Depends(require_role("admin"))):
|
||||
conn = get_db()
|
||||
row = conn.execute("SELECT id, name, owner_id FROM recipes WHERE id = ?", (recipe_id,)).fetchone()
|
||||
if not row:
|
||||
conn.close()
|
||||
raise HTTPException(404, "Recipe not found")
|
||||
reason = (body or {}).get("reason", "").strip()
|
||||
# Notify submitter
|
||||
old_owner = conn.execute("SELECT id, role, display_name, username FROM users WHERE id = ?", (row["owner_id"],)).fetchone()
|
||||
if old_owner and old_owner["id"] != user["id"]:
|
||||
msg = f"你共享的配方「{row['name']}」未被采纳。"
|
||||
if reason:
|
||||
msg += f"\n原因:{reason}"
|
||||
msg += "\n你可以修改后重新共享。"
|
||||
conn.execute(
|
||||
"INSERT INTO notifications (target_role, title, body, target_user_id) VALUES (?, ?, ?, ?)",
|
||||
(old_owner["role"], "配方未被采纳", msg, old_owner["id"])
|
||||
)
|
||||
# Delete the recipe
|
||||
conn.execute("DELETE FROM recipe_ingredients WHERE recipe_id = ?", (recipe_id,))
|
||||
conn.execute("DELETE FROM recipe_tags WHERE recipe_id = ?", (recipe_id,))
|
||||
conn.execute("DELETE FROM recipes WHERE id = ?", (recipe_id,))
|
||||
log_audit(conn, user["id"], "reject_recipe", "recipe", recipe_id, row["name"],
|
||||
json.dumps({"reason": reason}))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return {"ok": True}
|
||||
@@ -1407,11 +1444,17 @@ def my_contribution(user=Depends(get_current_user)):
|
||||
if not user.get("id"):
|
||||
return {"shared_count": 0}
|
||||
conn = get_db()
|
||||
# Count recipes adopted from this user (tracked in audit_log)
|
||||
count = conn.execute(
|
||||
"SELECT COUNT(*) FROM audit_log WHERE action = 'adopt_recipe' AND detail LIKE ?",
|
||||
(f'%"from_user": "{user.get("display_name") or user.get("username")}"%',)
|
||||
).fetchone()[0]
|
||||
# Also count recipes still owned by user in public library
|
||||
own_count = conn.execute(
|
||||
"SELECT COUNT(*) FROM recipes WHERE owner_id = ?", (user["id"],)
|
||||
).fetchone()[0]
|
||||
conn.close()
|
||||
return {"shared_count": count}
|
||||
return {"shared_count": count + own_count}
|
||||
|
||||
|
||||
# ── Notifications ──────────────────────────────────────
|
||||
|
||||
@@ -534,20 +534,28 @@ async function removeRecipe(recipe) {
|
||||
|
||||
async function approveRecipe(recipe) {
|
||||
try {
|
||||
await api('/api/recipes/' + recipe._id + '/adopt', { method: 'POST' })
|
||||
ui.showToast('已采纳')
|
||||
await recipeStore.loadRecipes()
|
||||
const res = await api('/api/recipes/' + recipe._id + '/adopt', { method: 'POST' })
|
||||
if (res.ok) {
|
||||
ui.showToast('已采纳并通知提交者')
|
||||
await recipeStore.loadRecipes()
|
||||
}
|
||||
} catch {
|
||||
ui.showToast('操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function rejectRecipe(recipe) {
|
||||
const ok = await showConfirm(`确定删除「${recipe.name}」?`)
|
||||
if (!ok) return
|
||||
const reason = await showPrompt(`拒绝「${recipe.name}」的原因(选填):`)
|
||||
if (reason === null) return
|
||||
try {
|
||||
await recipeStore.deleteRecipe(recipe._id)
|
||||
ui.showToast('已删除')
|
||||
const res = await api(`/api/recipes/${recipe._id}/reject`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ reason: reason || '' }),
|
||||
})
|
||||
if (res.ok) {
|
||||
await recipeStore.loadRecipes()
|
||||
ui.showToast('已拒绝并通知提交者')
|
||||
}
|
||||
} catch {
|
||||
ui.showToast('操作失败')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user