311 lines
9.4 KiB
Go
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
|
|
}
|