Add CORS support and response caching to aggregate endpoints
Publish telemt-api gateway Docker image / test (push) Successful in 25s
Publish telemt-api gateway Docker image / build-and-push (push) Successful in 1m19s

- Introduced CORS configuration options in config.example.yaml, allowing specification of allowed origins for cross-origin requests.
- Enhanced the aggregate handler to support response caching with a configurable TTL, improving performance for repeated requests.
- Updated the aggregate API to return a structured response indicating whether any upstream requests failed, enhancing error handling and response clarity.
- Modified documentation in AGGREGATE.md and README.md to reflect the new CORS and caching features.
- Added tests to validate the new functionality in the aggregate handler.
This commit is contained in:
Denozordec
2026-03-30 10:02:16 +07:00
parent 2a8390e687
commit 04c257a84e
14 changed files with 1071 additions and 142 deletions
+95 -6
View File
@@ -3,6 +3,7 @@ package aggregate
import (
"sort"
"strings"
"time"
)
// BuildTraffic builds traffic matrix from successful fetches only.
@@ -140,6 +141,11 @@ func sortedKeys(m map[string]struct{}) []string {
return s
}
type userOnServer struct {
alias string
u UserInfo
}
// BuildUsers merged rows with totals and optional links from first successful server per user.
func BuildUsers(results []ServerFetchResult, includeLinks bool, minTotalOctets uint64) []UsersRow {
type acc struct {
@@ -150,6 +156,7 @@ func BuildUsers(results []ServerFetchResult, includeLinks bool, minTotalOctets u
rec uint64
}
m := map[string]*acc{}
perUserServers := map[string][]userOnServer{}
for _, fr := range results {
if !fr.OK {
@@ -176,6 +183,7 @@ func BuildUsers(results []ServerFetchResult, includeLinks bool, minTotalOctets u
if includeLinks && u.Links != nil && a.links == nil {
a.links = u.Links
}
perUserServers[u.Username] = append(perUserServers[u.Username], userOnServer{alias: fr.Alias, u: u})
}
}
@@ -190,18 +198,99 @@ func BuildUsers(results []ServerFetchResult, includeLinks bool, minTotalOctets u
rows := make([]UsersRow, 0, len(names))
for _, name := range names {
a := m[name]
sort.Slice(perUserServers[name], func(i, j int) bool {
return perUserServers[name][i].alias < perUserServers[name][j].alias
})
ad, mt, ex, dq, mu := mergeUserLimitFields(perUserServers[name])
rows = append(rows, UsersRow{
Username: name,
TotalMegabytes: octetsToMegabytes(a.total),
ByServer: a.byServer,
Links: a.links,
ActiveUniqueIPs: a.act,
RecentUniqueIPs: a.rec,
Username: name,
TotalMegabytes: octetsToMegabytes(a.total),
ByServer: a.byServer,
Links: a.links,
ActiveUniqueIPs: a.act,
RecentUniqueIPs: a.rec,
UserAdTag: ad,
MaxTCPConns: mt,
ExpirationRFC3339: ex,
DataQuotaBytes: dq,
MaxUniqueIPs: mu,
})
}
return rows
}
// BuildSingleUser returns one merged user row or nil if the user is absent on all successful upstreams.
func BuildSingleUser(results []ServerFetchResult, username string, includeLinks bool) *UsersRow {
rows := BuildUsers(results, includeLinks, 0)
for i := range rows {
if rows[i].Username == username {
return &rows[i]
}
}
return nil
}
func mergeUserLimitFields(rows []userOnServer) (userAdTag *string, maxTCP *uint64, exp *string, quota *uint64, maxUip *uint64) {
for _, r := range rows {
if r.u.UserAdTag != nil && userAdTag == nil {
v := *r.u.UserAdTag
userAdTag = &v
}
}
var earliest time.Time
var earliestStr string
haveEarliest := false
for _, r := range rows {
if r.u.ExpirationRFC3339 == nil {
continue
}
s := *r.u.ExpirationRFC3339
t, err := time.Parse(time.RFC3339Nano, s)
if err != nil {
t, err = time.Parse(time.RFC3339, s)
}
if err != nil {
continue
}
if !haveEarliest || t.Before(earliest) {
earliest = t
earliestStr = s
haveEarliest = true
}
}
if haveEarliest {
exp = &earliestStr
}
for _, r := range rows {
if r.u.MaxTCPConns != nil {
v := *r.u.MaxTCPConns
if maxTCP == nil || v < *maxTCP {
maxTCP = new(uint64)
*maxTCP = v
}
}
}
for _, r := range rows {
if r.u.DataQuotaBytes != nil {
v := *r.u.DataQuotaBytes
if quota == nil || v < *quota {
quota = new(uint64)
*quota = v
}
}
}
for _, r := range rows {
if r.u.MaxUniqueIPs != nil {
v := *r.u.MaxUniqueIPs
if maxUip == nil || v < *maxUip {
maxUip = new(uint64)
*maxUip = v
}
}
}
return userAdTag, maxTCP, exp, quota, maxUip
}
// BuildSummary computes fleet totals and top users.
func BuildSummary(results []ServerFetchResult, topN int) SummaryData {
if topN <= 0 {