feat: use postgres

This commit is contained in:
Ilia Denisov
2026-04-26 20:34:39 +02:00
committed by GitHub
parent 48b0056b49
commit fe829285a6
365 changed files with 29223 additions and 24049 deletions
@@ -0,0 +1,101 @@
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
})
}