init push

This commit is contained in:
2026-07-16 22:16:45 +07:00
commit 8b068bdb10
1021 changed files with 332816 additions and 0 deletions

View File

@@ -0,0 +1,165 @@
package mysql
import (
"context"
"strings"
"time"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"wucher/internal/domain/auth"
)
func (r *AuthRepository) WithTransaction(ctx context.Context, fn func(repo auth.Repository, outbox auth.EmailOutboxWriter) error) error {
if fn == nil {
return nil
}
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
txRepo := &AuthRepository{db: tx}
return fn(txRepo, txRepo)
})
}
func (r *AuthRepository) CreateEmailOutboxMessage(ctx context.Context, message *auth.EmailOutboxMessage) error {
if message == nil {
return gorm.ErrInvalidData
}
return r.db.WithContext(ctx).Create(message).Error
}
func (r *AuthRepository) ClaimPendingEmailOutboxMessages(
ctx context.Context,
limit int,
now time.Time,
lockTTL time.Duration,
workerID string,
) ([]auth.EmailOutboxMessage, 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 []auth.EmailOutboxMessage
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 < ?))",
auth.EmailOutboxStatusPending,
now,
auth.EmailOutboxStatusProcessing,
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(&auth.EmailOutboxMessage{}).
Where("id IN ?", ids).
Updates(map[string]any{
"status": auth.EmailOutboxStatusProcessing,
"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 = auth.EmailOutboxStatusProcessing
messages[i].Attempts++
ts := now
messages[i].LockedAt = &ts
messages[i].LockedBy = workerID
messages[i].UpdatedAt = now
}
return nil
})
return messages, err
}
func (r *AuthRepository) MarkEmailOutboxMessagePublished(ctx context.Context, id []byte, publishedAt time.Time) error {
publishedAt = publishedAt.UTC()
return r.db.WithContext(ctx).
Model(&auth.EmailOutboxMessage{}).
Where("id = ?", id).
Updates(map[string]any{
"status": auth.EmailOutboxStatusPublished,
"published_at": publishedAt,
"locked_at": nil,
"locked_by": "",
"last_error": "",
"updated_at": publishedAt,
}).Error
}
func (r *AuthRepository) MarkEmailOutboxMessageRetry(ctx context.Context, id []byte, availableAt time.Time, lastErr string) error {
availableAt = availableAt.UTC()
return r.db.WithContext(ctx).
Model(&auth.EmailOutboxMessage{}).
Where("id = ?", id).
Updates(map[string]any{
"status": auth.EmailOutboxStatusPending,
"available_at": availableAt,
"locked_at": nil,
"locked_by": "",
"last_error": truncateOutboxError(strings.TrimSpace(lastErr), 4096),
"updated_at": time.Now().UTC(),
}).Error
}
func (r *AuthRepository) MarkEmailOutboxMessageDead(ctx context.Context, id []byte, failedAt time.Time, lastErr string) error {
failedAt = failedAt.UTC()
return r.db.WithContext(ctx).
Model(&auth.EmailOutboxMessage{}).
Where("id = ?", id).
Updates(map[string]any{
"status": auth.EmailOutboxStatusDead,
"available_at": failedAt,
"locked_at": nil,
"locked_by": "",
"last_error": truncateOutboxError(strings.TrimSpace(lastErr), 4096),
"updated_at": failedAt,
}).Error
}
func (r *AuthRepository) CountPendingEmailOutboxMessages(ctx context.Context, now time.Time) (int64, error) {
var count int64
err := r.db.WithContext(ctx).
Model(&auth.EmailOutboxMessage{}).
Where(
"(status = ? AND available_at <= ?) OR status = ?",
auth.EmailOutboxStatusPending,
now.UTC(),
auth.EmailOutboxStatusProcessing,
).
Count(&count).Error
return count, err
}
func truncateOutboxError(v string, max int) string {
if max <= 0 || len(v) <= max {
return v
}
return v[:max]
}