Files
goauto/server/app/goauto/device/handler.go
T

185 lines
5.0 KiB
Go

package device
import (
stdcontext "context"
"encoding/json"
"errors"
"io"
"net/http"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"github.com/go-admin-team/go-admin-core/sdk/pkg"
"gorm.io/gorm"
)
type Handler struct {
DB *gorm.DB
}
func (handler Handler) List(context *gin.Context) {
request, err := parseListRequest(context)
if err != nil {
writeError(context, err)
return
}
db, err := handler.database(context)
if err != nil {
writeError(context, internalError(err))
return
}
response, err := NewService(db).List(context.Request.Context(), request)
if err != nil {
writeError(context, err)
return
}
context.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "data": response})
}
func (handler Handler) Register(context *gin.Context) {
request, err := decodeRegisterRequest(context)
if err != nil {
writeError(context, invalidRequest("请求 JSON 无效"))
return
}
db, err := handler.database(context)
if err != nil {
writeError(context, internalError(err))
return
}
response, err := NewService(db).Register(context.Request.Context(), request, bearerToken(context.GetHeader("Authorization")), strings.TrimSpace(context.GetHeader("X-GoAuto-Device-Recovery-Code")))
if err != nil {
writeError(context, err)
return
}
context.Header("Cache-Control", "no-store")
context.JSON(http.StatusOK, gin.H{"data": response})
}
func (handler Handler) Heartbeat(context *gin.Context) {
var request HeartbeatRequest
if err := decodeJSON(context, &request); err != nil {
writeError(context, invalidRequest("请求 JSON 无效"))
return
}
db, err := handler.database(context)
if err != nil {
writeError(context, internalError(err))
return
}
response, err := NewService(db).Heartbeat(context.Request.Context(), request, bearerToken(context.GetHeader("Authorization")))
if err != nil {
writeError(context, err)
return
}
context.JSON(http.StatusOK, gin.H{"data": response})
}
func (handler Handler) Disable(context *gin.Context) {
handler.adminAction(context, (*Service).Disable)
}
func (handler Handler) RevokeToken(context *gin.Context) {
handler.adminAction(context, (*Service).RevokeToken)
}
func (handler Handler) ResetIdentity(context *gin.Context) {
deviceID, err := strconv.ParseUint(context.Param("deviceId"), 10, 64)
if err != nil || deviceID == 0 {
writeError(context, invalidRequest("deviceId 无效"))
return
}
db, err := handler.database(context)
if err != nil {
writeError(context, internalError(err))
return
}
response, err := NewService(db).ResetIdentity(context.Request.Context(), deviceID)
if err != nil {
writeError(context, err)
return
}
context.Header("Cache-Control", "no-store")
context.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "data": response})
}
func (handler Handler) adminAction(context *gin.Context, action func(*Service, stdcontext.Context, uint64) error) {
deviceID, err := strconv.ParseUint(context.Param("deviceId"), 10, 64)
if err != nil || deviceID == 0 {
writeError(context, invalidRequest("deviceId 无效"))
return
}
db, err := handler.database(context)
if err != nil {
writeError(context, internalError(err))
return
}
if err := action(NewService(db), context.Request.Context(), deviceID); err != nil {
writeError(context, err)
return
}
context.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "data": gin.H{"deviceId": deviceID}})
}
func (handler Handler) database(context *gin.Context) (*gorm.DB, error) {
if handler.DB != nil {
return handler.DB, nil
}
return pkg.GetOrm(context)
}
func decodeRegisterRequest(context *gin.Context) (RegisterRequest, error) {
var request RegisterRequest
if err := decodeJSON(context, &request); err != nil {
return RegisterRequest{}, err
}
return request, nil
}
func decodeJSON(context *gin.Context, destination any) error {
context.Request.Body = http.MaxBytesReader(context.Writer, context.Request.Body, 64<<10)
decoder := json.NewDecoder(context.Request.Body)
decoder.DisallowUnknownFields()
if err := decoder.Decode(destination); err != nil {
return err
}
if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) {
return errors.New("request body must contain one JSON object")
}
return nil
}
func bearerToken(header string) string {
parts := strings.Fields(header)
if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") {
return ""
}
return parts[1]
}
func writeError(context *gin.Context, err error) {
var serviceError *ServiceError
if !errors.As(err, &serviceError) {
serviceError = internalError(err).(*ServiceError)
}
status := http.StatusInternalServerError
switch serviceError.Code {
case CodeInvalidRequest:
status = http.StatusUnprocessableEntity
case CodeInstallIDConflict:
status = http.StatusConflict
case CodeTokenInvalid:
status = http.StatusUnauthorized
case CodeDeviceDisabled:
status = http.StatusForbidden
case CodeDeviceTaskMismatch:
status = http.StatusConflict
case CodeDeviceNotFound:
status = http.StatusNotFound
}
context.JSON(status, gin.H{
"code": serviceError.Code, "message": serviceError.Message, "retryable": serviceError.Retryable,
})
}