- Added videoWorkflowRepository with methods to manage video and user interactions. - Introduced catalog_mapper for converting database models to protobuf representations. - Created domain_helpers for normalizing domain and ad format values. - Defined service interfaces for payment, account, notification, domain, ad template, player config, video, and user management. - Implemented OAuth helpers for generating state and caching keys. - Developed payment_proto_helpers for mapping payment-related models to protobuf. - Added service policy helpers to enforce plan requirements and user permissions. - Created user_mapper for converting user payloads to protobuf format. - Implemented value_helpers for handling various value conversions and nil checks. - Developed video_helpers for normalizing video statuses and managing storage types. - Created video_mapper for mapping video models to protobuf format. - Implemented render workflow for managing video creation and job processing.
145 lines
3.9 KiB
Go
145 lines
3.9 KiB
Go
package redis
|
|
|
|
// DeadLetterQueue provides a simple implementation of a dead letter queue using Redis sorted sets and hashes. Each failed job is stored as a JSON-encoded entry with metadata such as failure time, reason, and retry count. The sorted set allows for efficient retrieval of jobs in order of failure time, while the hash stores the detailed metadata for each job.
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
"stream.api/internal/database/model"
|
|
)
|
|
|
|
const (
|
|
dlqKey = "picpic:dlq"
|
|
dlqMetaPrefix = "picpic:dlq:meta:"
|
|
)
|
|
|
|
type DeadLetterQueue struct {
|
|
client *redis.Client
|
|
}
|
|
|
|
type DLQEntry struct {
|
|
Job *model.Job `json:"job"`
|
|
FailureTime time.Time `json:"failure_time"`
|
|
Reason string `json:"reason"`
|
|
RetryCount int64 `json:"retry_count"`
|
|
}
|
|
|
|
func NewDeadLetterQueue(client *redis.Client) *DeadLetterQueue {
|
|
return &DeadLetterQueue{client: client}
|
|
}
|
|
|
|
// Add adds a failed job to the DLQ
|
|
func (dlq *DeadLetterQueue) Add(ctx context.Context, job *model.Job, reason string) error {
|
|
entry := DLQEntry{
|
|
Job: job,
|
|
FailureTime: time.Now(),
|
|
Reason: reason,
|
|
RetryCount: *job.RetryCount,
|
|
}
|
|
|
|
data, err := json.Marshal(entry)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal DLQ entry: %w", err)
|
|
}
|
|
|
|
// Add to sorted set with timestamp as score
|
|
score := float64(time.Now().Unix())
|
|
if err := dlq.client.ZAdd(ctx, dlqKey, redis.Z{
|
|
Score: score,
|
|
Member: job.ID,
|
|
}).Err(); err != nil {
|
|
return fmt.Errorf("failed to add to DLQ: %w", err)
|
|
}
|
|
|
|
// Store metadata
|
|
metaKey := dlqMetaPrefix + job.ID
|
|
if err := dlq.client.Set(ctx, metaKey, data, 0).Err(); err != nil {
|
|
return fmt.Errorf("failed to store DLQ metadata: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Get retrieves a job from the DLQ
|
|
func (dlq *DeadLetterQueue) Get(ctx context.Context, jobID string) (*DLQEntry, error) {
|
|
metaKey := dlqMetaPrefix + jobID
|
|
data, err := dlq.client.Get(ctx, metaKey).Bytes()
|
|
if err != nil {
|
|
if err == redis.Nil {
|
|
return nil, fmt.Errorf("job not found in DLQ")
|
|
}
|
|
return nil, fmt.Errorf("failed to get DLQ entry: %w", err)
|
|
}
|
|
|
|
var entry DLQEntry
|
|
if err := json.Unmarshal(data, &entry); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal DLQ entry: %w", err)
|
|
}
|
|
|
|
return &entry, nil
|
|
}
|
|
|
|
// List returns all jobs in the DLQ
|
|
func (dlq *DeadLetterQueue) List(ctx context.Context, offset, limit int64) ([]*DLQEntry, error) {
|
|
// Get job IDs from sorted set (newest first)
|
|
jobIDs, err := dlq.client.ZRevRange(ctx, dlqKey, offset, offset+limit-1).Result()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list DLQ: %w", err)
|
|
}
|
|
|
|
entries := make([]*DLQEntry, 0, len(jobIDs))
|
|
for _, jobID := range jobIDs {
|
|
entry, err := dlq.Get(ctx, jobID)
|
|
if err != nil {
|
|
// Skip if metadata not found
|
|
continue
|
|
}
|
|
entries = append(entries, entry)
|
|
}
|
|
|
|
return entries, nil
|
|
}
|
|
|
|
// Remove removes a job from the DLQ
|
|
func (dlq *DeadLetterQueue) Remove(ctx context.Context, jobID string) error {
|
|
// Remove from sorted set
|
|
if err := dlq.client.ZRem(ctx, dlqKey, jobID).Err(); err != nil {
|
|
return fmt.Errorf("failed to remove from DLQ: %w", err)
|
|
}
|
|
|
|
// Remove metadata
|
|
metaKey := dlqMetaPrefix + jobID
|
|
if err := dlq.client.Del(ctx, metaKey).Err(); err != nil {
|
|
return fmt.Errorf("failed to remove DLQ metadata: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Count returns the total number of jobs in the DLQ
|
|
func (dlq *DeadLetterQueue) Count(ctx context.Context) (int64, error) {
|
|
count, err := dlq.client.ZCard(ctx, dlqKey).Result()
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to count DLQ: %w", err)
|
|
}
|
|
return count, nil
|
|
}
|
|
|
|
// Retry removes a job from DLQ and returns it for retry
|
|
func (dlq *DeadLetterQueue) Retry(ctx context.Context, jobID string) (*model.Job, error) {
|
|
entry, err := dlq.Get(ctx, jobID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Remove from DLQ
|
|
if err := dlq.Remove(ctx, jobID); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return entry.Job, nil
|
|
}
|