init push
This commit is contained in:
161
internal/repository/mysql/file_processing_outbox_repo.go
Normal file
161
internal/repository/mysql/file_processing_outbox_repo.go
Normal file
@@ -0,0 +1,161 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user