feat(t215): add admin order authorization
This commit is contained in:
@@ -66,6 +66,11 @@ func (h *Handler) RegisterProtected(routes gin.IRoutes) {
|
||||
routes.POST("/tasks", SecurityHeaders(), h.CreateTask)
|
||||
routes.GET("/tasks/:id", SecurityHeaders(), h.TaskDetail)
|
||||
routes.POST("/tasks/:id/cancel", SecurityHeaders(), h.CancelTask)
|
||||
routes.POST(
|
||||
"/tasks/:id/order-authorizations",
|
||||
SecurityHeaders(),
|
||||
h.AuthorizeOrder,
|
||||
)
|
||||
}
|
||||
|
||||
func SecurityHeaders() gin.HandlerFunc {
|
||||
@@ -264,20 +269,85 @@ func (h *Handler) TaskDetail(ctx *gin.Context) {
|
||||
h.renderError(ctx, http.StatusInternalServerError, "页面暂时无法打开", "请稍后重试。")
|
||||
return
|
||||
}
|
||||
authorizationKey, keyErr := newToken()
|
||||
if keyErr != nil {
|
||||
h.renderError(ctx, http.StatusInternalServerError, "页面暂时无法打开", "请稍后重试。")
|
||||
return
|
||||
}
|
||||
page := taskDetailPage{
|
||||
Page: pageView{
|
||||
Title: "任务详情",
|
||||
TasksCurrent: true,
|
||||
CSRFToken: token,
|
||||
},
|
||||
Task: taskDetailViewFrom(task),
|
||||
CSRFToken: token,
|
||||
CancelKey: cancelKey,
|
||||
Notice: detailNotice(ctx.Query("notice")),
|
||||
Task: taskDetailViewFrom(task),
|
||||
CSRFToken: token,
|
||||
CancelKey: cancelKey,
|
||||
AuthorizationKey: authorizationKey,
|
||||
Notice: detailNotice(ctx.Query("notice")),
|
||||
}
|
||||
h.render(ctx, http.StatusOK, "task-detail", page)
|
||||
}
|
||||
|
||||
func (h *Handler) AuthorizeOrder(ctx *gin.Context) {
|
||||
if !validCSRF(ctx) {
|
||||
h.renderError(
|
||||
ctx,
|
||||
http.StatusForbidden,
|
||||
"请求已失效",
|
||||
"请返回任务详情后重新操作。",
|
||||
)
|
||||
return
|
||||
}
|
||||
taskID := strings.TrimSpace(ctx.Param("id"))
|
||||
authorizationKey := strings.TrimSpace(
|
||||
ctx.PostForm("authorization_key"),
|
||||
)
|
||||
expectedVersion, versionErr := strconv.ParseInt(
|
||||
strings.TrimSpace(ctx.PostForm("expected_task_version")),
|
||||
10,
|
||||
64,
|
||||
)
|
||||
if !validToken(authorizationKey) || versionErr != nil ||
|
||||
expectedVersion < 1 {
|
||||
h.renderError(
|
||||
ctx,
|
||||
http.StatusForbidden,
|
||||
"请求已失效",
|
||||
"请返回任务详情后重新操作。",
|
||||
)
|
||||
return
|
||||
}
|
||||
_, err := h.service.AuthorizeOrder(
|
||||
ctx.Request.Context(),
|
||||
AuthorizeOrderInput{
|
||||
TaskID: taskID,
|
||||
IdempotencyKey: authorizationKey,
|
||||
ExpectedTaskVersion: expectedVersion,
|
||||
CandidateKey: strings.TrimSpace(ctx.PostForm("candidate_key")),
|
||||
SelectedReasonCode: strings.TrimSpace(ctx.PostForm("selected_reason_code")),
|
||||
RejectedReasonCode: strings.TrimSpace(ctx.PostForm("rejected_reason_code")),
|
||||
Note: strings.TrimSpace(ctx.PostForm("authorization_note")),
|
||||
SupersedesAuthorizationID: strings.TrimSpace(ctx.PostForm("supersedes_authorization_id")),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrConflict) || errors.Is(err, ErrValidation) {
|
||||
ctx.Redirect(
|
||||
http.StatusSeeOther,
|
||||
"/tasks/"+pathEscape(taskID)+"?notice=authorization-conflict",
|
||||
)
|
||||
return
|
||||
}
|
||||
h.renderServiceError(ctx, err, "授权失败,请稍后重试。")
|
||||
return
|
||||
}
|
||||
ctx.Redirect(
|
||||
http.StatusSeeOther,
|
||||
"/tasks/"+pathEscape(taskID)+"?notice=authorization-created",
|
||||
)
|
||||
}
|
||||
|
||||
func (h *Handler) CancelTask(ctx *gin.Context) {
|
||||
if !validCSRF(ctx) {
|
||||
h.renderError(
|
||||
@@ -652,6 +722,10 @@ func detailNotice(value string) string {
|
||||
return "已请求设备安全停止;设备确认前任务仍保持当前执行状态。"
|
||||
case "cancel-conflict":
|
||||
return "任务状态已变化,当前不能取消。"
|
||||
case "authorization-created":
|
||||
return "候选已确认,待投递下单授权已创建。"
|
||||
case "authorization-conflict":
|
||||
return "候选或任务状态已变化,请检查最新证据后重新授权。"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
@@ -742,29 +816,35 @@ type newTaskPageView struct {
|
||||
}
|
||||
|
||||
type taskDetailView struct {
|
||||
ID string
|
||||
Title string
|
||||
SKU string
|
||||
Description string
|
||||
Quantity int64
|
||||
MaxBudget string
|
||||
Status string
|
||||
StatusLabel string
|
||||
StatusClass string
|
||||
ReferenceAssetID string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
CanCancel bool
|
||||
CancelRequiresAck bool
|
||||
ExecutionReport *ExecutionReport
|
||||
ID string
|
||||
Title string
|
||||
SKU string
|
||||
Description string
|
||||
Quantity int64
|
||||
MaxBudget string
|
||||
Status string
|
||||
Version int64
|
||||
StatusLabel string
|
||||
StatusClass string
|
||||
ReferenceAssetID string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
CanCancel bool
|
||||
CancelRequiresAck bool
|
||||
ExecutionReport *ExecutionReport
|
||||
Candidates []AuthorizationCandidate
|
||||
OrderAuthorizations []OrderAuthorization
|
||||
CanAuthorizeOrder bool
|
||||
ActiveAuthorizationID string
|
||||
}
|
||||
|
||||
type taskDetailPage struct {
|
||||
Page pageView
|
||||
Task taskDetailView
|
||||
CSRFToken string
|
||||
CancelKey string
|
||||
Notice string
|
||||
Page pageView
|
||||
Task taskDetailView
|
||||
CSRFToken string
|
||||
CancelKey string
|
||||
AuthorizationKey string
|
||||
Notice string
|
||||
}
|
||||
|
||||
type errorPage struct {
|
||||
@@ -774,6 +854,17 @@ type errorPage struct {
|
||||
}
|
||||
|
||||
func taskDetailViewFrom(task Task) taskDetailView {
|
||||
activeAuthorizationID := ""
|
||||
canAuthorizeOrder := task.Status == "WAITING_CONFIRMATION" &&
|
||||
len(task.Candidates) > 0
|
||||
for _, authorization := range task.OrderAuthorizations {
|
||||
switch authorization.Status {
|
||||
case "PENDING_DELIVERY":
|
||||
activeAuthorizationID = authorization.ID
|
||||
case "DELIVERED", "ACKNOWLEDGED", "EXECUTING":
|
||||
canAuthorizeOrder = false
|
||||
}
|
||||
}
|
||||
return taskDetailView{
|
||||
ID: task.ID,
|
||||
Title: task.Title,
|
||||
@@ -782,6 +873,7 @@ func taskDetailViewFrom(task Task) taskDetailView {
|
||||
Quantity: task.Quantity,
|
||||
MaxBudget: task.MaxBudget,
|
||||
Status: task.Status,
|
||||
Version: task.Version,
|
||||
StatusLabel: statusLabel(task.Status),
|
||||
StatusClass: statusClass(task.Status),
|
||||
ReferenceAssetID: task.ReferenceAssetID,
|
||||
@@ -790,7 +882,11 @@ func taskDetailViewFrom(task Task) taskDetailView {
|
||||
CanCancel: canCancelTaskStatus(task.Status),
|
||||
CancelRequiresAck: task.Status == "RUNNING" ||
|
||||
task.Status == "WAITING_CONFIRMATION",
|
||||
ExecutionReport: task.ExecutionReport,
|
||||
ExecutionReport: task.ExecutionReport,
|
||||
Candidates: task.Candidates,
|
||||
OrderAuthorizations: task.OrderAuthorizations,
|
||||
CanAuthorizeOrder: canAuthorizeOrder,
|
||||
ActiveAuthorizationID: activeAuthorizationID,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user