mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-19 07:27:43 +08:00
fix: align build version source with tags and VERSION fallback
This commit is contained in:
@@ -39,5 +39,6 @@ func RegisterRoutes(r chi.Router, h *Handler) {
|
||||
pr.Get("/export", h.exportConfig)
|
||||
pr.Get("/dev/captures", h.getDevCaptures)
|
||||
pr.Delete("/dev/captures", h.clearDevCaptures)
|
||||
pr.Get("/version", h.getVersion)
|
||||
})
|
||||
}
|
||||
|
||||
75
internal/admin/handler_version.go
Normal file
75
internal/admin/handler_version.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"ds2api/internal/version"
|
||||
)
|
||||
|
||||
const latestReleaseAPI = "https://api.github.com/repos/CJackHwang/ds2api/releases/latest"
|
||||
|
||||
type latestReleasePayload struct {
|
||||
TagName string `json:"tag_name"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
PublishedAt string `json:"published_at"`
|
||||
}
|
||||
|
||||
func (h *Handler) getVersion(w http.ResponseWriter, _ *http.Request) {
|
||||
current, source := version.Current()
|
||||
resp := map[string]any{
|
||||
"success": true,
|
||||
"current_version": current,
|
||||
"current_tag": version.Tag(current),
|
||||
"source": source,
|
||||
"checked_at": time.Now().UTC().Format(time.RFC3339),
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, latestReleaseAPI, nil)
|
||||
if err != nil {
|
||||
resp["check_error"] = err.Error()
|
||||
writeJSON(w, http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
req.Header.Set("Accept", "application/vnd.github+json")
|
||||
req.Header.Set("User-Agent", "ds2api-version-check")
|
||||
|
||||
client := &http.Client{Timeout: 4 * time.Second}
|
||||
r, err := client.Do(req)
|
||||
if err != nil {
|
||||
resp["check_error"] = err.Error()
|
||||
writeJSON(w, http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
if r.StatusCode < 200 || r.StatusCode >= 300 {
|
||||
resp["check_error"] = "github api status: " + r.Status
|
||||
writeJSON(w, http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
|
||||
var data latestReleasePayload
|
||||
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||||
resp["check_error"] = err.Error()
|
||||
writeJSON(w, http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
|
||||
latest := strings.TrimSpace(data.TagName)
|
||||
if latest == "" {
|
||||
resp["check_error"] = "missing latest tag"
|
||||
writeJSON(w, http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
latestVersion := strings.TrimPrefix(latest, "v")
|
||||
|
||||
resp["latest_tag"] = latest
|
||||
resp["latest_version"] = latestVersion
|
||||
resp["release_url"] = data.HTMLURL
|
||||
resp["published_at"] = data.PublishedAt
|
||||
resp["has_update"] = version.Compare(current, latestVersion) < 0
|
||||
|
||||
writeJSON(w, http.StatusOK, resp)
|
||||
}
|
||||
129
internal/version/version.go
Normal file
129
internal/version/version.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// BuildVersion can be injected at build time via -ldflags.
|
||||
// In release builds it should come from Git tag (e.g. v2.3.5).
|
||||
var BuildVersion = ""
|
||||
|
||||
var (
|
||||
currentOnce sync.Once
|
||||
currentVal string
|
||||
sourceVal string
|
||||
)
|
||||
|
||||
func Current() (value string, source string) {
|
||||
currentOnce.Do(func() {
|
||||
if build := strings.TrimSpace(BuildVersion); build != "" {
|
||||
currentVal = normalize(build)
|
||||
sourceVal = "build-ldflags"
|
||||
return
|
||||
}
|
||||
if fv := readVersionFile(); fv != "" {
|
||||
currentVal = normalize(fv)
|
||||
sourceVal = "file:VERSION"
|
||||
return
|
||||
}
|
||||
currentVal = "dev"
|
||||
sourceVal = "default"
|
||||
})
|
||||
return currentVal, sourceVal
|
||||
}
|
||||
|
||||
func readVersionFile() string {
|
||||
candidates := []string{"VERSION"}
|
||||
if wd, err := os.Getwd(); err == nil {
|
||||
candidates = append(candidates, filepath.Join(wd, "VERSION"))
|
||||
}
|
||||
if _, file, _, ok := runtime.Caller(0); ok {
|
||||
repoRoot := filepath.Clean(filepath.Join(filepath.Dir(file), "../.."))
|
||||
candidates = append(candidates, filepath.Join(repoRoot, "VERSION"))
|
||||
}
|
||||
seen := map[string]struct{}{}
|
||||
for _, c := range candidates {
|
||||
c = filepath.Clean(strings.TrimSpace(c))
|
||||
if c == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[c]; ok {
|
||||
continue
|
||||
}
|
||||
seen[c] = struct{}{}
|
||||
b, err := os.ReadFile(c)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if v := strings.TrimSpace(string(b)); v != "" {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func normalize(v string) string {
|
||||
v = strings.TrimSpace(v)
|
||||
if v == "" {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimPrefix(v, "v")
|
||||
}
|
||||
|
||||
func Tag(v string) string {
|
||||
v = normalize(v)
|
||||
if v == "" || v == "dev" {
|
||||
return v
|
||||
}
|
||||
return "v" + v
|
||||
}
|
||||
|
||||
func Compare(a, b string) int {
|
||||
pa := parse(normalize(a))
|
||||
pb := parse(normalize(b))
|
||||
for i := 0; i < 3; i++ {
|
||||
if pa[i] < pb[i] {
|
||||
return -1
|
||||
}
|
||||
if pa[i] > pb[i] {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func parse(v string) [3]int {
|
||||
var out [3]int
|
||||
parts := strings.SplitN(v, ".", 4)
|
||||
for i := 0; i < 3 && i < len(parts); i++ {
|
||||
n := readLeadingInt(parts[i])
|
||||
out[i] = n
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func readLeadingInt(s string) int {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return 0
|
||||
}
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
if s[i] < '0' || s[i] > '9' {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i == 0 {
|
||||
return 0
|
||||
}
|
||||
n, err := strconv.Atoi(s[:i])
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return n
|
||||
}
|
||||
24
internal/version/version_test.go
Normal file
24
internal/version/version_test.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package version
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNormalizeAndTag(t *testing.T) {
|
||||
if got := normalize("v2.3.5"); got != "2.3.5" {
|
||||
t.Fatalf("normalize failed: %q", got)
|
||||
}
|
||||
if got := Tag("2.3.5"); got != "v2.3.5" {
|
||||
t.Fatalf("tag failed: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompare(t *testing.T) {
|
||||
if Compare("2.3.5", "2.3.5") != 0 {
|
||||
t.Fatal("expected equal")
|
||||
}
|
||||
if Compare("2.3.5", "2.3.6") >= 0 {
|
||||
t.Fatal("expected less")
|
||||
}
|
||||
if Compare("v2.10.0", "2.3.9") <= 0 {
|
||||
t.Fatal("expected greater")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user