Files
2026-08-17 21:43:26 +08:00

311 lines
9.4 KiB
Go

package handler
import (
"encoding/json"
"net/http"
"time"
"silk-server-go/internal/middleware"
"silk-server-go/internal/model"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// RegisterCaseStudyRoutes 注册脱敏案例沉淀与审核发布路由。
func RegisterCaseStudyRoutes(rg *gin.RouterGroup, db *gorm.DB) {
read := middleware.RequirePermission(db, "case:read")
write := middleware.RequirePermission(db, "case:write")
rg.GET("/case-studies", read, listCaseStudies(db))
rg.GET("/case-studies/:id", read, getCaseStudy(db))
rg.POST("/case-studies", write, createCaseStudy(db))
rg.POST("/case-studies/from-consultation/:id", write, createCaseStudyFromConsultation(db))
rg.PATCH("/case-studies/:id", write, updateCaseStudy(db))
rg.POST("/case-studies/:id/review", write, reviewCaseStudy(db))
}
func listCaseStudies(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
q := db.Model(&model.CaseStudy{})
if status := c.Query("status"); status != "" {
if !canReviewKnowledge(c) {
c.JSON(http.StatusForbidden, gin.H{"error": "无权限查看非发布案例"})
return
}
q = q.Where("status = ?", status)
if !hasGlobalAccess(c) {
if userID := currentUserID(c); userID != nil {
q = q.Where(`
(source_room_id IS NOT NULL AND source_room_id IN (
SELECT r.id FROM rooms r
JOIN organization_members om ON om.organization_id = r.org_id
WHERE om.user_id = ?
)) OR created_by = ?`, *userID, *userID)
} else {
q = q.Where("1 = 0")
}
}
} else {
q = q.Where("status = ?", "published")
}
if disease := c.Query("disease"); disease != "" {
q = q.Where("disease = ?", disease)
}
var list []model.CaseStudy
q.Order("published_at DESC NULLS LAST, created_at DESC").Limit(200).Find(&list)
c.JSON(http.StatusOK, list)
}
}
func getCaseStudy(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
var study model.CaseStudy
if db.Where("id = ?", c.Param("id")).First(&study).Error != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "case study not found"})
return
}
if study.Status != "published" && !canReviewKnowledge(c) {
c.JSON(http.StatusNotFound, gin.H{"error": "case study not found"})
return
}
if study.Status != "published" && !hasGlobalAccess(c) && !canManageCaseStudy(db, c, study) {
c.JSON(http.StatusNotFound, gin.H{"error": "case study not found"})
return
}
c.JSON(http.StatusOK, study)
}
}
func createCaseStudy(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
var body model.CaseStudy
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
body.ID = ""
body.Status = "draft"
body.CreatedBy = currentUserID(c)
if body.Title == "" || body.Disease == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "标题和病种不能为空"})
return
}
if len(body.DesensitizedPayload) == 0 {
body.DesensitizedPayload = json.RawMessage(`{}`)
}
for _, id := range []*string{body.SourceConsultationID, body.SourceDiseaseEventID, body.SourceRoomID} {
if id != nil && *id != "" && !isUUID(*id) {
c.JSON(http.StatusBadRequest, gin.H{"error": "关联 ID 不是合法的 UUID"})
return
}
}
if body.SourceConsultationID != nil && !requireObjectAccess(c, db, "consultation", *body.SourceConsultationID) {
return
}
if body.SourceDiseaseEventID != nil && !requireObjectAccess(c, db, "disease_event", *body.SourceDiseaseEventID) {
return
}
if body.SourceRoomID != nil && !canAccessRoom(db, c, body.SourceRoomID) {
c.JSON(http.StatusForbidden, gin.H{"error": "无权引用该蚕房案例"})
return
}
if err := db.Create(&body).Error; err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "创建案例失败"})
return
}
c.JSON(http.StatusCreated, body)
}
}
func createCaseStudyFromConsultation(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id := c.Param("id")
if !requireObjectAccess(c, db, "consultation", id) {
return
}
var consultation model.Consultation
if db.Where("id = ?", id).First(&consultation).Error != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "consultation not found"})
return
}
payload := desensitizedConsultationPayload(db, consultation)
disease := "待确认"
var trace model.TraceRecord
if db.Where("consultation_id = ?", id).Order("created_at DESC").First(&trace).Error == nil {
disease = trace.Disease
}
title := "案例:" + disease
if consultation.Title != "" {
title = consultation.Title
}
study := model.CaseStudy{
Title: title,
Disease: disease,
SourceConsultationID: &consultation.ID,
SourceRoomID: consultation.RoomID,
Summary: consultation.Summary,
DesensitizedPayload: payload,
Status: "draft",
CreatedBy: currentUserID(c),
}
if err := db.Create(&study).Error; err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "创建脱敏案例失败"})
return
}
c.JSON(http.StatusCreated, study)
}
}
func updateCaseStudy(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id := c.Param("id")
var study model.CaseStudy
if db.Where("id = ?", id).First(&study).Error != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "case study not found"})
return
}
if !requireCaseStudyAccess(db, c, study) {
return
}
var body struct {
Title *string `json:"title"`
Disease *string `json:"disease"`
Region *string `json:"region"`
CaseDate *time.Time `json:"caseDate"`
Summary *string `json:"summary"`
DesensitizedPayload json.RawMessage `json:"desensitizedPayload"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
updates := map[string]interface{}{}
if body.Title != nil {
updates["title"] = *body.Title
}
if body.Disease != nil {
updates["disease"] = *body.Disease
}
if body.Region != nil {
updates["region"] = *body.Region
}
if body.CaseDate != nil {
updates["case_date"] = *body.CaseDate
}
if body.Summary != nil {
updates["summary"] = *body.Summary
}
if len(body.DesensitizedPayload) > 0 {
updates["desensitized_payload"] = body.DesensitizedPayload
}
if len(updates) > 0 {
if err := db.Model(&model.CaseStudy{}).Where("id = ?", id).Updates(updates).Error; err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "更新案例失败"})
return
}
}
db.Where("id = ?", id).First(&study)
c.JSON(http.StatusOK, study)
}
}
func reviewCaseStudy(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id := c.Param("id")
var study model.CaseStudy
if db.Where("id = ?", id).First(&study).Error != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "case study not found"})
return
}
if !requireCaseStudyAccess(db, c, study) {
return
}
var body struct {
Status string `json:"status"`
Note *string `json:"note"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if !model.ValidCaseStudyTransition(study.Status, body.Status) {
c.JSON(http.StatusBadRequest, gin.H{"error": "非法的案例状态流转"})
return
}
now := time.Now()
updates := map[string]interface{}{
"status": body.Status,
"reviewer_id": currentUserID(c),
"reviewed_at": now,
}
if body.Note != nil {
updates["review_note"] = *body.Note
}
if body.Status == "published" {
updates["published_at"] = now
}
if err := db.Model(&model.CaseStudy{}).Where("id = ?", id).Updates(updates).Error; err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "审核案例失败"})
return
}
db.Where("id = ?", id).First(&study)
c.JSON(http.StatusOK, study)
}
}
func requireCaseStudyAccess(db *gorm.DB, c *gin.Context, study model.CaseStudy) bool {
if canManageCaseStudy(db, c, study) {
return true
}
c.JSON(http.StatusForbidden, gin.H{"error": "无权管理该案例"})
return false
}
func canManageCaseStudy(db *gorm.DB, c *gin.Context, study model.CaseStudy) bool {
if hasGlobalAccess(c) {
return true
}
if study.SourceRoomID != nil && canAccessRoom(db, c, study.SourceRoomID) {
return true
}
userID := currentUserID(c)
if study.CreatedBy != nil && userID != nil && *study.CreatedBy == *userID {
return true
}
return false
}
func desensitizedConsultationPayload(db *gorm.DB, consultation model.Consultation) json.RawMessage {
data := map[string]interface{}{
"sourceStatus": consultation.Status,
"createdAt": consultation.CreatedAt,
}
if consultation.Summary != nil {
data["summary"] = *consultation.Summary
}
if consultation.Opinion != nil {
data["opinion"] = *consultation.Opinion
}
if consultation.Plan != nil {
data["plan"] = *consultation.Plan
}
if consultation.RoomID != nil {
var room model.Room
if db.Select("region").Where("id = ?", *consultation.RoomID).First(&room).Error == nil && room.Region != nil {
data["region"] = *room.Region
}
}
if consultation.LampTestID != nil {
var lamp model.LampTest
if db.Select("method", "result", "resulted_at", "status").
Where("id = ?", *consultation.LampTestID).First(&lamp).Error == nil {
data["method"] = lamp.Method
data["result"] = lamp.Result
data["detectionStatus"] = lamp.Status
data["resultedAt"] = lamp.ResultedAt
}
}
raw, _ := json.Marshal(data)
return raw
}