snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
This commit is contained in:
229
persistence/sql/persister_courier.go
Normal file
229
persistence/sql/persister_courier.go
Normal file
@@ -0,0 +1,229 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/ory/herodot"
|
||||
"github.com/ory/kratos/courier"
|
||||
"github.com/ory/kratos/persistence/sql/update"
|
||||
"github.com/ory/pop/v6"
|
||||
"github.com/ory/x/dbal"
|
||||
"github.com/ory/x/otelx"
|
||||
keysetpagination "github.com/ory/x/pagination/keysetpagination_v2"
|
||||
"github.com/ory/x/sqlcon"
|
||||
"github.com/ory/x/uuidx"
|
||||
)
|
||||
|
||||
var _ courier.Persister = new(Persister)
|
||||
|
||||
func (p *Persister) AddMessage(ctx context.Context, m *courier.Message) (err error) {
|
||||
ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.AddMessage")
|
||||
defer otelx.End(span, &err)
|
||||
|
||||
m.NID = p.NetworkID(ctx)
|
||||
m.Status = courier.MessageStatusQueued
|
||||
return sqlcon.HandleError(p.GetConnection(ctx).Create(m)) // do not create eager to avoid identity injection.
|
||||
}
|
||||
|
||||
func (p *Persister) ListMessages(ctx context.Context, filter courier.ListCourierMessagesParameters, opts []keysetpagination.Option) (_ []courier.Message, _ *keysetpagination.Paginator, err error) {
|
||||
ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.ListMessages")
|
||||
defer otelx.End(span, &err)
|
||||
|
||||
q := p.GetConnection(ctx).Where("nid=?", p.NetworkID(ctx))
|
||||
|
||||
if filter.Status != nil {
|
||||
q = q.Where("status=?", *filter.Status)
|
||||
}
|
||||
|
||||
if filter.Recipient != "" {
|
||||
q = q.Where("recipient=?", filter.Recipient)
|
||||
}
|
||||
|
||||
opts = append(opts, keysetpagination.WithDefaultToken(courier.Message{}.DefaultPageToken()))
|
||||
opts = append(opts, keysetpagination.WithDefaultSize(10))
|
||||
paginator, err := keysetpagination.NewPaginator(opts...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
messages := make([]courier.Message, paginator.Size())
|
||||
if err := q.Scope(keysetpagination.Paginate[courier.Message](paginator)).
|
||||
All(&messages); err != nil {
|
||||
return nil, nil, sqlcon.HandleError(err)
|
||||
}
|
||||
|
||||
messages, nextPage := keysetpagination.Result(messages, paginator)
|
||||
|
||||
return messages, nextPage, nil
|
||||
}
|
||||
|
||||
func (p *Persister) NextMessages(ctx context.Context, limit uint8) (messages []courier.Message, err error) {
|
||||
ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.NextMessages")
|
||||
defer otelx.End(span, &err)
|
||||
|
||||
if err := p.Transaction(ctx, func(ctx context.Context, tx *pop.Connection) error {
|
||||
q := tx.Where("nid = ? AND status = ?",
|
||||
p.NetworkID(ctx),
|
||||
courier.MessageStatusQueued,
|
||||
)
|
||||
if tx.Dialect.Name() == dbal.DriverCockroachDB {
|
||||
// On CockroachDB, exclude messages enqueued within the last second.
|
||||
// Concurrent INSERTs land at created_at ~ now; keeping them out of
|
||||
// the scanned span avoids read-write contention between this poll
|
||||
// and message enqueueing, which surfaces as transaction retries
|
||||
// (ReadWithinUncertaintyIntervalError). Skipped messages are picked
|
||||
// up by a subsequent poll. The other databases serve this query
|
||||
// from an MVCC snapshot that does not conflict with concurrent
|
||||
// inserts, so they keep the exact current behavior.
|
||||
q = q.Where("created_at < ?", time.Now().UTC().Add(-time.Second))
|
||||
}
|
||||
|
||||
var m []courier.Message
|
||||
if err := q.
|
||||
Order("created_at ASC").
|
||||
Limit(int(limit)).
|
||||
All(&m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(m) == 0 {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
for i := range m {
|
||||
message := &m[i]
|
||||
message.Status = courier.MessageStatusProcessing
|
||||
if err := update.Generic(ctx, tx, p.r.Tracer(ctx).Tracer(), message, "status"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
messages = m
|
||||
return nil
|
||||
}); err != nil {
|
||||
if errors.Cause(err) == sql.ErrNoRows {
|
||||
return nil, errors.WithStack(courier.ErrQueueEmpty)
|
||||
}
|
||||
return nil, sqlcon.HandleError(err)
|
||||
}
|
||||
|
||||
if len(messages) == 0 {
|
||||
return nil, errors.WithStack(courier.ErrQueueEmpty)
|
||||
}
|
||||
|
||||
return messages, nil
|
||||
}
|
||||
|
||||
func (p *Persister) LatestQueuedMessage(ctx context.Context) (_ *courier.Message, err error) {
|
||||
ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.LatestQueuedMessage")
|
||||
defer otelx.End(span, &err)
|
||||
|
||||
var m courier.Message
|
||||
if err := p.GetConnection(ctx).
|
||||
Where("nid = ? AND status = ?",
|
||||
p.NetworkID(ctx),
|
||||
courier.MessageStatusQueued,
|
||||
).
|
||||
Order("created_at DESC").
|
||||
First(&m); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, errors.WithStack(courier.ErrQueueEmpty)
|
||||
}
|
||||
return nil, sqlcon.HandleError(err)
|
||||
}
|
||||
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
func (p *Persister) SetMessageStatus(ctx context.Context, id uuid.UUID, ms courier.MessageStatus) (err error) {
|
||||
ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.SetMessageStatus")
|
||||
defer otelx.End(span, &err)
|
||||
|
||||
count, err := p.GetConnection(ctx).RawQuery(
|
||||
"UPDATE courier_messages SET status = ? WHERE id = ? AND nid = ?",
|
||||
ms,
|
||||
id,
|
||||
p.NetworkID(ctx),
|
||||
).ExecWithCount()
|
||||
if err != nil {
|
||||
return sqlcon.HandleError(err)
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
return errors.WithStack(sqlcon.ErrNoRows())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Persister) IncrementMessageSendCount(ctx context.Context, id uuid.UUID) (err error) {
|
||||
ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.IncrementMessageSendCount")
|
||||
defer otelx.End(span, &err)
|
||||
|
||||
count, err := p.GetConnection(ctx).RawQuery(
|
||||
"UPDATE courier_messages SET send_count = send_count + 1 WHERE id = ? AND nid = ?",
|
||||
id,
|
||||
p.NetworkID(ctx),
|
||||
).ExecWithCount()
|
||||
if err != nil {
|
||||
return sqlcon.HandleError(err)
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
return errors.WithStack(sqlcon.ErrNoRows())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Persister) FetchMessage(ctx context.Context, msgID uuid.UUID) (_ *courier.Message, err error) {
|
||||
ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.FetchMessage")
|
||||
defer otelx.End(span, &err)
|
||||
|
||||
var message courier.Message
|
||||
if err := p.GetConnection(ctx).
|
||||
Where("id = ? AND nid = ?", msgID, p.NetworkID(ctx)).
|
||||
Eager().
|
||||
First(&message); err != nil {
|
||||
return nil, sqlcon.HandleError(err)
|
||||
}
|
||||
|
||||
return &message, nil
|
||||
}
|
||||
|
||||
func (p *Persister) RecordDispatch(ctx context.Context, msgID uuid.UUID, status courier.CourierMessageDispatchStatus, err error) error {
|
||||
ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.RecordDispatch")
|
||||
defer otelx.End(span, &err)
|
||||
|
||||
dispatch := courier.MessageDispatch{
|
||||
ID: uuidx.NewV4(),
|
||||
MessageID: msgID,
|
||||
Status: status,
|
||||
NID: p.NetworkID(ctx),
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// We use herodot as a carrier for the error's data
|
||||
her := herodot.ToDefaultError(err, "")
|
||||
content, mErr := json.Marshal(her)
|
||||
if mErr != nil {
|
||||
return errors.WithStack(mErr)
|
||||
}
|
||||
dispatch.Error = content
|
||||
}
|
||||
|
||||
if err := p.GetConnection(ctx).Create(&dispatch); err != nil {
|
||||
return sqlcon.HandleError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user