Files
geo/server/internal/tenant/transport/desktop_release_handler.go
T
root d6866cd85b
Frontend CI / Frontend (push) Successful in 4m1s
Backend CI / Backend (push) Failing after 8m51s
feat: add downloadable release management for desktop client
- Extend DesktopClientReleaseCheckResponse with platform, arch, and channel fields.
- Introduce DesktopClientDownloadableRelease and DesktopClientDownloadableReleaseListResponse interfaces for managing downloadable releases.
- Implement ListDownloadable method in DesktopClientReleaseService to retrieve downloadable releases based on the specified channel.
- Update DesktopClientReleaseCheckResult to include platform, arch, and channel information.
- Add resolveInstallerDownloadURL method to handle download URL resolution for releases.
- Create new endpoint in DesktopReleaseHandler for listing downloadable releases.
- Update router to include the new downloadable releases route.
- Enhance tests to cover the new functionality for resolving download URLs and listing downloadable releases.
2026-05-31 19:28:35 +08:00

146 lines
4.1 KiB
Go

package transport
import (
"fmt"
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/geo-platform/tenant-api/internal/bootstrap"
opsapp "github.com/geo-platform/tenant-api/internal/ops/app"
opsrepo "github.com/geo-platform/tenant-api/internal/ops/repository"
"github.com/geo-platform/tenant-api/internal/shared/response"
)
type DesktopReleaseHandler struct {
svc *opsapp.DesktopClientReleaseService
}
func NewDesktopReleaseHandler(a *bootstrap.App) *DesktopReleaseHandler {
return &DesktopReleaseHandler{
svc: opsapp.NewDesktopClientReleaseService(
opsrepo.NewDesktopClientReleaseRepository(a.DB),
a.ObjectStorage,
nil,
),
}
}
func (h *DesktopReleaseHandler) Latest(c *gin.Context) {
client := MustDesktopClient(c.Request.Context())
data, err := h.svc.CheckLatest(c.Request.Context(), opsapp.DesktopClientReleaseCheckInput{
Platform: c.Query("platform"),
Arch: c.Query("arch"),
Channel: defaultDesktopReleaseChannel(c.Query("channel"), client.Channel),
CurrentVersion: c.Query("version"),
})
if err != nil {
response.Error(c, err)
return
}
response.Success(c, data)
}
func (h *DesktopReleaseHandler) DownloadURL(c *gin.Context) {
client := MustDesktopClient(c.Request.Context())
data, err := h.svc.ResolveLatestDownloadURL(c.Request.Context(), opsapp.DesktopClientReleaseCheckInput{
Platform: c.Query("platform"),
Arch: c.Query("arch"),
Channel: defaultDesktopReleaseChannel(c.Query("channel"), client.Channel),
CurrentVersion: c.Query("version"),
})
if err != nil {
response.Error(c, err)
return
}
response.Success(c, data)
}
func (h *DesktopReleaseHandler) LatestPublic(c *gin.Context) {
data, err := h.svc.CheckLatest(c.Request.Context(), opsapp.DesktopClientReleaseCheckInput{
Platform: c.Query("platform"),
Arch: c.Query("arch"),
Channel: c.DefaultQuery("channel", "stable"),
CurrentVersion: c.Query("version"),
})
if err != nil {
response.Error(c, err)
return
}
response.Success(c, data)
}
func (h *DesktopReleaseHandler) DownloadURLPublic(c *gin.Context) {
data, err := h.svc.ResolveLatestDownloadURL(c.Request.Context(), opsapp.DesktopClientReleaseCheckInput{
Platform: c.Query("platform"),
Arch: c.Query("arch"),
Channel: c.DefaultQuery("channel", "stable"),
CurrentVersion: c.Query("version"),
})
if err != nil {
response.Error(c, err)
return
}
response.Success(c, data)
}
func (h *DesktopReleaseHandler) ListDownloadable(c *gin.Context) {
data, err := h.svc.ListDownloadable(c.Request.Context(), c.DefaultQuery("channel", "stable"))
if err != nil {
response.Error(c, err)
return
}
response.Success(c, data)
}
func (h *DesktopReleaseHandler) UpdaterFeedPublic(c *gin.Context) {
feed, err := h.svc.ResolveLatestUpdaterFeed(c.Request.Context(), opsapp.DesktopClientReleaseCheckInput{
Platform: firstNonEmpty(c.Param("platform"), c.Query("platform")),
Arch: firstNonEmpty(c.Param("arch"), c.Query("arch")),
Channel: firstNonEmpty(c.Param("channel"), c.DefaultQuery("channel", "stable")),
CurrentVersion: firstNonEmpty(c.Param("version"), c.Query("version")),
}, c.Param("feed"))
if err != nil {
response.Error(c, err)
return
}
c.Header("Content-Type", "application/x-yaml; charset=utf-8")
c.Header("Cache-Control", "no-store")
c.Header("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, sanitizeHeaderFileName(feed.FileName)))
c.Data(http.StatusOK, "application/x-yaml; charset=utf-8", feed.Content)
}
func firstNonEmpty(values ...string) string {
for _, value := range values {
if strings.TrimSpace(value) != "" {
return value
}
}
return ""
}
func defaultDesktopReleaseChannel(queryValue string, clientChannel *string) string {
if queryValue != "" {
return queryValue
}
if clientChannel != nil && *clientChannel != "" {
return *clientChannel
}
return "stable"
}
func sanitizeHeaderFileName(value string) string {
value = strings.TrimSpace(value)
if value == "" {
return "latest.yml"
}
return strings.Map(func(r rune) rune {
if r == '"' || r == '\\' || r == '\r' || r == '\n' {
return -1
}
return r
}, value)
}