Files
weichenandCursor f6813cc272 feat(api): add additive HTTP/SSE error envelopes
Public errors now emit code, safe message, and request_id while keeping the legacy error field. Raw SQL, paths, tokens, and XSS payloads are stripped; the frontend branches on code instead of substring matching.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-15 14:40:56 +00:00

107 lines
2.6 KiB
Go

package server
// chat_unread_api.go — P3 / Step 2 双轨制未读消息与位点推进 API
//
// GET /api/v1/chat/unread?session_id=...&since_message_id=...
// POST /api/v1/chat/read
//
// user_id 只取自登录 Principal,忽略 query/body 中的身份字段。
import (
"encoding/json"
"net/http"
"strconv"
"chat-agent-system/internal/app/chatflow"
"chat-agent-system/internal/ctxmgr"
)
func (s *Server) handleChatUnread(w http.ResponseWriter, r *http.Request) {
user, ok := s.requireAuthenticatedUser(w, r)
if !ok {
return
}
sessionID := r.URL.Query().Get("session_id")
if sessionID == "" {
writeSafeError(w, r, http.StatusBadRequest, "missing session_id")
return
}
var sinceID int64
if sinceStr := r.URL.Query().Get("since_message_id"); sinceStr != "" {
if id, err := strconv.ParseInt(sinceStr, 10, 64); err == nil {
sinceID = id
}
}
if s.chatLog == nil {
writeJSONResponse(w, http.StatusOK, ctxmgr.ChatUnreadSummary{
Total: 0,
NextMessageID: 0,
UnreadMessageIDs: []int64{},
Items: []ctxmgr.UnreadDayItem{},
})
return
}
summary, err := s.chatLog.Unread(r.Context(), s.chatPrincipal(user, chatflow.ActionUnread), chatflow.UnreadQuery{
SessionID: sessionID,
SinceMessageID: sinceID,
})
if err != nil {
s.writeChatLogError(w, err)
return
}
if summary == nil {
summary = &ctxmgr.ChatUnreadSummary{
UnreadMessageIDs: []int64{},
Items: []ctxmgr.UnreadDayItem{},
}
}
writeJSONResponse(w, http.StatusOK, summary)
}
func (s *Server) handleChatRead(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
writeSafeError(w, r, http.StatusMethodNotAllowed, "method not allowed")
return
}
user, ok := s.requireAuthenticatedUser(w, r)
if !ok {
return
}
var req ctxmgr.MarkChatReadRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeSafeError(w, r, http.StatusBadRequest, "invalid request body: "+err.Error())
return
}
if req.SessionID == "" {
writeSafeError(w, r, http.StatusBadRequest, "missing session_id")
return
}
if s.chatLog == nil {
writeSafeError(w, r, http.StatusInternalServerError, "context manager not initialized")
return
}
pos, err := s.chatLog.MarkRead(r.Context(), s.chatPrincipal(user, chatflow.ActionUnread), req)
if err != nil {
s.writeChatLogError(w, err)
return
}
writeJSONResponse(w, http.StatusOK, map[string]any{
"ok": true,
"session_id": pos.SessionID,
"user_id": pos.UserID,
"last_read_message_id": pos.LastReadMessageID,
"dismissed_unread_ids": pos.DismissedUnreadIDs,
"updated_at": pos.UpdatedAt,
})
}