feat: centralize app release configuration

This commit is contained in:
richjohnrambo
2026-09-12 17:07:49 +08:00
parent 2728f35069
commit e5fbca735d
13 changed files with 257 additions and 104 deletions
+73 -8
View File
@@ -3215,12 +3215,9 @@ func serveIMAppReleaseLatest(wrt http.ResponseWriter, req *http.Request) {
writeIMError(wrt, req, http.StatusMethodNotAllowed, 30002, imReasonMethodNotAllowed, "method not allowed", nil)
return
}
tenant, ok := resolveIMHTTPTenant(wrt, req, "default")
if !ok {
return
}
platform := strings.ToLower(strings.TrimSpace(req.URL.Query().Get("platform")))
channel := strings.ToLower(strings.TrimSpace(req.URL.Query().Get("channel")))
currentVersion := strings.TrimSpace(req.URL.Query().Get("current_version"))
if channel == "" {
channel = "stable"
}
@@ -3228,12 +3225,34 @@ func serveIMAppReleaseLatest(wrt http.ResponseWriter, req *http.Request) {
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "malformed request", map[string]any{"field": "platform"})
return
}
release, err := store.AppReleases.Latest(tenant.ID, platform, channel)
currentParts, err := parseNumericAppVersion(currentVersion)
if err != nil {
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "malformed request", map[string]any{"field": "current_version"})
return
}
release, err := store.AppReleases.Latest(platform, channel)
if err != nil {
writeIMRelationError(wrt, req, err)
return
}
writeIMSuccess(wrt, req, appReleasePayload(release))
latestParts, latestErr := parseNumericAppVersion(release.LatestVersion)
minimumParts, minimumErr := parseNumericAppVersion(release.MinSupportedVersion)
if latestErr != nil || minimumErr != nil {
writeIMError(wrt, req, http.StatusInternalServerError, 50000, imReasonInternal, "invalid app release configuration", nil)
return
}
hasUpdate := compareNumericAppVersion(currentParts, latestParts) < 0
updateType := "none"
if hasUpdate {
updateType = strings.ToLower(strings.TrimSpace(release.UpdateType))
if compareNumericAppVersion(currentParts, minimumParts) < 0 {
updateType = "force"
}
if updateType != "force" && updateType != "recommended" {
updateType = "none"
}
}
writeIMSuccess(wrt, req, appReleasePayload(release, currentVersion, hasUpdate, updateType))
}
func serveIMYouthMode(wrt http.ResponseWriter, req *http.Request) {
@@ -9315,13 +9334,16 @@ func imCanViewLastSeen(tenantID types.TenantID, viewer, target types.Uid) bool {
}
}
func appReleasePayload(release *types.AppRelease) map[string]any {
func appReleasePayload(release *types.AppRelease, currentVersion string, hasUpdate bool, updateType string) map[string]any {
return map[string]any{
"current_version": currentVersion,
"has_update": hasUpdate,
"force_update": hasUpdate && updateType == "force",
"platform": release.Platform,
"channel": release.Channel,
"latest_version": release.LatestVersion,
"min_supported_version": release.MinSupportedVersion,
"update_type": release.UpdateType,
"update_type": updateType,
"release_notes": release.ReleaseNotes,
"store_url": release.StoreURL,
"package_url": release.PackageURL,
@@ -9331,6 +9353,49 @@ func appReleasePayload(release *types.AppRelease) map[string]any {
}
}
func parseNumericAppVersion(raw string) ([]uint64, error) {
raw = strings.TrimPrefix(strings.ToLower(strings.TrimSpace(raw)), "v")
parts := strings.Split(raw, ".")
if len(parts) == 0 || len(parts) > 8 {
return nil, types.ErrMalformed
}
parsed := make([]uint64, len(parts))
for i, part := range parts {
if part == "" {
return nil, types.ErrMalformed
}
value, err := strconv.ParseUint(part, 10, 32)
if err != nil {
return nil, types.ErrMalformed
}
parsed[i] = value
}
return parsed, nil
}
func compareNumericAppVersion(left, right []uint64) int {
length := len(left)
if len(right) > length {
length = len(right)
}
for i := 0; i < length; i++ {
var leftPart, rightPart uint64
if i < len(left) {
leftPart = left[i]
}
if i < len(right) {
rightPart = right[i]
}
if leftPart < rightPart {
return -1
}
if leftPart > rightPart {
return 1
}
}
return 0
}
func youthModePayload(settings *types.YouthModeSettings) map[string]any {
data := map[string]any{
"enabled": settings.Enabled,