// Code generated by sqlc. DO NOT EDIT. // versions: // sqlc v1.31.1 // source: notifications.sql package dbq import ( "context" "github.com/jackc/pgx/v5/pgtype" ) const countUnreadNotifications = `-- name: CountUnreadNotifications :one SELECT count(*) FROM user_notifications WHERE user_id = $1 AND read_at IS NULL ` func (q *Queries) CountUnreadNotifications(ctx context.Context, userID pgtype.UUID) (int64, error) { row := q.db.QueryRow(ctx, countUnreadNotifications, userID) var count int64 err := row.Scan(&count) return count, err } const getNotificationEmailSettings = `-- name: GetNotificationEmailSettings :one SELECT id, summary_hour, batch_window_minutes, updated_at FROM notification_email_settings WHERE id = true ` func (q *Queries) GetNotificationEmailSettings(ctx context.Context) (NotificationEmailSetting, error) { row := q.db.QueryRow(ctx, getNotificationEmailSettings) var i NotificationEmailSetting err := row.Scan( &i.ID, &i.SummaryHour, &i.BatchWindowMinutes, &i.UpdatedAt, ) return i, err } const insertNotification = `-- name: InsertNotification :one INSERT INTO user_notifications (user_id, kind, payload, emailed_at) VALUES ($1, $2, $3, CASE WHEN $4::boolean THEN NULL ELSE now() END) RETURNING id ` type InsertNotificationParams struct { UserID pgtype.UUID Kind string Payload []byte EmailWanted bool } // M489 notifications inbox (#726). Rows are written only through // internal/notifications.Notifier, which decides recipients and honours each // recipient's inbox preference before it reaches these. // email_wanted is the recipient's email channel for this kind as of now. A // row nobody wants emailed is stamped at once, so the digest never has to // judge it and a later "email on" doesn't send an old backlog. func (q *Queries) InsertNotification(ctx context.Context, arg InsertNotificationParams) (pgtype.UUID, error) { row := q.db.QueryRow(ctx, insertNotification, arg.UserID, arg.Kind, arg.Payload, arg.EmailWanted, ) var id pgtype.UUID err := row.Scan(&id) return id, err } const listAdminUserIDs = `-- name: ListAdminUserIDs :many SELECT id FROM users WHERE is_admin = true ORDER BY created_at, id ` func (q *Queries) ListAdminUserIDs(ctx context.Context) ([]pgtype.UUID, error) { rows, err := q.db.Query(ctx, listAdminUserIDs) if err != nil { return nil, err } defer rows.Close() var items []pgtype.UUID for rows.Next() { var id pgtype.UUID if err := rows.Scan(&id); err != nil { return nil, err } items = append(items, id) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listEmailPendingNotifications = `-- name: ListEmailPendingNotifications :many SELECT n.id, n.user_id, n.kind, n.payload, n.created_at, u.email::text AS email, u.username, u.display_name, u.timezone, now()::timestamptz AS read_as_of FROM user_notifications n JOIN users u ON u.id = n.user_id WHERE n.read_at IS NULL AND n.emailed_at IS NULL AND u.email IS NOT NULL AND u.email <> '' ORDER BY n.user_id, n.created_at, n.id ` type ListEmailPendingNotificationsRow struct { ID pgtype.UUID UserID pgtype.UUID Kind string Payload []byte CreatedAt pgtype.Timestamptz Email string Username string DisplayName *string Timezone string ReadAsOf pgtype.Timestamptz } // Email digest (#5346) ------------------------------------------------------ // Every unread, un-emailed row of a user who has an address, oldest first. // Read rows are never selected: the user has seen them, so they are not news. // read_as_of is the database's clock at the read, handed back to // MarkNotificationsEmailed. func (q *Queries) ListEmailPendingNotifications(ctx context.Context) ([]ListEmailPendingNotificationsRow, error) { rows, err := q.db.Query(ctx, listEmailPendingNotifications) if err != nil { return nil, err } defer rows.Close() var items []ListEmailPendingNotificationsRow for rows.Next() { var i ListEmailPendingNotificationsRow if err := rows.Scan( &i.ID, &i.UserID, &i.Kind, &i.Payload, &i.CreatedAt, &i.Email, &i.Username, &i.DisplayName, &i.Timezone, &i.ReadAsOf, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listNotificationEmailState = `-- name: ListNotificationEmailState :many SELECT user_id, email_group, batch_opened_at, last_sent_at, failures, retry_after FROM user_notification_email_state ` func (q *Queries) ListNotificationEmailState(ctx context.Context) ([]UserNotificationEmailState, error) { rows, err := q.db.Query(ctx, listNotificationEmailState) if err != nil { return nil, err } defer rows.Close() var items []UserNotificationEmailState for rows.Next() { var i UserNotificationEmailState if err := rows.Scan( &i.UserID, &i.EmailGroup, &i.BatchOpenedAt, &i.LastSentAt, &i.Failures, &i.RetryAfter, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listNotificationPrefsForKind = `-- name: ListNotificationPrefsForKind :many SELECT user_id, inbox, phone, email FROM user_notification_prefs WHERE kind = $1 AND user_id = ANY($2::uuid[]) ` type ListNotificationPrefsForKindParams struct { Kind string UserIds []pgtype.UUID } type ListNotificationPrefsForKindRow struct { UserID pgtype.UUID Inbox bool Phone bool Email bool } // The stored prefs of one kind for a set of recipients. A recipient with no // row has the kind's defaults. func (q *Queries) ListNotificationPrefsForKind(ctx context.Context, arg ListNotificationPrefsForKindParams) ([]ListNotificationPrefsForKindRow, error) { rows, err := q.db.Query(ctx, listNotificationPrefsForKind, arg.Kind, arg.UserIds) if err != nil { return nil, err } defer rows.Close() var items []ListNotificationPrefsForKindRow for rows.Next() { var i ListNotificationPrefsForKindRow if err := rows.Scan( &i.UserID, &i.Inbox, &i.Phone, &i.Email, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listNotificationPrefsForUser = `-- name: ListNotificationPrefsForUser :many SELECT kind, inbox, phone, email FROM user_notification_prefs WHERE user_id = $1 ` type ListNotificationPrefsForUserRow struct { Kind string Inbox bool Phone bool Email bool } func (q *Queries) ListNotificationPrefsForUser(ctx context.Context, userID pgtype.UUID) ([]ListNotificationPrefsForUserRow, error) { rows, err := q.db.Query(ctx, listNotificationPrefsForUser, userID) if err != nil { return nil, err } defer rows.Close() var items []ListNotificationPrefsForUserRow for rows.Next() { var i ListNotificationPrefsForUserRow if err := rows.Scan( &i.Kind, &i.Inbox, &i.Phone, &i.Email, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listNotifications = `-- name: ListNotifications :many SELECT id, kind, payload, created_at, read_at FROM user_notifications WHERE user_id = $1 AND ($2::timestamptz IS NULL OR (created_at, id) < ($2::timestamptz, $3::uuid)) ORDER BY created_at DESC, id DESC LIMIT $4 ` type ListNotificationsParams struct { UserID pgtype.UUID BeforeCreatedAt pgtype.Timestamptz BeforeID pgtype.UUID PageLimit int32 } type ListNotificationsRow struct { ID pgtype.UUID Kind string Payload []byte CreatedAt pgtype.Timestamptz ReadAt pgtype.Timestamptz } // Newest first, keyset-paged on (created_at, id). Pass both cursor halves // from the last row of the previous page, or neither for the first page. func (q *Queries) ListNotifications(ctx context.Context, arg ListNotificationsParams) ([]ListNotificationsRow, error) { rows, err := q.db.Query(ctx, listNotifications, arg.UserID, arg.BeforeCreatedAt, arg.BeforeID, arg.PageLimit, ) if err != nil { return nil, err } defer rows.Close() var items []ListNotificationsRow for rows.Next() { var i ListNotificationsRow if err := rows.Scan( &i.ID, &i.Kind, &i.Payload, &i.CreatedAt, &i.ReadAt, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const markAllNotificationsRead = `-- name: MarkAllNotificationsRead :execrows UPDATE user_notifications SET read_at = now() WHERE user_id = $1 AND read_at IS NULL AND ($2::timestamptz IS NULL OR created_at <= $2) ` type MarkAllNotificationsReadParams struct { UserID pgtype.UUID UpTo pgtype.Timestamptz } // up_to, when set, limits it to what existed when the user asked: a "mark // all read" queued offline and replayed later must not mark notices that // arrived in between, which the user never saw. A coalesced row updated // since then carries a newer created_at, so it stays unread too. func (q *Queries) MarkAllNotificationsRead(ctx context.Context, arg MarkAllNotificationsReadParams) (int64, error) { result, err := q.db.Exec(ctx, markAllNotificationsRead, arg.UserID, arg.UpTo) if err != nil { return 0, err } return result.RowsAffected(), nil } const markNotificationRead = `-- name: MarkNotificationRead :execrows UPDATE user_notifications SET read_at = COALESCE(read_at, now()) WHERE id = $1 AND user_id = $2 ` type MarkNotificationReadParams struct { ID pgtype.UUID UserID pgtype.UUID } // Scoped to the owner: another user's id matches no row. Marking an already // read row keeps its original read_at and still matches, so a repeat is not // mistaken for "not yours". func (q *Queries) MarkNotificationRead(ctx context.Context, arg MarkNotificationReadParams) (int64, error) { result, err := q.db.Exec(ctx, markNotificationRead, arg.ID, arg.UserID) if err != nil { return 0, err } return result.RowsAffected(), nil } const markNotificationsEmailed = `-- name: MarkNotificationsEmailed :execrows UPDATE user_notifications SET emailed_at = now() WHERE id = ANY($1::uuid[]) AND created_at <= $2::timestamptz AND emailed_at IS NULL ` type MarkNotificationsEmailedParams struct { Ids []pgtype.UUID ReadAsOf pgtype.Timestamptz } // Stamps the rows an email carried, or that were judged not to need one. // read_as_of is from ListEmailPendingNotifications: a coalesced row updated // since that read carries a later created_at, holds newer news, and stays // pending for the next email. func (q *Queries) MarkNotificationsEmailed(ctx context.Context, arg MarkNotificationsEmailedParams) (int64, error) { result, err := q.db.Exec(ctx, markNotificationsEmailed, arg.Ids, arg.ReadAsOf) if err != nil { return 0, err } return result.RowsAffected(), nil } const trimNotifications = `-- name: TrimNotifications :execrows DELETE FROM user_notifications WHERE (read_at IS NOT NULL AND read_at < $1) OR created_at < $2 ` type TrimNotificationsParams struct { ReadCutoff pgtype.Timestamptz AnyCutoff pgtype.Timestamptz } // Retention: read rows go after read_cutoff, and anything at all after // any_cutoff, so an inbox nobody opens doesn't grow without bound either. func (q *Queries) TrimNotifications(ctx context.Context, arg TrimNotificationsParams) (int64, error) { result, err := q.db.Exec(ctx, trimNotifications, arg.ReadCutoff, arg.AnyCutoff) if err != nil { return 0, err } return result.RowsAffected(), nil } const updateNotificationEmailSettings = `-- name: UpdateNotificationEmailSettings :one UPDATE notification_email_settings SET summary_hour = $1, batch_window_minutes = $2, updated_at = now() WHERE id = true RETURNING id, summary_hour, batch_window_minutes, updated_at ` type UpdateNotificationEmailSettingsParams struct { SummaryHour int32 BatchWindowMinutes int32 } // Migration 0074's CHECKs are the backstop behind the service's validation. func (q *Queries) UpdateNotificationEmailSettings(ctx context.Context, arg UpdateNotificationEmailSettingsParams) (NotificationEmailSetting, error) { row := q.db.QueryRow(ctx, updateNotificationEmailSettings, arg.SummaryHour, arg.BatchWindowMinutes) var i NotificationEmailSetting err := row.Scan( &i.ID, &i.SummaryHour, &i.BatchWindowMinutes, &i.UpdatedAt, ) return i, err } const upsertCoalescedNotification = `-- name: UpsertCoalescedNotification :one INSERT INTO user_notifications (user_id, kind, payload, coalesce_key, emailed_at) VALUES ($1, $2, $3, $4, CASE WHEN $5::boolean THEN NULL ELSE now() END) ON CONFLICT (user_id, coalesce_key) WHERE read_at IS NULL AND coalesce_key IS NOT NULL DO UPDATE SET payload = CASE WHEN $6::boolean THEN EXCLUDED.payload || jsonb_build_object('count', COALESCE((user_notifications.payload->>'count')::bigint, 0) + COALESCE((EXCLUDED.payload->>'count')::bigint, 0)) ELSE EXCLUDED.payload END, created_at = now(), emailed_at = EXCLUDED.emailed_at RETURNING id ` type UpsertCoalescedNotificationParams struct { UserID pgtype.UUID Kind string Payload []byte CoalesceKey *string EmailWanted bool SumCount bool } // One unread row per (user, coalesce_key). A new event while that row is // unread updates it in place and moves it back to the top of the inbox. // // sum_count: the payload's `count` adds to the unread row's count rather than // replacing it. For kinds whose event is "N more happened" (tracks marked // missing). Kinds whose event states the whole current total (pending // duplicate groups) pass false and the payload simply replaces. // // emailed_at is cleared so the newer state goes out in the next batch; an // emailed-but-unread row that keeps growing is news the user hasn't seen. // Unless email_wanted is false, as in InsertNotification. func (q *Queries) UpsertCoalescedNotification(ctx context.Context, arg UpsertCoalescedNotificationParams) (pgtype.UUID, error) { row := q.db.QueryRow(ctx, upsertCoalescedNotification, arg.UserID, arg.Kind, arg.Payload, arg.CoalesceKey, arg.EmailWanted, arg.SumCount, ) var id pgtype.UUID err := row.Scan(&id) return id, err } const upsertNotificationEmailState = `-- name: UpsertNotificationEmailState :exec INSERT INTO user_notification_email_state (user_id, email_group, batch_opened_at, last_sent_at, failures, retry_after) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (user_id, email_group) DO UPDATE SET batch_opened_at = EXCLUDED.batch_opened_at, last_sent_at = EXCLUDED.last_sent_at, failures = EXCLUDED.failures, retry_after = EXCLUDED.retry_after ` type UpsertNotificationEmailStateParams struct { UserID pgtype.UUID EmailGroup string BatchOpenedAt pgtype.Timestamptz LastSentAt pgtype.Timestamptz Failures int32 RetryAfter pgtype.Timestamptz } func (q *Queries) UpsertNotificationEmailState(ctx context.Context, arg UpsertNotificationEmailStateParams) error { _, err := q.db.Exec(ctx, upsertNotificationEmailState, arg.UserID, arg.EmailGroup, arg.BatchOpenedAt, arg.LastSentAt, arg.Failures, arg.RetryAfter, ) return err } const upsertNotificationPref = `-- name: UpsertNotificationPref :exec INSERT INTO user_notification_prefs (user_id, kind, inbox, phone, email) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (user_id, kind) DO UPDATE SET inbox = EXCLUDED.inbox, phone = EXCLUDED.phone, email = EXCLUDED.email, updated_at = now() ` type UpsertNotificationPrefParams struct { UserID pgtype.UUID Kind string Inbox bool Phone bool Email bool } func (q *Queries) UpsertNotificationPref(ctx context.Context, arg UpsertNotificationPrefParams) error { _, err := q.db.Exec(ctx, upsertNotificationPref, arg.UserID, arg.Kind, arg.Inbox, arg.Phone, arg.Email, ) return err }