Ideas: add 'awaiting feedback' status with amber section + admin comment
Status cycles open → awaiting → done → reopen.
Awaiting ideas float to the top in a 'Waiting for your feedback' section
with an amber border (#f59e0b).
Admin can attach an implementation note to any awaiting idea via
POST /api/ideas/{id}/comment. The note appears inside the same card
in a distinct sub-box with a subtle amber tint border, editable inline.
The sub-box is visible to all users once a note exists.
This commit is contained in:
@@ -12,7 +12,7 @@ from fastapi import APIRouter, Cookie, File, Form, HTTPException, UploadFile
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from bincio.serve import deps
|
||||
from bincio.serve.models import IdeaBody
|
||||
from bincio.serve.models import IdeaBody, IdeaCommentBody
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -43,7 +43,11 @@ async def list_ideas(
|
||||
idea["vote_count"] = len(votes)
|
||||
idea["my_vote"] = user.handle in votes
|
||||
ideas.append(idea)
|
||||
ideas.sort(key=lambda x: (x.get("status") == "done", -x["vote_count"], -x["created_at"]))
|
||||
def _sort_key(x: dict):
|
||||
s = x.get("status") or "open"
|
||||
order = {"awaiting": 0, "open": 1, "done": 2}
|
||||
return (order.get(s, 1), -x["vote_count"], -x["created_at"])
|
||||
ideas.sort(key=_sort_key)
|
||||
return JSONResponse({"ideas": ideas})
|
||||
|
||||
|
||||
@@ -114,13 +118,41 @@ async def toggle_idea_status(
|
||||
with open(path, "r+", encoding="utf-8") as f:
|
||||
_fcntl.flock(f, _fcntl.LOCK_EX)
|
||||
idea = json.load(f)
|
||||
idea["status"] = "open" if idea.get("status") == "done" else "done"
|
||||
cycle = {"open": "awaiting", "awaiting": "done", "done": "open"}
|
||||
idea["status"] = cycle.get(idea.get("status") or "open", "awaiting")
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
json.dump(idea, f, ensure_ascii=False, indent=2)
|
||||
return JSONResponse({"status": idea["status"]})
|
||||
|
||||
|
||||
@router.post("/api/ideas/{idea_id}/comment")
|
||||
async def set_idea_comment(
|
||||
idea_id: str,
|
||||
data: IdeaCommentBody,
|
||||
bincio_session: Optional[str] = Cookie(default=None),
|
||||
) -> JSONResponse:
|
||||
user = deps._require_user(bincio_session)
|
||||
if not user.is_admin:
|
||||
raise HTTPException(403, "Forbidden")
|
||||
dd = deps._get_data_dir()
|
||||
path = _ideas_dir(dd) / f"{idea_id}.json"
|
||||
if not path.exists():
|
||||
raise HTTPException(404, "Not found")
|
||||
comment = data.comment.strip()[:1000]
|
||||
with open(path, "r+", encoding="utf-8") as f:
|
||||
_fcntl.flock(f, _fcntl.LOCK_EX)
|
||||
idea = json.load(f)
|
||||
if comment:
|
||||
idea["admin_comment"] = comment
|
||||
else:
|
||||
idea.pop("admin_comment", None)
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
json.dump(idea, f, ensure_ascii=False, indent=2)
|
||||
return JSONResponse({"ok": True, "admin_comment": comment or None})
|
||||
|
||||
|
||||
@router.patch("/api/ideas/{idea_id}")
|
||||
async def edit_idea(
|
||||
idea_id: str,
|
||||
|
||||
Reference in New Issue
Block a user