245 lines
5.6 KiB
Go
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)
|
|
}
|