mirror of
https://github.com/mmahdium/TGSS.git
synced 2026-08-12 08:32:48 +03:30
- Created a Dockerfile to build and deploy the application using a multi-stage build process. - Added a GitHub Actions workflow to automatically build and publish the Docker image to GitHub Container Registry on pushes to the main branch. - Updated go.mod and go.sum to reflect dependency upgrades and changes. - Modified the RSS generator to extend the photo expiration time from 10 minutes to 12 hours. - Updated rate limiter configuration to allow for environment variable customization.
128 lines
2.7 KiB
Go
128 lines
2.7 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"go.uber.org/fx"
|
|
"go.uber.org/zap"
|
|
"golang.org/x/time/rate"
|
|
)
|
|
|
|
type ClientLimiter struct {
|
|
feedLimiter *rate.Limiter
|
|
imageLimiter *rate.Limiter
|
|
lastSeen time.Time
|
|
}
|
|
|
|
type RateLimiter struct {
|
|
logger *zap.Logger
|
|
clients map[string]*ClientLimiter
|
|
mu sync.Mutex
|
|
}
|
|
|
|
func NewRateLimiter(logger *zap.Logger) *RateLimiter {
|
|
return &RateLimiter{logger: logger, mu: sync.Mutex{}, clients: map[string]*ClientLimiter{}}
|
|
}
|
|
|
|
func (r *RateLimiter) getLimiter(ip string, kind string) *rate.Limiter {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
if c, ok := r.clients[ip]; ok {
|
|
c.lastSeen = time.Now()
|
|
if kind == "image" {
|
|
return c.imageLimiter
|
|
}
|
|
return c.feedLimiter
|
|
}
|
|
|
|
feedLim := rate.NewLimiter(rate.Every(3*time.Second), 5)
|
|
imageLim := rate.NewLimiter(rate.Every(5*time.Second), 4) // TODO: get from env
|
|
|
|
r.clients[ip] = &ClientLimiter{
|
|
feedLimiter: feedLim,
|
|
imageLimiter: imageLim,
|
|
lastSeen: time.Now(),
|
|
}
|
|
if kind == "image" {
|
|
return imageLim
|
|
}
|
|
return feedLim
|
|
}
|
|
|
|
func (r *RateLimiter) CleanupRateLimiter() {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
for ip, clientLimiter := range r.clients {
|
|
if time.Since(clientLimiter.lastSeen) > time.Hour { // Delete ip ratelimiters older that 1 hour
|
|
delete(r.clients, ip)
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
func RegisterRateLimiterCleanup(lc fx.Lifecycle, logger *zap.Logger, r *RateLimiter) {
|
|
var ticker *time.Ticker
|
|
var stopChan chan struct{}
|
|
|
|
lc.Append(fx.Hook{
|
|
OnStart: func(ctx context.Context) error {
|
|
logger.Info("Starting rate limiter cleanup task", zap.Duration("interval", 30*time.Minute))
|
|
ticker = time.NewTicker(30 * time.Minute)
|
|
stopChan = make(chan struct{})
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
logger.Info("Running rate limiter cleanup")
|
|
r.CleanupRateLimiter()
|
|
logger.Info("Rate limiter cleanup completed")
|
|
case <-stopChan:
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
return nil
|
|
},
|
|
OnStop: func(ctx context.Context) error {
|
|
logger.Info("Stopping rate limiter cleanup task")
|
|
if ticker != nil {
|
|
ticker.Stop()
|
|
}
|
|
if stopChan != nil {
|
|
close(stopChan)
|
|
}
|
|
return nil
|
|
},
|
|
})
|
|
}
|
|
|
|
func (r *RateLimiter) FeedRateLimit() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
ip := c.ClientIP()
|
|
lim := r.getLimiter(ip, "feed")
|
|
if !lim.Allow() {
|
|
c.AbortWithStatusJSON(429, gin.H{"error": "Slow down pls"})
|
|
return
|
|
}
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
func (r *RateLimiter) ImageRateLimit() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
ip := c.ClientIP()
|
|
lim := r.getLimiter(ip, "image")
|
|
if !lim.Allow() {
|
|
c.AbortWithStatusJSON(429, gin.H{"error": "Slow down pls, these arent your mothers nudes"})
|
|
return
|
|
}
|
|
c.Next()
|
|
}
|
|
}
|