Governed operator lists (organizations, grants, people, billing×4) gain
server-side search, status filters, and 50-row pages with true totals
from count(*) OVER(); state is URL-addressable, out-of-range pages
clamp,
and no-match is distinct from true-empty.
People is the eighth flat sidebar entry: /operator/persons lists persons
newest-joined first (excluding the reserved system person), rows linking
to the existing detail.
Billing gains an operator invoice detail at
/operator/billing/invoices/{invoiceID} reusing the member projection;
open invoices past due present as Overdue (derived, filterable, stored
status untouched); all four views lead with the linked organization and
mute object IDs.
Grants filter over the derived Live/Superseded/Inactive state, the SQL
HAVING predicate pinned to the Go derivation by test. Embedded lists
(org composite ledger, Tier changes) adopt the shared controls under
namespaced params with sibling-state-preserving URLs and scoped htmx
swaps that hold the viewport.
Review corrections: blocked ladder Delete renders disabled with tooltip
and mutations fire toasts; collapse triggers paint their open state;
sections use outside headings; plan topology drops the orphan-product
check; domains policy collapses behind a disclosure.
810 lines
27 KiB
Go
810 lines
27 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.29.0
|
|
// source: grants.sql
|
|
|
|
package entitlements
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/lib/pq"
|
|
"github.com/sqlc-dev/pqtype"
|
|
)
|
|
|
|
const countDeliveringDefaultGrants = `-- name: CountDeliveringDefaultGrants :one
|
|
SELECT COUNT(DISTINCT g.grant_id) FROM core.grants g
|
|
JOIN core.pool_provisions pp ON pp.grant_id = g.grant_id
|
|
WHERE pp.status = 'active' AND g.grant_reason = 'default'
|
|
`
|
|
|
|
// The delivering-grants caption: how many signup defaults are delivering,
|
|
// excluded from the headline above. Same provision join as
|
|
// CountDeliveringOperatorGrants so the two can never disagree about what
|
|
// "delivering" means.
|
|
func (q *Queries) CountDeliveringDefaultGrants(ctx context.Context) (int64, error) {
|
|
row := q.db.QueryRowContext(ctx, countDeliveringDefaultGrants)
|
|
var count int64
|
|
err := row.Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
const countDeliveringOperatorGrants = `-- name: CountDeliveringOperatorGrants :one
|
|
SELECT COUNT(DISTINCT g.grant_id) FROM core.grants g
|
|
JOIN core.pool_provisions pp ON pp.grant_id = g.grant_id
|
|
WHERE pp.status = 'active' AND g.grant_reason <> 'default'
|
|
`
|
|
|
|
// Operator overview headline: grants someone deliberately issued that are
|
|
// delivering right now. Signup defaults are excluded — conferral mints one
|
|
// per org, so they drown the number an operator can act on; their count
|
|
// rides the tile's caption (CountDeliveringDefaultGrants). Non-default is
|
|
// exactly the operator-authored set per chk_grants_default_iff_system_authored.
|
|
//
|
|
// grants.status is an issuance LEDGER — it records what was written and
|
|
// whether it was later revoked, not whether service is flowing today. The
|
|
// current-delivery fact lives on core.pool_provisions.status, so this counts
|
|
// distinct grants that own at least one active provision rather than
|
|
// filtering grants.status alone.
|
|
func (q *Queries) CountDeliveringOperatorGrants(ctx context.Context) (int64, error) {
|
|
row := q.db.QueryRowContext(ctx, countDeliveringOperatorGrants)
|
|
var count int64
|
|
err := row.Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
const createGrant = `-- name: CreateGrant :one
|
|
INSERT INTO core.grants (product_id, granted_to_org_id, granted_by_person_id, grant_reason, description, quantity, valid_from, valid_until, extends_grant_id)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
RETURNING grant_id, product_id, granted_to_billing_account_id, granted_to_org_id, granted_to_person_id, granted_by_person_id, grant_reason, description, quantity, valid_from, valid_until, status, revoked_at, revoked_by_person_id, revocation_reason, metadata, created_at, updated_at, extends_grant_id
|
|
`
|
|
|
|
type CreateGrantParams struct {
|
|
ProductID string `json:"product_id"`
|
|
GrantedToOrgID uuid.NullUUID `json:"granted_to_org_id"`
|
|
GrantedByPersonID uuid.NullUUID `json:"granted_by_person_id"`
|
|
GrantReason string `json:"grant_reason"`
|
|
Description sql.NullString `json:"description"`
|
|
Quantity int32 `json:"quantity"`
|
|
ValidFrom time.Time `json:"valid_from"`
|
|
ValidUntil sql.NullTime `json:"valid_until"`
|
|
ExtendsGrantID uuid.NullUUID `json:"extends_grant_id"`
|
|
}
|
|
|
|
func (q *Queries) CreateGrant(ctx context.Context, arg CreateGrantParams) (Grant, error) {
|
|
row := q.db.QueryRowContext(ctx, createGrant,
|
|
arg.ProductID,
|
|
arg.GrantedToOrgID,
|
|
arg.GrantedByPersonID,
|
|
arg.GrantReason,
|
|
arg.Description,
|
|
arg.Quantity,
|
|
arg.ValidFrom,
|
|
arg.ValidUntil,
|
|
arg.ExtendsGrantID,
|
|
)
|
|
var i Grant
|
|
err := row.Scan(
|
|
&i.GrantID,
|
|
&i.ProductID,
|
|
&i.GrantedToBillingAccountID,
|
|
&i.GrantedToOrgID,
|
|
&i.GrantedToPersonID,
|
|
&i.GrantedByPersonID,
|
|
&i.GrantReason,
|
|
&i.Description,
|
|
&i.Quantity,
|
|
&i.ValidFrom,
|
|
&i.ValidUntil,
|
|
&i.Status,
|
|
&i.RevokedAt,
|
|
&i.RevokedByPersonID,
|
|
&i.RevocationReason,
|
|
&i.Metadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.ExtendsGrantID,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const expireGrant = `-- name: ExpireGrant :one
|
|
UPDATE core.grants
|
|
SET status = 'expired'
|
|
WHERE grant_id = $1 AND status = 'active'
|
|
RETURNING grant_id, product_id, granted_to_billing_account_id, granted_to_org_id, granted_to_person_id, granted_by_person_id, grant_reason, description, quantity, valid_from, valid_until, status, revoked_at, revoked_by_person_id, revocation_reason, metadata, created_at, updated_at, extends_grant_id
|
|
`
|
|
|
|
// Marks a grant expired at its valid_until bound. Enacting the position end is
|
|
// end_conferral's job; this is the decree half (design D5).
|
|
func (q *Queries) ExpireGrant(ctx context.Context, grantID string) (Grant, error) {
|
|
row := q.db.QueryRowContext(ctx, expireGrant, grantID)
|
|
var i Grant
|
|
err := row.Scan(
|
|
&i.GrantID,
|
|
&i.ProductID,
|
|
&i.GrantedToBillingAccountID,
|
|
&i.GrantedToOrgID,
|
|
&i.GrantedToPersonID,
|
|
&i.GrantedByPersonID,
|
|
&i.GrantReason,
|
|
&i.Description,
|
|
&i.Quantity,
|
|
&i.ValidFrom,
|
|
&i.ValidUntil,
|
|
&i.Status,
|
|
&i.RevokedAt,
|
|
&i.RevokedByPersonID,
|
|
&i.RevocationReason,
|
|
&i.Metadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.ExtendsGrantID,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getGrantByID = `-- name: GetGrantByID :one
|
|
SELECT grant_id, product_id, granted_to_billing_account_id, granted_to_org_id, granted_to_person_id, granted_by_person_id, grant_reason, description, quantity, valid_from, valid_until, status, revoked_at, revoked_by_person_id, revocation_reason, metadata, created_at, updated_at, extends_grant_id FROM core.grants
|
|
WHERE grant_id = $1
|
|
`
|
|
|
|
func (q *Queries) GetGrantByID(ctx context.Context, grantID string) (Grant, error) {
|
|
row := q.db.QueryRowContext(ctx, getGrantByID, grantID)
|
|
var i Grant
|
|
err := row.Scan(
|
|
&i.GrantID,
|
|
&i.ProductID,
|
|
&i.GrantedToBillingAccountID,
|
|
&i.GrantedToOrgID,
|
|
&i.GrantedToPersonID,
|
|
&i.GrantedByPersonID,
|
|
&i.GrantReason,
|
|
&i.Description,
|
|
&i.Quantity,
|
|
&i.ValidFrom,
|
|
&i.ValidUntil,
|
|
&i.Status,
|
|
&i.RevokedAt,
|
|
&i.RevokedByPersonID,
|
|
&i.RevocationReason,
|
|
&i.Metadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.ExtendsGrantID,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getGrantLineage = `-- name: GetGrantLineage :many
|
|
WITH RECURSIVE lineage AS (
|
|
SELECT g.grant_id, g.product_id, g.granted_to_billing_account_id, g.granted_to_org_id, g.granted_to_person_id, g.granted_by_person_id, g.grant_reason, g.description, g.quantity, g.valid_from, g.valid_until, g.status, g.revoked_at, g.revoked_by_person_id, g.revocation_reason, g.metadata, g.created_at, g.updated_at, g.extends_grant_id, 0 AS depth FROM core.grants g
|
|
WHERE g.grant_id = $1
|
|
UNION ALL
|
|
SELECT g.grant_id, g.product_id, g.granted_to_billing_account_id, g.granted_to_org_id, g.granted_to_person_id, g.granted_by_person_id, g.grant_reason, g.description, g.quantity, g.valid_from, g.valid_until, g.status, g.revoked_at, g.revoked_by_person_id, g.revocation_reason, g.metadata, g.created_at, g.updated_at, g.extends_grant_id, l.depth + 1 FROM core.grants g
|
|
JOIN lineage l ON g.grant_id = l.extends_grant_id
|
|
)
|
|
SELECT lineage.grant_id, lineage.product_id,
|
|
lineage.granted_to_billing_account_id, lineage.granted_to_org_id,
|
|
lineage.granted_to_person_id, lineage.granted_by_person_id,
|
|
lineage.grant_reason, lineage.description, lineage.quantity,
|
|
lineage.valid_from, lineage.valid_until, lineage.status,
|
|
lineage.revoked_at, lineage.revoked_by_person_id,
|
|
lineage.revocation_reason, lineage.metadata, lineage.created_at,
|
|
lineage.updated_at, lineage.extends_grant_id
|
|
FROM lineage
|
|
ORDER BY lineage.depth ASC
|
|
`
|
|
|
|
type GetGrantLineageRow struct {
|
|
GrantID string `json:"grant_id"`
|
|
ProductID string `json:"product_id"`
|
|
GrantedToBillingAccountID uuid.NullUUID `json:"granted_to_billing_account_id"`
|
|
GrantedToOrgID uuid.NullUUID `json:"granted_to_org_id"`
|
|
GrantedToPersonID uuid.NullUUID `json:"granted_to_person_id"`
|
|
GrantedByPersonID uuid.NullUUID `json:"granted_by_person_id"`
|
|
GrantReason string `json:"grant_reason"`
|
|
Description sql.NullString `json:"description"`
|
|
Quantity int32 `json:"quantity"`
|
|
ValidFrom time.Time `json:"valid_from"`
|
|
ValidUntil sql.NullTime `json:"valid_until"`
|
|
Status string `json:"status"`
|
|
RevokedAt sql.NullTime `json:"revoked_at"`
|
|
RevokedByPersonID uuid.NullUUID `json:"revoked_by_person_id"`
|
|
RevocationReason sql.NullString `json:"revocation_reason"`
|
|
Metadata pqtype.NullRawMessage `json:"metadata"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
ExtendsGrantID uuid.NullUUID `json:"extends_grant_id"`
|
|
}
|
|
|
|
// Walks the ancestry chain of the focal grant via extends_grant_id, returning
|
|
// the focal grant first followed by each ancestor (parent, grandparent, ...)
|
|
// in chain-walk order. Revoked or expired ancestors are included; lineage is
|
|
// about who-came-before, not current validity.
|
|
func (q *Queries) GetGrantLineage(ctx context.Context, grantID string) ([]GetGrantLineageRow, error) {
|
|
rows, err := q.db.QueryContext(ctx, getGrantLineage, grantID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetGrantLineageRow{}
|
|
for rows.Next() {
|
|
var i GetGrantLineageRow
|
|
if err := rows.Scan(
|
|
&i.GrantID,
|
|
&i.ProductID,
|
|
&i.GrantedToBillingAccountID,
|
|
&i.GrantedToOrgID,
|
|
&i.GrantedToPersonID,
|
|
&i.GrantedByPersonID,
|
|
&i.GrantReason,
|
|
&i.Description,
|
|
&i.Quantity,
|
|
&i.ValidFrom,
|
|
&i.ValidUntil,
|
|
&i.Status,
|
|
&i.RevokedAt,
|
|
&i.RevokedByPersonID,
|
|
&i.RevocationReason,
|
|
&i.Metadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.ExtendsGrantID,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listAllGrants = `-- name: ListAllGrants :many
|
|
SELECT grant_id, product_id, granted_to_billing_account_id, granted_to_org_id, granted_to_person_id, granted_by_person_id, grant_reason, description, quantity, valid_from, valid_until, status, revoked_at, revoked_by_person_id, revocation_reason, metadata, created_at, updated_at, extends_grant_id FROM core.grants
|
|
ORDER BY created_at DESC
|
|
`
|
|
|
|
func (q *Queries) ListAllGrants(ctx context.Context) ([]Grant, error) {
|
|
rows, err := q.db.QueryContext(ctx, listAllGrants)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []Grant{}
|
|
for rows.Next() {
|
|
var i Grant
|
|
if err := rows.Scan(
|
|
&i.GrantID,
|
|
&i.ProductID,
|
|
&i.GrantedToBillingAccountID,
|
|
&i.GrantedToOrgID,
|
|
&i.GrantedToPersonID,
|
|
&i.GrantedByPersonID,
|
|
&i.GrantReason,
|
|
&i.Description,
|
|
&i.Quantity,
|
|
&i.ValidFrom,
|
|
&i.ValidUntil,
|
|
&i.Status,
|
|
&i.RevokedAt,
|
|
&i.RevokedByPersonID,
|
|
&i.RevocationReason,
|
|
&i.Metadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.ExtendsGrantID,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listDeliveringGrantsByOrgID = `-- name: ListDeliveringGrantsByOrgID :many
|
|
SELECT DISTINCT
|
|
g.grant_id,
|
|
g.product_id,
|
|
g.granted_to_billing_account_id,
|
|
g.granted_to_org_id,
|
|
g.granted_to_person_id,
|
|
g.granted_by_person_id,
|
|
g.grant_reason,
|
|
g.description,
|
|
g.quantity,
|
|
g.valid_from,
|
|
g.valid_until,
|
|
g.status,
|
|
g.revoked_at,
|
|
g.revoked_by_person_id,
|
|
g.revocation_reason,
|
|
g.metadata,
|
|
g.created_at,
|
|
g.updated_at,
|
|
g.extends_grant_id
|
|
FROM core.grants g
|
|
INNER JOIN core.pool_provisions p ON p.grant_id = g.grant_id
|
|
WHERE g.granted_to_org_id = $1
|
|
AND p.status = 'active'
|
|
ORDER BY g.created_at DESC
|
|
`
|
|
|
|
// Returns only grants currently delivering entitlements to a pool in the
|
|
// given org — i.e. grants with at least one provision in status='active'.
|
|
// Used by the member-facing Sources panel where audit history would be
|
|
// noise (members only need to see what they actually have right now).
|
|
// DISTINCT collapses multi-provision grants to one row.
|
|
func (q *Queries) ListDeliveringGrantsByOrgID(ctx context.Context, grantedToOrgID uuid.NullUUID) ([]Grant, error) {
|
|
rows, err := q.db.QueryContext(ctx, listDeliveringGrantsByOrgID, grantedToOrgID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []Grant{}
|
|
for rows.Next() {
|
|
var i Grant
|
|
if err := rows.Scan(
|
|
&i.GrantID,
|
|
&i.ProductID,
|
|
&i.GrantedToBillingAccountID,
|
|
&i.GrantedToOrgID,
|
|
&i.GrantedToPersonID,
|
|
&i.GrantedByPersonID,
|
|
&i.GrantReason,
|
|
&i.Description,
|
|
&i.Quantity,
|
|
&i.ValidFrom,
|
|
&i.ValidUntil,
|
|
&i.Status,
|
|
&i.RevokedAt,
|
|
&i.RevokedByPersonID,
|
|
&i.RevocationReason,
|
|
&i.Metadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.ExtendsGrantID,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listGrantsByOrgID = `-- name: ListGrantsByOrgID :many
|
|
SELECT grant_id, product_id, granted_to_billing_account_id, granted_to_org_id, granted_to_person_id, granted_by_person_id, grant_reason, description, quantity, valid_from, valid_until, status, revoked_at, revoked_by_person_id, revocation_reason, metadata, created_at, updated_at, extends_grant_id FROM core.grants
|
|
WHERE granted_to_org_id = $1
|
|
ORDER BY created_at DESC
|
|
`
|
|
|
|
func (q *Queries) ListGrantsByOrgID(ctx context.Context, grantedToOrgID uuid.NullUUID) ([]Grant, error) {
|
|
rows, err := q.db.QueryContext(ctx, listGrantsByOrgID, grantedToOrgID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []Grant{}
|
|
for rows.Next() {
|
|
var i Grant
|
|
if err := rows.Scan(
|
|
&i.GrantID,
|
|
&i.ProductID,
|
|
&i.GrantedToBillingAccountID,
|
|
&i.GrantedToOrgID,
|
|
&i.GrantedToPersonID,
|
|
&i.GrantedByPersonID,
|
|
&i.GrantReason,
|
|
&i.Description,
|
|
&i.Quantity,
|
|
&i.ValidFrom,
|
|
&i.ValidUntil,
|
|
&i.Status,
|
|
&i.RevokedAt,
|
|
&i.RevokedByPersonID,
|
|
&i.RevocationReason,
|
|
&i.Metadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.ExtendsGrantID,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listGrantsWithDelivery = `-- name: ListGrantsWithDelivery :many
|
|
SELECT
|
|
g.grant_id,
|
|
g.granted_to_org_id,
|
|
g.product_id,
|
|
g.grant_reason,
|
|
g.quantity,
|
|
g.status AS grant_status,
|
|
g.created_at,
|
|
g.extends_grant_id,
|
|
CASE
|
|
WHEN BOOL_OR(p.status = 'active') THEN 'live'
|
|
WHEN EXISTS (
|
|
SELECT 1 FROM core.grants child
|
|
WHERE child.extends_grant_id = g.grant_id
|
|
) THEN 'superseded'
|
|
ELSE 'inactive'
|
|
END AS delivery_state,
|
|
MAX((
|
|
SELECT child.grant_id::text FROM core.grants child
|
|
WHERE child.extends_grant_id = g.grant_id
|
|
ORDER BY child.created_at DESC
|
|
LIMIT 1
|
|
)) AS replaced_by_grant_id,
|
|
MAX(l.activated_at) AS activated_at,
|
|
MAX(CASE WHEN l.status = 'ended' THEN l.ended_at END) AS ended_at
|
|
FROM core.grants g
|
|
LEFT JOIN core.pool_provisions p ON p.grant_id = g.grant_id
|
|
LEFT JOIN core.pool_provision_ladders l ON l.provision_id = p.provision_id
|
|
WHERE $1::uuid IS NULL OR g.granted_to_org_id = $1::uuid
|
|
GROUP BY g.grant_id, g.granted_to_org_id, g.product_id, g.grant_reason, g.quantity, g.status, g.created_at, g.extends_grant_id
|
|
ORDER BY
|
|
CASE WHEN BOOL_OR(p.status = 'active') THEN 0 ELSE 1 END,
|
|
g.created_at DESC
|
|
`
|
|
|
|
type ListGrantsWithDeliveryRow struct {
|
|
GrantID string `json:"grant_id"`
|
|
GrantedToOrgID uuid.NullUUID `json:"granted_to_org_id"`
|
|
ProductID string `json:"product_id"`
|
|
GrantReason string `json:"grant_reason"`
|
|
Quantity int32 `json:"quantity"`
|
|
GrantStatus string `json:"grant_status"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
ExtendsGrantID uuid.NullUUID `json:"extends_grant_id"`
|
|
DeliveryState string `json:"delivery_state"`
|
|
ReplacedByGrantID interface{} `json:"replaced_by_grant_id"`
|
|
ActivatedAt interface{} `json:"activated_at"`
|
|
EndedAt interface{} `json:"ended_at"`
|
|
}
|
|
|
|
// The one shared delivery-state derivation (ux-honest-surfaces design
|
|
// decision 1): every operator surface that lists grants renders from this
|
|
// query so the grants index and the org-detail composite can never
|
|
// disagree about what "live" / "superseded" / "inactive" mean.
|
|
//
|
|
// live -> at least one linked pool_provision is status='active'
|
|
// superseded -> not live, AND a later grant actually names this one as
|
|
// its extends_grant_id ancestor (a replacement was issued
|
|
// -- see replaced_by_grant_id below)
|
|
// inactive -> not live and not superseded: revoked/expired with no
|
|
// recorded successor, or a grant that never delivered
|
|
//
|
|
// This is deliberately narrower than "not live" alone: a grant whose
|
|
// provision simply ended (revoked, expired) with no replacement grant on
|
|
// record is "inactive", not "superseded" -- "superseded" is reserved for
|
|
// the case the UI can point at a specific replacing grant (the lineage
|
|
// requirement: a superseded row must be able to name what replaced it).
|
|
//
|
|
// replaced_by_grant_id is the most recently created grant (if any) whose
|
|
// extends_grant_id points at this row; NULL when nothing replaced it.
|
|
// extends_grant_id (this grant's own ancestor pointer) is passed through
|
|
// unchanged so the UI can walk lineage in either direction.
|
|
//
|
|
// Off-ladder grants (add-ons / usage / one-time) have no ladder attachment,
|
|
// so activated_at / ended_at may be NULL even when delivery_state='live'.
|
|
// Ordered live-first, then created_at DESC so the operationally-relevant
|
|
// row appears at the top with history beneath.
|
|
//
|
|
// sqlc.narg(org_id): NULL returns every grant system-wide (the grants
|
|
// index, /operator/grants); set, scopes to one organization (org-detail).
|
|
func (q *Queries) ListGrantsWithDelivery(ctx context.Context, orgID uuid.NullUUID) ([]ListGrantsWithDeliveryRow, error) {
|
|
rows, err := q.db.QueryContext(ctx, listGrantsWithDelivery, orgID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListGrantsWithDeliveryRow{}
|
|
for rows.Next() {
|
|
var i ListGrantsWithDeliveryRow
|
|
if err := rows.Scan(
|
|
&i.GrantID,
|
|
&i.GrantedToOrgID,
|
|
&i.ProductID,
|
|
&i.GrantReason,
|
|
&i.Quantity,
|
|
&i.GrantStatus,
|
|
&i.CreatedAt,
|
|
&i.ExtendsGrantID,
|
|
&i.DeliveryState,
|
|
&i.ReplacedByGrantID,
|
|
&i.ActivatedAt,
|
|
&i.EndedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listGrantsWithDeliveryPage = `-- name: ListGrantsWithDeliveryPage :many
|
|
SELECT
|
|
g.grant_id,
|
|
g.granted_to_org_id,
|
|
g.product_id,
|
|
g.grant_reason,
|
|
g.quantity,
|
|
g.status AS grant_status,
|
|
g.created_at,
|
|
g.extends_grant_id,
|
|
CASE
|
|
WHEN BOOL_OR(p.status = 'active') THEN 'live'
|
|
WHEN EXISTS (
|
|
SELECT 1 FROM core.grants child
|
|
WHERE child.extends_grant_id = g.grant_id
|
|
) THEN 'superseded'
|
|
ELSE 'inactive'
|
|
END AS delivery_state,
|
|
MAX((
|
|
SELECT child.grant_id::text FROM core.grants child
|
|
WHERE child.extends_grant_id = g.grant_id
|
|
ORDER BY child.created_at DESC
|
|
LIMIT 1
|
|
)) AS replaced_by_grant_id,
|
|
MAX(l.activated_at) AS activated_at,
|
|
MAX(CASE WHEN l.status = 'ended' THEN l.ended_at END) AS ended_at,
|
|
count(*) OVER() AS total_count
|
|
FROM core.grants g
|
|
LEFT JOIN core.pool_provisions p ON p.grant_id = g.grant_id
|
|
LEFT JOIN core.pool_provision_ladders l ON l.provision_id = p.provision_id
|
|
LEFT JOIN core.organizations org ON org.org_id = g.granted_to_org_id
|
|
WHERE
|
|
$1::text IS NULL
|
|
OR org.name ILIKE ('%' || $1::text || '%')
|
|
OR g.product_id = ANY($2::uuid[])
|
|
GROUP BY g.grant_id, g.granted_to_org_id, g.product_id, g.grant_reason, g.quantity, g.status, g.created_at, g.extends_grant_id
|
|
HAVING
|
|
$3::text IS NULL
|
|
OR (
|
|
CASE
|
|
WHEN BOOL_OR(p.status = 'active') THEN 'live'
|
|
WHEN EXISTS (
|
|
SELECT 1 FROM core.grants child
|
|
WHERE child.extends_grant_id = g.grant_id
|
|
) THEN 'superseded'
|
|
ELSE 'inactive'
|
|
END
|
|
) = $3::text
|
|
ORDER BY
|
|
CASE WHEN BOOL_OR(p.status = 'active') THEN 0 ELSE 1 END,
|
|
g.created_at DESC
|
|
LIMIT $5
|
|
OFFSET $4
|
|
`
|
|
|
|
type ListGrantsWithDeliveryPageParams struct {
|
|
Q sql.NullString `json:"q"`
|
|
ProductIds []string `json:"product_ids"`
|
|
DeliveryState sql.NullString `json:"delivery_state"`
|
|
PageOffset int32 `json:"page_offset"`
|
|
PageLimit int32 `json:"page_limit"`
|
|
}
|
|
|
|
type ListGrantsWithDeliveryPageRow struct {
|
|
GrantID string `json:"grant_id"`
|
|
GrantedToOrgID uuid.NullUUID `json:"granted_to_org_id"`
|
|
ProductID string `json:"product_id"`
|
|
GrantReason string `json:"grant_reason"`
|
|
Quantity int32 `json:"quantity"`
|
|
GrantStatus string `json:"grant_status"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
ExtendsGrantID uuid.NullUUID `json:"extends_grant_id"`
|
|
DeliveryState string `json:"delivery_state"`
|
|
ReplacedByGrantID interface{} `json:"replaced_by_grant_id"`
|
|
ActivatedAt interface{} `json:"activated_at"`
|
|
EndedAt interface{} `json:"ended_at"`
|
|
TotalCount int64 `json:"total_count"`
|
|
}
|
|
|
|
// Paginated, searchable, filterable system-wide variant of
|
|
// ListGrantsWithDelivery for /operator/grants (operator-list-scale,
|
|
// design D4). Same derivation, join shape, and ordering as
|
|
// ListGrantsWithDelivery above -- do not let the two drift, and never
|
|
// scope this one to an org_id; the org-detail composite keeps calling
|
|
// ListGrantsWithDelivery unpaged.
|
|
//
|
|
// sqlc.narg(q): NULL means search is inactive and every row matches.
|
|
// Non-NULL matches a grant when the granted-to organization's name
|
|
// ILIKE's the term, OR the grant's product_id is present in
|
|
// sqlc.narg(product_ids) -- the grants module cannot query the billing
|
|
// schema directly, so the caller (Go) pre-resolves product IDs whose name
|
|
// matches the search term and passes them here; a NULL or empty array
|
|
// with a non-NULL q simply means the q criterion rides on org name alone.
|
|
//
|
|
// sqlc.narg(delivery_state): NULL returns every delivery state; otherwise
|
|
// only rows whose derived state equals the value. The HAVING clause below
|
|
// repeats the exact CASE expression from the SELECT list (HAVING cannot
|
|
// reference a SELECT-list alias) so the filter can never disagree with
|
|
// what the SELECT list -- and therefore the Go derivation reading it --
|
|
// renders as that row's state (a mixed-fixture test pins this agreement).
|
|
//
|
|
// count(*) OVER() is evaluated after GROUP BY/HAVING, so with the GROUP
|
|
// BY below it counts grouped (one-per-grant) rows that passed WHERE and
|
|
// HAVING -- the true total of matching grants, not a join-multiplied
|
|
// count of the underlying pool_provisions/pool_provision_ladders rows.
|
|
func (q *Queries) ListGrantsWithDeliveryPage(ctx context.Context, arg ListGrantsWithDeliveryPageParams) ([]ListGrantsWithDeliveryPageRow, error) {
|
|
rows, err := q.db.QueryContext(ctx, listGrantsWithDeliveryPage,
|
|
arg.Q,
|
|
pq.Array(arg.ProductIds),
|
|
arg.DeliveryState,
|
|
arg.PageOffset,
|
|
arg.PageLimit,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListGrantsWithDeliveryPageRow{}
|
|
for rows.Next() {
|
|
var i ListGrantsWithDeliveryPageRow
|
|
if err := rows.Scan(
|
|
&i.GrantID,
|
|
&i.GrantedToOrgID,
|
|
&i.ProductID,
|
|
&i.GrantReason,
|
|
&i.Quantity,
|
|
&i.GrantStatus,
|
|
&i.CreatedAt,
|
|
&i.ExtendsGrantID,
|
|
&i.DeliveryState,
|
|
&i.ReplacedByGrantID,
|
|
&i.ActivatedAt,
|
|
&i.EndedAt,
|
|
&i.TotalCount,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listRecentGrants = `-- name: ListRecentGrants :many
|
|
SELECT
|
|
grant_id,
|
|
product_id,
|
|
granted_to_org_id,
|
|
granted_by_person_id,
|
|
grant_reason,
|
|
quantity,
|
|
created_at
|
|
FROM core.grants
|
|
WHERE granted_to_org_id IS NOT NULL
|
|
ORDER BY created_at DESC
|
|
LIMIT $1
|
|
`
|
|
|
|
type ListRecentGrantsRow struct {
|
|
GrantID string `json:"grant_id"`
|
|
ProductID string `json:"product_id"`
|
|
GrantedToOrgID uuid.NullUUID `json:"granted_to_org_id"`
|
|
GrantedByPersonID uuid.NullUUID `json:"granted_by_person_id"`
|
|
GrantReason string `json:"grant_reason"`
|
|
Quantity int32 `json:"quantity"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// Recent grants for the operator landing activity timeline. Returns the
|
|
// raw row data with org_id + granted_by_person_id; Go-side merger resolves
|
|
// those UUIDs to display names via batch lookups (avoids cross-schema
|
|
// joins that don't fit per-module sqlc scope). Only org-targeted grants —
|
|
// person-targeted and billing-account-targeted grants don't show up on
|
|
// the org-centric operator timeline.
|
|
func (q *Queries) ListRecentGrants(ctx context.Context, limit int32) ([]ListRecentGrantsRow, error) {
|
|
rows, err := q.db.QueryContext(ctx, listRecentGrants, limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListRecentGrantsRow{}
|
|
for rows.Next() {
|
|
var i ListRecentGrantsRow
|
|
if err := rows.Scan(
|
|
&i.GrantID,
|
|
&i.ProductID,
|
|
&i.GrantedToOrgID,
|
|
&i.GrantedByPersonID,
|
|
&i.GrantReason,
|
|
&i.Quantity,
|
|
&i.CreatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const revokeGrant = `-- name: RevokeGrant :one
|
|
UPDATE core.grants
|
|
SET status = 'revoked', revoked_at = NOW(), revoked_by_person_id = $2, revocation_reason = $3
|
|
WHERE grant_id = $1 AND status = 'active'
|
|
RETURNING grant_id, product_id, granted_to_billing_account_id, granted_to_org_id, granted_to_person_id, granted_by_person_id, grant_reason, description, quantity, valid_from, valid_until, status, revoked_at, revoked_by_person_id, revocation_reason, metadata, created_at, updated_at, extends_grant_id
|
|
`
|
|
|
|
type RevokeGrantParams struct {
|
|
GrantID string `json:"grant_id"`
|
|
RevokedByPersonID uuid.NullUUID `json:"revoked_by_person_id"`
|
|
RevocationReason sql.NullString `json:"revocation_reason"`
|
|
}
|
|
|
|
func (q *Queries) RevokeGrant(ctx context.Context, arg RevokeGrantParams) (Grant, error) {
|
|
row := q.db.QueryRowContext(ctx, revokeGrant, arg.GrantID, arg.RevokedByPersonID, arg.RevocationReason)
|
|
var i Grant
|
|
err := row.Scan(
|
|
&i.GrantID,
|
|
&i.ProductID,
|
|
&i.GrantedToBillingAccountID,
|
|
&i.GrantedToOrgID,
|
|
&i.GrantedToPersonID,
|
|
&i.GrantedByPersonID,
|
|
&i.GrantReason,
|
|
&i.Description,
|
|
&i.Quantity,
|
|
&i.ValidFrom,
|
|
&i.ValidUntil,
|
|
&i.Status,
|
|
&i.RevokedAt,
|
|
&i.RevokedByPersonID,
|
|
&i.RevocationReason,
|
|
&i.Metadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.ExtendsGrantID,
|
|
)
|
|
return i, err
|
|
}
|