feat: HA scheduler leadership + templated email log

HA scheduler: the job scheduler and alert evaluator hold session-scoped
PostgreSQL advisory locks on pinned pool connections — replicas scan for
due work exactly once, failover verified live with two replicas. Manual
and hook-triggered runs stay available on any replica.

Email surface: mailer renders registered templates (invitation); every
send is logged to core.email_log with status and error text. New
GET /projects/:id/emails endpoint; Members page shows an Email log card.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-24 19:27:06 +02:00
1 parent 45a1621909
commit f747b33b46
24 files changed
+669 -16

No files matched your search

@@ -0,0 +1,91 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// source: email_log.sql
package db
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const insertEmailLog = `-- name: InsertEmailLog :one
INSERT INTO core.email_log (project_id, template, to_email, subject, status, error)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id, project_id, template, to_email, subject, status, error, created_at
`
type InsertEmailLogParams struct {
ProjectID pgtype.UUID `json:"project_id"`
Template string `json:"template"`
ToEmail string `json:"to_email"`
Subject string `json:"subject"`
Status string `json:"status"`
Error string `json:"error"`
}
func (q *Queries) InsertEmailLog(ctx context.Context, arg InsertEmailLogParams) (CoreEmailLog, error) {
row := q.db.QueryRow(ctx, insertEmailLog,
arg.ProjectID,
arg.Template,
arg.ToEmail,
arg.Subject,
arg.Status,
arg.Error,
)
var i CoreEmailLog
err := row.Scan(
&i.ID,
&i.ProjectID,
&i.Template,
&i.ToEmail,
&i.Subject,
&i.Status,
&i.Error,
&i.CreatedAt,
)
return i, err
}
const listEmailLog = `-- name: ListEmailLog :many
SELECT id, project_id, template, to_email, subject, status, error, created_at FROM core.email_log
WHERE project_id = $1
ORDER BY created_at DESC
LIMIT $2
`
type ListEmailLogParams struct {
ProjectID pgtype.UUID `json:"project_id"`
Limit int32 `json:"limit"`
}
func (q *Queries) ListEmailLog(ctx context.Context, arg ListEmailLogParams) ([]CoreEmailLog, error) {
rows, err := q.db.Query(ctx, listEmailLog, arg.ProjectID, arg.Limit)
if err != nil {
return nil, err
}
defer rows.Close()
var items []CoreEmailLog
for rows.Next() {
var i CoreEmailLog
if err := rows.Scan(
&i.ID,
&i.ProjectID,
&i.Template,
&i.ToEmail,
&i.Subject,
&i.Status,
&i.Error,
&i.CreatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
@@ -243,6 +243,17 @@ type CoreDocument struct {
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
type CoreEmailLog struct {
ID uuid.UUID `json:"id"`
ProjectID pgtype.UUID `json:"project_id"`
Template string `json:"template"`
ToEmail string `json:"to_email"`
Subject string `json:"subject"`
Status string `json:"status"`
Error string `json:"error"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
}
type CoreEvent struct {
ID int64 `json:"id"`
ProjectID uuid.UUID `json:"project_id"`
@@ -84,6 +84,7 @@ type Querier interface {
GetWebhookByID(ctx context.Context, id uuid.UUID) (CoreWebhook, error)
GetWebhookDeliveryByID(ctx context.Context, id uuid.UUID) (CoreWebhookDelivery, error)
HasFingerprintSeen(ctx context.Context, arg HasFingerprintSeenParams) (bool, error)
InsertEmailLog(ctx context.Context, arg InsertEmailLogParams) (CoreEmailLog, error)
InsertEvent(ctx context.Context, arg InsertEventParams) (CoreEvent, error)
InsertScheduledJobRun(ctx context.Context, arg InsertScheduledJobRunParams) (CoreScheduledJobRun, error)
InsertWebhookDelivery(ctx context.Context, arg InsertWebhookDeliveryParams) (CoreWebhookDelivery, error)
@@ -100,6 +101,7 @@ type Querier interface {
ListDBConnections(ctx context.Context, projectID uuid.UUID) ([]CoreDbConnection, error)
ListDocuments(ctx context.Context, arg ListDocumentsParams) ([]CoreDocument, error)
ListDueScheduledJobs(ctx context.Context, nextRunAt pgtype.Timestamptz) ([]CoreScheduledJob, error)
ListEmailLog(ctx context.Context, arg ListEmailLogParams) ([]CoreEmailLog, error)
ListEnabledAlertRules(ctx context.Context) ([]CoreAlertRule, error)
ListErrorGroups(ctx context.Context, arg ListErrorGroupsParams) ([]ListErrorGroupsRow, error)
ListEvents(ctx context.Context, arg ListEventsParams) ([]ListEventsRow, error)