package controller import ( "net/http" "strconv" "time" "github.com/gin-gonic/gin" "nl-pms-api/internal/commonservice" "nl-pms-api/internal/middleware" "nl-pms-api/internal/service" ) // AdminController 运营后台。 type AdminController struct { Admin *service.AdminService Sec *service.AdminSecurityService Rel *service.ReleaseService Act *service.ActivityService } func (h *AdminController) Overview(c *gin.Context) { days, _ := strconv.Atoi(c.DefaultQuery("days", "14")) out, err := h.Admin.Overview(days) if err != nil { writeErr(c, err) return } c.JSON(http.StatusOK, out) } func (h *AdminController) ListUsers(c *gin.Context) { items, err := h.Admin.ListUsers() if err != nil { writeErr(c, err) return } c.JSON(http.StatusOK, gin.H{"items": items}) } func (h *AdminController) PatchUser(c *gin.Context) { id := commonservice.ParseID(c.Param("id")) var req struct { AIBanned *int `json:"aiBanned"` Disabled *int `json:"disabled"` } if !bindJSON(c, &req) { return } if err := h.Admin.PatchUser(commonservice.UserID(c), id, req.AIBanned, req.Disabled); err != nil { writeErr(c, err) return } c.JSON(http.StatusOK, gin.H{"ok": true}) } func (h *AdminController) ListTeams(c *gin.Context) { items, err := h.Admin.ListTeams() if err != nil { writeErr(c, err) return } c.JSON(http.StatusOK, gin.H{"items": items}) } func (h *AdminController) PatchTeam(c *gin.Context) { id := commonservice.ParseID(c.Param("id")) var req struct { AIBanned *int `json:"aiBanned"` } if !bindJSON(c, &req) { return } if err := h.Admin.PatchTeam(commonservice.UserID(c), id, req.AIBanned); err != nil { writeErr(c, err) return } c.JSON(http.StatusOK, gin.H{"ok": true}) } func (h *AdminController) TOTPStatus(c *gin.Context) { out, err := h.Sec.Status(commonservice.UserID(c)) if err != nil { writeErr(c, err) return } c.JSON(http.StatusOK, out) } func (h *AdminController) TOTPSetupBegin(c *gin.Context) { out, err := h.Sec.SetupBegin(commonservice.UserID(c)) if err != nil { writeErr(c, err) return } c.JSON(http.StatusOK, out) } func (h *AdminController) TOTPSetupConfirm(c *gin.Context) { var req struct { Code string `json:"code"` } if !bindJSON(c, &req) { return } if err := h.Sec.SetupConfirm(commonservice.UserID(c), req.Code); err != nil { writeErr(c, err) return } c.JSON(http.StatusOK, gin.H{"ok": true}) } func (h *AdminController) StepUp(c *gin.Context) { var req struct { Code string `json:"code"` } if !bindJSON(c, &req) { return } token, exp, err := h.Sec.StepUp(commonservice.UserID(c), req.Code, middleware.ClientIP(c)) if err != nil { writeErr(c, err) return } c.JSON(http.StatusOK, gin.H{ "stepupToken": token, "expiresAt": exp.UTC().Format(time.RFC3339), }) } func (h *AdminController) ListReleases(c *gin.Context) { items, err := h.Rel.List(c.Query("channel")) if err != nil { writeErr(c, err) return } c.JSON(http.StatusOK, gin.H{"items": items}) } func (h *AdminController) UploadRelease(c *gin.Context) { version := c.PostForm("version") channel := c.PostForm("channel") changelog := c.PostForm("changelog") fh, err := c.FormFile("file") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "FILE_REQUIRED"}) return } f, err := fh.Open() if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "FILE_REQUIRED"}) return } defer f.Close() row, err := h.Rel.Upload(version, channel, changelog, f, fh.Size) if err != nil { writeErr(c, err) return } c.JSON(http.StatusOK, row) } func (h *AdminController) PublishRelease(c *gin.Context) { id := commonservice.ParseID(c.Param("id")) row, err := h.Rel.Publish(id) if err != nil { writeErr(c, err) return } c.JSON(http.StatusOK, row) } // ActivityController 日活心跳。 type ActivityController struct { Svc *service.ActivityService } func (h *ActivityController) Ping(c *gin.Context) { if err := h.Svc.Ping(commonservice.UserID(c), middleware.ClientIP(c)); err != nil { writeErr(c, err) return } c.JSON(http.StatusOK, gin.H{"ok": true}) } // AIController AI 策略与用量上报。 type AIController struct { Admin *service.AdminService } func (h *AIController) Policy(c *gin.Context) { out, err := h.Admin.GetAIPolicy(commonservice.UserID(c)) if err != nil { writeErr(c, err) return } c.JSON(http.StatusOK, out) } func (h *AIController) Usage(c *gin.Context) { var req struct { Provider string `json:"provider"` TeamID int64 `json:"teamId"` PromptTokens int64 `json:"promptTokens"` CompletionTokens int64 `json:"completionTokens"` Estimated bool `json:"estimated"` } if !bindJSON(c, &req) { return } if err := h.Admin.ReportAIUsage(commonservice.UserID(c), req.TeamID, req.Provider, req.PromptTokens, req.CompletionTokens, req.Estimated); err != nil { writeErr(c, err) return } c.JSON(http.StatusOK, gin.H{"ok": true}) } // AppController 客户端更新检查/下载。 type AppController struct { Rel *service.ReleaseService } func (h *AppController) Latest(c *gin.Context) { out, err := h.Rel.Latest(c.DefaultQuery("channel", "stable")) if err != nil { writeErr(c, err) return } c.JSON(http.StatusOK, out) } func (h *AppController) Download(c *gin.Context) { row, f, err := h.Rel.OpenFile(c.Param("version"), c.DefaultQuery("channel", "stable")) if err != nil { writeErr(c, err) return } defer f.Close() c.Header("Content-Type", "application/octet-stream") c.Header("Content-Disposition", `attachment; filename="`+row.Version+`-installer.exe"`) c.Header("X-Content-SHA256", row.SHA256) http.ServeContent(c.Writer, c.Request, row.Version+"-installer.exe", time.Time{}, f) }