// Code generated by sqlc. DO NOT EDIT. // versions: // sqlc v1.31.1 // source: diagnostics.sql package dbq import ( "context" "github.com/jackc/pgx/v5/pgtype" ) const gcPruneDiagnostics = `-- name: GcPruneDiagnostics :execrows DELETE FROM diagnostic_events WHERE received_at < now() - INTERVAL '30 days' ` // Retention sweep for the gc worker. Deletes diagnostic_events older // than 30 days by server clock (received_at) so a skewed client clock // can't keep rows alive. Matches the other gc lifecycle sweeps' // hardcoded-threshold style; diagnostics are debug telemetry, not user // data, and 30 days is ample to investigate a live incident after the // fact. Returns the affected row count for the sweep log line. func (q *Queries) GcPruneDiagnostics(ctx context.Context) (int64, error) { result, err := q.db.Exec(ctx, gcPruneDiagnostics) if err != nil { return 0, err } return result.RowsAffected(), nil } const insertDiagnosticEvent = `-- name: InsertDiagnosticEvent :one INSERT INTO diagnostic_events ( user_id, client_id, app_version, os_version, kind, payload, occurred_at ) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id, received_at ` type InsertDiagnosticEventParams struct { UserID pgtype.UUID ClientID string AppVersion *string OsVersion *string Kind string Payload []byte OccurredAt pgtype.Timestamptz } type InsertDiagnosticEventRow struct { ID pgtype.UUID ReceivedAt pgtype.Timestamptz } // Records one client-reported diagnostic event. The writer validates the // `kind` category against its whitelist before this runs; the CHECK // constraint is the belt-and-braces guard. payload carries the finer // event discriminator + all event-specific fields. Returns received_at // so the caller can confirm ingest. func (q *Queries) InsertDiagnosticEvent(ctx context.Context, arg InsertDiagnosticEventParams) (InsertDiagnosticEventRow, error) { row := q.db.QueryRow(ctx, insertDiagnosticEvent, arg.UserID, arg.ClientID, arg.AppVersion, arg.OsVersion, arg.Kind, arg.Payload, arg.OccurredAt, ) var i InsertDiagnosticEventRow err := row.Scan(&i.ID, &i.ReceivedAt) return i, err } const listAdminDiagnostics = `-- name: ListAdminDiagnostics :many SELECT de.id AS id, de.user_id AS user_id, u.username AS username, de.client_id AS client_id, de.app_version AS app_version, de.os_version AS os_version, de.kind AS kind, de.payload AS payload, de.occurred_at AS occurred_at, de.received_at AS received_at FROM diagnostic_events de JOIN users u ON u.id = de.user_id WHERE ($1::uuid IS NULL OR de.user_id = $1::uuid) AND ($2::text IS NULL OR de.client_id = $2::text) AND ($3::text IS NULL OR de.kind = $3::text) AND ($4::timestamptz IS NULL OR de.occurred_at >= $4::timestamptz) AND ($5::timestamptz IS NULL OR de.occurred_at <= $5::timestamptz) ORDER BY de.occurred_at DESC LIMIT $7::int OFFSET $6::int ` type ListAdminDiagnosticsParams struct { UserID pgtype.UUID ClientID *string Kind *string FromTs pgtype.Timestamptz ToTs pgtype.Timestamptz Off int32 Lim int32 } type ListAdminDiagnosticsRow struct { ID pgtype.UUID UserID pgtype.UUID Username string ClientID string AppVersion *string OsVersion *string Kind string Payload []byte OccurredAt pgtype.Timestamptz ReceivedAt pgtype.Timestamptz } // Admin timeline query. All filters are optional (NULL = no filter): // account (user_id), device (client_id), category (kind), and the // occurred_at window (from_ts/to_ts). Newest-first; the SPA reverses for // a chronological timeline / export. Joined with users for the username // so the view renders without a second round-trip. func (q *Queries) ListAdminDiagnostics(ctx context.Context, arg ListAdminDiagnosticsParams) ([]ListAdminDiagnosticsRow, error) { rows, err := q.db.Query(ctx, listAdminDiagnostics, arg.UserID, arg.ClientID, arg.Kind, arg.FromTs, arg.ToTs, arg.Off, arg.Lim, ) if err != nil { return nil, err } defer rows.Close() var items []ListAdminDiagnosticsRow for rows.Next() { var i ListAdminDiagnosticsRow if err := rows.Scan( &i.ID, &i.UserID, &i.Username, &i.ClientID, &i.AppVersion, &i.OsVersion, &i.Kind, &i.Payload, &i.OccurredAt, &i.ReceivedAt, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listDiagnosticDevices = `-- name: ListDiagnosticDevices :many SELECT de.client_id AS client_id, de.user_id AS user_id, u.username AS username, max(de.app_version)::text AS app_version, max(de.os_version)::text AS os_version, max(de.occurred_at)::timestamptz AS last_seen, count(*) AS event_count FROM diagnostic_events de JOIN users u ON u.id = de.user_id WHERE ($1::uuid IS NULL OR de.user_id = $1::uuid) GROUP BY de.client_id, de.user_id, u.username ORDER BY last_seen DESC ` type ListDiagnosticDevicesRow struct { ClientID string UserID pgtype.UUID Username string AppVersion string OsVersion string LastSeen pgtype.Timestamptz EventCount int64 } // Distinct devices that have reported, for the admin device filter + // "who is currently reporting" overview. Optionally scoped to one account. func (q *Queries) ListDiagnosticDevices(ctx context.Context, userID pgtype.UUID) ([]ListDiagnosticDevicesRow, error) { rows, err := q.db.Query(ctx, listDiagnosticDevices, userID) if err != nil { return nil, err } defer rows.Close() var items []ListDiagnosticDevicesRow for rows.Next() { var i ListDiagnosticDevicesRow if err := rows.Scan( &i.ClientID, &i.UserID, &i.Username, &i.AppVersion, &i.OsVersion, &i.LastSeen, &i.EventCount, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil }