feat(t216): deliver recoverable device order commands
This commit is contained in:
@@ -20,10 +20,12 @@ type DeviceServices struct {
|
||||
Lifecycle *usecase.LifecycleService
|
||||
Assets *usecase.AssetService
|
||||
Results *usecase.ExecutionResultService
|
||||
Commands *usecase.DeviceOrderCommandService
|
||||
}
|
||||
|
||||
func (services DeviceServices) validate() error {
|
||||
if services.Lifecycle == nil || services.Assets == nil || services.Results == nil {
|
||||
if services.Lifecycle == nil || services.Assets == nil ||
|
||||
services.Results == nil || services.Commands == nil {
|
||||
return errors.New("device services are required")
|
||||
}
|
||||
return nil
|
||||
@@ -73,12 +75,125 @@ func NewDeviceRouteRegistrar(
|
||||
routes.POST("/api/v1/tasks/:id/evidence", handler.uploadEvidence)
|
||||
routes.POST("/api/v1/tasks/:id/candidates", handler.storeCandidates)
|
||||
routes.POST("/api/v1/tasks/:id/human-reviews", handler.storeHumanReview)
|
||||
routes.POST(
|
||||
"/api/v1/tasks/:id/commands/next",
|
||||
handler.pullOrderCommand,
|
||||
)
|
||||
routes.POST(
|
||||
"/api/v1/tasks/:id/commands/:command_id/ack",
|
||||
handler.acknowledgeOrderCommand,
|
||||
)
|
||||
routes.POST("/api/v1/tasks/:id/complete", handler.completeTask)
|
||||
routes.POST("/api/v1/tasks/:id/fail", handler.failTask)
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (handler *deviceHandlers) pullOrderCommand(ctx *gin.Context) {
|
||||
principal, ok := devicePrincipal(ctx)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var request struct {
|
||||
DeviceID string `json:"device_id"`
|
||||
ExecutionID string `json:"execution_id"`
|
||||
ClaimGeneration int64 `json:"claim_generation"`
|
||||
}
|
||||
if !decodeDeviceJSON(ctx, &request) ||
|
||||
!deviceIDMatches(ctx, request.DeviceID, principal.DeviceID) {
|
||||
return
|
||||
}
|
||||
command, err := handler.services.Commands.Pull(
|
||||
ctx.Request.Context(),
|
||||
usecase.PullDeviceOrderCommand{
|
||||
UserID: principal.UserID,
|
||||
DeviceID: principal.DeviceID,
|
||||
TaskID: ctx.Param("id"),
|
||||
ExecutionID: request.ExecutionID,
|
||||
ClaimGeneration: request.ClaimGeneration,
|
||||
ClaimToken: ctx.GetHeader(claimTokenHeader),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
writeUsecaseError(ctx, err)
|
||||
return
|
||||
}
|
||||
ctx.Header("Cache-Control", "no-store")
|
||||
if command == nil {
|
||||
ctx.Status(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, deviceOrderCommandResponse(*command))
|
||||
}
|
||||
|
||||
func (handler *deviceHandlers) acknowledgeOrderCommand(ctx *gin.Context) {
|
||||
principal, ok := devicePrincipal(ctx)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var request struct {
|
||||
DeviceID string `json:"device_id"`
|
||||
ExecutionID string `json:"execution_id"`
|
||||
ClaimGeneration int64 `json:"claim_generation"`
|
||||
CommandSHA256 string `json:"command_sha256"`
|
||||
}
|
||||
if !decodeDeviceJSON(ctx, &request) ||
|
||||
!deviceIDMatches(ctx, request.DeviceID, principal.DeviceID) {
|
||||
return
|
||||
}
|
||||
result, err := handler.services.Commands.Acknowledge(
|
||||
ctx.Request.Context(),
|
||||
usecase.AcknowledgeDeviceOrderCommand{
|
||||
UserID: principal.UserID,
|
||||
DeviceID: principal.DeviceID,
|
||||
TaskID: ctx.Param("id"),
|
||||
ExecutionID: request.ExecutionID,
|
||||
AuthorizationID: ctx.Param("command_id"),
|
||||
ClaimGeneration: request.ClaimGeneration,
|
||||
ClaimToken: ctx.GetHeader(claimTokenHeader),
|
||||
CommandSHA256: request.CommandSHA256,
|
||||
IdempotencyKey: ctx.GetHeader("Idempotency-Key"),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
writeUsecaseError(ctx, err)
|
||||
return
|
||||
}
|
||||
ctx.Header("Cache-Control", "no-store")
|
||||
ctx.JSON(http.StatusOK, gin.H{
|
||||
"command_id": result.Authorization.ID,
|
||||
"status": result.Authorization.Status,
|
||||
"replayed": result.Replayed,
|
||||
})
|
||||
}
|
||||
|
||||
func deviceOrderCommandResponse(command domain.DeviceOrderCommand) gin.H {
|
||||
return gin.H{
|
||||
"id": command.ID,
|
||||
"schema_version": command.SchemaVersion,
|
||||
"type": command.Type,
|
||||
"authorization_version": command.AuthorizationVersion,
|
||||
"task_id": command.TaskID,
|
||||
"execution_id": command.ExecutionID,
|
||||
"task_content_sha256": command.TaskContentSHA256,
|
||||
"original_sku": command.OriginalSKU,
|
||||
"quantity": command.Quantity,
|
||||
"candidate": gin.H{
|
||||
"candidate_key": command.CandidateKey,
|
||||
"observed_ordinal": command.ObservedOrdinal,
|
||||
"title": command.CandidateTitle,
|
||||
"sku_text": command.CandidateSKUText,
|
||||
"price_text": command.CandidatePriceText,
|
||||
"card_signature": command.CardSignature,
|
||||
"detail_signature": command.DetailSignature,
|
||||
"detail_evidence_sha256": command.DetailEvidenceSHA256,
|
||||
"specification_evidence_sha256": command.SpecificationEvidenceSHA256,
|
||||
},
|
||||
"command_sha256": command.CommandSHA256,
|
||||
"authorization_status": command.AuthorizationStatus,
|
||||
}
|
||||
}
|
||||
|
||||
func (handler *deviceHandlers) referenceImage(ctx *gin.Context) {
|
||||
principal, ok := devicePrincipal(ctx)
|
||||
if !ok {
|
||||
|
||||
Reference in New Issue
Block a user