87f97a2f9b
build / build (push) Successful in 1m7s
详情使用共享主表字典,稀疏修改所选字段并保留原始问答及旧复核审计。 月周统计提供文本和下拉双入口;每租户每周一份持久草稿,按技能六节双语模板真实预填。 加入幂等自动保存、冲突保留、未知响应重试、切周及离开保护和schema58非破坏降级保护。 验证:完整make verify、隔离PostgreSQL全mirror竞态回归、真实恢复库浏览器验收通过。 浏览器实测字段精确读回、月周双输入、模板预填、MD/CSV、自动保存、冲突、失败重试与API重启恢复。 未连接或改写DobotAI生产源库,未运行付费模型;提交时尚未部署。
83 lines
2.9 KiB
Go
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})
|
|
}
|