feat: add desktop bug report collection

This commit is contained in:
2026-05-31 21:55:29 +08:00
parent 0a9dcec70f
commit e490a267ff
27 changed files with 2938 additions and 3 deletions
@@ -0,0 +1,69 @@
package middleware
import (
"sync"
"time"
"github.com/gin-gonic/gin"
"github.com/geo-platform/tenant-api/internal/shared/response"
)
type rateLimitEntry struct {
windowStart time.Time
count int
}
type inMemoryRateLimiter struct {
mu sync.Mutex
entries map[string]rateLimitEntry
max int
window time.Duration
lastSweep time.Time
}
func RateLimitByClientIP(max int, window time.Duration, code int, message, detail string) gin.HandlerFunc {
limiter := &inMemoryRateLimiter{
entries: make(map[string]rateLimitEntry),
max: max,
window: window,
}
return func(c *gin.Context) {
if !limiter.allow(c.ClientIP(), time.Now()) {
response.Error(c, response.ErrTooManyRequests(code, message, detail))
c.Abort()
return
}
c.Next()
}
}
func (l *inMemoryRateLimiter) allow(key string, now time.Time) bool {
if l == nil || l.max <= 0 || l.window <= 0 || key == "" {
return true
}
l.mu.Lock()
defer l.mu.Unlock()
if l.lastSweep.IsZero() || now.Sub(l.lastSweep) >= l.window {
l.lastSweep = now
for entryKey, entry := range l.entries {
if now.Sub(entry.windowStart) >= l.window {
delete(l.entries, entryKey)
}
}
}
entry, ok := l.entries[key]
if !ok || now.Sub(entry.windowStart) >= l.window {
l.entries[key] = rateLimitEntry{windowStart: now, count: 1}
return true
}
if entry.count >= l.max {
return false
}
entry.count++
l.entries[key] = entry
return true
}
@@ -0,0 +1,38 @@
package middleware
import (
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/gin-gonic/gin"
)
func TestRateLimitByClientIPBlocksAfterLimit(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.New()
router.GET(
"/limited",
RateLimitByClientIP(2, time.Minute, 42961, "rate_limited", "too many requests"),
func(c *gin.Context) {
c.Status(http.StatusNoContent)
},
)
for i := 0; i < 2; i++ {
req := httptest.NewRequest(http.MethodGet, "/limited", nil)
res := httptest.NewRecorder()
router.ServeHTTP(res, req)
if res.Code != http.StatusNoContent {
t.Fatalf("request %d status = %d, want %d", i+1, res.Code, http.StatusNoContent)
}
}
req := httptest.NewRequest(http.MethodGet, "/limited", nil)
res := httptest.NewRecorder()
router.ServeHTTP(res, req)
if res.Code != http.StatusTooManyRequests {
t.Fatalf("third request status = %d, want %d", res.Code, http.StatusTooManyRequests)
}
}