Replace the entity slugs on organizations, workspaces, resource pools, and plan ladders with nullable `key` columns and add keys to products, prices, and entitlement sets. Rename `providers.slug` to `provider` and add partial unique indexes for system and org role names. Assign invoice numbers per billing account from a gapless transactional counter; Stripe's number moves to the invoice mapping as an external reference. Seeds, fixtures, and the operator lookup address rows by key, and the returning-login resync no longer blanks a display name when the IdP sends no `name` claim.
432 lines
13 KiB
Go
432 lines
13 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.29.0
|
|
// source: organizations.sql
|
|
|
|
package organization
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"time"
|
|
)
|
|
|
|
const countTeamOrganizations = `-- name: CountTeamOrganizations :one
|
|
SELECT COUNT(*) FROM core.organizations o
|
|
JOIN core.org_types t ON t.org_type = o.org_type
|
|
WHERE o.status = 'active' AND o.org_type <> 'personal' AND NOT t.is_reserved
|
|
`
|
|
|
|
// Operator overview headline. Personal orgs exist one-per-member by
|
|
// structural convention (design/organization/architecture.md §2), so an
|
|
// organizations count that includes them tracks the people count and reads
|
|
// as an error. Negative-space predicate instead of org_type = 'team':
|
|
// everything active that is neither the auto-created personal type nor a
|
|
// reserved type counts, so team, enterprise, and deployment-defined types
|
|
// need no code change here and the reserved system org never appears.
|
|
func (q *Queries) CountTeamOrganizations(ctx context.Context) (int64, error) {
|
|
row := q.db.QueryRowContext(ctx, countTeamOrganizations)
|
|
var count int64
|
|
err := row.Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
const createOrganization = `-- name: CreateOrganization :one
|
|
INSERT INTO core.organizations (name, org_type, owner_person_id, key)
|
|
VALUES ($1, $2, $3, $4)
|
|
RETURNING org_id, name, key, org_type, owner_person_id, status, created_at, updated_at
|
|
`
|
|
|
|
type CreateOrganizationParams struct {
|
|
Name string `json:"name"`
|
|
OrgType string `json:"org_type"`
|
|
OwnerPersonID string `json:"owner_person_id"`
|
|
Key sql.NullString `json:"key"`
|
|
}
|
|
|
|
// Identity is org_id, presentation is name, and organization names are
|
|
// deliberately not unique. `key` is the optional declarative address
|
|
// (entity-keys §1 to §4): a caller outside the database -- a seed, a
|
|
// configuration loader, an API client -- supplies it to name this row
|
|
// literally. UI-created organizations leave it NULL, and a personal
|
|
// organization never gets one: a key is never derived from a person's login
|
|
// or display name (§4).
|
|
func (q *Queries) CreateOrganization(ctx context.Context, arg CreateOrganizationParams) (Organization, error) {
|
|
row := q.db.QueryRowContext(ctx, createOrganization,
|
|
arg.Name,
|
|
arg.OrgType,
|
|
arg.OwnerPersonID,
|
|
arg.Key,
|
|
)
|
|
var i Organization
|
|
err := row.Scan(
|
|
&i.OrgID,
|
|
&i.Name,
|
|
&i.Key,
|
|
&i.OrgType,
|
|
&i.OwnerPersonID,
|
|
&i.Status,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const ensureSystemOrganization = `-- name: EnsureSystemOrganization :exec
|
|
INSERT INTO core.organizations (name, org_type, owner_person_id, key)
|
|
VALUES ($1, $2, $3, 'system')
|
|
ON CONFLICT (org_type) WHERE org_type = 'system' DO NOTHING
|
|
`
|
|
|
|
type EnsureSystemOrganizationParams struct {
|
|
Name string `json:"name"`
|
|
OrgType string `json:"org_type"`
|
|
OwnerPersonID string `json:"owner_person_id"`
|
|
}
|
|
|
|
// Idempotently insert the singleton System organization. The partial unique
|
|
// index uq_organizations_one_system enforces "exactly one System tenant", and
|
|
// the conflict target repeats the index predicate so Postgres can infer it.
|
|
// Read back with GetSystemOrganization.
|
|
//
|
|
// `system` is one of the two keys the system writes for itself (entity-keys
|
|
// §4): a fixed constant for a row the console creates by design, not a value
|
|
// derived from anybody's data. It is written here rather than by the Go
|
|
// caller so the only path that creates the System tenant is also the only
|
|
// path that names it.
|
|
func (q *Queries) EnsureSystemOrganization(ctx context.Context, arg EnsureSystemOrganizationParams) error {
|
|
_, err := q.db.ExecContext(ctx, ensureSystemOrganization, arg.Name, arg.OrgType, arg.OwnerPersonID)
|
|
return err
|
|
}
|
|
|
|
const getOrganizationByID = `-- name: GetOrganizationByID :one
|
|
SELECT org_id, name, key, org_type, owner_person_id, status, created_at, updated_at FROM core.organizations
|
|
WHERE org_id = $1
|
|
`
|
|
|
|
func (q *Queries) GetOrganizationByID(ctx context.Context, orgID string) (Organization, error) {
|
|
row := q.db.QueryRowContext(ctx, getOrganizationByID, orgID)
|
|
var i Organization
|
|
err := row.Scan(
|
|
&i.OrgID,
|
|
&i.Name,
|
|
&i.Key,
|
|
&i.OrgType,
|
|
&i.OwnerPersonID,
|
|
&i.Status,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getOrganizationByKey = `-- name: GetOrganizationByKey :one
|
|
SELECT org_id, name, key, org_type, owner_person_id, status, created_at, updated_at FROM core.organizations
|
|
WHERE key = $1
|
|
`
|
|
|
|
// Root-scoped key resolver (entity-keys §5): the query that makes `key` a
|
|
// whole feature rather than a seed-only column. uq_organizations_key
|
|
// guarantees at most one row matches; a NULL key never matches because the
|
|
// parameter is compared with `=`.
|
|
func (q *Queries) GetOrganizationByKey(ctx context.Context, key sql.NullString) (Organization, error) {
|
|
row := q.db.QueryRowContext(ctx, getOrganizationByKey, key)
|
|
var i Organization
|
|
err := row.Scan(
|
|
&i.OrgID,
|
|
&i.Name,
|
|
&i.Key,
|
|
&i.OrgType,
|
|
&i.OwnerPersonID,
|
|
&i.Status,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getOrganizationsByOwner = `-- name: GetOrganizationsByOwner :many
|
|
SELECT org_id, name, key, org_type, owner_person_id, status, created_at, updated_at FROM core.organizations
|
|
WHERE owner_person_id = $1
|
|
ORDER BY name
|
|
`
|
|
|
|
func (q *Queries) GetOrganizationsByOwner(ctx context.Context, ownerPersonID string) ([]Organization, error) {
|
|
rows, err := q.db.QueryContext(ctx, getOrganizationsByOwner, ownerPersonID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []Organization{}
|
|
for rows.Next() {
|
|
var i Organization
|
|
if err := rows.Scan(
|
|
&i.OrgID,
|
|
&i.Name,
|
|
&i.Key,
|
|
&i.OrgType,
|
|
&i.OwnerPersonID,
|
|
&i.Status,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
); 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 getSystemOrganization = `-- name: GetSystemOrganization :one
|
|
SELECT org_id, name, key, org_type, owner_person_id, status, created_at, updated_at FROM core.organizations
|
|
WHERE org_type = 'system'
|
|
`
|
|
|
|
// The singleton System organization, resolved by its natural key. Paired
|
|
// with EnsureSystemOrganization; uq_organizations_one_system guarantees at
|
|
// most one row matches.
|
|
func (q *Queries) GetSystemOrganization(ctx context.Context) (Organization, error) {
|
|
row := q.db.QueryRowContext(ctx, getSystemOrganization)
|
|
var i Organization
|
|
err := row.Scan(
|
|
&i.OrgID,
|
|
&i.Name,
|
|
&i.Key,
|
|
&i.OrgType,
|
|
&i.OwnerPersonID,
|
|
&i.Status,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const listOrganizations = `-- name: ListOrganizations :many
|
|
SELECT org_id, name, key, org_type, owner_person_id, status, created_at, updated_at FROM core.organizations
|
|
ORDER BY name
|
|
`
|
|
|
|
func (q *Queries) ListOrganizations(ctx context.Context) ([]Organization, error) {
|
|
rows, err := q.db.QueryContext(ctx, listOrganizations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []Organization{}
|
|
for rows.Next() {
|
|
var i Organization
|
|
if err := rows.Scan(
|
|
&i.OrgID,
|
|
&i.Name,
|
|
&i.Key,
|
|
&i.OrgType,
|
|
&i.OwnerPersonID,
|
|
&i.Status,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
); 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 listOrganizationsByType = `-- name: ListOrganizationsByType :many
|
|
SELECT org_id, name, key, org_type, owner_person_id, status, created_at, updated_at FROM core.organizations
|
|
WHERE org_type = $1
|
|
ORDER BY name
|
|
`
|
|
|
|
func (q *Queries) ListOrganizationsByType(ctx context.Context, orgType string) ([]Organization, error) {
|
|
rows, err := q.db.QueryContext(ctx, listOrganizationsByType, orgType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []Organization{}
|
|
for rows.Next() {
|
|
var i Organization
|
|
if err := rows.Scan(
|
|
&i.OrgID,
|
|
&i.Name,
|
|
&i.Key,
|
|
&i.OrgType,
|
|
&i.OwnerPersonID,
|
|
&i.Status,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
); 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 listOrganizationsPage = `-- name: ListOrganizationsPage :many
|
|
SELECT o.org_id, o.name, o.org_type, o.owner_person_id, o.status, o.created_at, o.updated_at,
|
|
p.display_name AS owner_display_name,
|
|
count(*) OVER() AS total_count
|
|
FROM core.organizations o
|
|
LEFT JOIN core.persons p ON p.person_id = o.owner_person_id
|
|
WHERE ($1::text IS NULL
|
|
OR o.name ILIKE '%' || $1::text || '%'
|
|
OR p.display_name ILIKE '%' || $1::text || '%'
|
|
OR p.primary_email ILIKE '%' || $1::text || '%')
|
|
AND ($2::text IS NULL OR o.org_type = $2::text)
|
|
ORDER BY o.name
|
|
LIMIT $4::int OFFSET $3::int
|
|
`
|
|
|
|
type ListOrganizationsPageParams struct {
|
|
Q sql.NullString `json:"q"`
|
|
OrgType sql.NullString `json:"org_type"`
|
|
PageOffset int32 `json:"page_offset"`
|
|
PageLimit int32 `json:"page_limit"`
|
|
}
|
|
|
|
type ListOrganizationsPageRow struct {
|
|
OrgID string `json:"org_id"`
|
|
Name string `json:"name"`
|
|
OrgType string `json:"org_type"`
|
|
OwnerPersonID string `json:"owner_person_id"`
|
|
Status string `json:"status"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
OwnerDisplayName sql.NullString `json:"owner_display_name"`
|
|
TotalCount int64 `json:"total_count"`
|
|
}
|
|
|
|
// Organizations list operator surface (operator-list-scale UX-4): the
|
|
// paged, searched, org-type-filtered successor to ListOrganizationsWithOwner
|
|
// -- each row still carries its owner's display name so no organization can
|
|
// read as unowned (ux-honest-surfaces UX-16). owner_person_id is NOT NULL
|
|
// (every organization has an owner person, the System tenant included --
|
|
// 00001_init.sql), so the LEFT JOIN is defensive rather than expected to
|
|
// ever drop the owner's name.
|
|
//
|
|
// sqlc.narg(q): NULL matches every row; set, a case-insensitive substring
|
|
// match against the organization's name, or its owner's display
|
|
// name or primary email (operator-list-scale: "carries a scoped
|
|
// server-side search" -- "organizations: name and owner name or
|
|
// email").
|
|
// sqlc.narg(org_type): NULL matches every org type; set, an exact facet
|
|
// match (operator-list-scale: "Status filters exist ... organizations: org
|
|
// type").
|
|
// count(*) OVER() carries the true total for the filtered set alongside
|
|
// the page's rows (design D2), so the stated total never reflects only the
|
|
// page.
|
|
func (q *Queries) ListOrganizationsPage(ctx context.Context, arg ListOrganizationsPageParams) ([]ListOrganizationsPageRow, error) {
|
|
rows, err := q.db.QueryContext(ctx, listOrganizationsPage,
|
|
arg.Q,
|
|
arg.OrgType,
|
|
arg.PageOffset,
|
|
arg.PageLimit,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListOrganizationsPageRow{}
|
|
for rows.Next() {
|
|
var i ListOrganizationsPageRow
|
|
if err := rows.Scan(
|
|
&i.OrgID,
|
|
&i.Name,
|
|
&i.OrgType,
|
|
&i.OwnerPersonID,
|
|
&i.Status,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.OwnerDisplayName,
|
|
&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 searchOrganizationsByName = `-- name: SearchOrganizationsByName :many
|
|
SELECT o.org_id, o.name, o.org_type,
|
|
p.display_name AS owner_display_name
|
|
FROM core.organizations o
|
|
LEFT JOIN core.persons p ON p.person_id = o.owner_person_id
|
|
WHERE o.status = 'active'
|
|
AND o.name ILIKE '%' || $1::text || '%'
|
|
ORDER BY o.name
|
|
LIMIT $2::int
|
|
`
|
|
|
|
type SearchOrganizationsByNameParams struct {
|
|
Name string `json:"name"`
|
|
RowLimit int32 `json:"row_limit"`
|
|
}
|
|
|
|
type SearchOrganizationsByNameRow struct {
|
|
OrgID string `json:"org_id"`
|
|
Name string `json:"name"`
|
|
OrgType string `json:"org_type"`
|
|
OwnerDisplayName sql.NullString `json:"owner_display_name"`
|
|
}
|
|
|
|
// Landing lookup's organization branch (entity-keys). Runs only
|
|
// when no person matches the lookup term; a dedicated query rather than
|
|
// ListOrganizationsPage because that query's owner-email term would blur
|
|
// the person/organization split the lookup depends on. Active
|
|
// organizations only, name substring match, bounded by LIMIT so a short
|
|
// term cannot return the whole table.
|
|
func (q *Queries) SearchOrganizationsByName(ctx context.Context, arg SearchOrganizationsByNameParams) ([]SearchOrganizationsByNameRow, error) {
|
|
rows, err := q.db.QueryContext(ctx, searchOrganizationsByName, arg.Name, arg.RowLimit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []SearchOrganizationsByNameRow{}
|
|
for rows.Next() {
|
|
var i SearchOrganizationsByNameRow
|
|
if err := rows.Scan(
|
|
&i.OrgID,
|
|
&i.Name,
|
|
&i.OrgType,
|
|
&i.OwnerDisplayName,
|
|
); 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
|
|
}
|