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) }