102 lines
3.5 KiB
Go
102 lines
3.5 KiB
Go
package mailstore
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
|
|
pgtable "galaxy/mail/internal/adapters/postgres/jet/mail/table"
|
|
"galaxy/mail/internal/service/renderdelivery"
|
|
|
|
pg "github.com/go-jet/jet/v2/postgres"
|
|
)
|
|
|
|
// RenderDelivery returns a handle that satisfies renderdelivery.Store.
|
|
func (store *Store) RenderDelivery() *RenderDeliveryStore {
|
|
return &RenderDeliveryStore{store: store}
|
|
}
|
|
|
|
// RenderDeliveryStore is the renderdelivery.Store handle returned by
|
|
// Store.RenderDelivery.
|
|
type RenderDeliveryStore struct {
|
|
store *Store
|
|
}
|
|
|
|
var _ renderdelivery.Store = (*RenderDeliveryStore)(nil)
|
|
|
|
// MarkRendered persists the rendered subject, bodies, and locale_fallback
|
|
// flag for a queued template-mode delivery and transitions its status to
|
|
// rendered. The active attempt remains scheduled with its existing
|
|
// scheduled_for so the scheduler picks the row up via next_attempt_at.
|
|
func (handle *RenderDeliveryStore) MarkRendered(ctx context.Context, input renderdelivery.MarkRenderedInput) error {
|
|
if handle == nil || handle.store == nil {
|
|
return errors.New("mark rendered: nil store")
|
|
}
|
|
if ctx == nil {
|
|
return errors.New("mark rendered: nil context")
|
|
}
|
|
if err := input.Validate(); err != nil {
|
|
return fmt.Errorf("mark rendered: %w", err)
|
|
}
|
|
|
|
return handle.store.withTx(ctx, "mark rendered", func(ctx context.Context, tx *sql.Tx) error {
|
|
// Lock the active attempt for the duration of the update so a
|
|
// concurrent attempt-claim races against the same row.
|
|
lockStmt := pg.SELECT(pgtable.Attempts.ScheduledFor).
|
|
FROM(pgtable.Attempts).
|
|
WHERE(pg.AND(
|
|
pgtable.Attempts.DeliveryID.EQ(pg.String(input.Delivery.DeliveryID.String())),
|
|
pgtable.Attempts.AttemptNo.EQ(pg.Int(int64(input.Delivery.AttemptCount))),
|
|
)).
|
|
FOR(pg.UPDATE())
|
|
|
|
lockQuery, lockArgs := lockStmt.Sql()
|
|
row := tx.QueryRowContext(ctx, lockQuery, lockArgs...)
|
|
var ignored any
|
|
if err := row.Scan(&ignored); err != nil {
|
|
return fmt.Errorf("mark rendered: lock active attempt: %w", err)
|
|
}
|
|
if err := lockDelivery(ctx, tx, input.Delivery.DeliveryID); err != nil {
|
|
return fmt.Errorf("mark rendered: %w", err)
|
|
}
|
|
|
|
activeAttempt, err := loadActiveAttempt(ctx, tx, input.Delivery.DeliveryID, input.Delivery.AttemptCount)
|
|
if err != nil {
|
|
return fmt.Errorf("mark rendered: load active attempt: %w", err)
|
|
}
|
|
if err := updateDelivery(ctx, tx, input.Delivery, &activeAttempt); err != nil {
|
|
return fmt.Errorf("mark rendered: update delivery: %w", err)
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// MarkRenderFailed persists one classified terminal render failure. The
|
|
// active attempt becomes terminal (`render_failed`) and the delivery becomes
|
|
// `failed`.
|
|
func (handle *RenderDeliveryStore) MarkRenderFailed(ctx context.Context, input renderdelivery.MarkRenderFailedInput) error {
|
|
if handle == nil || handle.store == nil {
|
|
return errors.New("mark render failed: nil store")
|
|
}
|
|
if ctx == nil {
|
|
return errors.New("mark render failed: nil context")
|
|
}
|
|
if err := input.Validate(); err != nil {
|
|
return fmt.Errorf("mark render failed: %w", err)
|
|
}
|
|
|
|
return handle.store.withTx(ctx, "mark render failed", func(ctx context.Context, tx *sql.Tx) error {
|
|
if err := lockDelivery(ctx, tx, input.Delivery.DeliveryID); err != nil {
|
|
return fmt.Errorf("mark render failed: %w", err)
|
|
}
|
|
if err := updateAttempt(ctx, tx, input.Attempt); err != nil {
|
|
return fmt.Errorf("mark render failed: update attempt: %w", err)
|
|
}
|
|
if err := updateDelivery(ctx, tx, input.Delivery, nil); err != nil {
|
|
return fmt.Errorf("mark render failed: update delivery: %w", err)
|
|
}
|
|
return nil
|
|
})
|
|
}
|