feat(api): make device rate limits, OPDS page size, and conversion cache configurable
Move three more hardcoded values behind the settings registry. All apply immediately on the next request (no restart needed). device_auth.go: - DeviceAuthMiddleware reads per-route device rate limits (sync / progress / metadata per minute) from the registry on each authenticated request via a rateLimitConfig() helper, falling back to the Default* constants when no registry is wired. - The X-RateLimit-Limit response header previously hardcoded "60" for every request type; it now reflects the actual configured limit for the request type via rateLimitForRequestType(). opds.go: - Default (50) and maximum (200) OPDS page sizes come from the registry's OpdsDefaultPageSize()/OpdsMaxPageSize() instead of inline literals, so catalog pagination can be tuned without a redeploy. conversion_service.go: - The 24h kepub cache lifetime is read from the registry via a cacheTTL() helper (was a bare 24 * time.Hour literal in the constructor). The field default is retained for tests that construct the service directly. - conversion_service_test.go updated to assert both the field default and the cacheTTL() accessor return 24h.
This commit is contained in:
@@ -25,6 +25,7 @@ type DeviceContext struct {
|
||||
type DeviceAuthMiddleware struct {
|
||||
db *database.Queries
|
||||
rateLimiter *DeviceRateLimiter
|
||||
settings *database.SettingsRegistry
|
||||
}
|
||||
|
||||
func NewDeviceAuthMiddleware(db *database.Queries) *DeviceAuthMiddleware {
|
||||
@@ -34,6 +35,41 @@ func NewDeviceAuthMiddleware(db *database.Queries) *DeviceAuthMiddleware {
|
||||
}
|
||||
}
|
||||
|
||||
// SetSettings wires the tunable settings registry so device rate limits are
|
||||
// read live on each authenticated request.
|
||||
func (m *DeviceAuthMiddleware) SetSettings(s *database.SettingsRegistry) { m.settings = s }
|
||||
|
||||
// rateLimitConfig returns the active device rate limits from the registry, or
|
||||
// the historical defaults when no registry is wired.
|
||||
func (m *DeviceAuthMiddleware) rateLimitConfig() DeviceRateLimitConfig {
|
||||
if m.settings != nil {
|
||||
dl := m.settings.DeviceRateLimits()
|
||||
return DeviceRateLimitConfig{
|
||||
SyncRequestsPerMinute: dl.Sync,
|
||||
ProgressUpdatesPerMinute: dl.Progress,
|
||||
MetadataRequestsPerMinute: dl.Metadata,
|
||||
}
|
||||
}
|
||||
return DeviceRateLimitConfig{
|
||||
SyncRequestsPerMinute: DefaultSyncRequestsPerMinute,
|
||||
ProgressUpdatesPerMinute: DefaultProgressUpdatesPerMinute,
|
||||
MetadataRequestsPerMinute: DefaultMetadataRequestsPerMinute,
|
||||
}
|
||||
}
|
||||
|
||||
// rateLimitForRequestType returns the configured per-minute limit for a given
|
||||
// request type, for use in X-RateLimit-* headers.
|
||||
func (m *DeviceAuthMiddleware) rateLimitForRequestType(requestType string, config DeviceRateLimitConfig) int {
|
||||
switch requestType {
|
||||
case "progress":
|
||||
return config.ProgressUpdatesPerMinute
|
||||
case "metadata":
|
||||
return config.MetadataRequestsPerMinute
|
||||
default: // "sync" and any unknown type
|
||||
return config.SyncRequestsPerMinute
|
||||
}
|
||||
}
|
||||
|
||||
func (m *DeviceAuthMiddleware) Authenticate(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c *echo.Context) error {
|
||||
var device database.Devices
|
||||
@@ -115,15 +151,12 @@ func (m *DeviceAuthMiddleware) Authenticate(next echo.HandlerFunc) echo.HandlerF
|
||||
deviceUUID := uuid.UUID(device.ID.Bytes)
|
||||
deviceID := deviceUUID.String()
|
||||
|
||||
config := DeviceRateLimitConfig{
|
||||
SyncRequestsPerMinute: 60,
|
||||
ProgressUpdatesPerMinute: 120,
|
||||
MetadataRequestsPerMinute: 30,
|
||||
}
|
||||
config := m.rateLimitConfig()
|
||||
limitForType := m.rateLimitForRequestType(requestType, config)
|
||||
|
||||
if !m.rateLimiter.CheckRateLimit(deviceID, requestType, config) {
|
||||
remaining := m.rateLimiter.GetRemainingRequests(deviceID, requestType, config)
|
||||
c.Response().Header().Set("X-RateLimit-Limit", "60")
|
||||
c.Response().Header().Set("X-RateLimit-Limit", strconv.Itoa(limitForType))
|
||||
c.Response().Header().Set("X-RateLimit-Remaining", strconv.Itoa(remaining))
|
||||
c.Response().Header().Set("X-RateLimit-Reset", "60")
|
||||
return c.JSON(http.StatusTooManyRequests, map[string]string{
|
||||
@@ -134,7 +167,7 @@ func (m *DeviceAuthMiddleware) Authenticate(next echo.HandlerFunc) echo.HandlerF
|
||||
}
|
||||
|
||||
remaining := m.rateLimiter.GetRemainingRequests(deviceID, requestType, config)
|
||||
c.Response().Header().Set("X-RateLimit-Limit", "60")
|
||||
c.Response().Header().Set("X-RateLimit-Limit", strconv.Itoa(limitForType))
|
||||
c.Response().Header().Set("X-RateLimit-Remaining", strconv.Itoa(remaining))
|
||||
|
||||
ctx := DeviceContext{
|
||||
|
||||
Reference in New Issue
Block a user