ci / verify (push) Canceled after 0s
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
80 lines
3.8 KiB
Go
80 lines
3.8 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"data-terminal-cloud/internal/deviceauth"
|
|
"data-terminal-cloud/internal/devicebinding"
|
|
"data-terminal-cloud/internal/devicereset"
|
|
"data-terminal-cloud/internal/idempotency"
|
|
)
|
|
|
|
type DeviceFactoryResetService interface {
|
|
Reset(context.Context, deviceauth.Identity, string, string, []byte, devicereset.Input) (devicereset.Response, bool, error)
|
|
}
|
|
|
|
func DeviceFactoryResetHandler(service DeviceFactoryResetService) http.Handler {
|
|
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
|
writer.Header().Set("Cache-Control", "no-store")
|
|
writer.Header().Set("Pragma", "no-cache")
|
|
identity, ok := deviceauth.FromContext(request.Context())
|
|
if !ok {
|
|
WriteError(writer, request, http.StatusUnauthorized, "DEVICE_AUTHENTICATION_FAILED", "设备身份验证失败")
|
|
return
|
|
}
|
|
key := strings.TrimSpace(request.Header.Get("Idempotency-Key"))
|
|
if len(key) < 16 || len(key) > 128 {
|
|
WriteError(writer, request, http.StatusBadRequest, "VALIDATION_FAILED", "缺少有效的重复请求识别码")
|
|
return
|
|
}
|
|
rawBody, err := io.ReadAll(request.Body)
|
|
if err != nil {
|
|
WriteError(writer, request, http.StatusBadRequest, "INVALID_REQUEST_BODY", "请求正文无法读取")
|
|
return
|
|
}
|
|
decoder := json.NewDecoder(bytes.NewReader(rawBody))
|
|
decoder.DisallowUnknownFields()
|
|
var input devicereset.Input
|
|
if err := decoder.Decode(&input); err != nil || ensureJSONEOF(decoder) != nil || devicereset.Validate(input) != nil {
|
|
WriteError(writer, request, http.StatusBadRequest, "VALIDATION_FAILED", "设备恢复出厂参数不正确")
|
|
return
|
|
}
|
|
response, replayed, err := service.Reset(request.Context(), identity, key, RequestID(request.Context()), rawBody, input)
|
|
if replayed {
|
|
writer.Header().Set("Idempotency-Replayed", "true")
|
|
}
|
|
if err != nil {
|
|
switch {
|
|
case errors.Is(err, devicebinding.ErrConfirmationRequired):
|
|
WriteError(writer, request, http.StatusConflict, "DEVICE_RESET_AUTHORIZATION_REQUIRED", "请先由当前家庭授权解绑,随后再执行设备重置")
|
|
case errors.Is(err, devicereset.ErrPendingTranscripts):
|
|
WriteError(writer, request, http.StatusConflict, "DEVICE_RESET_PENDING_TRANSCRIPTS", "设备仍有未上传的聊天记录")
|
|
case errors.Is(err, devicereset.ErrCallInProgress):
|
|
WriteError(writer, request, http.StatusConflict, "DEVICE_RESET_CALL_IN_PROGRESS", "设备正在通话,请结束后再恢复出厂")
|
|
case errors.Is(err, devicereset.ErrFirmwareInProgress):
|
|
WriteError(writer, request, http.StatusConflict, "DEVICE_RESET_FIRMWARE_IN_PROGRESS", "设备正在升级固件,暂时不能恢复出厂")
|
|
case errors.Is(err, devicereset.ErrResetCounterConflict):
|
|
WriteError(writer, request, http.StatusConflict, "DEVICE_RESET_COUNTER_CONFLICT", "设备重置计数器不是递增值")
|
|
case errors.Is(err, devicereset.ErrStateConflict):
|
|
WriteError(writer, request, http.StatusConflict, "DEVICE_RESET_STATE_CONFLICT", "当前设备状态不允许恢复出厂")
|
|
case errors.Is(err, idempotency.ErrKeyConflict):
|
|
WriteError(writer, request, http.StatusConflict, "IDEMPOTENCY_KEY_CONFLICT", "重复请求识别码已用于不同的请求")
|
|
case errors.Is(err, idempotency.ErrInProgress):
|
|
WriteAPIError(writer, request, http.StatusConflict, Error{Code: "IDEMPOTENCY_IN_PROGRESS", Message: "相同恢复出厂请求仍在处理中", Retryable: true})
|
|
case errors.Is(err, devicereset.ErrInvalid):
|
|
WriteError(writer, request, http.StatusBadRequest, "VALIDATION_FAILED", "设备恢复出厂参数不正确")
|
|
default:
|
|
WriteAPIError(writer, request, http.StatusServiceUnavailable, Error{Code: "DEVICE_RESET_FAILED", Message: "设备恢复出厂暂时失败", Retryable: true})
|
|
}
|
|
return
|
|
}
|
|
WriteJSON(writer, request, http.StatusOK, response)
|
|
})
|
|
}
|