- history/message_ops.go(新): Search(会话内, q/after互斥, LIKE参数化+%/_转义+大小写不敏感, 严格限定会话doc) / UpdateMessage(返回更新后Message) / DeleteMessage / messageByID / escapeLike
- history/session_pin.go(新): SetPinned; 迁移0009 chat_sessions加pinned INTEGER DEFAULT 0; store.go SessionMeta.Pinned + createSessionsTable加列
- session_preview.go: 查询加s.pinned + ORDER BY pinned DESC,(type=main) DESC,created_at ASC
- gateway history_write.go(新,保持history.go≤500): handlePatchSession(pinned+title) / handlePatchMessage / handleDeleteMessage; server.go加PATCH/DELETE /api/v1/messages/{id} + /sessions/{id}置顶
- history.go: handleListMessages读q分流Search; 契约PATCH title/置顶
- D2会话内搜索(不做跨会话,隔离会话不外泄红线); D3不做重发端点(客户端编辑后再发); D5用pinned bool
- 测试(message_ops/history_messages/migrate_pinned), build/vet/test绿+3平台交叉编译; Docs/api-client v0.30
- 未做: 编辑/删除WS实时广播(message_edited/deleted), 下批
34 lines
983 B
Go
34 lines
983 B
Go
package history
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
)
|
|
|
|
// SetPinned pins or unpins a persistent session. Pinning is display/order metadata
|
|
// only (like the title): both the fixed main session and sub sessions may be pinned,
|
|
// and identity/permissions never derive from it. An unknown id returns
|
|
// ErrSessionNotFound.
|
|
func (s *Store) SetPinned(ctx context.Context, sessionID string, pinned bool) error {
|
|
res, err := s.db.ExecContext(ctx, `UPDATE chat_sessions SET pinned = ? WHERE id = ?`, pinnedValue(pinned), sessionID)
|
|
if err != nil {
|
|
return fmt.Errorf("history: set pinned %s: %w", sessionID, err)
|
|
}
|
|
n, err := res.RowsAffected()
|
|
if err != nil {
|
|
return fmt.Errorf("history: set pinned %s rows: %w", sessionID, err)
|
|
}
|
|
if n == 0 {
|
|
return fmt.Errorf("history: set pinned %s: %w", sessionID, ErrSessionNotFound)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// pinnedValue maps the boolean to SQLite's 0/1 integer representation.
|
|
func pinnedValue(pinned bool) int {
|
|
if pinned {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|