162 lines
4.6 KiB
Go
162 lines
4.6 KiB
Go
package mysql
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
|
|
filemanager "wucher/internal/domain/file_manager"
|
|
)
|
|
|
|
func (r *FileManagerFileRepository) WithTransaction(
|
|
ctx context.Context,
|
|
fn func(repo filemanager.FileRepository, outbox filemanager.FileProcessingOutboxWriter) error,
|
|
) error {
|
|
if fn == nil {
|
|
return nil
|
|
}
|
|
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
txRepo := &FileManagerFileRepository{db: tx}
|
|
return fn(txRepo, txRepo)
|
|
})
|
|
}
|
|
|
|
func (r *FileManagerFileRepository) CreateFileProcessingOutboxMessage(ctx context.Context, message *filemanager.FileProcessingOutboxMessage) error {
|
|
if message == nil {
|
|
return gorm.ErrInvalidData
|
|
}
|
|
return r.db.WithContext(ctx).Create(message).Error
|
|
}
|
|
|
|
func (r *FileManagerFileRepository) ClaimPendingFileProcessingOutboxMessages(
|
|
ctx context.Context,
|
|
limit int,
|
|
now time.Time,
|
|
lockTTL time.Duration,
|
|
workerID string,
|
|
) ([]filemanager.FileProcessingOutboxMessage, error) {
|
|
if limit <= 0 {
|
|
limit = 1
|
|
}
|
|
if lockTTL <= 0 {
|
|
lockTTL = time.Minute
|
|
}
|
|
now = now.UTC()
|
|
staleBefore := now.Add(-lockTTL)
|
|
workerID = strings.TrimSpace(workerID)
|
|
if workerID == "" {
|
|
workerID = "worker"
|
|
}
|
|
|
|
var messages []filemanager.FileProcessingOutboxMessage
|
|
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
if err := tx.
|
|
Clauses(clause.Locking{Strength: "UPDATE", Options: "SKIP LOCKED"}).
|
|
Where(
|
|
"((status = ? AND available_at <= ?) OR (status = ? AND locked_at IS NOT NULL AND locked_at < ?))",
|
|
filemanager.FileProcessingOutboxStatusPending,
|
|
now,
|
|
filemanager.FileProcessingOutboxStatusProcessing,
|
|
staleBefore,
|
|
).
|
|
Order("available_at ASC, created_at ASC").
|
|
Limit(limit).
|
|
Find(&messages).Error; err != nil {
|
|
return err
|
|
}
|
|
if len(messages) == 0 {
|
|
return nil
|
|
}
|
|
|
|
ids := make([][]byte, 0, len(messages))
|
|
for i := range messages {
|
|
ids = append(ids, messages[i].ID)
|
|
}
|
|
|
|
if err := tx.Model(&filemanager.FileProcessingOutboxMessage{}).
|
|
Where("id IN ?", ids).
|
|
Updates(map[string]any{
|
|
"status": filemanager.FileProcessingOutboxStatusProcessing,
|
|
"locked_at": now,
|
|
"locked_by": workerID,
|
|
"updated_at": now,
|
|
"attempts": gorm.Expr("attempts + ?", 1),
|
|
}).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
for i := range messages {
|
|
messages[i].Status = filemanager.FileProcessingOutboxStatusProcessing
|
|
messages[i].Attempts++
|
|
ts := now
|
|
messages[i].LockedAt = &ts
|
|
messages[i].LockedBy = workerID
|
|
messages[i].UpdatedAt = now
|
|
}
|
|
return nil
|
|
})
|
|
return messages, err
|
|
}
|
|
|
|
func (r *FileManagerFileRepository) MarkFileProcessingOutboxMessagePublished(ctx context.Context, id []byte, publishedAt time.Time) error {
|
|
publishedAt = publishedAt.UTC()
|
|
return r.db.WithContext(ctx).
|
|
Model(&filemanager.FileProcessingOutboxMessage{}).
|
|
Where("id = ?", id).
|
|
Updates(map[string]any{
|
|
"status": filemanager.FileProcessingOutboxStatusPublished,
|
|
"published_at": publishedAt,
|
|
"locked_at": nil,
|
|
"locked_by": "",
|
|
"last_error": "",
|
|
"updated_at": publishedAt,
|
|
}).Error
|
|
}
|
|
|
|
func (r *FileManagerFileRepository) MarkFileProcessingOutboxMessageRetry(ctx context.Context, id []byte, availableAt time.Time, lastErr string) error {
|
|
availableAt = availableAt.UTC()
|
|
return r.db.WithContext(ctx).
|
|
Model(&filemanager.FileProcessingOutboxMessage{}).
|
|
Where("id = ?", id).
|
|
Updates(map[string]any{
|
|
"status": filemanager.FileProcessingOutboxStatusPending,
|
|
"available_at": availableAt,
|
|
"locked_at": nil,
|
|
"locked_by": "",
|
|
"last_error": truncateOutboxError(strings.TrimSpace(lastErr), 4096),
|
|
"updated_at": time.Now().UTC(),
|
|
}).Error
|
|
}
|
|
|
|
func (r *FileManagerFileRepository) MarkFileProcessingOutboxMessageDead(ctx context.Context, id []byte, failedAt time.Time, lastErr string) error {
|
|
failedAt = failedAt.UTC()
|
|
return r.db.WithContext(ctx).
|
|
Model(&filemanager.FileProcessingOutboxMessage{}).
|
|
Where("id = ?", id).
|
|
Updates(map[string]any{
|
|
"status": filemanager.FileProcessingOutboxStatusDead,
|
|
"available_at": failedAt,
|
|
"locked_at": nil,
|
|
"locked_by": "",
|
|
"last_error": truncateOutboxError(strings.TrimSpace(lastErr), 4096),
|
|
"updated_at": failedAt,
|
|
}).Error
|
|
}
|
|
|
|
func (r *FileManagerFileRepository) CountPendingFileProcessingOutboxMessages(ctx context.Context, now time.Time) (int64, error) {
|
|
var count int64
|
|
err := r.db.WithContext(ctx).
|
|
Model(&filemanager.FileProcessingOutboxMessage{}).
|
|
Where(
|
|
"(status = ? AND available_at <= ?) OR status = ?",
|
|
filemanager.FileProcessingOutboxStatusPending,
|
|
now.UTC(),
|
|
filemanager.FileProcessingOutboxStatusProcessing,
|
|
).
|
|
Count(&count).Error
|
|
return count, err
|
|
}
|