feat(history,gateway): D4 会话未读/已读游标 — unreadCount + POST /sessions/{id}/read + session_read 广播
- chat_sessions 增 read_at(毫秒,与 chat_messages.created_at 同单位);迁移 0010 并把既有会话回填为各自最新消息时间(升级当天不误报)
- ListSessions 单查询派生 unreadCount(无 N+1,复用 idx_chat_msgs_session_created 覆盖索引)
- MarkSessionRead 只前进 MAX(read_at, ?);未知会话 ErrSessionNotFound;游标方法挂在网关消费侧窄接口(沿用 GetMessage/messageLookup 先例,避免污染 runtime 接口与其 fake)
- POST /api/v1/sessions/{id}/read:writeAuth + 400/404/501,成功广播 session_read、失败路径不广播
- Docs/api-client v0.37(§1 unreadCount / 新行 8a / §3.9 session_read / §4.2 用法)
- 含迁移回填测试、store/handler 测试与变异验证;顺带把 migrate_pinned_test 的「回退一步=0009」硬编码改为对迁移数量免疫
This commit is contained in:
1 parent
04a8ee0727
commit
e8eccd2bb2
14 files changed
+855
-25
No files matched your search
@@ -121,10 +121,20 @@ type SessionMeta struct {
|
||||
// A client uses it to render the conversation row without a per-session request;
|
||||
// identity and permissions never derive from it.
|
||||
LastMessage *MessageSummary
|
||||
// UnreadCount is the number of messages newer than the session's read cursor
|
||||
// (design/16 §4.2, D4), computed by ListSessions in the same query. The cursor
|
||||
// is session-scoped and single-owner: reading on any one end marks the session
|
||||
// read for every end of the account. Like LastMessage it is display-only —
|
||||
// identity and permissions never derive from it.
|
||||
UnreadCount int
|
||||
}
|
||||
|
||||
// HistoryStore abstracts the chat-history store so callers (the gateway) and tests can
|
||||
// hold an interface rather than a concrete *Store.
|
||||
// hold an interface rather than a concrete *Store. It is deliberately kept to the calls
|
||||
// the runtime-level consumers (channelbridge, backthink) actually make: the D4 read
|
||||
// cursor (MarkSessionRead) is declared on the consumer-side interface in
|
||||
// internal/services/gateway (contracts.go) instead, so a narrower store fake elsewhere
|
||||
// does not have to implement a cursor it never advances. *Store satisfies both.
|
||||
type HistoryStore interface {
|
||||
// Append persists msg, assigning an id and timestamp when the caller omitted them.
|
||||
Append(ctx context.Context, msg Message) error
|
||||
@@ -579,6 +589,7 @@ CREATE TABLE IF NOT EXISTS chat_sessions (
|
||||
type TEXT NOT NULL,
|
||||
title TEXT NOT NULL DEFAULT '',
|
||||
pinned INTEGER NOT NULL DEFAULT 0,
|
||||
read_at INTEGER NOT NULL DEFAULT 0,
|
||||
created_at INTEGER NOT NULL
|
||||
)`
|
||||
|
||||
|
||||
Reference in new issue
Block a user