Files
member-console/internal/integrations/fedwiki/store/sites.sql.go
T
cgalo5758 8d05934e93 Add domains registry with claims and placements
Domain names become an allocatable resource with one authority. A new
core module (schema `domains`, own migration stream between core and the
integrations) owns claims — a DNS node plus its whole subtree, mutually
disjoint: operator shared-domain roots, member claims carved from them,
and bring-your-own names proven by TXT verification — and placements,
which bind a name inside a claim to a provider slug and resource ref.

Verification moves to the claim and decouples from creation. A member
proves control of a domain once; afterwards every name inside it places
instantly, wildcard-CNAME friendly, with no further DNS work. The claim
workflow activates the claim and stops — it no longer creates a site —
so the sites list offers a one-click create once a domain verifies.

/domains/ask answers from placements and is registered by core rather
than the FedWiki adapter; its HTTP contract is unchanged. A configured
`domains-ask-fallback-url` forwards names the registry does not know to
a legacy answerer, the strangler seam wiki.cafe's migration needs; a
name the registry knows but has archived is refused locally.

FedWiki's create saga reserves the name before the farm call, carrying a
workflow-minted site id so retries are idempotent, and compensates on
failure. Sync places only names it owns, never stealing a member's;
lifecycle transitions and the retention purge maintain servability. An
unconditional boot pass seeds operator roots, releases orphaned
placements, and adopts pre-existing sites — grandfathering member-owned
external domains shortest-name-first, and skipping name policy, so a
live single-letter site cannot lose its certificate.

Members manage domains at /domains: claims with verification status, DNS
records including an optional wildcard row, check-now, cancel, release.
Name policy (reserved, blocked, premium, plus a single-letter guard) is
operator data; refusals collapse to a plain "unavailable" so the console
never becomes an oracle for who holds what.

BREAKING (pre-release): `fedwiki.custom_domain_verifications` and
`sites.is_custom_domain` are dropped, the flag now derived from the
placement's claim kind; resource key `fedwiki_custom_domains` migrates
to the platform-owned `external_domain_claims`; running
verify-custom-domain workflows must be terminated before deploy.
2026-07-24 21:25:40 -05:00

722 lines
22 KiB
Go

// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// source: sites.sql
package fedwiki
import (
"context"
"database/sql"
"time"
)
const clearForceReducedByDomain = `-- name: ClearForceReducedByDomain :exec
UPDATE fedwiki.sites
SET force_reduced_at = NULL, updated_at = NOW()
WHERE domain = $1
`
func (q *Queries) ClearForceReducedByDomain(ctx context.Context, domain string) error {
_, err := q.db.ExecContext(ctx, clearForceReducedByDomain, domain)
return err
}
const countActiveSitesByWorkspace = `-- name: CountActiveSitesByWorkspace :one
SELECT COUNT(*) AS count FROM fedwiki.sites
WHERE workspace_id = $1 AND status = 'active'
`
func (q *Queries) CountActiveSitesByWorkspace(ctx context.Context, workspaceID string) (int64, error) {
row := q.db.QueryRowContext(ctx, countActiveSitesByWorkspace, workspaceID)
var count int64
err := row.Scan(&count)
return count, err
}
const countSitesByWorkspace = `-- name: CountSitesByWorkspace :one
SELECT COUNT(*) AS count FROM fedwiki.sites
WHERE workspace_id = $1
`
func (q *Queries) CountSitesByWorkspace(ctx context.Context, workspaceID string) (int64, error) {
row := q.db.QueryRowContext(ctx, countSitesByWorkspace, workspaceID)
var count int64
err := row.Scan(&count)
return count, err
}
const createSite = `-- name: CreateSite :one
INSERT INTO fedwiki.sites (site_id, workspace_id, domain)
VALUES ($1, $2, $3)
RETURNING site_id, workspace_id, domain, created_at, updated_at, status, storage_bytes, last_modified_at, force_reduced_at, archived_at
`
type CreateSiteParams struct {
SiteID string `json:"site_id"`
WorkspaceID string `json:"workspace_id"`
Domain string `json:"domain"`
}
// site_id is supplied by the caller, not defaulted: the creation saga
// pre-generates it so the registry placement can reserve the name
// (resource_ref = site_id) BEFORE the farm call, and this insert then binds
// the same id (domains-registry D8).
func (q *Queries) CreateSite(ctx context.Context, arg CreateSiteParams) (Site, error) {
row := q.db.QueryRowContext(ctx, createSite, arg.SiteID, arg.WorkspaceID, arg.Domain)
var i Site
err := row.Scan(
&i.SiteID,
&i.WorkspaceID,
&i.Domain,
&i.CreatedAt,
&i.UpdatedAt,
&i.Status,
&i.StorageBytes,
&i.LastModifiedAt,
&i.ForceReducedAt,
&i.ArchivedAt,
)
return i, err
}
const deleteSite = `-- name: DeleteSite :exec
DELETE FROM fedwiki.sites
WHERE site_id = $1
`
func (q *Queries) DeleteSite(ctx context.Context, siteID string) error {
_, err := q.db.ExecContext(ctx, deleteSite, siteID)
return err
}
const deleteSiteByDomain = `-- name: DeleteSiteByDomain :exec
DELETE FROM fedwiki.sites
WHERE domain = $1
`
func (q *Queries) DeleteSiteByDomain(ctx context.Context, domain string) error {
_, err := q.db.ExecContext(ctx, deleteSiteByDomain, domain)
return err
}
const getSiteByDomain = `-- name: GetSiteByDomain :one
SELECT s.site_id, s.workspace_id, s.domain, s.created_at, s.updated_at, s.status, s.storage_bytes, s.last_modified_at, s.force_reduced_at, s.archived_at, (EXISTS (
SELECT 1 FROM domains.placements p
JOIN domains.claims c ON c.claim_id = p.claim_id
WHERE p.provider = 'fedwiki'
AND p.resource_ref = s.site_id::text
AND c.kind = 'external'
))::boolean AS is_custom_domain
FROM fedwiki.sites AS s
WHERE s.domain = $1
`
type GetSiteByDomainRow struct {
SiteID string `json:"site_id"`
WorkspaceID string `json:"workspace_id"`
Domain string `json:"domain"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Status string `json:"status"`
StorageBytes sql.NullInt64 `json:"storage_bytes"`
LastModifiedAt sql.NullTime `json:"last_modified_at"`
ForceReducedAt sql.NullTime `json:"force_reduced_at"`
ArchivedAt sql.NullTime `json:"archived_at"`
IsCustomDomain bool `json:"is_custom_domain"`
}
func (q *Queries) GetSiteByDomain(ctx context.Context, domain string) (GetSiteByDomainRow, error) {
row := q.db.QueryRowContext(ctx, getSiteByDomain, domain)
var i GetSiteByDomainRow
err := row.Scan(
&i.SiteID,
&i.WorkspaceID,
&i.Domain,
&i.CreatedAt,
&i.UpdatedAt,
&i.Status,
&i.StorageBytes,
&i.LastModifiedAt,
&i.ForceReducedAt,
&i.ArchivedAt,
&i.IsCustomDomain,
)
return i, err
}
const getSiteByID = `-- name: GetSiteByID :one
SELECT s.site_id, s.workspace_id, s.domain, s.created_at, s.updated_at, s.status, s.storage_bytes, s.last_modified_at, s.force_reduced_at, s.archived_at, (EXISTS (
SELECT 1 FROM domains.placements p
JOIN domains.claims c ON c.claim_id = p.claim_id
WHERE p.provider = 'fedwiki'
AND p.resource_ref = s.site_id::text
AND c.kind = 'external'
))::boolean AS is_custom_domain
FROM fedwiki.sites AS s
WHERE s.site_id = $1
`
type GetSiteByIDRow struct {
SiteID string `json:"site_id"`
WorkspaceID string `json:"workspace_id"`
Domain string `json:"domain"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Status string `json:"status"`
StorageBytes sql.NullInt64 `json:"storage_bytes"`
LastModifiedAt sql.NullTime `json:"last_modified_at"`
ForceReducedAt sql.NullTime `json:"force_reduced_at"`
ArchivedAt sql.NullTime `json:"archived_at"`
IsCustomDomain bool `json:"is_custom_domain"`
}
// The display queries below derive `is_custom_domain` instead of reading a
// column: whether a site sits on a member-owned domain is a fact about its
// registry placement's claim, not about the site (domains-registry D8, the
// dropped fedwiki.sites.is_custom_domain). The EXISTS is cast to boolean so
// the generated field stays a plain `bool` named IsCustomDomain, keeping
// every render site unchanged. Only the queries that feed member, API, and
// operator display carry it; the reconcile/sweep queries
// (ListActiveSitesByWorkspace, ListForceReducedSitesByWorkspace,
// ListArchivedSitesArchivedBefore) do not render it and stay plain
// SELECT * so they keep returning the Site model.
func (q *Queries) GetSiteByID(ctx context.Context, siteID string) (GetSiteByIDRow, error) {
row := q.db.QueryRowContext(ctx, getSiteByID, siteID)
var i GetSiteByIDRow
err := row.Scan(
&i.SiteID,
&i.WorkspaceID,
&i.Domain,
&i.CreatedAt,
&i.UpdatedAt,
&i.Status,
&i.StorageBytes,
&i.LastModifiedAt,
&i.ForceReducedAt,
&i.ArchivedAt,
&i.IsCustomDomain,
)
return i, err
}
const getSiteSwapPolicy = `-- name: GetSiteSwapPolicy :one
SELECT last_swap_at FROM fedwiki.site_swap_policy WHERE workspace_id = $1
`
func (q *Queries) GetSiteSwapPolicy(ctx context.Context, workspaceID string) (time.Time, error) {
row := q.db.QueryRowContext(ctx, getSiteSwapPolicy, workspaceID)
var last_swap_at time.Time
err := row.Scan(&last_swap_at)
return last_swap_at, err
}
const listActiveSitesByWorkspace = `-- name: ListActiveSitesByWorkspace :many
SELECT site_id, workspace_id, domain, created_at, updated_at, status, storage_bytes, last_modified_at, force_reduced_at, archived_at FROM fedwiki.sites
WHERE workspace_id = $1 AND status = 'active'
ORDER BY last_modified_at DESC NULLS LAST, created_at DESC
`
// Active sites, most-recently-modified first — the force_reduce keep/park order.
func (q *Queries) ListActiveSitesByWorkspace(ctx context.Context, workspaceID string) ([]Site, error) {
rows, err := q.db.QueryContext(ctx, listActiveSitesByWorkspace, workspaceID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Site{}
for rows.Next() {
var i Site
if err := rows.Scan(
&i.SiteID,
&i.WorkspaceID,
&i.Domain,
&i.CreatedAt,
&i.UpdatedAt,
&i.Status,
&i.StorageBytes,
&i.LastModifiedAt,
&i.ForceReducedAt,
&i.ArchivedAt,
); 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 listAllSites = `-- name: ListAllSites :many
SELECT s.site_id, s.workspace_id, s.domain, s.created_at, s.updated_at, s.status, s.storage_bytes, s.last_modified_at, s.force_reduced_at, s.archived_at, (EXISTS (
SELECT 1 FROM domains.placements p
JOIN domains.claims c ON c.claim_id = p.claim_id
WHERE p.provider = 'fedwiki'
AND p.resource_ref = s.site_id::text
AND c.kind = 'external'
))::boolean AS is_custom_domain
FROM fedwiki.sites AS s
ORDER BY s.domain ASC
`
type ListAllSitesRow struct {
SiteID string `json:"site_id"`
WorkspaceID string `json:"workspace_id"`
Domain string `json:"domain"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Status string `json:"status"`
StorageBytes sql.NullInt64 `json:"storage_bytes"`
LastModifiedAt sql.NullTime `json:"last_modified_at"`
ForceReducedAt sql.NullTime `json:"force_reduced_at"`
ArchivedAt sql.NullTime `json:"archived_at"`
IsCustomDomain bool `json:"is_custom_domain"`
}
func (q *Queries) ListAllSites(ctx context.Context) ([]ListAllSitesRow, error) {
rows, err := q.db.QueryContext(ctx, listAllSites)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListAllSitesRow{}
for rows.Next() {
var i ListAllSitesRow
if err := rows.Scan(
&i.SiteID,
&i.WorkspaceID,
&i.Domain,
&i.CreatedAt,
&i.UpdatedAt,
&i.Status,
&i.StorageBytes,
&i.LastModifiedAt,
&i.ForceReducedAt,
&i.ArchivedAt,
&i.IsCustomDomain,
); 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 listArchivedSitesArchivedBefore = `-- name: ListArchivedSitesArchivedBefore :many
SELECT site_id, workspace_id, domain, created_at, updated_at, status, storage_bytes, last_modified_at, force_reduced_at, archived_at FROM fedwiki.sites
WHERE status = 'archived' AND archived_at IS NOT NULL AND archived_at < $1
ORDER BY archived_at ASC
`
// Retention purge: archived sites whose archive predates the cutoff.
func (q *Queries) ListArchivedSitesArchivedBefore(ctx context.Context, archivedAt sql.NullTime) ([]Site, error) {
rows, err := q.db.QueryContext(ctx, listArchivedSitesArchivedBefore, archivedAt)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Site{}
for rows.Next() {
var i Site
if err := rows.Scan(
&i.SiteID,
&i.WorkspaceID,
&i.Domain,
&i.CreatedAt,
&i.UpdatedAt,
&i.Status,
&i.StorageBytes,
&i.LastModifiedAt,
&i.ForceReducedAt,
&i.ArchivedAt,
); 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 listForceReducedSitesByWorkspace = `-- name: ListForceReducedSitesByWorkspace :many
SELECT site_id, workspace_id, domain, created_at, updated_at, status, storage_bytes, last_modified_at, force_reduced_at, archived_at FROM fedwiki.sites
WHERE workspace_id = $1 AND force_reduced_at IS NOT NULL
ORDER BY last_modified_at DESC NULLS LAST, created_at DESC
`
// Sites parked read-only by a downgrade, most-recently-modified first — the
// re-upgrade reactivation order.
func (q *Queries) ListForceReducedSitesByWorkspace(ctx context.Context, workspaceID string) ([]Site, error) {
rows, err := q.db.QueryContext(ctx, listForceReducedSitesByWorkspace, workspaceID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Site{}
for rows.Next() {
var i Site
if err := rows.Scan(
&i.SiteID,
&i.WorkspaceID,
&i.Domain,
&i.CreatedAt,
&i.UpdatedAt,
&i.Status,
&i.StorageBytes,
&i.LastModifiedAt,
&i.ForceReducedAt,
&i.ArchivedAt,
); 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 listOrphanedPlacements = `-- name: ListOrphanedPlacements :many
SELECT p.placement_id, p.fqdn, p.resource_ref
FROM domains.placements p
WHERE p.provider = 'fedwiki'
AND NOT EXISTS (
SELECT 1 FROM fedwiki.sites s WHERE s.site_id::text = p.resource_ref
)
ORDER BY p.fqdn ASC
`
type ListOrphanedPlacementsRow struct {
PlacementID string `json:"placement_id"`
Fqdn string `json:"fqdn"`
ResourceRef string `json:"resource_ref"`
}
// Boot reconciliation's orphan set (domains-registry D8), the mirror image of
// ListSitesWithoutPlacement: every `fedwiki` placement whose resource_ref
// names no `fedwiki.sites` row. A create saga whose compensation never ran and
// a delete interrupted between freeing the row and freeing the name both leave
// one, and until it is released `/domains/ask` keeps authorizing certificates
// for a name nothing serves. Ordered by name for a stable pass.
func (q *Queries) ListOrphanedPlacements(ctx context.Context) ([]ListOrphanedPlacementsRow, error) {
rows, err := q.db.QueryContext(ctx, listOrphanedPlacements)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListOrphanedPlacementsRow{}
for rows.Next() {
var i ListOrphanedPlacementsRow
if err := rows.Scan(&i.PlacementID, &i.Fqdn, &i.ResourceRef); 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 listSitesByWorkspace = `-- name: ListSitesByWorkspace :many
SELECT s.site_id, s.workspace_id, s.domain, s.created_at, s.updated_at, s.status, s.storage_bytes, s.last_modified_at, s.force_reduced_at, s.archived_at, (EXISTS (
SELECT 1 FROM domains.placements p
JOIN domains.claims c ON c.claim_id = p.claim_id
WHERE p.provider = 'fedwiki'
AND p.resource_ref = s.site_id::text
AND c.kind = 'external'
))::boolean AS is_custom_domain
FROM fedwiki.sites AS s
WHERE s.workspace_id = $1
ORDER BY s.domain ASC
`
type ListSitesByWorkspaceRow struct {
SiteID string `json:"site_id"`
WorkspaceID string `json:"workspace_id"`
Domain string `json:"domain"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Status string `json:"status"`
StorageBytes sql.NullInt64 `json:"storage_bytes"`
LastModifiedAt sql.NullTime `json:"last_modified_at"`
ForceReducedAt sql.NullTime `json:"force_reduced_at"`
ArchivedAt sql.NullTime `json:"archived_at"`
IsCustomDomain bool `json:"is_custom_domain"`
}
func (q *Queries) ListSitesByWorkspace(ctx context.Context, workspaceID string) ([]ListSitesByWorkspaceRow, error) {
rows, err := q.db.QueryContext(ctx, listSitesByWorkspace, workspaceID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListSitesByWorkspaceRow{}
for rows.Next() {
var i ListSitesByWorkspaceRow
if err := rows.Scan(
&i.SiteID,
&i.WorkspaceID,
&i.Domain,
&i.CreatedAt,
&i.UpdatedAt,
&i.Status,
&i.StorageBytes,
&i.LastModifiedAt,
&i.ForceReducedAt,
&i.ArchivedAt,
&i.IsCustomDomain,
); 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 listSitesWithoutPlacement = `-- name: ListSitesWithoutPlacement :many
SELECT site_id, workspace_id, domain, created_at, updated_at, status, storage_bytes, last_modified_at, force_reduced_at, archived_at FROM fedwiki.sites
WHERE NOT EXISTS (
SELECT 1 FROM domains.placements p
WHERE p.provider = 'fedwiki' AND p.resource_ref = fedwiki.sites.site_id::text
)
ORDER BY length(domain) ASC, domain ASC
`
// Boot reconciliation's backfill set (domains-registry D8): every site row the
// registry holds no placement for. Ordered shortest name first so a nested
// pre-registry site (`blog.example.org`) is classified AFTER the name that
// contains it (`example.org`) and lands as a placement inside the claim that
// one created, instead of minting an overlapping second claim.
func (q *Queries) ListSitesWithoutPlacement(ctx context.Context) ([]Site, error) {
rows, err := q.db.QueryContext(ctx, listSitesWithoutPlacement)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Site{}
for rows.Next() {
var i Site
if err := rows.Scan(
&i.SiteID,
&i.WorkspaceID,
&i.Domain,
&i.CreatedAt,
&i.UpdatedAt,
&i.Status,
&i.StorageBytes,
&i.LastModifiedAt,
&i.ForceReducedAt,
&i.ArchivedAt,
); 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 listWorkspaceIDsWithSites = `-- name: ListWorkspaceIDsWithSites :many
SELECT DISTINCT workspace_id FROM fedwiki.sites
`
// Workspaces that own at least one site — the units the quota reconcile sweeps.
func (q *Queries) ListWorkspaceIDsWithSites(ctx context.Context) ([]string, error) {
rows, err := q.db.QueryContext(ctx, listWorkspaceIDsWithSites)
if err != nil {
return nil, err
}
defer rows.Close()
items := []string{}
for rows.Next() {
var workspace_id string
if err := rows.Scan(&workspace_id); err != nil {
return nil, err
}
items = append(items, workspace_id)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const setForceReducedByDomain = `-- name: SetForceReducedByDomain :exec
UPDATE fedwiki.sites
SET force_reduced_at = NOW(), updated_at = NOW()
WHERE domain = $1 AND force_reduced_at IS NULL
`
func (q *Queries) SetForceReducedByDomain(ctx context.Context, domain string) error {
_, err := q.db.ExecContext(ctx, setForceReducedByDomain, domain)
return err
}
const setSiteStatusByDomain = `-- name: SetSiteStatusByDomain :one
UPDATE fedwiki.sites
SET status = $2,
archived_at = CASE
WHEN $2 = 'archived' AND status <> 'archived' THEN NOW()
WHEN $2 <> 'archived' THEN NULL
ELSE archived_at
END,
updated_at = NOW()
WHERE domain = $1
RETURNING site_id, workspace_id, domain, created_at, updated_at, status, storage_bytes, last_modified_at, force_reduced_at, archived_at
`
type SetSiteStatusByDomainParams struct {
Domain string `json:"domain"`
Status string `json:"status"`
}
// Project a lifecycle status change locally. archived_at is set on entering
// `archived` (idempotent — preserves the original timestamp) and cleared on
// leaving it, driving the Trash retention purge.
func (q *Queries) SetSiteStatusByDomain(ctx context.Context, arg SetSiteStatusByDomainParams) (Site, error) {
row := q.db.QueryRowContext(ctx, setSiteStatusByDomain, arg.Domain, arg.Status)
var i Site
err := row.Scan(
&i.SiteID,
&i.WorkspaceID,
&i.Domain,
&i.CreatedAt,
&i.UpdatedAt,
&i.Status,
&i.StorageBytes,
&i.LastModifiedAt,
&i.ForceReducedAt,
&i.ArchivedAt,
)
return i, err
}
const upsertSiteByDomain = `-- name: UpsertSiteByDomain :one
INSERT INTO fedwiki.sites (workspace_id, domain)
VALUES ($1, $2)
ON CONFLICT(domain) DO UPDATE SET
workspace_id = excluded.workspace_id,
updated_at = NOW()
RETURNING site_id, workspace_id, domain, created_at, updated_at, status, storage_bytes, last_modified_at, force_reduced_at, archived_at
`
type UpsertSiteByDomainParams struct {
WorkspaceID string `json:"workspace_id"`
Domain string `json:"domain"`
}
func (q *Queries) UpsertSiteByDomain(ctx context.Context, arg UpsertSiteByDomainParams) (Site, error) {
row := q.db.QueryRowContext(ctx, upsertSiteByDomain, arg.WorkspaceID, arg.Domain)
var i Site
err := row.Scan(
&i.SiteID,
&i.WorkspaceID,
&i.Domain,
&i.CreatedAt,
&i.UpdatedAt,
&i.Status,
&i.StorageBytes,
&i.LastModifiedAt,
&i.ForceReducedAt,
&i.ArchivedAt,
)
return i, err
}
const upsertSiteFromFarm = `-- name: UpsertSiteFromFarm :one
INSERT INTO fedwiki.sites (workspace_id, domain, status, storage_bytes, last_modified_at)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT(domain) DO UPDATE SET
status = excluded.status,
storage_bytes = excluded.storage_bytes,
last_modified_at = excluded.last_modified_at,
updated_at = NOW()
RETURNING site_id, workspace_id, domain, created_at, updated_at, status, storage_bytes, last_modified_at, force_reduced_at, archived_at
`
type UpsertSiteFromFarmParams struct {
WorkspaceID string `json:"workspace_id"`
Domain string `json:"domain"`
Status string `json:"status"`
StorageBytes sql.NullInt64 `json:"storage_bytes"`
LastModifiedAt sql.NullTime `json:"last_modified_at"`
}
// Sync projection: insert a newly-seen farm site (default workspace), or project
// the observed status/storage/last-modified onto an existing row WITHOUT touching
// its workspace_id (user-created sites keep their owner).
func (q *Queries) UpsertSiteFromFarm(ctx context.Context, arg UpsertSiteFromFarmParams) (Site, error) {
row := q.db.QueryRowContext(ctx, upsertSiteFromFarm,
arg.WorkspaceID,
arg.Domain,
arg.Status,
arg.StorageBytes,
arg.LastModifiedAt,
)
var i Site
err := row.Scan(
&i.SiteID,
&i.WorkspaceID,
&i.Domain,
&i.CreatedAt,
&i.UpdatedAt,
&i.Status,
&i.StorageBytes,
&i.LastModifiedAt,
&i.ForceReducedAt,
&i.ArchivedAt,
)
return i, err
}
const upsertSiteSwapPolicy = `-- name: UpsertSiteSwapPolicy :exec
INSERT INTO fedwiki.site_swap_policy (workspace_id, last_swap_at)
VALUES ($1, NOW())
ON CONFLICT (workspace_id) DO UPDATE SET last_swap_at = NOW()
`
// Records that a swap just happened (resets the cooldown clock for the workspace).
func (q *Queries) UpsertSiteSwapPolicy(ctx context.Context, workspaceID string) error {
_, err := q.db.ExecContext(ctx, upsertSiteSwapPolicy, workspaceID)
return err
}