134 lines
3.9 KiB
Go
134 lines
3.9 KiB
Go
package httpapi
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"data-terminal-cloud/internal/devicefleet"
|
||
|
|
"data-terminal-cloud/internal/saas"
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"github.com/jackc/pgx/v5"
|
||
|
|
"io"
|
||
|
|
"net/http"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
func writeFleetError(w http.ResponseWriter, r *http.Request, err error) {
|
||
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
||
|
|
WriteError(w, r, 404, "FIRMWARE_NOT_FOUND", "固件或设备不存在,请刷新后重试")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
var e *devicefleet.Error
|
||
|
|
if errors.As(err, &e) {
|
||
|
|
WriteError(w, r, 409, e.Code, e.Message)
|
||
|
|
} else {
|
||
|
|
writeSaaSError(w, r, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
func FleetFirmwareHandler(s *saas.Service, action string) http.Handler {
|
||
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
p, public, ok := opsFirmwarePrincipal(w, r)
|
||
|
|
if !ok {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
tenant, err := s.FleetTenant(r.Context(), p, public, r.Method == "POST")
|
||
|
|
if err != nil {
|
||
|
|
writeFleetError(w, r, err)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
id := r.PathValue("id")
|
||
|
|
if id != "" && !validUUID(id) {
|
||
|
|
WriteError(w, r, 400, "INVALID_ID", "固件ID不正确")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
var out any
|
||
|
|
switch action {
|
||
|
|
case "list":
|
||
|
|
var items []devicefleet.Release
|
||
|
|
items, err = s.Fleet.Releases(r.Context(), tenant)
|
||
|
|
out = map[string]any{"items": items}
|
||
|
|
case "models":
|
||
|
|
var items []string
|
||
|
|
items, err = s.Fleet.Models(r.Context(), tenant)
|
||
|
|
out = map[string]any{"items": items}
|
||
|
|
case "prepare":
|
||
|
|
var input devicefleet.ReleaseInput
|
||
|
|
if !decodeWebJSON(w, r, &input, "固件信息不正确") {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
out, err = s.Fleet.Prepare(r.Context(), tenant, input)
|
||
|
|
case "finalize":
|
||
|
|
out, err = s.Fleet.Finalize(r.Context(), tenant, id)
|
||
|
|
case "download":
|
||
|
|
out, err = s.Fleet.Download(r.Context(), tenant, id)
|
||
|
|
}
|
||
|
|
if err != nil {
|
||
|
|
writeFleetError(w, r, err)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
WriteJSON(w, r, 200, out)
|
||
|
|
})
|
||
|
|
}
|
||
|
|
func FleetDeviceHandler(s *saas.Service, upgrade bool) http.Handler {
|
||
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
p, ok := resourcePrincipal(w, r, "id")
|
||
|
|
if !ok {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
var out any
|
||
|
|
var err error
|
||
|
|
if upgrade {
|
||
|
|
var in struct {
|
||
|
|
Code int64 `json:"version_code"`
|
||
|
|
}
|
||
|
|
if !decodeWebJSON(w, r, &in, "目标版本顺序号不正确") {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
out, err = s.FleetOTA(r.Context(), p, r.PathValue("id"), RequestID(r.Context()), in.Code)
|
||
|
|
} else {
|
||
|
|
tenant := r.Header.Get("X-Tenant-Id")
|
||
|
|
if tenant != "" && !validUUID(tenant) {
|
||
|
|
WriteError(w, r, 400, "INVALID_TENANT", "租户ID不正确")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
out, err = s.FleetState(r.Context(), p, r.PathValue("id"), tenant)
|
||
|
|
}
|
||
|
|
if err != nil {
|
||
|
|
writeFleetError(w, r, err)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
WriteJSON(w, r, 200, out)
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func FleetFirmwareDownloadHandler(s *devicefleet.Service) http.Handler {
|
||
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
grant, err := s.ResolveDownload(r.Context(), r.PathValue("token"))
|
||
|
|
if errors.Is(err, devicefleet.ErrDownloadTicketUnavailable) {
|
||
|
|
http.NotFound(w, r)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if err != nil {
|
||
|
|
http.Error(w, "firmware download unavailable", http.StatusServiceUnavailable)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
object, err := s.OpenDownload(r.Context(), grant)
|
||
|
|
if err != nil {
|
||
|
|
http.Error(w, "firmware download unavailable", http.StatusServiceUnavailable)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
defer object.Close()
|
||
|
|
serveFleetFirmwareContent(w, r, grant, object)
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func serveFleetFirmwareContent(w http.ResponseWriter, r *http.Request, grant devicefleet.DownloadGrant, object io.ReadSeeker) {
|
||
|
|
_ = http.NewResponseController(w).SetWriteDeadline(time.Now().Add(20 * time.Minute))
|
||
|
|
w.Header().Set("Accept-Ranges", "bytes")
|
||
|
|
w.Header().Set("Cache-Control", "private, no-store")
|
||
|
|
w.Header().Set("Content-Disposition", `attachment; filename="firmware.bin"`)
|
||
|
|
w.Header().Set("Content-Type", "application/octet-stream")
|
||
|
|
w.Header().Set("ETag", fmt.Sprintf(`"sha256-%s"`, grant.SHA256))
|
||
|
|
w.Header().Set("Referrer-Policy", "no-referrer")
|
||
|
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||
|
|
http.ServeContent(w, r, "firmware.bin", grant.CreatedAt, object)
|
||
|
|
}
|