Files
fm_be/internal/repository/mysql/file_processing_outbox_repo.go
2026-07-16 22:16:45 +07:00

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
}