init push

This commit is contained in:
2026-07-16 22:16:45 +07:00
commit 8b068bdb10
1021 changed files with 332816 additions and 0 deletions

638
internal/config/loader.go Normal file
View File

@@ -0,0 +1,638 @@
package config
import (
"encoding/json"
"fmt"
"os"
"strings"
"time"
)
// Duration unmarshals a Go duration string (e.g. "30s", "5m", "150ms") from JSON.
// An empty string is treated as zero duration.
type Duration struct{ time.Duration }
func (d *Duration) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
if s == "" {
d.Duration = 0
return nil
}
dur, err := time.ParseDuration(s)
if err != nil {
return fmt.Errorf("invalid duration %q: %w", s, err)
}
d.Duration = dur
return nil
}
// AppConfig is the unified config loaded from a JSON file.
type AppConfig struct {
HTTPPort int
MySQL MySQLConfig
Logging LoggingConfig
Audit AuditConfig
Auth AuthConfig
SES SESConfig
Email EmailDeliveryConfig
MicrosoftSSO MicrosoftSSOConfig
HTTP HTTPConfig
Resilience ResilienceConfig
Queue QueueConfig
FileQueue FileQueueConfig
SQS SQSConfig
FileSQS SQSConfig
FileStorage FileStorageConfig
FileUpload FileManagerUploadPolicyConfig
SAML SAMLConfig
WOPI WOPIConfig
Gotenberg GotenbergConfig
}
// LoadFromFile reads path, parses it as JSON, and returns a populated AppConfig.
// The file is typically produced by scripts/inject-secrets.sh.
func LoadFromFile(path string) (AppConfig, error) {
data, err := os.ReadFile(path)
if err != nil {
return AppConfig{}, fmt.Errorf("config: read %q: %w", path, err)
}
var raw jsonAppConfig
if err := json.Unmarshal(data, &raw); err != nil {
return AppConfig{}, fmt.Errorf("config: parse %q: %w", path, err)
}
return raw.toAppConfig(), nil
}
// ── JSON intermediate types ───────────────────────────────────────────────────
type jsonCircuitBreakerPolicy struct {
Enabled bool `json:"enabled"`
MaxRequests uint32 `json:"maxRequests"`
Interval Duration `json:"interval"`
BucketPeriod Duration `json:"bucketPeriod"`
Timeout Duration `json:"timeout"`
MinRequests uint32 `json:"minRequests"`
FailureRatio float64 `json:"failureRatio"`
ConsecutiveFailures uint32 `json:"consecutiveFailures"`
}
func (j jsonCircuitBreakerPolicy) toPolicy() CircuitBreakerPolicy {
return CircuitBreakerPolicy{
Enabled: j.Enabled,
MaxRequests: j.MaxRequests,
Interval: j.Interval.Duration,
BucketPeriod: j.BucketPeriod.Duration,
Timeout: j.Timeout.Duration,
MinRequests: j.MinRequests,
FailureRatio: j.FailureRatio,
ConsecutiveFailures: j.ConsecutiveFailures,
}
}
type jsonWorker struct {
Concurrency int `json:"concurrency"`
MaxBatchSize int `json:"maxBatchSize"`
ProcessTimeout Duration `json:"processTimeout"`
ShutdownTimeout Duration `json:"shutdownTimeout"`
BackoffMin Duration `json:"backoffMin"`
BackoffMax Duration `json:"backoffMax"`
PermanentFailureDelay Duration `json:"permanentFailureDelay"`
MonitorAddr string `json:"monitorAddr"`
HealthErrorThreshold int `json:"healthErrorThreshold"`
DepthPollInterval Duration `json:"depthPollInterval"`
}
type jsonOutbox struct {
Enabled bool `json:"enabled"`
PollInterval Duration `json:"pollInterval"`
BatchSize int `json:"batchSize"`
DispatchTimeout Duration `json:"dispatchTimeout"`
LockTTL Duration `json:"lockTTL"`
MaxAttempts int `json:"maxAttempts"`
}
type jsonSQS struct {
Region string `json:"region"`
Endpoint string `json:"endpoint"`
AllowInsecureEndpoint bool `json:"allowInsecureEndpoint"`
QueueURL string `json:"queueUrl"`
QueueType string `json:"queueType"`
MessageGroupID string `json:"messageGroupId"`
MaxMessages int `json:"maxMessages"`
WaitTimeSeconds int32 `json:"waitTimeSeconds"`
VisibilityTimeout Duration `json:"visibilityTimeout"`
}
func (j jsonSQS) toSQSConfig(regionFallback, endpointFallback, accessKeyID, secretAccessKey string) SQSConfig {
region := j.Region
if region == "" {
region = regionFallback
}
endpoint := j.Endpoint
if endpoint == "" {
endpoint = endpointFallback
}
return SQSConfig{
Region: region,
AccessKeyID: strings.TrimSpace(accessKeyID),
SecretAccessKey: strings.TrimSpace(secretAccessKey),
Endpoint: endpoint,
QueueURL: j.QueueURL,
QueueType: j.QueueType,
MessageGroupID: j.MessageGroupID,
MaxMessages: j.MaxMessages,
WaitTimeSeconds: j.WaitTimeSeconds,
VisibilityTimeout: j.VisibilityTimeout.Duration,
AllowInsecureEndpoint: j.AllowInsecureEndpoint,
}
}
type jsonAppConfig struct {
HTTP struct {
Port int `json:"port"`
BodyLimit int `json:"bodyLimit"`
Timeouts struct {
Read Duration `json:"read"`
Write Duration `json:"write"`
Idle Duration `json:"idle"`
Request Duration `json:"request"`
RequestOverrides string `json:"requestOverrides"`
Shutdown Duration `json:"shutdown"`
} `json:"timeouts"`
CORS struct {
AllowOrigins string `json:"allowOrigins"`
AllowMethods string `json:"allowMethods"`
AllowHeaders string `json:"allowHeaders"`
ExposeHeaders string `json:"exposeHeaders"`
AllowCredentials bool `json:"allowCredentials"`
MaxAge int `json:"maxAge"`
} `json:"cors"`
RateLimit struct {
Max int `json:"max"`
Window Duration `json:"window"`
Endpoints string `json:"endpoints"`
} `json:"rateLimit"`
Security struct {
HSTSMaxAge int `json:"hstsMaxAge"`
} `json:"security"`
} `json:"http"`
Database struct {
MySQL struct {
DSN string `json:"dsn"`
MaxOpenConns int `json:"maxOpenConns"`
MaxIdleConns int `json:"maxIdleConns"`
ConnMaxLifetime Duration `json:"connMaxLifetime"`
ConnMaxIdleTime Duration `json:"connMaxIdleTime"`
} `json:"mysql"`
} `json:"database"`
Logging struct {
Level string `json:"level"`
Format string `json:"format"`
} `json:"logging"`
Audit struct {
QueueSize int `json:"queueSize"`
Workers int `json:"workers"`
} `json:"audit"`
Email struct {
Provider string `json:"provider"`
MockDir string `json:"mockDir"`
} `json:"email"`
CircuitBreaker struct {
Default jsonCircuitBreakerPolicy `json:"default"`
MicrosoftSSO jsonCircuitBreakerPolicy `json:"microsoftSSO"`
SES jsonCircuitBreakerPolicy `json:"ses"`
SQS jsonCircuitBreakerPolicy `json:"sqs"`
} `json:"circuitBreaker"`
Auth struct {
PublicBaseURL string `json:"publicBaseURL"`
PasswordPepper string `json:"passwordPepper"`
IPEncryptionKey string `json:"ipEncryptionKey"`
DefaultRole string `json:"defaultRole"`
DisableRegister bool `json:"disableRegister"`
Hash struct {
Time uint32 `json:"time"`
MemoryKB uint32 `json:"memoryKB"`
Threads uint8 `json:"threads"`
KeyLen uint32 `json:"keyLen"`
} `json:"hash"`
Email struct {
VerifyTTL Duration `json:"verifyTTL"`
VerifyURL string `json:"verifyURL"`
InviteTTL Duration `json:"inviteTTL"`
} `json:"email"`
Password struct {
SetURL string `json:"setURL"`
ResetTTL Duration `json:"resetTTL"`
ResetURL string `json:"resetURL"`
ForgotEmailCooldown Duration `json:"forgotEmailCooldown"`
ForgotMinDuration Duration `json:"forgotMinDuration"`
ForgotIPRateMax int `json:"forgotIPRateMax"`
ForgotIPRateWindow Duration `json:"forgotIPRateWindow"`
LoginIPRateMax int `json:"loginIPRateMax"`
LoginIPRateWindow Duration `json:"loginIPRateWindow"`
} `json:"password"`
SecurityPin struct {
ResetTTL Duration `json:"resetTTL"`
ResetURL string `json:"resetURL"`
ForgotEmailCooldown Duration `json:"forgotEmailCooldown"`
ForgotMinDuration Duration `json:"forgotMinDuration"`
MaxAttempts int `json:"maxAttempts"`
LockDuration Duration `json:"lockDuration"`
ActionTokenTTL Duration `json:"actionTokenTTL"`
} `json:"securityPin"`
JWT struct {
AccessSecret string `json:"accessSecret"`
RefreshSecret string `json:"refreshSecret"`
AccessTTL Duration `json:"accessTTL"`
RefreshTTL Duration `json:"refreshTTL"`
RefreshTotpTTL Duration `json:"refreshTotpTTL"`
LocalRefreshTTL Duration `json:"localRefreshTTL"`
SessionIdleTTL Duration `json:"sessionIdleTTL"`
Cookie struct {
Domain string `json:"domain"`
Secure bool `json:"secure"`
SameSite string `json:"sameSite"`
AccessName string `json:"accessName"`
RefreshName string `json:"refreshName"`
} `json:"cookie"`
} `json:"jwt"`
TOTP struct {
ChallengeTTL Duration `json:"challengeTTL"`
} `json:"totp"`
SSO struct {
StateTTL Duration `json:"stateTTL"`
SuccessRedirect string `json:"successRedirect"`
} `json:"sso"`
WebAuthn struct {
RPID string `json:"rpId"`
RPDisplayName string `json:"rpDisplayName"`
RPOrigins string `json:"rpOrigins"`
ChallengeTTL Duration `json:"challengeTTL"`
} `json:"webauthn"`
} `json:"auth"`
TOTP struct {
Issuer string `json:"issuer"`
SecretKey string `json:"secretKey"`
} `json:"totp"`
MicrosoftSSO struct {
TenantID string `json:"tenantId"`
ClientID string `json:"clientId"`
ClientSecret string `json:"clientSecret"`
RedirectURL string `json:"redirectUrl"`
Authority string `json:"authority"`
Scopes string `json:"scopes"`
} `json:"microsoftSSO"`
SAML struct {
CertFile string `json:"certFile"`
KeyFile string `json:"keyFile"`
MetadataFile string `json:"metadataFile"`
RootURL string `json:"rootUrl"`
FrontendURL string `json:"frontendUrl"`
CookieDomain string `json:"cookieDomain"`
CookieSecure bool `json:"cookieSecure"`
SessionTTLHr int `json:"sessionTTLHour"`
} `json:"saml"`
Queue struct {
MessageSchemaVersion string `json:"messageSchemaVersion"`
EnableLegacyPayloadFallback bool `json:"enableLegacyPayloadFallback"`
IdempotencyKeyPrefix string `json:"idempotencyKeyPrefix"`
IdempotencyTTL Duration `json:"idempotencyTTL"`
ProcessingTTL Duration `json:"processingTTL"`
Worker jsonWorker `json:"worker"`
Outbox jsonOutbox `json:"outbox"`
} `json:"queue"`
FileQueue struct {
MessageSchemaVersion string `json:"messageSchemaVersion"`
IdempotencyKeyPrefix string `json:"idempotencyKeyPrefix"`
IdempotencyTTL Duration `json:"idempotencyTTL"`
ProcessingTTL Duration `json:"processingTTL"`
Worker jsonWorker `json:"worker"`
Outbox jsonOutbox `json:"outbox"`
} `json:"fileQueue"`
AWS struct {
Region string `json:"region"`
AccessKeyID string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
SQS jsonSQS `json:"sqs"`
FileSQS jsonSQS `json:"fileSqs"`
SES struct {
Region string `json:"region"`
Endpoint string `json:"endpoint"`
From string `json:"from"`
ConfigurationSet string `json:"configurationSet"`
SendTimeout Duration `json:"sendTimeout"`
AllowInsecureEndpoint bool `json:"allowInsecureEndpoint"`
} `json:"ses"`
} `json:"aws"`
File struct {
Provider string `json:"provider"`
S3 struct {
Bucket string `json:"bucket"`
Region string `json:"region"`
Endpoint string `json:"endpoint"`
ForcePathStyle bool `json:"forcePathStyle"`
AllowInsecureEndpoint bool `json:"allowInsecureEndpoint"`
AutoCreateBucket bool `json:"autoCreateBucket"`
ObjectKeyPrefix string `json:"objectKeyPrefix"`
PresignPutTTL Duration `json:"presignPutTTL"`
PresignGetTTL Duration `json:"presignGetTTL"`
} `json:"s3"`
Upload struct {
MaxSize int64 `json:"maxSize"`
AllowedMimeTypes string `json:"allowedMimeTypes"`
AllowedExtensions string `json:"allowedExtensions"`
AllowEmptyFile bool `json:"allowEmptyFile"`
MaxFilenameLength int `json:"maxFilenameLength"`
} `json:"upload"`
} `json:"file"`
WOPI struct {
TokenSecret string `json:"tokenSecret"`
ProofSecret string `json:"proofSecret"`
DiscoveryURL string `json:"discoveryUrl"`
PublicBaseURL string `json:"publicBaseUrl"`
EditActionURL string `json:"editActionUrl"`
TokenTTL Duration `json:"tokenTTL"`
MaxBodyBytes int `json:"maxBodyBytes"`
RequireProof bool `json:"requireProof"`
} `json:"wopi"`
Gotenberg struct {
URL string `json:"url"`
} `json:"gotenberg"`
}
func (r *jsonAppConfig) toAppConfig() AppConfig {
// IPEncryptionKey falls back to PasswordPepper when not set (mirrors env loader behaviour)
ipKey := r.Auth.IPEncryptionKey
if ipKey == "" {
ipKey = r.Auth.PasswordPepper
}
// RefreshTotpTTL falls back to RefreshTTL when not set
refreshTotpTTL := r.Auth.JWT.RefreshTotpTTL.Duration
if refreshTotpTTL == 0 {
refreshTotpTTL = r.Auth.JWT.RefreshTTL.Duration
}
// SES region falls back to aws.region
sesRegion := r.AWS.SES.Region
if sesRegion == "" {
sesRegion = r.AWS.Region
}
return AppConfig{
HTTPPort: r.HTTP.Port,
MySQL: MySQLConfig{
DSN: r.Database.MySQL.DSN,
MaxOpenConns: r.Database.MySQL.MaxOpenConns,
MaxIdleConns: r.Database.MySQL.MaxIdleConns,
ConnMaxLifetime: r.Database.MySQL.ConnMaxLifetime.Duration,
ConnMaxIdleTime: r.Database.MySQL.ConnMaxIdleTime.Duration,
},
Logging: LoggingConfig{
Level: r.Logging.Level,
Format: r.Logging.Format,
},
Audit: AuditConfig{
QueueSize: r.Audit.QueueSize,
Workers: r.Audit.Workers,
},
Email: EmailDeliveryConfig{
Provider: strings.ToLower(strings.TrimSpace(r.Email.Provider)),
MockDir: strings.TrimSpace(r.Email.MockDir),
},
SES: SESConfig{
Region: sesRegion,
Endpoint: r.AWS.SES.Endpoint,
From: r.AWS.SES.From,
ConfigurationSetName: r.AWS.SES.ConfigurationSet,
SendTimeout: r.AWS.SES.SendTimeout.Duration,
AllowInsecureEndpoint: r.AWS.SES.AllowInsecureEndpoint,
},
Auth: AuthConfig{
PublicBaseURL: r.Auth.PublicBaseURL,
PasswordPepper: r.Auth.PasswordPepper,
IPEncryptionKey: ipKey,
HashTime: r.Auth.Hash.Time,
HashMemoryKB: r.Auth.Hash.MemoryKB,
HashThreads: r.Auth.Hash.Threads,
HashKeyLen: r.Auth.Hash.KeyLen,
EmailVerifyTTL: r.Auth.Email.VerifyTTL.Duration,
EmailVerifyURL: r.Auth.Email.VerifyURL,
InviteTTL: r.Auth.Email.InviteTTL.Duration,
SetPasswordURL: r.Auth.Password.SetURL,
PasswordResetTTL: r.Auth.Password.ResetTTL.Duration,
PasswordResetURL: r.Auth.Password.ResetURL,
SecurityPINResetTTL: r.Auth.SecurityPin.ResetTTL.Duration,
SecurityPINResetURL: r.Auth.SecurityPin.ResetURL,
ForgotSecurityPINCooldown: r.Auth.SecurityPin.ForgotEmailCooldown.Duration,
ForgotSecurityPINMinDuration: r.Auth.SecurityPin.ForgotMinDuration.Duration,
SecurityPINMaxAttempts: r.Auth.SecurityPin.MaxAttempts,
SecurityPINLockDuration: r.Auth.SecurityPin.LockDuration.Duration,
SecurityPINActionTokenTTL: r.Auth.SecurityPin.ActionTokenTTL.Duration,
ForgotPasswordEmailCooldown: r.Auth.Password.ForgotEmailCooldown.Duration,
ForgotPasswordMinDuration: r.Auth.Password.ForgotMinDuration.Duration,
ForgotPasswordIPRateMax: r.Auth.Password.ForgotIPRateMax,
ForgotPasswordIPRateWindow: r.Auth.Password.ForgotIPRateWindow.Duration,
LoginIPRateMax: r.Auth.Password.LoginIPRateMax,
LoginIPRateWindow: r.Auth.Password.LoginIPRateWindow.Duration,
TOTPChallengeTTL: r.Auth.TOTP.ChallengeTTL.Duration,
SSOStateTTL: r.Auth.SSO.StateTTL.Duration,
TOTPIssuer: r.TOTP.Issuer,
TOTPSecretKeyB64: r.TOTP.SecretKey,
JWTAccessSecret: r.Auth.JWT.AccessSecret,
JWTRefreshSecret: r.Auth.JWT.RefreshSecret,
JWTAccessTTL: r.Auth.JWT.AccessTTL.Duration,
JWTRefreshTTL: r.Auth.JWT.RefreshTTL.Duration,
JWTRefreshTOTPTTL: refreshTotpTTL,
JWTLocalRefreshTTL: r.Auth.JWT.LocalRefreshTTL.Duration,
SessionIdleTTL: r.Auth.JWT.SessionIdleTTL.Duration,
JWTCookieDomain: r.Auth.JWT.Cookie.Domain,
JWTCookieSecure: r.Auth.JWT.Cookie.Secure,
JWTCookieSameSite: r.Auth.JWT.Cookie.SameSite,
JWTAccessCookieName: r.Auth.JWT.Cookie.AccessName,
JWTRefreshCookieName: r.Auth.JWT.Cookie.RefreshName,
WebAuthnRPID: r.Auth.WebAuthn.RPID,
WebAuthnRPDisplayName: r.Auth.WebAuthn.RPDisplayName,
WebAuthnRPOrigins: splitCSV(r.Auth.WebAuthn.RPOrigins),
WebAuthnChallengeTTL: r.Auth.WebAuthn.ChallengeTTL.Duration,
SSOSuccessRedirectURL: r.Auth.SSO.SuccessRedirect,
DefaultRoleName: r.Auth.DefaultRole,
DisableRegister: r.Auth.DisableRegister,
},
MicrosoftSSO: MicrosoftSSOConfig{
TenantID: r.MicrosoftSSO.TenantID,
ClientID: r.MicrosoftSSO.ClientID,
ClientSecret: r.MicrosoftSSO.ClientSecret,
RedirectURL: r.MicrosoftSSO.RedirectURL,
Authority: r.MicrosoftSSO.Authority,
Scopes: splitCSV(r.MicrosoftSSO.Scopes),
},
SAML: SAMLConfig{
CertFile: strings.TrimSpace(r.SAML.CertFile),
KeyFile: strings.TrimSpace(r.SAML.KeyFile),
MetadataFile: strings.TrimSpace(r.SAML.MetadataFile),
RootURL: strings.TrimSpace(r.SAML.RootURL),
FrontendURL: strings.TrimSpace(r.SAML.FrontendURL),
CookieDomain: strings.TrimSpace(r.SAML.CookieDomain),
CookieSecure: r.SAML.CookieSecure,
SessionTTLHr: r.SAML.SessionTTLHr,
},
HTTP: HTTPConfig{
BodyLimit: r.HTTP.BodyLimit,
ReadTimeout: r.HTTP.Timeouts.Read.Duration,
WriteTimeout: r.HTTP.Timeouts.Write.Duration,
IdleTimeout: r.HTTP.Timeouts.Idle.Duration,
RequestTimeout: r.HTTP.Timeouts.Request.Duration,
RequestTimeoutByPath: parseHTTPTimeoutOverrides(r.HTTP.Timeouts.RequestOverrides),
ShutdownTimeout: r.HTTP.Timeouts.Shutdown.Duration,
CORSAllowOrigins: r.HTTP.CORS.AllowOrigins,
CORSAllowMethods: r.HTTP.CORS.AllowMethods,
CORSAllowHeaders: r.HTTP.CORS.AllowHeaders,
CORSExposeHeaders: r.HTTP.CORS.ExposeHeaders,
CORSAllowCredentials: r.HTTP.CORS.AllowCredentials,
CORSMaxAge: r.HTTP.CORS.MaxAge,
RateLimitMax: r.HTTP.RateLimit.Max,
RateLimitWindow: r.HTTP.RateLimit.Window.Duration,
RateLimitByEndpoint: parseHTTPEndpointRateLimitOverrides(r.HTTP.RateLimit.Endpoints),
HSTSMaxAge: r.HTTP.Security.HSTSMaxAge,
},
Resilience: ResilienceConfig{
Default: r.CircuitBreaker.Default.toPolicy(),
MicrosoftSSO: r.CircuitBreaker.MicrosoftSSO.toPolicy(),
SES: r.CircuitBreaker.SES.toPolicy(),
SQS: r.CircuitBreaker.SQS.toPolicy(),
},
Queue: QueueConfig{
MessageSchemaVersion: r.Queue.MessageSchemaVersion,
EnableLegacyPayloadFallback: r.Queue.EnableLegacyPayloadFallback,
IdempotencyKeyPrefix: r.Queue.IdempotencyKeyPrefix,
IdempotencyTTL: r.Queue.IdempotencyTTL.Duration,
ProcessingTTL: r.Queue.ProcessingTTL.Duration,
WorkerConcurrency: r.Queue.Worker.Concurrency,
WorkerMaxBatchSize: r.Queue.Worker.MaxBatchSize,
WorkerProcessTimeout: r.Queue.Worker.ProcessTimeout.Duration,
WorkerShutdownTimeout: r.Queue.Worker.ShutdownTimeout.Duration,
WorkerBackoffMin: r.Queue.Worker.BackoffMin.Duration,
WorkerBackoffMax: r.Queue.Worker.BackoffMax.Duration,
WorkerPermanentFailureDelay: r.Queue.Worker.PermanentFailureDelay.Duration,
WorkerMonitorAddr: r.Queue.Worker.MonitorAddr,
WorkerHealthErrorThreshold: r.Queue.Worker.HealthErrorThreshold,
WorkerDepthPollInterval: r.Queue.Worker.DepthPollInterval.Duration,
OutboxEnabled: r.Queue.Outbox.Enabled,
OutboxPollInterval: r.Queue.Outbox.PollInterval.Duration,
OutboxBatchSize: r.Queue.Outbox.BatchSize,
OutboxDispatchTimeout: r.Queue.Outbox.DispatchTimeout.Duration,
OutboxLockTTL: r.Queue.Outbox.LockTTL.Duration,
OutboxMaxAttempts: r.Queue.Outbox.MaxAttempts,
},
FileQueue: FileQueueConfig{
MessageSchemaVersion: r.FileQueue.MessageSchemaVersion,
IdempotencyKeyPrefix: r.FileQueue.IdempotencyKeyPrefix,
IdempotencyTTL: r.FileQueue.IdempotencyTTL.Duration,
ProcessingTTL: r.FileQueue.ProcessingTTL.Duration,
WorkerConcurrency: r.FileQueue.Worker.Concurrency,
WorkerMaxBatchSize: r.FileQueue.Worker.MaxBatchSize,
WorkerProcessTimeout: r.FileQueue.Worker.ProcessTimeout.Duration,
WorkerShutdownTimeout: r.FileQueue.Worker.ShutdownTimeout.Duration,
WorkerBackoffMin: r.FileQueue.Worker.BackoffMin.Duration,
WorkerBackoffMax: r.FileQueue.Worker.BackoffMax.Duration,
WorkerPermanentFailureDelay: r.FileQueue.Worker.PermanentFailureDelay.Duration,
WorkerMonitorAddr: r.FileQueue.Worker.MonitorAddr,
WorkerHealthErrorThreshold: r.FileQueue.Worker.HealthErrorThreshold,
WorkerDepthPollInterval: r.FileQueue.Worker.DepthPollInterval.Duration,
OutboxEnabled: r.FileQueue.Outbox.Enabled,
OutboxPollInterval: r.FileQueue.Outbox.PollInterval.Duration,
OutboxBatchSize: r.FileQueue.Outbox.BatchSize,
OutboxDispatchTimeout: r.FileQueue.Outbox.DispatchTimeout.Duration,
OutboxLockTTL: r.FileQueue.Outbox.LockTTL.Duration,
OutboxMaxAttempts: r.FileQueue.Outbox.MaxAttempts,
},
// SQS email queue: region falls back to aws.region
SQS: r.AWS.SQS.toSQSConfig(r.AWS.Region, "", r.AWS.AccessKeyID, r.AWS.SecretAccessKey),
// FileSQS: region + endpoint fall back to aws.region / aws.sqs.endpoint
FileSQS: r.AWS.FileSQS.toSQSConfig(r.AWS.Region, r.AWS.SQS.Endpoint, r.AWS.AccessKeyID, r.AWS.SecretAccessKey),
FileStorage: FileStorageConfig{
Provider: strings.ToLower(strings.TrimSpace(r.File.Provider)),
Bucket: strings.TrimSpace(r.File.S3.Bucket),
Region: strings.TrimSpace(r.File.S3.Region),
Endpoint: strings.TrimSpace(r.File.S3.Endpoint),
AccessKeyID: strings.TrimSpace(r.AWS.AccessKeyID),
SecretAccessKey: strings.TrimSpace(r.AWS.SecretAccessKey),
ForcePathStyle: r.File.S3.ForcePathStyle,
AllowInsecureEndpoint: r.File.S3.AllowInsecureEndpoint,
AutoCreateBucket: r.File.S3.AutoCreateBucket,
ObjectKeyPrefix: strings.TrimSpace(r.File.S3.ObjectKeyPrefix),
PresignPutTTL: r.File.S3.PresignPutTTL.Duration,
PresignGetTTL: r.File.S3.PresignGetTTL.Duration,
},
FileUpload: FileManagerUploadPolicyConfig{
MaxFileSizeBytes: r.File.Upload.MaxSize,
AllowedMimeTypes: normalizeMimeTypes(splitCSV(r.File.Upload.AllowedMimeTypes)),
AllowedExtensions: normalizeExtensions(splitCSV(r.File.Upload.AllowedExtensions)),
AllowEmptyFile: r.File.Upload.AllowEmptyFile,
MaxFilenameLength: r.File.Upload.MaxFilenameLength,
},
WOPI: WOPIConfig{
TokenSecret: strings.TrimSpace(r.WOPI.TokenSecret),
ProofSecret: strings.TrimSpace(r.WOPI.ProofSecret),
DiscoveryURL: strings.TrimSpace(r.WOPI.DiscoveryURL),
PublicBaseURL: strings.TrimSpace(r.WOPI.PublicBaseURL),
EditActionURL: strings.TrimSpace(r.WOPI.EditActionURL),
TokenTTL: r.WOPI.TokenTTL.Duration,
MaxBodyBytes: r.WOPI.MaxBodyBytes,
RequireProof: r.WOPI.RequireProof,
},
Gotenberg: GotenbergConfig{
URL: strings.TrimSpace(r.Gotenberg.URL),
},
}
}
func splitCSV(s string) []string {
s = strings.TrimSpace(s)
if s == "" {
return nil
}
parts := strings.Split(s, ",")
out := make([]string, 0, len(parts))
for _, p := range parts {
if p = strings.TrimSpace(p); p != "" {
out = append(out, p)
}
}
return out
}