Files
knowledge-governance/internal/httpapi/qa_weekly_drafts.go
姚章浩 87f97a2f9b
build / build (push) Successful in 1m7s
feat(qaops): 完成详情下拉与每周自动保存草稿
详情使用共享主表字典,稀疏修改所选字段并保留原始问答及旧复核审计。
月周统计提供文本和下拉双入口;每租户每周一份持久草稿,按技能六节双语模板真实预填。
加入幂等自动保存、冲突保留、未知响应重试、切周及离开保护和schema58非破坏降级保护。

验证:完整make verify、隔离PostgreSQL全mirror竞态回归、真实恢复库浏览器验收通过。
浏览器实测字段精确读回、月周双输入、模板预填、MD/CSV、自动保存、冲突、失败重试与API重启恢复。
未连接或改写DobotAI生产源库,未运行付费模型;提交时尚未部署。
2026-10-20 05:44:02 +08:00

83 lines
2.9 KiB
Go

package httpapi
import (
"context"
"errors"
"knowledge-governance/internal/dobotaiops"
"knowledge-governance/internal/qaopsmirror"
"knowledge-governance/internal/qaopsquality"
"net/http"
"time"
)
type weeklyDraftStore interface {
ReadWeeklyDraft(context.Context, string, time.Time) (qaopsmirror.WeeklyDraft, error)
SaveWeeklyDraft(context.Context, string, qaopsmirror.WeeklyDraftRequest, string, time.Time) (qaopsmirror.WeeklyDraftReceipt, error)
PrefillWeeklyDraft(context.Context, string, qaopsmirror.WeeklyDraftPrefillRequest, string, time.Time) (qaopsmirror.WeeklyDraftReceipt, error)
}
func weeklyDraftError(w http.ResponseWriter, err error) {
switch {
case errors.Is(err, qaopsquality.ErrConflict):
writeJSON(w, 409, map[string]string{"error": "本周草稿已被其他窗口修改;你的文字未被覆盖,请先下载或核对服务器版本", "code": "weekly_draft_conflict"})
case errors.Is(err, dobotaiops.ErrInvalidQuery):
writeJSON(w, 400, map[string]string{"error": "周次或草稿内容无效,请使用已到达的合法ISO周,正文不超过256KiB", "code": "weekly_draft_invalid"})
default:
writeJSON(w, 503, map[string]string{"error": "草稿存储暂不可用,请保留文字并重试;未标记为已保存", "code": "weekly_draft_unavailable"})
}
}
func qaWeeklyDraftRead(w http.ResponseWriter, r *http.Request, c Config) {
if !authorizeQAOps(w, r) {
return
}
if r.URL.RawQuery != "" {
weeklyDraftError(w, dobotaiops.ErrInvalidQuery)
return
}
store, ok := c.QAOps.(weeklyDraftStore)
if !ok || c.QAOpsTenantKey == "" {
weeklyDraftError(w, dobotaiops.ErrUnavailable)
return
}
w.Header().Set("Cache-Control", "no-store")
out, err := store.ReadWeeklyDraft(r.Context(), r.PathValue("week"), c.Now())
if err != nil {
weeklyDraftError(w, err)
return
}
writeJSON(w, 200, map[string]any{"data": out})
}
func qaWeeklyDraftWrite(w http.ResponseWriter, r *http.Request, c Config, prefill bool) {
if !authorizeQAOps(w, r) || !authorizeQAIntent(w, r, "qa-weekly-draft") {
return
}
store, ok := c.QAOps.(weeklyDraftStore)
if !ok || c.QAOpsTenantKey == "" {
weeklyDraftError(w, dobotaiops.ErrUnavailable)
return
}
identity, _ := sessionFrom(r.Context())
var out qaopsmirror.WeeklyDraftReceipt
var err error
if prefill {
var q qaopsmirror.WeeklyDraftPrefillRequest
if decodeQualityJSON(w, r, &q, "base_revision", "nonce") != nil {
weeklyDraftError(w, dobotaiops.ErrInvalidQuery)
return
}
out, err = store.PrefillWeeklyDraft(r.Context(), r.PathValue("week"), q, identity.Username, c.Now())
} else {
var q qaopsmirror.WeeklyDraftRequest
if decodeGovernanceJSON(w, r, &q, 512<<10, "base_revision", "nonce", "markdown") != nil {
weeklyDraftError(w, dobotaiops.ErrInvalidQuery)
return
}
out, err = store.SaveWeeklyDraft(r.Context(), r.PathValue("week"), q, identity.Username, c.Now())
}
if err != nil {
weeklyDraftError(w, err)
return
}
writeJSON(w, 200, map[string]any{"data": out})
}