Files
nl-pms-api/internal/controller/admin.go
2026-08-15 17:04:47 +08:00

245 lines
5.6 KiB
Go

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)
}