refactor: move API key cleanup job to actor (#1687)

This commit is contained in:
Alessandro (Ale) Segala
2026-08-15 00:39:02 +02:00
committed by GitHub
parent 6fba970159
commit 2dc062ac0f
12 changed files with 303 additions and 147 deletions
+2 -2
View File
@@ -24,8 +24,8 @@ require (
github.com/go-webauthn/webauthn v0.17.4
github.com/golang-migrate/migrate/v4 v4.19.1
github.com/google/uuid v1.6.0
github.com/italypaleale/francis v0.1.0-beta.22
github.com/italypaleale/go-kit v0.0.0-20260806152440-491aff6b42b5
github.com/italypaleale/francis v0.1.0-beta.23
github.com/italypaleale/go-kit v0.0.0-20260810215935-944b377ddc2f
github.com/italypaleale/go-sql-utils v0.3.5
github.com/jackc/pgx/v5 v5.10.0
github.com/jinzhu/copier v0.4.0
+4 -8
View File
@@ -243,14 +243,10 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/italypaleale/francis v0.1.0-beta.21 h1:pYfPQVR9TIHihigrCmUiRYwHDfUoFHGoVQl5MUmysvg=
github.com/italypaleale/francis v0.1.0-beta.21/go.mod h1:Ii6GFfm7AOt8kwWVsza2Tlq2PX17ofq/vJJaNsAoS3U=
github.com/italypaleale/francis v0.1.0-beta.22 h1:ONOCfPEgvjy6fraAyKqX+wobTXIMZperWRruhj27jxQ=
github.com/italypaleale/francis v0.1.0-beta.22/go.mod h1:qB+0OgLCTWw3/AmdHe30bN81TTo5BcI95kAwAJdLPuM=
github.com/italypaleale/go-kit v0.0.0-20260806152440-491aff6b42b5 h1:bT2aB2nHewW4pHmVk36aRADW1owTDz1q1V5mRa5BCs0=
github.com/italypaleale/go-kit v0.0.0-20260806152440-491aff6b42b5/go.mod h1:wg4UsIbsbtDiVqUjJdo/tO9lXo0OXBuwPOpSJ+u+1jI=
github.com/italypaleale/go-sql-utils v0.3.4 h1:g9LGdXHhUKMC/3eufRYyY9mN9Vvi4PnLV+OXJwBKGt4=
github.com/italypaleale/go-sql-utils v0.3.4/go.mod h1:STWS4qGjiGKt2tf9OdRUlf025FhKZvUA31siUOnejj0=
github.com/italypaleale/francis v0.1.0-beta.23 h1:Yu0K/LE7NwQWVC1yOH6/uVE/+1JZ966aAftPnDTUDmw=
github.com/italypaleale/francis v0.1.0-beta.23/go.mod h1:qB+0OgLCTWw3/AmdHe30bN81TTo5BcI95kAwAJdLPuM=
github.com/italypaleale/go-kit v0.0.0-20260810215935-944b377ddc2f h1:0WjKnQPDkpvDcOIDT0Xd1LtXhnn59agn5vVmN/ItD+g=
github.com/italypaleale/go-kit v0.0.0-20260810215935-944b377ddc2f/go.mod h1:wg4UsIbsbtDiVqUjJdo/tO9lXo0OXBuwPOpSJ+u+1jI=
github.com/italypaleale/go-sql-utils v0.3.5 h1:kkrhIo1tVJvcsjRc1kePWE9Cqdm26+Et5XZYSNmv764=
github.com/italypaleale/go-sql-utils v0.3.5/go.mod h1:STWS4qGjiGKt2tf9OdRUlf025FhKZvUA31siUOnejj0=
github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 h1:D/V0gu4zQ3cL2WKeVNVM4r2gLxGGf6McLwgXzRTo2RQ=
+101
View File
@@ -0,0 +1,101 @@
package apikey
import (
"context"
"fmt"
"log/slog"
"time"
"github.com/italypaleale/francis/builtin/cronjob"
"github.com/pocket-id/pocket-id/backend/internal/appconfig"
)
type APIKeyExpiryEmailSender interface {
SendAPIKeyExpiringSoon(ctx context.Context, dbConfig *appconfig.AppConfigModel, userFullName, userEmail, firstName, apiKeyName string, expiresAt time.Time) error
}
type expiryJob struct {
service *Service
appConfig appconfig.AppConfigResolver
email APIKeyExpiryEmailSender
}
func newExpiryJob(service *Service, appConfig appconfig.AppConfigResolver, email APIKeyExpiryEmailSender) (*cronjob.CronJob, error) {
job := &expiryJob{
service: service,
appConfig: appConfig,
email: email,
}
cronActor, err := cronjob.New(
"ExpiredApiKeyEmailJob",
cronjob.WithJob(job.checkAndNotifyExpiringAPIKeys),
// Run at midnight (± 2 minutes)
// We want a consistent time because we are emailing users
cronjob.WithCron("0 0 * * *"),
cronjob.WithLogger(slog.Default()),
cronjob.WithJitter(2*time.Minute),
)
if err != nil {
return nil, fmt.Errorf("error creating API key expiration cron job: %w", err)
}
return cronActor, nil
}
func (j *expiryJob) checkAndNotifyExpiringAPIKeys(ctx context.Context) error {
dbConfig, err := j.appConfig.GetConfig(ctx)
if err != nil {
return fmt.Errorf("error loading app config: %w", err)
}
// Skip the database query when expiration notifications are disabled
if !dbConfig.EmailApiKeyExpirationEnabled.IsTrue() {
return nil
}
// Load API keys that entered the notification window since the last occurrence
apiKeys, err := j.service.ListExpiringApiKeys(ctx, 7)
if err != nil {
return fmt.Errorf("failed to list expiring API keys: %w", err)
}
for _, key := range apiKeys {
if key.User.Email == nil {
continue
}
// Continue processing other recipients when one delivery fails
err = j.email.SendAPIKeyExpiringSoon(
ctx,
dbConfig,
key.User.FullName(),
*key.User.Email,
key.User.FirstName,
key.Name,
key.ExpiresAt.ToTime(),
)
if err != nil {
slog.ErrorContext(ctx,
"Failed to send expiring API key notification email",
slog.String("key", key.ID),
slog.String("user", key.User.ID),
slog.Any("error", err),
)
continue
}
// Mark successful deliveries so future cron occurrences do not resend them
err = j.service.MarkExpirationEmailSent(ctx, key.ID)
if err != nil {
slog.ErrorContext(ctx,
"Failed to record that the expiration email was sent",
slog.String("key", key.ID),
slog.Any("error", err),
)
}
}
return nil
}
+101
View File
@@ -0,0 +1,101 @@
package apikey
import (
"context"
"testing"
"time"
"github.com/italypaleale/francis/host/local"
"github.com/stretchr/testify/require"
"github.com/pocket-id/pocket-id/backend/internal/appconfig"
"github.com/pocket-id/pocket-id/backend/internal/model"
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
testutils "github.com/pocket-id/pocket-id/backend/internal/utils/testing"
)
type expiryJobTestConfig struct {
config *appconfig.AppConfigModel
}
func (c expiryJobTestConfig) GetConfig(context.Context) (*appconfig.AppConfigModel, error) {
return c.config, nil
}
type expiryJobTestSender struct {
keyNames []string
}
func (s *expiryJobTestSender) SendAPIKeyExpiringSoon(_ context.Context, _ *appconfig.AppConfigModel, _, _, _, apiKeyName string, _ time.Time) error {
s.keyNames = append(s.keyNames, apiKeyName)
return nil
}
func TestModuleRegistersAPIKeyExpiryCronJob(t *testing.T) {
db := testutils.NewDatabaseForTest(t)
config := expiryJobTestConfig{config: &appconfig.AppConfigModel{}}
sender := &expiryJobTestSender{}
testutils.NewActorHostForTest(t, func(t *testing.T, host *local.Host) {
t.Helper()
_, err := New(t.Context(), Dependencies{
DB: db,
Actors: host,
AppConfig: config,
EmailSender: sender,
})
require.NoError(t, err)
})
}
func TestAPIKeyExpiryCronJobNotifiesAndMarksExpiringKeys(t *testing.T) {
db := testutils.NewDatabaseForTest(t)
user := model.User{
Username: "expiry-job-user",
Email: new("expiry-job@example.com"),
FirstName: "Expiry",
LastName: "Job",
DisplayName: "Expiry Job",
}
require.NoError(t, db.Create(&user).Error)
now := time.Now()
expiringKey := ApiKey{
Name: "Expiring",
Key: "expiring-hash",
ExpiresAt: datatype.DateTime(now.Add(3 * 24 * time.Hour)),
UserID: user.ID,
}
laterKey := ApiKey{
Name: "Later",
Key: "later-hash",
ExpiresAt: datatype.DateTime(now.Add(30 * 24 * time.Hour)),
UserID: user.ID,
}
require.NoError(t, db.Create(&expiringKey).Error)
require.NoError(t, db.Create(&laterKey).Error)
service, err := newService(t.Context(), db, "")
require.NoError(t, err)
config := expiryJobTestConfig{config: &appconfig.AppConfigModel{
EmailApiKeyExpirationEnabled: "true",
}}
sender := &expiryJobTestSender{}
cronActor, err := newExpiryJob(service, config, sender)
require.NoError(t, err)
require.Equal(t, "cronjob.ExpiredApiKeyEmailJob", cronActor.ActorType())
job := &expiryJob{
service: service,
appConfig: config,
email: sender,
}
require.NoError(t, job.checkAndNotifyExpiringAPIKeys(t.Context()))
require.Equal(t, []string{"Expiring"}, sender.keyNames)
require.NoError(t, db.First(&expiringKey, "id = ?", expiringKey.ID).Error)
require.True(t, expiringKey.ExpirationEmailSent)
require.NoError(t, db.First(&laterKey, "id = ?", laterKey.ID).Error)
require.False(t, laterKey.ExpirationEmailSent)
}
+12 -6
View File
@@ -39,7 +39,8 @@ func (h *handler) list(c *gin.Context) error {
}
var apiKeysDto []apiKeyDto
if err := dto.MapStructList(apiKeys, &apiKeysDto); err != nil {
err = dto.MapStructList(apiKeys, &apiKeysDto)
if err != nil {
return err
}
@@ -61,7 +62,8 @@ func (h *handler) create(c *gin.Context) error {
userID := c.GetString("userID")
var input apiKeyCreateDto
if err := httpserver.BindJSON(c, &input); err != nil {
err := httpserver.BindJSON(c, &input)
if err != nil {
return err
}
@@ -71,7 +73,8 @@ func (h *handler) create(c *gin.Context) error {
}
var responseDto apiKeyDto
if err := dto.MapStruct(apiKey, &responseDto); err != nil {
err = dto.MapStruct(apiKey, &responseDto)
if err != nil {
return err
}
@@ -94,7 +97,8 @@ func (h *handler) renew(c *gin.Context) error {
apiKeyID := c.Param("id")
var input apiKeyRenewDto
if err := httpserver.BindJSON(c, &input); err != nil {
err := httpserver.BindJSON(c, &input)
if err != nil {
return err
}
@@ -104,7 +108,8 @@ func (h *handler) renew(c *gin.Context) error {
}
var responseDto apiKeyDto
if err := dto.MapStruct(apiKey, &responseDto); err != nil {
err = dto.MapStruct(apiKey, &responseDto)
if err != nil {
return err
}
@@ -126,7 +131,8 @@ func (h *handler) revoke(c *gin.Context) error {
userID := c.GetString("userID")
apiKeyID := c.Param("id")
if err := h.service.RevokeApiKey(c.Request.Context(), userID, apiKeyID); err != nil {
err := h.service.RevokeApiKey(c.Request.Context(), userID, apiKeyID)
if err != nil {
return err
}
+34 -14
View File
@@ -2,17 +2,25 @@ package apikey
import (
"context"
"errors"
"fmt"
"github.com/gin-gonic/gin"
"github.com/italypaleale/francis/host/local"
"gorm.io/gorm"
"github.com/pocket-id/pocket-id/backend/internal/appconfig"
"github.com/pocket-id/pocket-id/backend/internal/httpserver"
"github.com/pocket-id/pocket-id/backend/internal/model"
)
type Dependencies struct {
DB *gorm.DB
StaticApiKey string
DB *gorm.DB
Actors *local.Host
StaticApiKey string
AppConfig appconfig.AppConfigResolver
EmailSender APIKeyExpiryEmailSender
CleanupDisabled bool
}
type Module struct {
@@ -26,10 +34,32 @@ func New(ctx context.Context, deps Dependencies) (*Module, error) {
return nil, err
}
return &Module{
module := &Module{
service: service,
handler: newHandler(service),
}, nil
}
// Register the cleanup job for expired API keys
if !deps.CleanupDisabled {
if deps.Actors == nil {
return nil, errors.New("actor host is required for the API key expiration cron job")
}
if deps.AppConfig == nil || deps.EmailSender == nil {
return nil, errors.New("notification dependencies are required for the API key expiration cron job")
}
expiryJob, err := newExpiryJob(service, deps.AppConfig, deps.EmailSender)
if err != nil {
return nil, err
}
err = deps.Actors.RegisterBuiltInActor(expiryJob)
if err != nil {
return nil, fmt.Errorf("error registering API key expiration cron actor: %w", err)
}
}
return module, nil
}
// RegisterRoutes mounts the API key management endpoints
@@ -47,13 +77,3 @@ func (m *Module) RegisterRoutes(apiGroup *gin.RouterGroup, auth, authWithoutApiK
func (m *Module) ValidateApiKey(ctx context.Context, apiKey string) (model.User, error) {
return m.service.ValidateApiKey(ctx, apiKey)
}
// ListExpiringApiKeys returns API keys expiring within the given number of days that have not been notified yet
func (m *Module) ListExpiringApiKeys(ctx context.Context, daysAhead int) ([]ApiKey, error) {
return m.service.ListExpiringApiKeys(ctx, daysAhead)
}
// MarkExpirationEmailSent records that the expiration notification email was sent for the given API key
func (m *Module) MarkExpirationEmailSent(ctx context.Context, apiKeyID string) error {
return m.service.MarkExpirationEmailSent(ctx, apiKeyID)
}
+40 -26
View File
@@ -3,6 +3,7 @@ package apikey
import (
"context"
"errors"
"fmt"
"time"
"gorm.io/gorm"
@@ -46,7 +47,7 @@ func (s *Service) ListApiKeys(ctx context.Context, userID string, listRequestOpt
var apiKeys []ApiKey
pagination, err := utils.PaginateFilterAndSort(listRequestOptions, query, &apiKeys)
if err != nil {
return nil, utils.PaginationResponse{}, err
return nil, utils.PaginationResponse{}, fmt.Errorf("error listing API keys: %w", err)
}
return apiKeys, pagination, nil
@@ -61,7 +62,7 @@ func (s *Service) CreateApiKey(ctx context.Context, userID string, input apiKeyC
// Generate a secure random API key
token, err := utils.GenerateRandomAlphanumericString(32)
if err != nil {
return ApiKey{}, "", err
return ApiKey{}, "", fmt.Errorf("error generating API key token: %w", err)
}
apiKey := ApiKey{
@@ -80,7 +81,7 @@ func (s *Service) CreateApiKey(ctx context.Context, userID string, input apiKeyC
if errors.Is(err, gorm.ErrDuplicatedKey) {
return ApiKey{}, "", apperror.AlreadyInUse("API key name")
}
return ApiKey{}, "", err
return ApiKey{}, "", fmt.Errorf("error creating API key: %w", err)
}
// Return the raw token only once - it cannot be retrieved later
@@ -103,12 +104,10 @@ func (s *Service) RenewApiKey(ctx context.Context, userID, apiKeyID string, expi
Where("id = ? AND user_id = ?", apiKeyID, userID).
First(&apiKey).
Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ApiKey{}, "", apperror.APIKeyNotFound()
}
return ApiKey{}, "", err
if errors.Is(err, gorm.ErrRecordNotFound) {
return ApiKey{}, "", apperror.APIKeyNotFound()
} else if err != nil {
return ApiKey{}, "", fmt.Errorf("error loading API key: %w", err)
}
// Only allow renewal if the key has already expired
@@ -119,7 +118,7 @@ func (s *Service) RenewApiKey(ctx context.Context, userID, apiKeyID string, expi
// Generate a secure random API key
token, err := utils.GenerateRandomAlphanumericString(32)
if err != nil {
return ApiKey{}, "", err
return ApiKey{}, "", fmt.Errorf("error generating API key token: %w", err)
}
apiKey.Key = utils.CreateSha256Hash(token)
@@ -127,11 +126,12 @@ func (s *Service) RenewApiKey(ctx context.Context, userID, apiKeyID string, expi
err = tx.WithContext(ctx).Save(&apiKey).Error
if err != nil {
return ApiKey{}, "", err
return ApiKey{}, "", fmt.Errorf("error saving API key: %w", err)
}
if err := tx.Commit().Error; err != nil {
return ApiKey{}, "", err
err = tx.Commit().Error
if err != nil {
return ApiKey{}, "", fmt.Errorf("error committing transaction: %w", err)
}
return apiKey, token, nil
@@ -144,7 +144,7 @@ func (s *Service) RevokeApiKey(ctx context.Context, userID, apiKeyID string) err
Where("id = ? AND user_id = ?", apiKeyID, userID).
Delete(&apiKey)
if result.Error != nil {
return result.Error
return fmt.Errorf("error deleting API key: %w", result.Error)
}
if result.RowsAffected == 0 {
return apperror.APIKeyNotFound()
@@ -177,12 +177,10 @@ func (s *Service) ValidateApiKey(ctx context.Context, apiKey string) (model.User
Preload("User").
First(&key).
Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return model.User{}, apperror.InvalidAPIKey()
}
return model.User{}, err
if errors.Is(err, gorm.ErrRecordNotFound) {
return model.User{}, apperror.InvalidAPIKey()
} else if err != nil {
return model.User{}, fmt.Errorf("error loading API key: %w", err)
}
return key.User, nil
@@ -199,17 +197,25 @@ func (s *Service) ListExpiringApiKeys(ctx context.Context, daysAhead int) ([]Api
Where("expires_at > ? AND expires_at <= ? AND expiration_email_sent = ?", datatype.DateTime(now), datatype.DateTime(cutoff), false).
Find(&keys).
Error
if err != nil {
return nil, fmt.Errorf("error listing API keys: %w", err)
}
return keys, err
return keys, nil
}
// MarkExpirationEmailSent records that the expiration notification email was sent for the given API key
func (s *Service) MarkExpirationEmailSent(ctx context.Context, apiKeyID string) error {
return s.db.WithContext(ctx).
err := s.db.WithContext(ctx).
Model(&ApiKey{}).
Where("id = ?", apiKeyID).
Update("expiration_email_sent", true).
Error
if err != nil {
return fmt.Errorf("error marking API key expiration email sent: %w", err)
}
return nil
}
func (s *Service) initStaticApiKeyUser(ctx context.Context) (user model.User, err error) {
@@ -223,12 +229,12 @@ func (s *Service) initStaticApiKeyUser(ctx context.Context) (user model.User, er
}
if !errors.Is(err, gorm.ErrRecordNotFound) {
return model.User{}, err
return model.User{}, fmt.Errorf("error loading static API key user: %w", err)
}
usernameSuffix, err := utils.GenerateRandomAlphanumericString(6)
if err != nil {
return model.User{}, err
return model.User{}, fmt.Errorf("error generating static API key username suffix: %w", err)
}
user = model.User{
@@ -245,13 +251,21 @@ func (s *Service) initStaticApiKeyUser(ctx context.Context) (user model.User, er
WithContext(ctx).
Create(&user).
Error
if err != nil {
return model.User{}, fmt.Errorf("error creating static API key user: %w", err)
}
return user, err
return user, nil
}
func (s *Service) deleteStaticApiKeyUser(ctx context.Context) error {
return s.db.
err := s.db.
WithContext(ctx).
Delete(&model.User{}, "id = ?", common.StaticApiKeyUserID).
Error
if err != nil {
return fmt.Errorf("error deleting static API key user: %w", err)
}
return nil
}
@@ -14,10 +14,6 @@ func registerScheduledJobs(ctx context.Context, db *gorm.DB, svc *services, sche
if err != nil {
return fmt.Errorf("failed to register DB cleanup jobs in scheduler: %w", err)
}
err = scheduler.RegisterApiKeyExpiryJob(ctx, svc.apiKeyModule, svc.appConfigService, svc.emailModule)
if err != nil {
return fmt.Errorf("failed to register API key expiration jobs in scheduler: %w", err)
}
err = scheduler.RegisterScimJobs(ctx, svc.scimService)
if err != nil {
return fmt.Errorf("failed to register SCIM scheduler job: %w", err)
@@ -170,8 +170,12 @@ func initServices(
}
svc.apiKeyModule, err = apikey.New(ctx, apikey.Dependencies{
DB: db,
StaticApiKey: common.EnvConfig.StaticApiKey,
DB: db,
Actors: actors,
StaticApiKey: common.EnvConfig.StaticApiKey,
AppConfig: svc.appConfigService,
EmailSender: svc.emailModule,
CleanupDisabled: common.EnvConfig.AppEnv.IsTest(),
})
if err != nil {
return nil, fmt.Errorf("failed to create API key module: %w", err)
@@ -1,84 +0,0 @@
package job
import (
"context"
"fmt"
"log/slog"
"time"
"github.com/go-co-op/gocron/v2"
"github.com/pocket-id/pocket-id/backend/internal/apikey"
"github.com/pocket-id/pocket-id/backend/internal/appconfig"
"github.com/pocket-id/pocket-id/backend/internal/service"
)
type APIKeyExpiryEmailSender interface {
SendAPIKeyExpiringSoon(ctx context.Context, dbConfig *appconfig.AppConfigModel, userFullName, userEmail, firstName, apiKeyName string, expiresAt time.Time) error
}
type ApiKeyEmailJobs struct {
apiKeyModule *apikey.Module
appConfigService *appconfig.AppConfigService
emailSender APIKeyExpiryEmailSender
}
func (s *Scheduler) RegisterApiKeyExpiryJob(ctx context.Context, apiKeyModule *apikey.Module, appConfigService *appconfig.AppConfigService, emailSender APIKeyExpiryEmailSender) error {
jobs := &ApiKeyEmailJobs{
apiKeyModule: apiKeyModule,
appConfigService: appConfigService,
emailSender: emailSender,
}
// Send every day at midnight
return s.RegisterJob(ctx, "ExpiredApiKeyEmailJob", gocron.CronJob("0 0 * * *", false), jobs.checkAndNotifyExpiringApiKeys, service.RegisterJobOpts{})
}
func (j *ApiKeyEmailJobs) checkAndNotifyExpiringApiKeys(ctx context.Context) error {
dbConfig, err := j.appConfigService.GetConfig(ctx)
if err != nil {
return fmt.Errorf("error load app config: %w", err)
}
// Skip if the feature is disabled
if !dbConfig.EmailApiKeyExpirationEnabled.IsTrue() {
return nil
}
apiKeys, err := j.apiKeyModule.ListExpiringApiKeys(ctx, 7)
if err != nil {
return fmt.Errorf("failed to list expiring API keys: %w", err)
}
for _, key := range apiKeys {
if key.User.Email == nil {
continue
}
err = j.emailSender.SendAPIKeyExpiringSoon(
ctx,
dbConfig,
key.User.FullName(),
*key.User.Email,
key.User.FirstName,
key.Name,
key.ExpiresAt.ToTime(),
)
if err != nil {
slog.ErrorContext(ctx, "Failed to send expiring API key notification email",
slog.String("key", key.ID),
slog.String("user", key.User.ID),
slog.Any("error", err),
)
continue
}
if err = j.apiKeyModule.MarkExpirationEmailSent(ctx, key.ID); err != nil {
slog.ErrorContext(ctx, "Failed to record that the expiration email was sent",
slog.String("key", key.ID),
slog.Any("error", err),
)
}
}
return nil
}
+2
View File
@@ -29,6 +29,7 @@ func GetFileCleanupJobs(db *gorm.DB, fileStorage storage.FileStorage) (cjs []*cr
// Run every 24 hours
cronjob.WithInterval(24*time.Hour),
cronjob.WithLogger(slog.Default()),
cronjob.WithJitter(5*time.Minute),
)
if err != nil {
return nil, fmt.Errorf("error creating ClearUnusedDefaultProfilePictures job: %w", err)
@@ -44,6 +45,7 @@ func GetFileCleanupJobs(db *gorm.DB, fileStorage storage.FileStorage) (cjs []*cr
// Run every 12 hours
cronjob.WithInterval(12*time.Hour),
cronjob.WithLogger(slog.Default()),
cronjob.WithJitter(5*time.Minute),
)
if err != nil {
return nil, fmt.Errorf("error creating ClearOrphanedTempFiles job: %w", err)
@@ -40,7 +40,7 @@ func TestWithApiKeyAuthDisabled(t *testing.T) {
require.NoError(t, err)
userService := service.NewUserService(db, jwtService, nil, nil, nil, nil, nil)
apiKeyModule, err := apikey.New(t.Context(), apikey.Dependencies{DB: db})
apiKeyModule, err := apikey.New(t.Context(), apikey.Dependencies{DB: db, CleanupDisabled: true})
require.NoError(t, err)
authMiddleware := NewAuthMiddleware(apiKeyModule, userService, jwtService)