feat(ops): add job center for cross-source job operations
Provide a unified ops console for inspecting, retrying and cancelling jobs across generation, template/kol assist, knowledge parse, desktop publish/task, compliance review and monitoring collect sources. Wires RabbitMQ for retry republish and consolidates the desktop_publish_jobs columns into the base migration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/geo-platform/tenant-api/internal/ops/app"
|
||||
"github.com/geo-platform/tenant-api/internal/ops/domain"
|
||||
"github.com/geo-platform/tenant-api/internal/shared/response"
|
||||
)
|
||||
|
||||
func listJobsHandler(svc *app.JobService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
size, _ := strconv.Atoi(c.DefaultQuery("size", "50"))
|
||||
|
||||
filter := domain.JobFilter{
|
||||
Source: strings.TrimSpace(c.Query("source")),
|
||||
Phase: strings.TrimSpace(c.Query("phase")),
|
||||
Status: strings.TrimSpace(c.Query("status")),
|
||||
Kind: strings.TrimSpace(c.Query("kind")),
|
||||
Keyword: strings.TrimSpace(c.Query("keyword")),
|
||||
}
|
||||
|
||||
if v := strings.TrimSpace(c.Query("tenant_id")); v != "" {
|
||||
id, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil || id <= 0 {
|
||||
response.Error(c, response.ErrBadRequest(40070, "invalid_tenant_id", "tenant_id 不合法"))
|
||||
return
|
||||
}
|
||||
filter.TenantID = &id
|
||||
}
|
||||
if v := strings.TrimSpace(c.Query("workspace_id")); v != "" {
|
||||
id, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil || id <= 0 {
|
||||
response.Error(c, response.ErrBadRequest(40071, "invalid_workspace_id", "workspace_id 不合法"))
|
||||
return
|
||||
}
|
||||
filter.WorkspaceID = &id
|
||||
}
|
||||
if v := strings.TrimSpace(c.Query("user_id")); v != "" {
|
||||
id, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil || id <= 0 {
|
||||
response.Error(c, response.ErrBadRequest(40072, "invalid_user_id", "user_id 不合法"))
|
||||
return
|
||||
}
|
||||
filter.UserID = &id
|
||||
}
|
||||
if v := strings.TrimSpace(c.Query("start_at")); v != "" {
|
||||
t, err := time.Parse(time.RFC3339, v)
|
||||
if err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40073, "invalid_start_at", "start_at 必须是 RFC3339"))
|
||||
return
|
||||
}
|
||||
filter.StartAt = &t
|
||||
}
|
||||
if v := strings.TrimSpace(c.Query("end_at")); v != "" {
|
||||
t, err := time.Parse(time.RFC3339, v)
|
||||
if err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40074, "invalid_end_at", "end_at 必须是 RFC3339"))
|
||||
return
|
||||
}
|
||||
filter.EndAt = &t
|
||||
}
|
||||
|
||||
result, err := svc.List(c.Request.Context(), filter, page, size)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, result)
|
||||
}
|
||||
}
|
||||
|
||||
func getJobHandler(svc *app.JobService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
item, err := svc.Get(c.Request.Context(), c.Param("source"), c.Param("id"))
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, item)
|
||||
}
|
||||
}
|
||||
|
||||
func retryJobHandler(svc *app.JobService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
item, err := svc.Retry(c.Request.Context(), actorFromGin(c), c.Param("source"), c.Param("id"))
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, item)
|
||||
}
|
||||
}
|
||||
|
||||
func cancelJobHandler(svc *app.JobService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var req struct {
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
if c.Request.Body != nil {
|
||||
if err := c.ShouldBindJSON(&req); err != nil && !errors.Is(err, io.EOF) {
|
||||
response.Error(c, response.ErrBadRequest(40075, "invalid_payload", err.Error()))
|
||||
return
|
||||
}
|
||||
}
|
||||
item, err := svc.Cancel(c.Request.Context(), actorFromGin(c), c.Param("source"), c.Param("id"), req.Reason)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, item)
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ type Deps struct {
|
||||
Auth *app.AuthService
|
||||
Accounts *app.AccountService
|
||||
AdminUsers *app.AdminUserService
|
||||
Jobs *app.JobService
|
||||
KolSubs *app.KolSubscriptionService
|
||||
Audits *app.AuditService
|
||||
SiteDomains *app.SiteDomainMappingService
|
||||
@@ -92,6 +93,11 @@ func RegisterRoutes(d Deps) {
|
||||
|
||||
authed.GET("/audits", listAuditsHandler(d.Audits))
|
||||
|
||||
authed.GET("/jobs", listJobsHandler(d.Jobs))
|
||||
authed.GET("/jobs/:source/:id", getJobHandler(d.Jobs))
|
||||
authed.POST("/jobs/:source/:id/retry", retryJobHandler(d.Jobs))
|
||||
authed.POST("/jobs/:source/:id/cancel", cancelJobHandler(d.Jobs))
|
||||
|
||||
authed.GET("/site-domain-mappings", listSiteDomainMappingsHandler(d.SiteDomains))
|
||||
authed.POST("/site-domain-mappings", createSiteDomainMappingHandler(d.SiteDomains))
|
||||
authed.PATCH("/site-domain-mappings/:id", updateSiteDomainMappingHandler(d.SiteDomains))
|
||||
|
||||
Reference in New Issue
Block a user