package repository import ( "baron-sso-backend/internal/domain" "context" "time" "gorm.io/gorm" "gorm.io/gorm/clause" ) type WorksmobileOutboxRepository interface { Create(ctx context.Context, item *domain.WorksmobileOutbox) error ListRecent(ctx context.Context, limit int) ([]domain.WorksmobileOutbox, error) ListReady(ctx context.Context, limit int) ([]domain.WorksmobileOutbox, error) FindByID(ctx context.Context, id string) (*domain.WorksmobileOutbox, error) MarkRetry(ctx context.Context, id string) error MarkProcessing(ctx context.Context, id string) error MarkProcessed(ctx context.Context, id string) error MarkFailed(ctx context.Context, id string, message string, nextAttemptAt time.Time) error } type worksmobileOutboxRepository struct { db *gorm.DB } func NewWorksmobileOutboxRepository(db *gorm.DB) WorksmobileOutboxRepository { return &worksmobileOutboxRepository{db: db} } func (r *worksmobileOutboxRepository) Create(ctx context.Context, item *domain.WorksmobileOutbox) error { if item.Payload == nil { item.Payload = domain.JSONMap{} } if item.Status == "" { item.Status = domain.WorksmobileOutboxStatusPending } return r.db.WithContext(ctx).Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "dedupe_key"}}, DoUpdates: clause.Assignments(map[string]any{ "payload": item.Payload, "status": domain.WorksmobileOutboxStatusPending, "last_error": "", "next_attempt_at": nil, "updated_at": time.Now(), }), }).Create(item).Error } func (r *worksmobileOutboxRepository) ListRecent(ctx context.Context, limit int) ([]domain.WorksmobileOutbox, error) { if limit <= 0 || limit > 1000 { limit = 50 } var rows []domain.WorksmobileOutbox err := r.db.WithContext(ctx).Order("created_at desc").Limit(limit).Find(&rows).Error return rows, err } func (r *worksmobileOutboxRepository) ListReady(ctx context.Context, limit int) ([]domain.WorksmobileOutbox, error) { if limit <= 0 || limit > 100 { limit = 20 } var rows []domain.WorksmobileOutbox err := r.db.WithContext(ctx). Where("status = ? AND (next_attempt_at IS NULL OR next_attempt_at <= ?)", domain.WorksmobileOutboxStatusPending, time.Now()). Order("created_at asc"). Limit(limit). Find(&rows).Error return rows, err } func (r *worksmobileOutboxRepository) FindByID(ctx context.Context, id string) (*domain.WorksmobileOutbox, error) { var row domain.WorksmobileOutbox if err := r.db.WithContext(ctx).First(&row, "id = ?", id).Error; err != nil { return nil, err } return &row, nil } func (r *worksmobileOutboxRepository) MarkRetry(ctx context.Context, id string) error { return r.db.WithContext(ctx).Model(&domain.WorksmobileOutbox{}).Where("id = ?", id).Updates(map[string]any{ "status": domain.WorksmobileOutboxStatusPending, "last_error": "", "next_attempt_at": nil, "updated_at": time.Now(), }).Error } func (r *worksmobileOutboxRepository) MarkProcessing(ctx context.Context, id string) error { return r.db.WithContext(ctx).Model(&domain.WorksmobileOutbox{}).Where("id = ? AND status = ?", id, domain.WorksmobileOutboxStatusPending).Updates(map[string]any{ "status": domain.WorksmobileOutboxStatusProcessing, "updated_at": time.Now(), }).Error } func (r *worksmobileOutboxRepository) MarkProcessed(ctx context.Context, id string) error { now := time.Now() return r.db.WithContext(ctx).Model(&domain.WorksmobileOutbox{}).Where("id = ?", id).Updates(map[string]any{ "status": domain.WorksmobileOutboxStatusProcessed, "last_error": "", "processed_at": &now, "updated_at": now, }).Error } func (r *worksmobileOutboxRepository) MarkFailed(ctx context.Context, id string, message string, nextAttemptAt time.Time) error { return r.db.WithContext(ctx).Model(&domain.WorksmobileOutbox{}).Where("id = ?", id).Updates(map[string]any{ "status": domain.WorksmobileOutboxStatusFailed, "retry_count": gorm.Expr("retry_count + 1"), "last_error": message, "next_attempt_at": &nextAttemptAt, "updated_at": time.Now(), }).Error }