--
--

\restrict JB82uK0VLDvnlLfNV5slgltXKvuSbMlatGwnho8h5H6tIyux4rud7W97E3Ke9mH


SET transaction_timeout = 0;
SELECT pg_catalog.set_config('search_path', '', false);

--
-- Name: citext; Type: EXTENSION; Schema: -; Owner: -
--

CREATE EXTENSION IF NOT EXISTS citext WITH SCHEMA public;


--
-- Name: EXTENSION citext; Type: COMMENT; Schema: -; Owner: -
--

COMMENT ON EXTENSION citext IS 'data type for case-insensitive character strings';


--
-- Name: pgcrypto; Type: EXTENSION; Schema: -; Owner: -
--

CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public;


--
-- Name: EXTENSION pgcrypto; Type: COMMENT; Schema: -; Owner: -
--

COMMENT ON EXTENSION pgcrypto IS 'cryptographic functions';


--
-- Name: vector; Type: EXTENSION; Schema: -; Owner: -
--

CREATE EXTENSION IF NOT EXISTS vector WITH SCHEMA public;


--
-- Name: EXTENSION vector; Type: COMMENT; Schema: -; Owner: -
--

COMMENT ON EXTENSION vector IS 'vector data type and ivfflat and hnsw access methods';


--
-- Name: atlas_role_for_email(character varying); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.atlas_role_for_email(p_email character varying) RETURNS character varying
    LANGUAGE plpgsql
    AS $$
DECLARE
  v_role varchar;
BEGIN
  IF p_email IS NULL OR p_email = '' THEN
    RETURN NULL;
  END IF;

  SELECT role INTO v_role
    FROM atlas_user_roles
   WHERE email = p_email AND is_active = true
   ORDER BY
     CASE role
       WHEN 'atlas_admin' THEN 1
       WHEN 'atlas_writer_ops' THEN 2
       WHEN 'atlas_writer_inventory' THEN 2
       WHEN 'atlas_writer_content' THEN 2
       WHEN 'atlas_user_admin' THEN 3
       WHEN 'atlas_reader' THEN 4
       ELSE 5
     END,
     granted_at ASC
   LIMIT 1;

  IF v_role IS NULL THEN
    -- JIT enroll new email as atlas_reader.
    -- Runs under the pool's superuser so the INSERT succeeds regardless
    -- of caller privileges.
    INSERT INTO atlas_user_roles (email, role, granted_by, last_seen_at)
    VALUES (p_email, 'atlas_reader', 'system', now())
    ON CONFLICT (email, role) DO NOTHING;
    RETURN 'atlas_reader';
  END IF;

  UPDATE atlas_user_roles
     SET last_seen_at = now()
   WHERE email = p_email AND role = v_role;

  RETURN v_role;
END$$;


--
-- Name: FUNCTION atlas_role_for_email(p_email character varying); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.atlas_role_for_email(p_email character varying) IS 'The single identity resolver used by the ops-mcp introspection layer. On first sight of an email, auto-enrolls as atlas_reader. On every call, bumps last_seen_at on the row used for SET ROLE. When the caller holds multiple roles, returns the highest-privilege one for data access — app-layer tool gates read the full role set separately to decide admin capabilities.';


--
-- Name: set_supplier_site_audit_actor(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.set_supplier_site_audit_actor() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
DECLARE
  actor text;
BEGIN
  actor := current_setting('app.actor', true);
  IF actor IS NOT NULL AND actor <> '' THEN
    NEW.audited_by := actor;
  END IF;
  RETURN NEW;
END;
$$;




--
-- Name: activity_log; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.activity_log (
    id integer NOT NULL,
    entity_type character varying(30) NOT NULL,
    entity_id integer NOT NULL,
    action character varying(50) NOT NULL,
    actor character varying(255),
    actor_role character varying(30),
    details jsonb,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: TABLE activity_log; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.activity_log IS 'Global audit log for any entity change in the system. Tracks who did what to which entity and when. Used for compliance, debugging, and undo support. Any significant create/update/delete operation should log here. The entity_type + entity_id combination identifies what was changed.';


--
-- Name: COLUMN activity_log.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.activity_log.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN activity_log.entity_type; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.activity_log.entity_type IS 'What type of entity was affected. Values: ''order'', ''assignment'', ''supplier'', ''site'', ''user'', ''invoice'', ''writer'', ''content_doc''. Varchar(30).';


--
-- Name: COLUMN activity_log.entity_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.activity_log.entity_id IS 'Primary key of the affected entity in its own table. Combined with entity_type to locate the record.';


--
-- Name: COLUMN activity_log.action; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.activity_log.action IS 'What action was performed. Values: ''create'', ''update'', ''delete'', ''status_change'', ''assign'', ''approve'', ''reject'', ''ban'', ''unban'', ''pay''. Varchar(50).';


--
-- Name: COLUMN activity_log.actor; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.activity_log.actor IS 'Who performed the action. Staff name, vendor email, or ''system'' for automated actions.';


--
-- Name: COLUMN activity_log.actor_role; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.activity_log.actor_role IS 'Role of the actor at the time of the action. Values: ''super_admin'', ''admin'', ''editor'', ''vendor'', ''writer'', ''system''. Varchar(30).';


--
-- Name: COLUMN activity_log.details; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.activity_log.details IS 'JSONB with additional context. May include old/new values for updates, reason for action, or related entity references. Structure varies by action type.';


--
-- Name: COLUMN activity_log.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.activity_log.created_at IS 'Timestamp when this action occurred. Immutable — log entries are never updated.';


--
-- Name: activity_log_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.activity_log_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: activity_log_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.activity_log_id_seq OWNED BY public.activity_log.id;


--
-- Name: ai_overviews; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.ai_overviews (
    id integer NOT NULL,
    site_id integer NOT NULL,
    ai_overview_score numeric(5,2),
    json_data jsonb,
    created_at timestamp with time zone DEFAULT now(),
    updated_at timestamp with time zone DEFAULT now()
);


--
-- Name: TABLE ai_overviews; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.ai_overviews IS 'AI Overview citation analysis results for each site. Tracks how often a site appears in Google''s AI Overview (formerly SGE) results. Score is derived by checking the site''s top ranking keywords in SERPs and counting AI Overview citations. Higher scores indicate the site has strong AI visibility — valuable for clients.';


--
-- Name: COLUMN ai_overviews.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.ai_overviews.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN ai_overviews.site_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.ai_overviews.site_id IS 'Foreign key to sites.site_id. ON DELETE CASCADE.';


--
-- Name: COLUMN ai_overviews.ai_overview_score; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.ai_overviews.ai_overview_score IS 'Composite score (0-100) representing AI Overview citation frequency across the site''s ranking keywords. Also denormalized to sites.ai_overview_score for fast access.';


--
-- Name: COLUMN ai_overviews.json_data; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.ai_overviews.json_data IS 'JSONB with detailed breakdown — which keywords triggered AI Overviews, which URLs were cited, citation positions. Raw data for debugging and deeper analysis.';


--
-- Name: COLUMN ai_overviews.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.ai_overviews.created_at IS 'Timestamp when this analysis was first run.';


--
-- Name: COLUMN ai_overviews.updated_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.ai_overviews.updated_at IS 'Timestamp of last analysis refresh.';


--
-- Name: ai_overviews_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.ai_overviews_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: ai_overviews_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.ai_overviews_id_seq OWNED BY public.ai_overviews.id;


--
-- Name: api_keys; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.api_keys (
    id integer NOT NULL,
    user_id integer NOT NULL,
    key_hash character varying(128) NOT NULL,
    key_prefix character varying(12) NOT NULL,
    label character varying(255),
    scopes jsonb DEFAULT '[]'::jsonb,
    rate_limit integer DEFAULT 1000,
    is_active boolean DEFAULT true,
    last_used_at timestamp with time zone,
    expires_at timestamp with time zone,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    revoked_at timestamp with time zone
);


--
-- Name: TABLE api_keys; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.api_keys IS 'API keys for programmatic access to loganix_ops. Each user can have multiple keys. Keys are generated as random tokens — the full key is shown ONCE at creation (never stored). Only the SHA-256 hash (key_hash) and a display prefix (key_prefix, first 8 chars like "lgnx_abc...") are stored. Authentication: client sends key in Authorization header, server hashes it and looks up key_hash. Keys can be scoped to specific endpoints, rate-limited, expired, or revoked.';


--
-- Name: COLUMN api_keys.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_keys.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN api_keys.user_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_keys.user_id IS 'Foreign key to users.user_id. ON DELETE CASCADE. The user who owns this API key. A user can have multiple active keys.';


--
-- Name: COLUMN api_keys.key_hash; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_keys.key_hash IS 'SHA-256 hash of the full API key. The raw key is never stored — only shown once at creation. Unique constraint ensures no hash collisions. Authentication flow: hash the incoming key, look up this column.';


--
-- Name: COLUMN api_keys.key_prefix; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_keys.key_prefix IS 'First 8-12 characters of the API key (e.g. "lgnx_a3f8"). Stored in plaintext for display in the UI so users can identify which key is which without exposing the full key. Format: "lgnx_" + 7 random chars.';


--
-- Name: COLUMN api_keys.label; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_keys.label IS 'User-provided label for this key. E.g. "Production server", "CI/CD pipeline", "Josh local dev". Helps users manage multiple keys.';


--
-- Name: COLUMN api_keys.scopes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_keys.scopes IS 'JSONB array of permitted API scopes. E.g. ["sites:read", "orders:read", "orders:write", "suppliers:write"]. Empty array or null = full access based on user role. Scopes are additive — a key can only narrow permissions, never exceed the user''s role.';


--
-- Name: COLUMN api_keys.rate_limit; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_keys.rate_limit IS 'Maximum API requests per hour for this key. Default: 1000. Set to 0 for unlimited (internal/admin keys). Rate limiting is per-key, not per-user.';


--
-- Name: COLUMN api_keys.is_active; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_keys.is_active IS 'Boolean. False = key is disabled but not deleted. Soft-disable for temporary suspension. Check this AND revoked_at IS NULL AND (expires_at IS NULL OR expires_at > now()) for valid keys.';


--
-- Name: COLUMN api_keys.last_used_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_keys.last_used_at IS 'Timestamp of last successful API request with this key. Updated on each authenticated request. Null if never used. Used to identify stale keys.';


--
-- Name: COLUMN api_keys.expires_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_keys.expires_at IS 'Optional expiration timestamp. Null = never expires. After this time, the key is rejected. Useful for temporary access or contractor keys.';


--
-- Name: COLUMN api_keys.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_keys.created_at IS 'Timestamp when this key was generated.';


--
-- Name: COLUMN api_keys.revoked_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_keys.revoked_at IS 'Timestamp when this key was revoked. Null = not revoked. Revocation is permanent — revoked keys cannot be re-enabled. The key row is preserved for audit trail.';


--
-- Name: api_keys_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.api_keys_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: api_keys_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.api_keys_id_seq OWNED BY public.api_keys.id;


--
-- Name: api_request_log; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.api_request_log (
    id bigint NOT NULL,
    api_key_id integer,
    user_id integer,
    method character varying(10) NOT NULL,
    path character varying(500) NOT NULL,
    query_params jsonb,
    request_body jsonb,
    response_status smallint NOT NULL,
    response_time_ms integer,
    ip_address inet,
    user_agent character varying(500),
    error_message text,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: TABLE api_request_log; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.api_request_log IS 'Audit log of every API request. Logs method, path, response status, timing, and the requesting key/user. Used for: (1) rate limiting enforcement — count recent requests per api_key_id, (2) security auditing — detect abuse patterns, (3) debugging — reproduce issues from request/response data, (4) analytics — understand API usage patterns. This table grows fast — consider partitioning by created_at or archiving old rows.';


--
-- Name: COLUMN api_request_log.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_request_log.id IS 'Auto-incrementing primary key. Bigserial (int8) because this table grows fast.';


--
-- Name: COLUMN api_request_log.api_key_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_request_log.api_key_id IS 'Foreign key to api_keys.id. ON DELETE SET NULL (preserve log even if key is deleted). Null for unauthenticated requests (e.g. health checks, login endpoints).';


--
-- Name: COLUMN api_request_log.user_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_request_log.user_id IS 'Foreign key to users.user_id. ON DELETE SET NULL. Denormalized from api_keys for faster queries. Null for unauthenticated requests.';


--
-- Name: COLUMN api_request_log.method; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_request_log.method IS 'HTTP method. Values: GET, POST, PUT, PATCH, DELETE. Varchar(10).';


--
-- Name: COLUMN api_request_log.path; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_request_log.path IS 'API endpoint path (without query string). E.g. "/api/v1/orders", "/api/v1/sites/123". Varchar(500).';


--
-- Name: COLUMN api_request_log.query_params; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_request_log.query_params IS 'JSONB of query string parameters. E.g. {"page": 1, "limit": 50, "status": "active"}. Null if no query params.';


--
-- Name: COLUMN api_request_log.request_body; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_request_log.request_body IS 'JSONB of the request body for POST/PUT/PATCH. Sensitive fields (passwords, keys) should be redacted before logging. Null for GET/DELETE.';


--
-- Name: COLUMN api_request_log.response_status; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_request_log.response_status IS 'HTTP response status code. E.g. 200, 201, 400, 401, 403, 404, 429 (rate limited), 500.';


--
-- Name: COLUMN api_request_log.response_time_ms; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_request_log.response_time_ms IS 'Server-side response time in milliseconds. Measured from request received to response sent. Used for performance monitoring.';


--
-- Name: COLUMN api_request_log.ip_address; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_request_log.ip_address IS 'Client IP address (inet type supports both IPv4 and IPv6). Used for rate limiting, geo analysis, and security auditing.';


--
-- Name: COLUMN api_request_log.user_agent; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_request_log.user_agent IS 'HTTP User-Agent header. Identifies the client software. E.g. "python-requests/2.28", "Loganix-MCP/1.0", "curl/8.1".';


--
-- Name: COLUMN api_request_log.error_message; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_request_log.error_message IS 'For non-2xx responses, the error message returned to the client. Null for successful requests.';


--
-- Name: COLUMN api_request_log.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_request_log.created_at IS 'Timestamp when this request was received. Indexed for time-range queries and rate limiting windows.';


--
-- Name: api_request_log_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.api_request_log_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: api_request_log_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.api_request_log_id_seq OWNED BY public.api_request_log.id;


--
-- Name: api_scopes; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.api_scopes (
    scope character varying(50) NOT NULL,
    resource character varying(30) NOT NULL,
    action character varying(10) NOT NULL,
    description text NOT NULL,
    CONSTRAINT api_scopes_action_check CHECK (((action)::text = ANY (ARRAY[('create'::character varying)::text, ('read'::character varying)::text, ('update'::character varying)::text, ('delete'::character varying)::text])))
);


--
-- Name: TABLE api_scopes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.api_scopes IS 'Defines every possible API scope in the system. Each scope is a resource:action pair (e.g. "orders:read", "inventory:update"). Scopes are referenced by api_keys.scopes (JSONB array) to restrict what an API key can do. A key''s effective permissions are: intersection of (user role permissions) AND (key scopes). Scopes can only narrow access, never expand beyond what the role allows.';


--
-- Name: COLUMN api_scopes.scope; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_scopes.scope IS 'The scope identifier. Format: "resource:action". E.g. "orders:read", "inventory:create". This is the value stored in api_keys.scopes arrays.';


--
-- Name: COLUMN api_scopes.resource; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_scopes.resource IS 'The resource/table this scope applies to. E.g. "orders", "supplier_sites", "content_docs". Maps to role_permissions.resource.';


--
-- Name: COLUMN api_scopes.action; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_scopes.action IS 'CRUD action. Values: "create", "read", "update", "delete". CHECK constraint enforced.';


--
-- Name: COLUMN api_scopes.description; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.api_scopes.description IS 'Human-readable description of what this scope allows. Shown in the API key creation UI.';


--
-- Name: atlas_bug_events; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.atlas_bug_events (
    id integer NOT NULL,
    bug_id integer NOT NULL,
    event_type text NOT NULL,
    actor_email text NOT NULL,
    from_status text,
    to_status text,
    note text,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: atlas_bug_events_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.atlas_bug_events_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: atlas_bug_events_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.atlas_bug_events_id_seq OWNED BY public.atlas_bug_events.id;


--
-- Name: atlas_bug_reports; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.atlas_bug_reports (
    id integer NOT NULL,
    reporter_email text NOT NULL,
    mcp_server text,
    tool_name text,
    tool_args jsonb,
    tool_response text,
    expected text NOT NULL,
    actual text NOT NULL,
    repro_steps text,
    convo_summary text,
    convo_turn_count integer,
    severity_guess text,
    reporter_notes text,
    status text DEFAULT 'new'::text NOT NULL,
    triage_notes text,
    triage_confidence integer,
    action_item_id text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: atlas_bug_reports_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.atlas_bug_reports_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: atlas_bug_reports_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.atlas_bug_reports_id_seq OWNED BY public.atlas_bug_reports.id;


--
-- Name: atlas_query_log; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.atlas_query_log (
    id bigint NOT NULL,
    ts timestamp with time zone DEFAULT now() NOT NULL,
    tool character varying(64) NOT NULL,
    table_name character varying(64),
    args jsonb,
    row_count integer,
    duration_ms integer,
    user_email character varying(255),
    error text
);


--
-- Name: TABLE atlas_query_log; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.atlas_query_log IS 'Observability log for the MCP-first introspection tools (ops_list_tables, ops_describe_table, ops_query). Append-only. One row per tool call. Used to discover which tables/filters are queried most often so we can pre-compose them as semantic views. Logging is fail-open: if insert fails the caller still succeeds.';


--
-- Name: COLUMN atlas_query_log.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.atlas_query_log.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN atlas_query_log.ts; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.atlas_query_log.ts IS 'Timestamp when the tool call was logged (end of call, after the underlying query returned).';


--
-- Name: COLUMN atlas_query_log.tool; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.atlas_query_log.tool IS 'Tool name — e.g. ops_query, ops_describe_table, ops_list_tables.';


--
-- Name: COLUMN atlas_query_log.table_name; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.atlas_query_log.table_name IS 'Target table/view for the call, when applicable. Null for ops_list_tables. Null for any future tool that does not operate on a single table.';


--
-- Name: COLUMN atlas_query_log.args; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.atlas_query_log.args IS 'Full args payload passed to the tool, as JSONB. Preserves filter shape so we can cluster common filter patterns. May be empty.';


--
-- Name: COLUMN atlas_query_log.row_count; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.atlas_query_log.row_count IS 'Number of rows returned. Null on error. Use as a rough heuristic for query cost.';


--
-- Name: COLUMN atlas_query_log.duration_ms; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.atlas_query_log.duration_ms IS 'Wall-clock duration of the underlying DB query in milliseconds. Null on error before the query ran.';


--
-- Name: COLUMN atlas_query_log.user_email; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.atlas_query_log.user_email IS 'Identity of the caller, when available from the MCP transport. Null until Step 6 wires JWT email through the pool connection.';


--
-- Name: COLUMN atlas_query_log.error; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.atlas_query_log.error IS 'Error message if the call failed. Null on success. Populated from the first error surfaced by the handler (validation or DB).';


--
-- Name: atlas_query_log_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.atlas_query_log_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: atlas_query_log_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.atlas_query_log_id_seq OWNED BY public.atlas_query_log.id;


--
-- Name: atlas_user_roles; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.atlas_user_roles (
    email character varying(255) NOT NULL,
    role character varying(50) NOT NULL,
    granted_by character varying(255) DEFAULT 'system'::character varying NOT NULL,
    granted_at timestamp with time zone DEFAULT now() NOT NULL,
    is_active boolean DEFAULT true NOT NULL,
    last_seen_at timestamp with time zone,
    CONSTRAINT atlas_user_roles_role_check CHECK (((role)::text = ANY ((ARRAY['atlas_reader'::character varying, 'atlas_writer_ops'::character varying, 'atlas_writer_inventory'::character varying, 'atlas_writer_audit'::character varying, 'atlas_writer_content'::character varying, 'atlas_user_admin'::character varying, 'atlas_admin'::character varying])::text[])))
);


--
-- Name: TABLE atlas_user_roles; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.atlas_user_roles IS 'Authorization and enrollment for every identity that has called an Atlas MCP tool. One row per (email, role) — a user can hold multiple roles (e.g. a writer role plus atlas_user_admin). Populated by JIT enrollment via atlas_role_for_email() on first seen; mutated by ops_grant_role / ops_revoke_role.';


--
-- Name: COLUMN atlas_user_roles.email; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.atlas_user_roles.email IS 'Caller email as extracted from their Keycloak JWT. Primary key — one role per email. Service accounts and crons use synthetic emails (e.g. system@cron.loganix.local) and share the role namespace with humans.';


--
-- Name: COLUMN atlas_user_roles.role; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.atlas_user_roles.role IS 'Postgres role the caller executes under. Enum enforced by CHECK: atlas_reader, atlas_writer_ops, atlas_writer_inventory, atlas_writer_content, atlas_admin. Role rights are set via GRANTs on the Postgres role itself, not in this table.';


--
-- Name: COLUMN atlas_user_roles.granted_by; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.atlas_user_roles.granted_by IS 'Who granted the current role — either "system" for JIT-enrolled readers, or the email of the admin who ran ops_grant_role.';


--
-- Name: COLUMN atlas_user_roles.granted_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.atlas_user_roles.granted_at IS 'Timestamp of the most recent role assignment. Bumped on grant/revoke.';


--
-- Name: COLUMN atlas_user_roles.is_active; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.atlas_user_roles.is_active IS 'False = revoked. The caller is blocked at the application layer when is_active is false. Row is retained for audit; ops_revoke_role sets false rather than deleting.';


--
-- Name: COLUMN atlas_user_roles.last_seen_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.atlas_user_roles.last_seen_at IS 'Updated by atlas_role_for_email on every successful lookup. Gives you a cheap "who used Atlas in the last 24h" without reading the full query log.';


--
-- Name: campaigns; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.campaigns (
    campaign_id integer NOT NULL,
    client_id integer NOT NULL,
    base_domain character varying(255) NOT NULL,
    anchor_text text,
    target_url character varying(255),
    budget_cap numeric(10,2),
    max_links integer,
    goal text,
    service_type character varying(50),
    status character varying(20) DEFAULT 'active'::character varying NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    archived_at timestamp with time zone,
    CONSTRAINT campaigns_status_check CHECK (((status)::text = ANY ((ARRAY['active'::character varying, 'paused'::character varying, 'complete'::character varying, 'archived'::character varying])::text[])))
);


--
-- Name: TABLE campaigns; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.campaigns IS 'Client campaigns managed inside loganix_ops. A campaign is a client + a domain they are promoting. Budget and link caps enforced at campaign level. DIY clients on the stlv2 storefront (10.69.0.44) have their own campaigns table — that is a separate system; the shared identity across systems is (client.email, base_domain). Cross-system linkage is out of scope for this schema. Populated at runtime by staff + order intake; no backfill migration.';


--
-- Name: COLUMN campaigns.campaign_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.campaigns.campaign_id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN campaigns.client_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.campaigns.client_id IS 'FK to clients. NOT NULL — a campaign without a client is meaningless.';


--
-- Name: COLUMN campaigns.base_domain; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.campaigns.base_domain IS 'The domain being promoted (e.g. "example.com"). Natural key paired with client_id: one active campaign per (client, domain).';


--
-- Name: COLUMN campaigns.anchor_text; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.campaigns.anchor_text IS 'Default anchor text for the campaign. Per-order anchors override via orders.anchor_text.';


--
-- Name: COLUMN campaigns.target_url; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.campaigns.target_url IS 'Default target URL. Per-order values override.';


--
-- Name: COLUMN campaigns.budget_cap; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.campaigns.budget_cap IS 'Dollar cap for the campaign. Enforced at order intake by summing orders.retail for orders with this campaign_id.';


--
-- Name: COLUMN campaigns.max_links; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.campaigns.max_links IS 'Count cap (number of links). Enforced same way as budget_cap.';


--
-- Name: COLUMN campaigns.goal; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.campaigns.goal IS 'Free-text campaign goal (e.g. "build topical authority for mental-health niche").';


--
-- Name: COLUMN campaigns.service_type; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.campaigns.service_type IS 'Default service type (guest_post / niche_edit / etc). Order-level values override.';


--
-- Name: COLUMN campaigns.status; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.campaigns.status IS 'active | paused | complete | archived. Orders cannot be created against paused/complete/archived campaigns.';


--
-- Name: COLUMN campaigns.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.campaigns.created_at IS 'Row creation timestamp. Set automatically on INSERT.';


--
-- Name: COLUMN campaigns.updated_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.campaigns.updated_at IS 'Row last-modified timestamp. Maintained by application layer.';


--
-- Name: COLUMN campaigns.archived_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.campaigns.archived_at IS 'Timestamp the campaign was archived. Null for active / paused / complete campaigns.';


--
-- Name: campaigns_campaign_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.campaigns_campaign_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: campaigns_campaign_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.campaigns_campaign_id_seq OWNED BY public.campaigns.campaign_id;


--
-- Name: category_vocabulary; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.category_vocabulary (
    category_id integer NOT NULL,
    slug character varying(40) NOT NULL,
    name character varying(100) NOT NULL,
    display_label character varying(150) NOT NULL,
    description text,
    active boolean DEFAULT true NOT NULL,
    sort_order integer DEFAULT 0 NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT category_vocabulary_slug_check CHECK (((slug)::text ~ '^[a-z][a-z0-9_]*$'::text))
);


--
-- Name: TABLE category_vocabulary; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.category_vocabulary IS 'Controlled vocabulary of approved site categories. Source of truth for the multi-label soft-scoring model. Each row defines one category; the slug maps to a future sites.cat_<slug> column that stores the 0-1 score for that category. Vocabulary lockdown target: 2026-05-01. After lockdown, a follow-up migration adds the score columns to sites and the AI scoring batch populates them. Adding a new row later requires a migration (ADD COLUMN on sites) + a re-score batch.';


--
-- Name: COLUMN category_vocabulary.category_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.category_vocabulary.category_id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN category_vocabulary.slug; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.category_vocabulary.slug IS 'Column-safe identifier. Lowercase letters, digits, and underscores only; must start with a letter. Becomes the suffix in sites.cat_<slug>. Example: "health_wellness" produces sites.cat_health_wellness. Immutable once scoring columns exist — renaming the slug would orphan the corresponding site column.';


--
-- Name: COLUMN category_vocabulary.name; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.category_vocabulary.name IS 'Canonical internal name (e.g. "Health & Wellness"). Used in reporting and admin UI.';


--
-- Name: COLUMN category_vocabulary.display_label; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.category_vocabulary.display_label IS 'Client-facing display label (e.g. "Health, Fitness & Wellness"). Shown in dashboard filter checkboxes and catalog category pills.';


--
-- Name: COLUMN category_vocabulary.description; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.category_vocabulary.description IS 'Optional free-text description guiding the AI scoring prompt and staff during manual overrides. Helps anchor marginal cases (e.g. does "mental health" belong under Health or Lifestyle?).';


--
-- Name: COLUMN category_vocabulary.active; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.category_vocabulary.active IS 'If false, the category is retained in history but not used for new scoring or shown in the UI. Prevents dropping a category from breaking sites.cat_<slug> columns that may still hold historical scores.';


--
-- Name: COLUMN category_vocabulary.sort_order; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.category_vocabulary.sort_order IS 'Display ordering hint for the dashboard filter UI. Lower sorts first.';


--
-- Name: category_vocabulary_category_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.category_vocabulary_category_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: category_vocabulary_category_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.category_vocabulary_category_id_seq OWNED BY public.category_vocabulary.category_id;


--
-- Name: clients; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.clients (
    client_id integer NOT NULL,
    email character varying(255) NOT NULL,
    name character varying(255),
    company character varying(255),
    client_type character varying(20) DEFAULT 'self_serve'::character varying NOT NULL,
    niche_classification character varying(20) DEFAULT 'standard'::character varying NOT NULL,
    standing_instructions text,
    account_manager_id integer,
    spp_client_id character varying(31),
    stlv2_user_id character varying(32),
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT clients_client_type_check CHECK (((client_type)::text = ANY ((ARRAY['self_serve'::character varying, 'managed'::character varying, 'reseller'::character varying])::text[]))),
    CONSTRAINT clients_niche_classification_check CHECK (((niche_classification)::text = ANY ((ARRAY['standard'::character varying, 'sensitive'::character varying, 'restricted'::character varying])::text[])))
);


--
-- Name: TABLE clients; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.clients IS 'One row per Loganix client (managed, self-serve, or reseller). Email is the stable identity. account_manager_id populated for managed clients. stlv2_user_id is a cross-system hint — the DIY storefront on 10.69.0.44 owns its own user table; we do not FK across. spp_client_id bridges to legacy SPP and will be dropped after cutover.';


--
-- Name: COLUMN clients.client_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.clients.client_id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN clients.email; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.clients.email IS 'Stable client identity. Unique. Same email that appears on orders.client_email.';


--
-- Name: COLUMN clients.name; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.clients.name IS 'Display name. First + last, or company contact name.';


--
-- Name: COLUMN clients.company; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.clients.company IS 'Optional company name.';


--
-- Name: COLUMN clients.client_type; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.clients.client_type IS 'self_serve (DIY storefront) | managed (staff-driven, has AM) | reseller (rebrands our service).';


--
-- Name: COLUMN clients.niche_classification; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.clients.niche_classification IS 'Applies system-wide across all client types. standard | sensitive (CBD / adult / gambling / crypto / payday — triggers sensitive pricing tier automatically) | restricted (do-not-fulfill niches).';


--
-- Name: COLUMN clients.standing_instructions; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.clients.standing_instructions IS 'Free-text standing rules (e.g. "all dofollow, no sponsored"). Surfaces at order intake + assignment. Replaces team-note knowledge that disappears with SPP.';


--
-- Name: COLUMN clients.account_manager_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.clients.account_manager_id IS 'FK to users.user_id. The AM who owns this client relationship. Null for self-serve.';


--
-- Name: COLUMN clients.spp_client_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.clients.spp_client_id IS 'Legacy SPP client ID bridge. Drop once SPP cutover is complete.';


--
-- Name: COLUMN clients.stlv2_user_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.clients.stlv2_user_id IS 'Optional cross-system hint pointing to the DIY storefront user_id on 10.69.0.44. Informational only — no FK, no enforcement. Use email + domain as the real join key when bridging.';


--
-- Name: COLUMN clients.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.clients.created_at IS 'Row creation timestamp. Set automatically on INSERT.';


--
-- Name: COLUMN clients.updated_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.clients.updated_at IS 'Row last-modified timestamp. Maintained by application layer.';


--
-- Name: clients_client_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.clients_client_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: clients_client_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.clients_client_id_seq OWNED BY public.clients.client_id;


--
-- Name: content_activity_log; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.content_activity_log (
    id integer NOT NULL,
    order_id integer,
    doc_id integer,
    action character varying(50) NOT NULL,
    actor character varying(255),
    target character varying(255),
    notes text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    writer_id integer
);


--
-- Name: TABLE content_activity_log; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.content_activity_log IS 'Activity log for the content/writing workflow. Tracks actions like assignments, deliveries, QA reviews, revisions, and sends. Separate from the global activity_log because content workflow has different actors (writers, editors, QA reviewers) and different action types.';


--
-- Name: COLUMN content_activity_log.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_activity_log.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN content_activity_log.order_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_activity_log.order_id IS 'Foreign key to orders.order_id. ON DELETE SET NULL. The order this activity relates to.';


--
-- Name: COLUMN content_activity_log.doc_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_activity_log.doc_id IS 'Foreign key to content_docs.doc_id. ON DELETE SET NULL. The document this activity relates to.';


--
-- Name: COLUMN content_activity_log.action; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_activity_log.action IS 'The action performed. Values: ''assigned'', ''accepted'', ''delivered'', ''revision_requested'', ''approved'', ''sent_to_publisher'', ''ai_check'', ''copyscape_check'', etc.';


--
-- Name: COLUMN content_activity_log.actor; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_activity_log.actor IS 'Who performed the action. Name or email of the staff member, writer, or ''system'' for automated actions.';


--
-- Name: COLUMN content_activity_log.target; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_activity_log.target IS 'Who/what the action was performed on. E.g. writer name, document title.';


--
-- Name: COLUMN content_activity_log.notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_activity_log.notes IS 'Additional details about the action.';


--
-- Name: COLUMN content_activity_log.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_activity_log.created_at IS 'Timestamp when this action occurred.';


--
-- Name: COLUMN content_activity_log.writer_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_activity_log.writer_id IS 'FK to writers.writer_id when the actor is a writer. Null when the actor is staff, system, or an external user. The free-text actor column remains as the canonical display name; writer_id enables reliable joins for aggregate reporting (writer throughput, revision rate per writer, etc.).';


--
-- Name: content_activity_log_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.content_activity_log_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: content_activity_log_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.content_activity_log_id_seq OWNED BY public.content_activity_log.id;


--
-- Name: content_assignments; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.content_assignments (
    assignment_id integer NOT NULL,
    doc_id integer NOT NULL,
    writer_id integer NOT NULL,
    status character varying(20) DEFAULT 'assigned'::character varying NOT NULL,
    ai_guidelines text,
    ai_ideas text,
    qa_notes text,
    is_current boolean DEFAULT true,
    assigned_at timestamp with time zone,
    accepted_at timestamp with time zone,
    delivered_at timestamp with time zone,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    revision_count smallint DEFAULT 0 NOT NULL,
    reviewer_user_id integer,
    qa_reviewed_at timestamp with time zone,
    due_at timestamp with time zone,
    revision_reason character varying(30),
    CONSTRAINT content_assignments_revision_reason_check CHECK (((revision_reason)::text = ANY ((ARRAY['quality'::character varying, 'ai_detected'::character varying, 'wrong_tone'::character varying, 'word_count_short'::character varying, 'wrong_niche'::character varying, 'plagiarism'::character varying, 'other'::character varying])::text[])))
);


--
-- Name: TABLE content_assignments; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.content_assignments IS 'Assigns a content document to a writer. Similar to order_assignments but for the writing workflow. A doc can be reassigned — is_current tracks which assignment is active. Tracks acceptance, delivery, and QA notes for the writing process.';


--
-- Name: COLUMN content_assignments.assignment_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_assignments.assignment_id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN content_assignments.doc_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_assignments.doc_id IS 'Foreign key to content_docs.doc_id. ON DELETE CASCADE.';


--
-- Name: COLUMN content_assignments.writer_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_assignments.writer_id IS 'Foreign key to writers.writer_id.';


--
-- Name: COLUMN content_assignments.status; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_assignments.status IS 'Writer-side workflow state for this assignment. Canonical vocabulary (no CHECK, kept flexible): queued (in the unassigned pool — team lead picks from here), assigned (writer accepted), writing (actively being worked on), submitted_for_qa (writer finished; team lead needs to review), revision_requested (team lead sent back — loops back to writing, revision_count increments), qa_approved (team lead passed; doc ready to hand off to fulfillment), handed_off (content_docs.handed_off_at was set; fulfillment owns it), canceled (order killed before content completed).';


--
-- Name: COLUMN content_assignments.ai_guidelines; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_assignments.ai_guidelines IS 'AI-generated writing guidelines specific to this assignment. May include topic suggestions, tone guidance, keyword targets.';


--
-- Name: COLUMN content_assignments.ai_ideas; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_assignments.ai_ideas IS 'AI-generated content ideas and angle suggestions for the writer.';


--
-- Name: COLUMN content_assignments.qa_notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_assignments.qa_notes IS 'QA feedback notes for this specific writing assignment.';


--
-- Name: COLUMN content_assignments.is_current; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_assignments.is_current IS 'Boolean. True = this is the active assignment. False = superseded by reassignment. Default: true.';


--
-- Name: COLUMN content_assignments.assigned_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_assignments.assigned_at IS 'Timestamp when the writer was assigned.';


--
-- Name: COLUMN content_assignments.accepted_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_assignments.accepted_at IS 'Timestamp when the writer accepted.';


--
-- Name: COLUMN content_assignments.delivered_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_assignments.delivered_at IS 'Timestamp when the writer submitted their work.';


--
-- Name: COLUMN content_assignments.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_assignments.created_at IS 'Timestamp when this assignment was created.';


--
-- Name: COLUMN content_assignments.updated_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_assignments.updated_at IS 'Timestamp of last modification.';


--
-- Name: COLUMN content_assignments.revision_count; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_assignments.revision_count IS 'How many times this assignment has been sent back for revision by the team lead. Increments each time status transitions from submitted_for_qa -> revision_requested. Useful signal for writer quality trends and client-facing SLA reports.';


--
-- Name: COLUMN content_assignments.reviewer_user_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_assignments.reviewer_user_id IS 'The internal staff user (team lead / editor like Matt, Kat) who reviewed this specific assignment. FK to users.user_id — distinct from writer_id which points to the third-party writer. Null until first QA review. ON DELETE SET NULL so assignment history survives if the reviewer leaves.';


--
-- Name: COLUMN content_assignments.qa_reviewed_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_assignments.qa_reviewed_at IS 'Timestamp when the team lead last completed a QA review of this assignment. Set regardless of outcome (approve or revision_requested). Null while assignment is still in writing phase.';


--
-- Name: COLUMN content_assignments.due_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_assignments.due_at IS 'Deadline for this specific writer assignment. Distinct from content_docs.late_at — late_at is doc-level and resets on reassignment; due_at is per-assignment and preserves the original deadline context when a doc is reassigned. Set at assignment time.';


--
-- Name: COLUMN content_assignments.revision_reason; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_assignments.revision_reason IS 'Structured cause when a team lead sends the doc back for revision. Populated alongside the free-text qa_notes. Values: quality, ai_detected, wrong_tone, word_count_short, wrong_niche, plagiarism, other. Enables reporting on revision drivers per writer / niche / week.';


--
-- Name: content_assignments_assignment_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.content_assignments_assignment_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: content_assignments_assignment_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.content_assignments_assignment_id_seq OWNED BY public.content_assignments.assignment_id;


--
-- Name: content_docs; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.content_docs (
    doc_id integer NOT NULL,
    order_id integer,
    doc_url character varying(255),
    word_count integer,
    word_count_estimate character varying(255),
    category character varying(255),
    status character varying(30) DEFAULT 'new'::character varying NOT NULL,
    admin_notes text,
    qa_approved boolean DEFAULT false,
    qa_approved_at timestamp with time zone,
    last_ai_check text,
    last_copyscape text,
    archived boolean DEFAULT false,
    late_at timestamp with time zone,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    qa_notes text,
    handed_off_at timestamp with time zone,
    topic_title character varying(255),
    topic_summary text,
    topic_proposed_by character varying(20),
    topic_sent_to character varying(255),
    topic_sent_at timestamp with time zone,
    topic_approval_status character varying(20),
    topic_approved_at timestamp with time zone,
    topic_rejection_reason text,
    repurpose_available boolean DEFAULT false NOT NULL,
    repurpose_status character varying(20),
    repurpose_notes text,
    qa_approved_by integer,
    CONSTRAINT content_docs_repurpose_status_check CHECK (((repurpose_status)::text = ANY ((ARRAY['unused'::character varying, 'earmarked'::character varying, 'repurposed'::character varying])::text[]))),
    CONSTRAINT content_docs_topic_approval_status_check CHECK (((topic_approval_status)::text = ANY ((ARRAY['pending'::character varying, 'approved'::character varying, 'rejected'::character varying])::text[]))),
    CONSTRAINT content_docs_topic_proposed_by_check CHECK (((topic_proposed_by)::text = ANY ((ARRAY['content_team'::character varying, 'client'::character varying])::text[])))
);


--
-- Name: TABLE content_docs; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.content_docs IS 'Google Docs created for order content. Each row represents one content document — typically a Google Doc that writers edit. Tracks QA status (AI detection, plagiarism checks), word count, and delivery status. The doc_url is the Google Docs URL that writers use to write content.';


--
-- Name: COLUMN content_docs.doc_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.doc_id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN content_docs.order_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.order_id IS 'Foreign key to orders.order_id. ON DELETE CASCADE. The order this content is for.';


--
-- Name: COLUMN content_docs.doc_url; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.doc_url IS 'URL to the Google Doc. Format: ''https://docs.google.com/document/d/...''.';


--
-- Name: COLUMN content_docs.word_count; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.word_count IS 'Actual word count of the written content. Updated after writing.';


--
-- Name: COLUMN content_docs.word_count_estimate; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.word_count_estimate IS 'Expected word count range. May be like ''500-750'' or ''1000+''. Set at doc creation.';


--
-- Name: COLUMN content_docs.category; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.category IS 'Content category/topic. Used for writer matching.';


--
-- Name: COLUMN content_docs.status; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.status IS 'Doc-level status mirroring the writer workflow. Canonical values (not enforced): new, writing, submitted_for_qa, revision_requested, qa_approved, handed_off, archived. Use content_assignments.status for the active assignment''s state; this column is the doc-level summary for quick order-side queries.';


--
-- Name: COLUMN content_docs.admin_notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.admin_notes IS 'Internal notes from content managers about this document.';


--
-- Name: COLUMN content_docs.qa_approved; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.qa_approved IS 'Boolean. Has this document passed QA review?';


--
-- Name: COLUMN content_docs.qa_approved_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.qa_approved_at IS 'Timestamp of QA approval.';


--
-- Name: COLUMN content_docs.last_ai_check; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.last_ai_check IS 'Result of last AI detection check (e.g. Originality.ai score). Free text.';


--
-- Name: COLUMN content_docs.last_copyscape; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.last_copyscape IS 'Result of last Copyscape plagiarism check. Free text.';


--
-- Name: COLUMN content_docs.archived; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.archived IS 'Boolean. Document has been archived (completed lifecycle).';


--
-- Name: COLUMN content_docs.late_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.late_at IS 'Timestamp when this document became overdue. Null if on time.';


--
-- Name: COLUMN content_docs.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.created_at IS 'Timestamp when the document was created.';


--
-- Name: COLUMN content_docs.updated_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.updated_at IS 'Timestamp of last modification.';


--
-- Name: COLUMN content_docs.qa_notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.qa_notes IS 'QA reviewer notes — what was checked, any issues found.';


--
-- Name: COLUMN content_docs.handed_off_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.handed_off_at IS 'Timestamp content was handed to fulfillment (sets order_assignments.content_handed_at for SLA clock). Auto-set by the app when qa_approved flips to true; the app simultaneously inserts an order_triggers row (department=fulfillment, trigger_type=content_handoff). Manual override allowed but discouraged.';


--
-- Name: COLUMN content_docs.topic_title; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.topic_title IS 'Proposed article topic title. Populated when the site requires topic approval before writing starts.';


--
-- Name: COLUMN content_docs.topic_summary; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.topic_summary IS 'Brief summary of the proposed topic for the publisher to approve or reject.';


--
-- Name: COLUMN content_docs.topic_proposed_by; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.topic_proposed_by IS 'Who drafted the topic proposal. content_team = staff proposed; client = client-supplied topic.';


--
-- Name: COLUMN content_docs.topic_sent_to; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.topic_sent_to IS 'Email / identifier of the publisher contact the topic was submitted to.';


--
-- Name: COLUMN content_docs.topic_sent_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.topic_sent_at IS 'When the topic was sent for approval. Null when no approval is needed or not yet sent.';


--
-- Name: COLUMN content_docs.topic_approval_status; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.topic_approval_status IS 'pending / approved / rejected. Writer queue should only surface docs with approved (or no-approval-required) topics.';


--
-- Name: COLUMN content_docs.topic_approved_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.topic_approved_at IS 'Timestamp of publisher approval. Null while pending or rejected.';


--
-- Name: COLUMN content_docs.topic_rejection_reason; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.topic_rejection_reason IS 'Publisher-supplied rejection reason. Null unless topic_approval_status=rejected.';


--
-- Name: COLUMN content_docs.repurpose_available; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.repurpose_available IS 'True when this archived doc has reusable content that can be repurposed for another order. Distinct from archived — archived means "no longer active on this order"; repurpose_available means "still usable somewhere else."';


--
-- Name: COLUMN content_docs.repurpose_status; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.repurpose_status IS 'unused (available for repurpose, not yet claimed), earmarked (content team has assigned it to a pending order), repurposed (used in another order — new doc_id references this one). Null when repurpose_available=false.';


--
-- Name: COLUMN content_docs.repurpose_notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.repurpose_notes IS 'Free-text notes about why this content is reusable and any constraints on reuse (e.g. "anchor-agnostic, niche-locked to health").';


--
-- Name: COLUMN content_docs.qa_approved_by; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_docs.qa_approved_by IS 'FK to users.user_id — the staff member who approved this doc in QA. Null until QA passes. Previously VARCHAR storing emails/names as a soft reference; converted to INTEGER FK for referential integrity.';


--
-- Name: content_docs_doc_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.content_docs_doc_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: content_docs_doc_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.content_docs_doc_id_seq OWNED BY public.content_docs.doc_id;


--
-- Name: content_notes; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.content_notes (
    note_id integer NOT NULL,
    doc_id integer NOT NULL,
    body text NOT NULL,
    created_by integer,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: TABLE content_notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.content_notes IS 'Append-only log of staff notes on a content doc. One row per note, preserved across revision cycles. Replaces the flat content_docs.admin_notes field for note history; admin_notes stays for the "current state" summary a team lead wants to see at a glance.';


--
-- Name: COLUMN content_notes.note_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_notes.note_id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN content_notes.doc_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_notes.doc_id IS 'FK to content_docs. ON DELETE CASCADE — notes go with the doc.';


--
-- Name: COLUMN content_notes.body; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_notes.body IS 'Free-text note body.';


--
-- Name: COLUMN content_notes.created_by; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_notes.created_by IS 'FK to users.user_id. Null for system-generated notes.';


--
-- Name: COLUMN content_notes.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.content_notes.created_at IS 'Row creation timestamp.';


--
-- Name: content_notes_note_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.content_notes_note_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: content_notes_note_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.content_notes_note_id_seq OWNED BY public.content_notes.note_id;


--
-- Name: cron_executions; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.cron_executions (
    id integer NOT NULL,
    cron_name character varying(50) NOT NULL,
    started_at timestamp with time zone DEFAULT now() NOT NULL,
    ended_at timestamp with time zone,
    duration_ms integer,
    status character varying(20) DEFAULT 'running'::character varying NOT NULL,
    rows_processed integer,
    error_message text,
    details jsonb,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT cron_executions_status_check CHECK (((status)::text = ANY ((ARRAY['running'::character varying, 'success'::character varying, 'failed'::character varying, 'timeout'::character varying])::text[])))
);


--
-- Name: TABLE cron_executions; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.cron_executions IS 'Append-only audit log of every cron execution. Insert on start (status=running); update on finish (status=success|failed|timeout, ended_at, duration_ms, rows_processed, error_message). One row per run — full history. The analytics portal drill-down charts failure rate, duration, and throughput over time from this table. Pair a retention policy (e.g. 90 days) with a partitioning strategy if volume grows.';


--
-- Name: COLUMN cron_executions.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_executions.id IS 'Auto-incrementing primary key for this execution record.';


--
-- Name: COLUMN cron_executions.cron_name; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_executions.cron_name IS 'Which cron ran. FK to cron_jobs.cron_name. ON DELETE CASCADE.';


--
-- Name: COLUMN cron_executions.started_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_executions.started_at IS 'Execution start time. Indexed for efficient time-window queries.';


--
-- Name: COLUMN cron_executions.ended_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_executions.ended_at IS 'When the run completed. Null while status=running.';


--
-- Name: COLUMN cron_executions.duration_ms; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_executions.duration_ms IS 'Total run time in milliseconds. Computed on finish.';


--
-- Name: COLUMN cron_executions.status; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_executions.status IS 'running (in flight), success (finished cleanly), failed (caught exception), timeout (exceeded cron_jobs.timeout_seconds or watchdog). Enforced by CHECK.';


--
-- Name: COLUMN cron_executions.rows_processed; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_executions.rows_processed IS 'Records affected / touched in this run. Useful for throughput dashboards.';


--
-- Name: COLUMN cron_executions.error_message; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_executions.error_message IS 'Exception message or failure reason. Null on success.';


--
-- Name: COLUMN cron_executions.details; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_executions.details IS 'Flexible per-cron metadata: API quota remaining, batch sizes, upstream response codes, skipped reasons. Extend keys without migrations.';


--
-- Name: COLUMN cron_executions.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_executions.created_at IS 'Row insert timestamp. In practice equals started_at — the row is created when the runner starts the execution.';


--
-- Name: cron_executions_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.cron_executions_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: cron_executions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.cron_executions_id_seq OWNED BY public.cron_executions.id;


--
-- Name: cron_jobs; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.cron_jobs (
    cron_name character varying(50) NOT NULL,
    enabled boolean DEFAULT true NOT NULL,
    schedule character varying(100),
    description text,
    last_run_at timestamp with time zone,
    last_success_at timestamp with time zone,
    last_failure_at timestamp with time zone,
    last_duration_ms integer,
    last_rows_processed integer,
    last_error text,
    consecutive_failures smallint DEFAULT 0 NOT NULL,
    run_count integer DEFAULT 0 NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: TABLE cron_jobs; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.cron_jobs IS 'Registry of every cron job we run, plus its current health state. One row per cron. The analytics portal reads this as its top-level summary: which crons are enabled, when each last succeeded or failed, last run duration / rows processed, consecutive failure count. Updated by the cron runner on each execution. Keep this focused on current state; historical trending lives in cron_executions.';


--
-- Name: COLUMN cron_jobs.cron_name; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_jobs.cron_name IS 'Stable identifier for the cron (e.g. ahrefs_batch, ai_overview, pinecone_sync). Primary key — cron_executions FKs here.';


--
-- Name: COLUMN cron_jobs.enabled; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_jobs.enabled IS 'Toggle to pause the cron without redeployment. Runner checks this before executing; disabled crons log nothing. Default true.';


--
-- Name: COLUMN cron_jobs.schedule; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_jobs.schedule IS 'Cron expression or human-readable cadence (e.g. "0 */6 * * *", "every 15 minutes", "daily 03:00 UTC"). Purely documentation — the actual schedule is enforced by systemd / cron / the runner.';


--
-- Name: COLUMN cron_jobs.description; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_jobs.description IS 'What the cron does, in a sentence. Surfaces in the analytics portal tooltip.';


--
-- Name: COLUMN cron_jobs.last_run_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_jobs.last_run_at IS 'Most recent time the cron started, regardless of outcome.';


--
-- Name: COLUMN cron_jobs.last_success_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_jobs.last_success_at IS 'Most recent successful completion. Distinguishing this from last_run_at lets the portal flag "ran but failed" vs. "hasn''t run at all".';


--
-- Name: COLUMN cron_jobs.last_failure_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_jobs.last_failure_at IS 'Most recent failed run. Paired with last_error for quick triage.';


--
-- Name: COLUMN cron_jobs.last_duration_ms; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_jobs.last_duration_ms IS 'Duration of the most recent completed run in milliseconds. Baseline for SLA alerting.';


--
-- Name: COLUMN cron_jobs.last_rows_processed; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_jobs.last_rows_processed IS 'Rows affected / records processed in the most recent run. Throughput signal.';


--
-- Name: COLUMN cron_jobs.last_error; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_jobs.last_error IS 'Error message from the most recent failed run. Cleared on next success.';


--
-- Name: COLUMN cron_jobs.consecutive_failures; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_jobs.consecutive_failures IS 'Count of consecutive failed runs; reset to 0 on success. Drives alerting thresholds.';


--
-- Name: COLUMN cron_jobs.run_count; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_jobs.run_count IS 'Lifetime count of executions for this cron.';


--
-- Name: COLUMN cron_jobs.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_jobs.created_at IS 'Row creation timestamp — when this cron job was first registered.';


--
-- Name: COLUMN cron_jobs.updated_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_jobs.updated_at IS 'Timestamp of the most recent update. Bumped whenever the runner records a new run result (last_run_at, last_success_at, failure, duration, etc.).';


--
-- Name: cron_runs; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.cron_runs (
    id integer NOT NULL,
    site_id integer NOT NULL,
    cron_name character varying(50) NOT NULL,
    last_run_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: TABLE cron_runs; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.cron_runs IS 'Tracks when each enrichment cron job last ran for each site. Used to schedule which sites need refreshing — the cron picks sites where last_run_at is oldest. Prevents re-running metrics on sites that were recently updated.';


--
-- Name: COLUMN cron_runs.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_runs.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN cron_runs.site_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_runs.site_id IS 'Foreign key to sites.site_id. ON DELETE CASCADE.';


--
-- Name: COLUMN cron_runs.cron_name; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_runs.cron_name IS 'Name of the cron job. Values include: ''ahrefs_traffic'', ''ahrefs_dr'', ''ahrefs_rd'', ''ahrefs_backlinks'', ''ahrefs_keywords'', ''majestic'', ''indexed_pages'', ''ai_categorize'', ''ai_describe'', ''embeddings'', ''ai_overview'', ''serp_match''. Each cron has its own refresh schedule.';


--
-- Name: COLUMN cron_runs.last_run_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.cron_runs.last_run_at IS 'Timestamp when this cron last processed this site. Crons query for sites with the oldest last_run_at to prioritize stale data.';


--
-- Name: cron_runs_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.cron_runs_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: cron_runs_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.cron_runs_id_seq OWNED BY public.cron_runs.id;


--
-- Name: directus_access; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_access (
    id uuid NOT NULL,
    role uuid,
    "user" uuid,
    policy uuid NOT NULL,
    sort integer
);


--
-- Name: directus_activity; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_activity (
    id integer NOT NULL,
    action character varying(45) NOT NULL,
    "user" uuid,
    "timestamp" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
    ip character varying(50),
    user_agent text,
    collection character varying(64) NOT NULL,
    item character varying(255) NOT NULL,
    origin character varying(255)
);


--
-- Name: directus_activity_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.directus_activity_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: directus_activity_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.directus_activity_id_seq OWNED BY public.directus_activity.id;


--
-- Name: directus_collections; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_collections (
    collection character varying(64) NOT NULL,
    icon character varying(64),
    note text,
    display_template character varying(255),
    hidden boolean DEFAULT false NOT NULL,
    singleton boolean DEFAULT false NOT NULL,
    translations json,
    archive_field character varying(64),
    archive_app_filter boolean DEFAULT true NOT NULL,
    archive_value character varying(255),
    unarchive_value character varying(255),
    sort_field character varying(64),
    accountability character varying(255) DEFAULT 'all'::character varying,
    color character varying(255),
    item_duplication_fields json,
    sort integer,
    "group" character varying(64),
    collapse character varying(255) DEFAULT 'open'::character varying NOT NULL,
    preview_url character varying(255),
    versioning boolean DEFAULT false NOT NULL
);


--
-- Name: directus_comments; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_comments (
    id uuid NOT NULL,
    collection character varying(64) NOT NULL,
    item character varying(255) NOT NULL,
    comment text NOT NULL,
    date_created timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
    date_updated timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
    user_created uuid,
    user_updated uuid
);


--
-- Name: directus_dashboards; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_dashboards (
    id uuid NOT NULL,
    name character varying(255) NOT NULL,
    icon character varying(64) DEFAULT 'dashboard'::character varying NOT NULL,
    note text,
    date_created timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
    user_created uuid,
    color character varying(255)
);


--
-- Name: directus_extensions; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_extensions (
    enabled boolean DEFAULT true NOT NULL,
    id uuid NOT NULL,
    folder character varying(255) NOT NULL,
    source character varying(255) NOT NULL,
    bundle uuid
);


--
-- Name: directus_fields; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_fields (
    id integer NOT NULL,
    collection character varying(64) NOT NULL,
    field character varying(64) NOT NULL,
    special character varying(64),
    interface character varying(64),
    options json,
    display character varying(64),
    display_options json,
    readonly boolean DEFAULT false NOT NULL,
    hidden boolean DEFAULT false NOT NULL,
    sort integer,
    width character varying(30) DEFAULT 'full'::character varying,
    translations json,
    note text,
    conditions json,
    required boolean DEFAULT false,
    "group" character varying(64),
    validation json,
    validation_message text,
    searchable boolean DEFAULT true NOT NULL
);


--
-- Name: directus_fields_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.directus_fields_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: directus_fields_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.directus_fields_id_seq OWNED BY public.directus_fields.id;


--
-- Name: directus_files; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_files (
    id uuid NOT NULL,
    storage character varying(255) NOT NULL,
    filename_disk character varying(255),
    filename_download character varying(255) NOT NULL,
    title character varying(255),
    type character varying(255),
    folder uuid,
    uploaded_by uuid,
    created_on timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
    modified_by uuid,
    modified_on timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
    charset character varying(50),
    filesize bigint,
    width integer,
    height integer,
    duration integer,
    embed character varying(200),
    description text,
    location text,
    tags text,
    metadata json,
    focal_point_x integer,
    focal_point_y integer,
    tus_id character varying(64),
    tus_data json,
    uploaded_on timestamp with time zone
);


--
-- Name: directus_flows; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_flows (
    id uuid NOT NULL,
    name character varying(255) NOT NULL,
    icon character varying(64),
    color character varying(255),
    description text,
    status character varying(255) DEFAULT 'active'::character varying NOT NULL,
    trigger character varying(255),
    accountability character varying(255) DEFAULT 'all'::character varying,
    options json,
    operation uuid,
    date_created timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
    user_created uuid
);


--
-- Name: directus_folders; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_folders (
    id uuid NOT NULL,
    name character varying(255) NOT NULL,
    parent uuid
);


--
-- Name: directus_migrations; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_migrations (
    version character varying(255) NOT NULL,
    name character varying(255) NOT NULL,
    "timestamp" timestamp with time zone DEFAULT CURRENT_TIMESTAMP
);


--
-- Name: directus_notifications; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_notifications (
    id integer NOT NULL,
    "timestamp" timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
    status character varying(255) DEFAULT 'inbox'::character varying,
    recipient uuid NOT NULL,
    sender uuid,
    subject character varying(255) NOT NULL,
    message text,
    collection character varying(64),
    item character varying(255)
);


--
-- Name: directus_notifications_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.directus_notifications_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: directus_notifications_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.directus_notifications_id_seq OWNED BY public.directus_notifications.id;


--
-- Name: directus_operations; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_operations (
    id uuid NOT NULL,
    name character varying(255),
    key character varying(255) NOT NULL,
    type character varying(255) NOT NULL,
    position_x integer NOT NULL,
    position_y integer NOT NULL,
    options json,
    resolve uuid,
    reject uuid,
    flow uuid NOT NULL,
    date_created timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
    user_created uuid
);


--
-- Name: directus_panels; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_panels (
    id uuid NOT NULL,
    dashboard uuid NOT NULL,
    name character varying(255),
    icon character varying(64) DEFAULT NULL::character varying,
    color character varying(10),
    show_header boolean DEFAULT false NOT NULL,
    note text,
    type character varying(255) NOT NULL,
    position_x integer NOT NULL,
    position_y integer NOT NULL,
    width integer NOT NULL,
    height integer NOT NULL,
    options json,
    date_created timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
    user_created uuid
);


--
-- Name: directus_permissions; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_permissions (
    id integer NOT NULL,
    collection character varying(64) NOT NULL,
    action character varying(10) NOT NULL,
    permissions json,
    validation json,
    presets json,
    fields text,
    policy uuid NOT NULL
);


--
-- Name: directus_permissions_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.directus_permissions_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: directus_permissions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.directus_permissions_id_seq OWNED BY public.directus_permissions.id;


--
-- Name: directus_policies; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_policies (
    id uuid NOT NULL,
    name character varying(100) NOT NULL,
    icon character varying(64) DEFAULT 'badge'::character varying NOT NULL,
    description text,
    ip_access text,
    enforce_tfa boolean DEFAULT false NOT NULL,
    admin_access boolean DEFAULT false NOT NULL,
    app_access boolean DEFAULT false NOT NULL
);


--
-- Name: directus_presets; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_presets (
    id integer NOT NULL,
    bookmark character varying(255),
    "user" uuid,
    role uuid,
    collection character varying(64),
    search character varying(100),
    layout character varying(100) DEFAULT 'tabular'::character varying,
    layout_query json,
    layout_options json,
    refresh_interval integer,
    filter json,
    icon character varying(64) DEFAULT 'bookmark'::character varying,
    color character varying(255)
);


--
-- Name: directus_presets_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.directus_presets_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: directus_presets_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.directus_presets_id_seq OWNED BY public.directus_presets.id;


--
-- Name: directus_relations; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_relations (
    id integer NOT NULL,
    many_collection character varying(64) NOT NULL,
    many_field character varying(64) NOT NULL,
    one_collection character varying(64),
    one_field character varying(64),
    one_collection_field character varying(64),
    one_allowed_collections text,
    junction_field character varying(64),
    sort_field character varying(64),
    one_deselect_action character varying(255) DEFAULT 'nullify'::character varying NOT NULL
);


--
-- Name: directus_relations_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.directus_relations_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: directus_relations_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.directus_relations_id_seq OWNED BY public.directus_relations.id;


--
-- Name: directus_revisions; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_revisions (
    id integer NOT NULL,
    activity integer NOT NULL,
    collection character varying(64) NOT NULL,
    item character varying(255) NOT NULL,
    data json,
    delta json,
    parent integer,
    version uuid
);


--
-- Name: directus_revisions_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.directus_revisions_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: directus_revisions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.directus_revisions_id_seq OWNED BY public.directus_revisions.id;


--
-- Name: directus_roles; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_roles (
    id uuid NOT NULL,
    name character varying(100) NOT NULL,
    icon character varying(64) DEFAULT 'supervised_user_circle'::character varying NOT NULL,
    description text,
    parent uuid
);


--
-- Name: directus_sessions; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_sessions (
    token character varying(64) NOT NULL,
    "user" uuid,
    expires timestamp with time zone NOT NULL,
    ip character varying(255),
    user_agent text,
    share uuid,
    origin character varying(255),
    next_token character varying(64)
);


--
-- Name: directus_settings; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_settings (
    id integer NOT NULL,
    project_name character varying(100) DEFAULT 'Directus'::character varying NOT NULL,
    project_url character varying(255),
    project_color character varying(255) DEFAULT '#6644FF'::character varying NOT NULL,
    project_logo uuid,
    public_foreground uuid,
    public_background uuid,
    public_note text,
    auth_login_attempts integer DEFAULT 25,
    auth_password_policy character varying(100),
    storage_asset_transform character varying(7) DEFAULT 'all'::character varying,
    storage_asset_presets json,
    custom_css text,
    storage_default_folder uuid,
    basemaps json,
    mapbox_key character varying(255),
    module_bar json,
    project_descriptor character varying(100),
    default_language character varying(255) DEFAULT 'en-US'::character varying NOT NULL,
    custom_aspect_ratios json,
    public_favicon uuid,
    default_appearance character varying(255) DEFAULT 'auto'::character varying NOT NULL,
    default_theme_light character varying(255),
    theme_light_overrides json,
    default_theme_dark character varying(255),
    theme_dark_overrides json,
    report_error_url character varying(255),
    report_bug_url character varying(255),
    report_feature_url character varying(255),
    public_registration boolean DEFAULT false NOT NULL,
    public_registration_verify_email boolean DEFAULT true NOT NULL,
    public_registration_role uuid,
    public_registration_email_filter json,
    visual_editor_urls json,
    project_id uuid,
    mcp_enabled boolean DEFAULT false NOT NULL,
    mcp_allow_deletes boolean DEFAULT false NOT NULL,
    mcp_prompts_collection character varying(255) DEFAULT NULL::character varying,
    mcp_system_prompt_enabled boolean DEFAULT true NOT NULL,
    mcp_system_prompt text,
    project_owner character varying(255),
    project_usage character varying(255),
    org_name character varying(255),
    product_updates boolean,
    project_status character varying(255)
);


--
-- Name: directus_settings_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.directus_settings_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: directus_settings_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.directus_settings_id_seq OWNED BY public.directus_settings.id;


--
-- Name: directus_shares; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_shares (
    id uuid NOT NULL,
    name character varying(255),
    collection character varying(64) NOT NULL,
    item character varying(255) NOT NULL,
    role uuid,
    password character varying(255),
    user_created uuid,
    date_created timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
    date_start timestamp with time zone,
    date_end timestamp with time zone,
    times_used integer DEFAULT 0,
    max_uses integer
);


--
-- Name: directus_translations; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_translations (
    id uuid NOT NULL,
    language character varying(255) NOT NULL,
    key character varying(255) NOT NULL,
    value text NOT NULL
);


--
-- Name: directus_users; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_users (
    id uuid NOT NULL,
    first_name character varying(50),
    last_name character varying(50),
    email character varying(128),
    password character varying(255),
    location character varying(255),
    title character varying(50),
    description text,
    tags json,
    avatar uuid,
    language character varying(255) DEFAULT NULL::character varying,
    tfa_secret character varying(255),
    status character varying(16) DEFAULT 'active'::character varying NOT NULL,
    role uuid,
    token character varying(255),
    last_access timestamp with time zone,
    last_page character varying(255),
    provider character varying(128) DEFAULT 'default'::character varying NOT NULL,
    external_identifier character varying(255),
    auth_data json,
    email_notifications boolean DEFAULT true,
    appearance character varying(255),
    theme_dark character varying(255),
    theme_light character varying(255),
    theme_light_overrides json,
    theme_dark_overrides json,
    text_direction character varying(255) DEFAULT 'auto'::character varying NOT NULL
);


--
-- Name: directus_versions; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_versions (
    id uuid NOT NULL,
    key character varying(64) NOT NULL,
    name character varying(255),
    collection character varying(64) NOT NULL,
    item character varying(255) NOT NULL,
    hash character varying(255),
    date_created timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
    date_updated timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
    user_created uuid,
    user_updated uuid,
    delta json
);


--
-- Name: directus_webhooks; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.directus_webhooks (
    id integer NOT NULL,
    name character varying(255) NOT NULL,
    method character varying(10) DEFAULT 'POST'::character varying NOT NULL,
    url character varying(255) NOT NULL,
    status character varying(10) DEFAULT 'active'::character varying NOT NULL,
    data boolean DEFAULT true NOT NULL,
    actions character varying(100) NOT NULL,
    collections character varying(255) NOT NULL,
    headers json,
    was_active_before_deprecation boolean DEFAULT false NOT NULL,
    migrated_flow uuid
);


--
-- Name: directus_webhooks_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.directus_webhooks_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: directus_webhooks_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.directus_webhooks_id_seq OWNED BY public.directus_webhooks.id;


--
-- Name: link_checks; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.link_checks (
    id integer NOT NULL,
    order_id integer NOT NULL,
    link_found boolean,
    link_rel character varying(50),
    anchor_matched boolean,
    anchor_similarity character varying(20),
    found_anchor character varying(255),
    surrounding_text text,
    context_quality character varying(20),
    quality_grade character(2),
    ai_summary text,
    links_scanned integer,
    internal_links integer,
    external_links integer,
    checked_at timestamp with time zone NOT NULL,
    assignment_id integer,
    placement_url text,
    domain_matched boolean,
    triggered_by character varying(30),
    trigger_reason text
);


--
-- Name: TABLE link_checks; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.link_checks IS 'Automated QA results from Atlas link checking system. After a vendor submits a final_url, Atlas crawls the page to verify: link exists, anchor text matches, link is dofollow, surrounding content quality is acceptable. Each check produces a quality grade (A+ to F). Multiple checks can exist per order (re-checks after fixes). The most recent check is authoritative.';


--
-- Name: COLUMN link_checks.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.link_checks.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN link_checks.order_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.link_checks.order_id IS 'Foreign key to orders.order_id. ON DELETE CASCADE.';


--
-- Name: COLUMN link_checks.link_found; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.link_checks.link_found IS 'Boolean. Was the client''s link found on the page? False = link is missing or broken.';


--
-- Name: COLUMN link_checks.link_rel; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.link_checks.link_rel IS 'The rel attribute of the found link. E.g. ''dofollow'', ''nofollow'', ''sponsored'', ''ugc''. Null if link not found.';


--
-- Name: COLUMN link_checks.anchor_matched; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.link_checks.anchor_matched IS 'Boolean. Does the found anchor text match (or closely match) the requested anchor text?';


--
-- Name: COLUMN link_checks.anchor_similarity; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.link_checks.anchor_similarity IS 'How close the found anchor is to the requested one. Values: ''exact'', ''close'', ''partial'', ''none''.';


--
-- Name: COLUMN link_checks.found_anchor; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.link_checks.found_anchor IS 'The actual anchor text found on the page. For comparison with orders.anchor_text.';


--
-- Name: COLUMN link_checks.surrounding_text; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.link_checks.surrounding_text IS 'Text excerpt surrounding the placed link. Used to assess contextual quality — is the link naturally embedded in relevant content?';


--
-- Name: COLUMN link_checks.context_quality; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.link_checks.context_quality IS 'Quality assessment of the surrounding content context. Values: ''excellent'', ''good'', ''fair'', ''poor''.';


--
-- Name: COLUMN link_checks.quality_grade; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.link_checks.quality_grade IS 'Overall quality grade for this link check. Values: ''A+'', ''A'', ''B+'', ''B'', ''C+'', ''C'', ''D'', ''F''. Char(2). Based on composite of all check factors.';


--
-- Name: COLUMN link_checks.ai_summary; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.link_checks.ai_summary IS 'AI-generated summary of the link check results. Plain English explanation of findings for staff review.';


--
-- Name: COLUMN link_checks.links_scanned; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.link_checks.links_scanned IS 'Total number of links found on the page during the scan.';


--
-- Name: COLUMN link_checks.internal_links; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.link_checks.internal_links IS 'Number of internal links (same domain) found on the page.';


--
-- Name: COLUMN link_checks.external_links; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.link_checks.external_links IS 'Number of external links (different domains) found on the page.';


--
-- Name: COLUMN link_checks.checked_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.link_checks.checked_at IS 'Timestamp when this link check was performed.';


--
-- Name: COLUMN link_checks.assignment_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.link_checks.assignment_id IS 'FK to order_assignments. Ties this check to the specific vendor submission that triggered it. Nullable — existing historical checks pre-dating this column have this unset; all new checks must populate it. ON DELETE SET NULL so a deleted assignment does not cascade-destroy its check history.';


--
-- Name: COLUMN link_checks.placement_url; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.link_checks.placement_url IS 'The actual URL of the page where the link was found. Set by the link-checker cron when it locates the client link. Null when link_found=false. Used alongside domain_matched to detect wrong-domain placements (e.g. link on sub.example.com when example.com was ordered).';


--
-- Name: COLUMN link_checks.domain_matched; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.link_checks.domain_matched IS 'Boolean set by the link-checker cron. True when the placement_url host matches (or is a subdomain of) the ordered sites.domain. False when the link was placed on the wrong domain entirely. Null when link_found=false (no placement_url to compare). Feeds the wrong_domain rework trigger.';


--
-- Name: COLUMN link_checks.triggered_by; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.link_checks.triggered_by IS 'What caused this check to run. Free text (no CHECK enum). Conventional values: initial (first check after vendor submission), rework_initial (first check after rework), cron_sweep (routine scheduled check), vendor_fix (vendor reported restoration, re-verify), client_complaint (client flagged an issue), user (staff manually re-ran). Null on legacy rows pre-dating this column.';


--
-- Name: COLUMN link_checks.trigger_reason; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.link_checks.trigger_reason IS 'Free-text context for why this specific re-check fired. Useful when triggered_by is not routine. Example: "Vendor reported they re-added the link after accidental removal". Null when routine.';


--
-- Name: link_checks_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.link_checks_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: link_checks_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.link_checks_id_seq OWNED BY public.link_checks.id;


--
-- Name: managed_clients; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.managed_clients (
    managed_client_id integer NOT NULL,
    client_name text NOT NULL,
    client_domain text,
    internal_am text,
    agency text,
    bl_approved boolean,
    bl_only boolean,
    requires_qa boolean,
    requires_approval boolean,
    link_directive text,
    order_sheet_url text,
    source text DEFAULT 'jen_master_sheet'::text NOT NULL,
    last_synced_at timestamp with time zone,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: TABLE managed_clients; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.managed_clients IS 'The real-client lookup for managed (MGP*) orders. Each row is one client whose campaigns Loganix drives end-to-end — distinct from regular SPP-style customers who shop the list themselves. Source of truth is Jen''s "Managed Link Building Master" Google Sheet; this table mirrors it via the sync_managed_clients.py script. Orders join in via orders.managed_client_id (NULL for non-managed orders). Around 60-80 active rows; grows slowly.';


--
-- Name: COLUMN managed_clients.managed_client_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.managed_clients.managed_client_id IS 'Surrogate primary key. Referenced by orders.managed_client_id. Use this for joins; do not rely on client_name for joins since clients can be renamed.';


--
-- Name: COLUMN managed_clients.client_name; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.managed_clients.client_name IS 'Canonical client name as it appears in Jen''s master sheet (e.g. "AIU", "Axiom Roofing"). UNIQUE — used as the natural key when syncing from the sheet. Display this in UIs and reports.';


--
-- Name: COLUMN managed_clients.client_domain; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.managed_clients.client_domain IS 'Bare domain of the client''s primary website (e.g. "aiu.edu"). Populated by resolve_managed_orders.py — it extracts target URLs from Google Doc titles like "MGP02013 - sdnews.com - mycanadapayday.com/..." and reduces them to the bare domain. May be NULL for clients we have not yet matched against any document. Indexed lower-case for case-insensitive lookup.';


--
-- Name: COLUMN managed_clients.internal_am; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.managed_clients.internal_am IS 'Loganix-side account manager (e.g. "Jonathan", "Jen"). Mirrored from Jen''s master sheet column "Internal AM".';


--
-- Name: COLUMN managed_clients.agency; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.managed_clients.agency IS 'Reseller agency that brings the client to Loganix, if any (e.g. "Bizango", "Direct"). Mirrored from Jen''s master sheet column "Agency". "Direct" means the client buys from Loganix directly with no agency in between.';


--
-- Name: COLUMN managed_clients.bl_approved; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.managed_clients.bl_approved IS 'Whether brand links (BL) placements are pre-approved for this client. Mirrored from Jen''s master sheet column "BL Approved". When TRUE, ops can place brand links without per-link client approval.';


--
-- Name: COLUMN managed_clients.bl_only; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.managed_clients.bl_only IS 'Whether this client only buys brand links (no authority links / guest posts). Mirrored from Jen''s master sheet column "BL Only".';


--
-- Name: COLUMN managed_clients.requires_qa; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.managed_clients.requires_qa IS 'Whether placements for this client must go through Loganix QA before delivery. Mirrored from Jen''s master sheet column "Requires QA".';


--
-- Name: COLUMN managed_clients.requires_approval; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.managed_clients.requires_approval IS 'Whether placements require explicit client approval before going live. Mirrored from Jen''s master sheet column "Requires Approval".';


--
-- Name: COLUMN managed_clients.link_directive; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.managed_clients.link_directive IS 'Free-text monthly link plan from Jen''s master sheet, describing the tiers, target pages, and per-link prices the client buys (e.g. "Jan-Mar 2026 (3 links | $2,103) • 1 ($600 tier) AL - homepage..."). Human-readable; not parsed.';


--
-- Name: COLUMN managed_clients.order_sheet_url; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.managed_clients.order_sheet_url IS 'URL of the client''s individual Google order sheet, where ops tracks the per-link assignments and status for this client. Sourced from Jen''s master sheet column "Order Sheet URL".';


--
-- Name: COLUMN managed_clients.source; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.managed_clients.source IS 'Where this row was synced from. Currently always "jen_master_sheet". Reserved for future sources (e.g. a successor app that replaces Jen''s sheet).';


--
-- Name: COLUMN managed_clients.last_synced_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.managed_clients.last_synced_at IS 'When sync_managed_clients.py last refreshed this row from its source. NULL until first sync. If significantly older than the source sheet''s modifiedTime, the row may be stale.';


--
-- Name: COLUMN managed_clients.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.managed_clients.created_at IS 'When this row was first inserted into managed_clients.';


--
-- Name: COLUMN managed_clients.updated_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.managed_clients.updated_at IS 'When any column on this row was last updated. Bumped by sync_managed_clients.py whenever the source sheet shows a change.';


--
-- Name: managed_clients_managed_client_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.managed_clients_managed_client_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: managed_clients_managed_client_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.managed_clients_managed_client_id_seq OWNED BY public.managed_clients.managed_client_id;


--
-- Name: order_assignments; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.order_assignments (
    assignment_id integer NOT NULL,
    order_id integer NOT NULL,
    supplier_id integer NOT NULL,
    vendor_hash character varying(31),
    status character varying(20) DEFAULT 'pending'::character varying NOT NULL,
    cost numeric(10,2),
    vendor_tat smallint,
    vendor_stated_tat character varying(50),
    risk_score character varying(20),
    risk_factors jsonb,
    rework_reason character varying(255),
    cancellation_reason character varying(255),
    extension_hours smallint,
    pay_email character varying(255),
    pay_notes text,
    is_current boolean DEFAULT true,
    assigned_at timestamp with time zone,
    accepted_at timestamp with time zone,
    completed_at timestamp with time zone,
    rejected_at timestamp with time zone,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    vendor_notes text,
    internal_notes text,
    paid_at timestamp with time zone,
    content_handed_at timestamp with time zone,
    sla_threshold_hours smallint,
    restore_by_at timestamp with time zone,
    rework_trigger_type character varying(30),
    payment_method character varying(20),
    payment_proof_url text,
    payment_transaction_id character varying(128),
    invoice_id integer,
    selection_reason text,
    CONSTRAINT order_assignments_payment_method_check CHECK (((payment_method IS NULL) OR ((payment_method)::text = ANY ((ARRAY['paypal'::character varying, 'payoneer'::character varying, 'wise'::character varying, 'bank_transfer'::character varying, 'credit_card'::character varying, 'wire'::character varying, 'crypto'::character varying, 'other'::character varying])::text[])))),
    CONSTRAINT order_assignments_rework_needs_type CHECK ((((status)::text <> 'rework'::text) OR (rework_trigger_type IS NOT NULL))),
    CONSTRAINT order_assignments_rework_trigger_type_check CHECK (((rework_trigger_type)::text = ANY ((ARRAY['nf_ordered_df'::character varying, 'sponsored_not_ordered'::character varying, 'dead_link'::character varying, 'client_link_not_found'::character varying, 'wrong_domain'::character varying, 'other'::character varying])::text[])))
);


--
-- Name: TABLE order_assignments; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.order_assignments IS 'Assigns an order to a specific vendor/publisher for fulfillment. Each assignment represents one attempt to fulfill — if a vendor rejects or fails, a new assignment row is created (is_current=true on the new one, false on the old). This is what vendors see in their portal. Tracks acceptance, cost, TAT, payment status, and completion.';


--
-- Name: COLUMN order_assignments.assignment_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.assignment_id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN order_assignments.order_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.order_id IS 'Foreign key to orders.order_id. ON DELETE CASCADE. The order being assigned.';


--
-- Name: COLUMN order_assignments.supplier_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.supplier_id IS 'Foreign key to suppliers.supplier_id. The vendor/publisher assigned to fulfill this order.';


--
-- Name: COLUMN order_assignments.vendor_hash; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.vendor_hash IS 'Unique hash used in vendor-facing URLs for security. Vendors access their assignment via this hash rather than sequential IDs.';


--
-- Name: COLUMN order_assignments.status; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.status IS 'Assignment status. Values: ''pending'' (awaiting vendor response), ''accepted'' (vendor accepted, working on it), ''completed'' (vendor submitted final URL), ''rejected'' (vendor declined), ''cancelled'' (cancelled by staff), ''rework'' (sent back for corrections). Default: ''pending''.';


--
-- Name: COLUMN order_assignments.cost; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.cost IS 'What we pay the supplier for this assignment. Decimal(10,2). Sourced from supplier_site_pricing at assignment time.';


--
-- Name: COLUMN order_assignments.vendor_tat; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.vendor_tat IS 'Agreed turnaround time in days for this assignment.';


--
-- Name: COLUMN order_assignments.vendor_stated_tat; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.vendor_stated_tat IS 'What the vendor claims their typical TAT is. Free text, may be like ''2 weeks''. For comparison with actual performance.';


--
-- Name: COLUMN order_assignments.risk_score; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.risk_score IS 'Assignment-level risk assessment. May differ from order-level risk. Values: ''low'', ''medium'', ''high''.';


--
-- Name: COLUMN order_assignments.risk_factors; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.risk_factors IS 'JSONB with assignment-specific risk factors.';


--
-- Name: COLUMN order_assignments.rework_reason; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.rework_reason IS 'If status=''rework'', explains why the work was sent back. Free text.';


--
-- Name: COLUMN order_assignments.cancellation_reason; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.cancellation_reason IS 'If status=''cancelled'' or ''rejected'', explains why. Free text.';


--
-- Name: COLUMN order_assignments.extension_hours; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.extension_hours IS 'Additional hours granted beyond the agreed TAT. Null if no extension requested.';


--
-- Name: COLUMN order_assignments.pay_email; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.pay_email IS 'Email used for payment. May differ from supplier''s primary email.';


--
-- Name: COLUMN order_assignments.pay_notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.pay_notes IS 'Notes related to payment processing.';


--
-- Name: COLUMN order_assignments.is_current; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.is_current IS 'Boolean. True = this is the active assignment for this order. False = superseded by a newer assignment (e.g. after rejection/reassignment). Only one assignment per order should have is_current=true.';


--
-- Name: COLUMN order_assignments.assigned_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.assigned_at IS 'Timestamp when this assignment was created and sent to the vendor.';


--
-- Name: COLUMN order_assignments.accepted_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.accepted_at IS 'Timestamp when the vendor accepted the assignment.';


--
-- Name: COLUMN order_assignments.completed_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.completed_at IS 'Timestamp when the vendor submitted the final URL.';


--
-- Name: COLUMN order_assignments.rejected_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.rejected_at IS 'Timestamp when the vendor rejected the assignment.';


--
-- Name: COLUMN order_assignments.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.created_at IS 'Timestamp when this assignment record was created.';


--
-- Name: COLUMN order_assignments.updated_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.updated_at IS 'Timestamp of last modification.';


--
-- Name: COLUMN order_assignments.vendor_notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.vendor_notes IS 'Notes from the vendor about this assignment. Vendor-editable.';


--
-- Name: COLUMN order_assignments.internal_notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.internal_notes IS 'Internal staff notes about this assignment. Not visible to vendors.';


--
-- Name: COLUMN order_assignments.paid_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.paid_at IS 'Timestamp when the supplier was paid for this assignment. Null = unpaid. Manually set by staff via the fulfillment dashboard; may be automated later. Paired with pay_email and pay_notes for the full payment record.';


--
-- Name: COLUMN order_assignments.content_handed_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.content_handed_at IS 'Timestamp the vendor could actually start work. Path 1 (Loganix writes): set when content QA hands off. Path 2 (vendor writes): set when vendor accepts. Path 3 (client provides): set at order intake. This is the SLA clock start - NOT assigned_at.';


--
-- Name: COLUMN order_assignments.sla_threshold_hours; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.sla_threshold_hours IS 'Hours vendor has from content_handed_at to mark complete. Seeded from supplier_sites.tat or suppliers.default_tat at assignment time; can be overridden per-order. Null means no SLA tracking. Breach detection via v_order_assignments_sla view.';


--
-- Name: COLUMN order_assignments.restore_by_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.restore_by_at IS 'Timestamp by which the vendor must restore a dead link before the debit auto-fires against their supplier_ledger. Set when a dead-link event is detected and the vendor is notified. Null when there is no active restoration grace period. Ties into the structured debit workflow (supplier_ledger.debit_reason = dead_link_unrestorable). Renamed from restoration_deadline in migration 031 to match the project-wide *_at convention.';


--
-- Name: COLUMN order_assignments.rework_trigger_type; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.rework_trigger_type IS 'Structured cause of rework. NOT NULL when status=rework. Values: nf_ordered_df (link placed nofollow when dofollow ordered), sponsored_not_ordered (link tagged as sponsored without being ordered that way), dead_link (link returns 404 / target missing from page), client_link_not_found (target URL missing from the page entirely), wrong_domain (link placed on wrong domain or subdomain from what was ordered), other (edge case not covered by the five — use rework_reason free text for specifics). Feeds into reporting on rework drivers across vendors and into the rework_* trigger_types on order_triggers.';


--
-- Name: COLUMN order_assignments.payment_method; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.payment_method IS 'Channel used for this individual order payment. Same enum as supplier_invoices.payment_method. Used for Flow 2 direct-publisher payments paid outside the invoice batch cycle.';


--
-- Name: COLUMN order_assignments.payment_proof_url; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.payment_proof_url IS 'URL to the payment receipt / confirmation for this per-order payment. Null until paid.';


--
-- Name: COLUMN order_assignments.payment_transaction_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.payment_transaction_id IS 'PayPal transaction ID or equivalent channel-specific reference. Nullable now, auto-populated later when payment API integration is built.';


--
-- Name: COLUMN order_assignments.invoice_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.invoice_id IS 'FK to supplier_invoices.id. Links this assignment back to the batch invoice it was settled in (Flow 1). Null for Flow 2 direct-publisher assignments that were paid per-order and not batched.';


--
-- Name: COLUMN order_assignments.selection_reason; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_assignments.selection_reason IS 'Dedicated free-text rationale for why this vendor was chosen for this order. Example uses: client preference, prior relationship, only vendor with matching inventory, fallback after rejection, lowest cost, fastest TAT, trusted on this niche. Left as free text (not an enum) because the reasons are open-ended. internal_notes stays for general-purpose assignment notes; selection_reason is scoped specifically to vendor selection rationale. Enables reporting on "how often do we pick by cost vs by client preference" once the data accumulates.';


--
-- Name: order_assignments_assignment_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.order_assignments_assignment_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: order_assignments_assignment_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.order_assignments_assignment_id_seq OWNED BY public.order_assignments.assignment_id;


--
-- Name: order_attachments; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.order_attachments (
    id integer NOT NULL,
    order_id integer NOT NULL,
    assignment_id integer,
    file_url character varying(500) NOT NULL,
    file_type character varying(50),
    uploaded_by character varying(255),
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    attachment_type character varying(20),
    file_size integer,
    scan_status character varying(20),
    scanned_at timestamp with time zone,
    uploader_type character varying(10),
    uploader_user_id integer,
    uploader_client_id integer,
    uploader_supplier_id integer,
    CONSTRAINT order_attachments_attachment_type_check CHECK (((attachment_type)::text = ANY ((ARRAY['screenshot'::character varying, 'email_proof'::character varying, 'content'::character varying, 'other'::character varying])::text[]))),
    CONSTRAINT order_attachments_scan_status_check CHECK (((scan_status)::text = ANY ((ARRAY['pending'::character varying, 'clean'::character varying, 'flagged'::character varying, 'skipped'::character varying])::text[]))),
    CONSTRAINT order_attachments_uploader_type_check CHECK (((uploader_type)::text = ANY ((ARRAY['staff'::character varying, 'client'::character varying, 'supplier'::character varying])::text[])))
);


--
-- Name: TABLE order_attachments; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.order_attachments IS 'Files attached to orders or assignments. Includes: proof of placement screenshots uploaded by vendors, content documents, communication attachments. Vendors upload these from their portal as evidence of completed work.';


--
-- Name: COLUMN order_attachments.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_attachments.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN order_attachments.order_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_attachments.order_id IS 'Foreign key to orders.order_id. ON DELETE CASCADE.';


--
-- Name: COLUMN order_attachments.assignment_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_attachments.assignment_id IS 'Foreign key to order_assignments.assignment_id. Null if attachment is order-level rather than assignment-level.';


--
-- Name: COLUMN order_attachments.file_url; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_attachments.file_url IS 'URL to the uploaded file. May be an S3 URL, internal storage path, or external URL.';


--
-- Name: COLUMN order_attachments.file_type; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_attachments.file_type IS 'MIME type or file extension. E.g. ''image/png'', ''application/pdf'', ''screenshot''.';


--
-- Name: COLUMN order_attachments.uploaded_by; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_attachments.uploaded_by IS 'Name or email of who uploaded this file.';


--
-- Name: COLUMN order_attachments.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_attachments.created_at IS 'Timestamp when the file was uploaded.';


--
-- Name: COLUMN order_attachments.attachment_type; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_attachments.attachment_type IS 'Category of what the file represents (as opposed to file_type which is MIME/extension). Values: screenshot (proof-of-placement screenshot), email_proof (emailed confirmation from publisher), content (the actual article or asset), other. Null on legacy rows pre-dating this column.';


--
-- Name: COLUMN order_attachments.file_size; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_attachments.file_size IS 'File size in bytes. Populated by whatever service writes the attachment row. Null on legacy rows.';


--
-- Name: COLUMN order_attachments.scan_status; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_attachments.scan_status IS 'Antivirus / safety scan result. Values: pending (queued but not yet scanned), clean (scan passed), flagged (scan detected something), skipped (scan bypassed — typically internal uploads from trusted sources). Null on legacy rows and when no scanning workflow has run. The actual scanning service is application layer — this column only records its result.';


--
-- Name: COLUMN order_attachments.scanned_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_attachments.scanned_at IS 'Timestamp the scan ran. Null when scan_status is pending or null.';


--
-- Name: COLUMN order_attachments.uploader_type; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_attachments.uploader_type IS 'Role that uploaded the file. Values: staff (Loganix employee, typically for managed clients), client (client uploaded via their portal — DIY or managed self-upload), supplier (vendor or publisher uploaded — rare but possible). Only one of uploader_user_id / uploader_client_id / uploader_supplier_id is populated per row, matching uploader_type.';


--
-- Name: COLUMN order_attachments.uploader_user_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_attachments.uploader_user_id IS 'FK to users.user_id when uploader_type=staff. Null otherwise.';


--
-- Name: COLUMN order_attachments.uploader_client_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_attachments.uploader_client_id IS 'FK to clients.client_id when uploader_type=client. Null otherwise.';


--
-- Name: COLUMN order_attachments.uploader_supplier_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_attachments.uploader_supplier_id IS 'FK to suppliers.supplier_id when uploader_type=supplier. Null otherwise.';


--
-- Name: order_attachments_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.order_attachments_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: order_attachments_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.order_attachments_id_seq OWNED BY public.order_attachments.id;


--
-- Name: order_cancellation_reasons; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.order_cancellation_reasons (
    id integer NOT NULL,
    order_id integer NOT NULL,
    assignment_id integer,
    source character varying(20) NOT NULL,
    reason_category character varying(50),
    reason text NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    created_by integer,
    CONSTRAINT order_cancellation_reasons_source_check CHECK (((source)::text = ANY ((ARRAY['us'::character varying, 'supplier'::character varying, 'client'::character varying])::text[])))
);


--
-- Name: TABLE order_cancellation_reasons; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.order_cancellation_reasons IS 'Multi-source cancellation and rework reason log for orders. Captures cancellation context from all three parties: us (internal / fulfillment), the supplier (vendor / publisher explaining why they denied or rework-flagged), and the client (why they asked to cancel). Multiple rows per order allowed. The denormalized order_assignments.cancellation_reason remains for quick display of the primary reason.';


--
-- Name: COLUMN order_cancellation_reasons.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_cancellation_reasons.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN order_cancellation_reasons.order_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_cancellation_reasons.order_id IS 'Foreign key to orders.order_id. ON DELETE CASCADE.';


--
-- Name: COLUMN order_cancellation_reasons.assignment_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_cancellation_reasons.assignment_id IS 'Foreign key to order_assignments.assignment_id. Nullable — cancellation can happen before assignment. ON DELETE SET NULL so reason history survives assignment changes.';


--
-- Name: COLUMN order_cancellation_reasons.source; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_cancellation_reasons.source IS 'Who issued the cancellation / rework request. Values: us (internal fulfillment or admin), supplier (vendor/publisher), client (end customer).';


--
-- Name: COLUMN order_cancellation_reasons.reason_category; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_cancellation_reasons.reason_category IS 'Optional structured category for reporting (e.g. duplicate, price, content_quality, site_down, tat_miss, client_request). Nullable — freetext reason is always required; category is for aggregation.';


--
-- Name: COLUMN order_cancellation_reasons.reason; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_cancellation_reasons.reason IS 'Freetext explanation required on every row.';


--
-- Name: COLUMN order_cancellation_reasons.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_cancellation_reasons.created_at IS 'Timestamp when this cancellation reason was logged. Immutable — these rows are append-only.';


--
-- Name: COLUMN order_cancellation_reasons.created_by; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_cancellation_reasons.created_by IS 'FK to users.user_id — the staff member who recorded this cancellation reason. Null for system-generated entries. Previously VARCHAR storing emails/names as a soft reference; converted to INTEGER FK for referential integrity.';


--
-- Name: order_cancellation_reasons_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.order_cancellation_reasons_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: order_cancellation_reasons_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.order_cancellation_reasons_id_seq OWNED BY public.order_cancellation_reasons.id;


--
-- Name: order_messages; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.order_messages (
    id integer NOT NULL,
    order_id integer NOT NULL,
    sender character varying(255) NOT NULL,
    message text NOT NULL,
    seen boolean DEFAULT false,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: TABLE order_messages; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.order_messages IS 'Messages between staff and vendors/publishers on specific orders. This is the order-level communication channel. Vendors send messages from their portal; staff reply from the fulfillment dashboard. Messages are order-scoped (not assignment-scoped) so conversation history persists across reassignments.';


--
-- Name: COLUMN order_messages.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_messages.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN order_messages.order_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_messages.order_id IS 'Foreign key to orders.order_id. ON DELETE CASCADE.';


--
-- Name: COLUMN order_messages.sender; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_messages.sender IS 'Who sent the message. May be a staff name, vendor initials, or email. Varchar(255).';


--
-- Name: COLUMN order_messages.message; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_messages.message IS 'The message body. May contain HTML (legacy) or plain text.';


--
-- Name: COLUMN order_messages.seen; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_messages.seen IS 'Boolean. Has the intended recipient seen this message? Used for unread indicators in the portal. Default: false.';


--
-- Name: COLUMN order_messages.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_messages.created_at IS 'Timestamp when the message was sent.';


--
-- Name: order_messages_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.order_messages_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: order_messages_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.order_messages_id_seq OWNED BY public.order_messages.id;


--
-- Name: order_status_log; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.order_status_log (
    id integer NOT NULL,
    order_id integer NOT NULL,
    assignment_id integer,
    status character varying(63) NOT NULL,
    user_id integer,
    notes text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    source character varying(30)
);


--
-- Name: TABLE order_status_log; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.order_status_log IS 'Full audit trail of every status change on an order. One row per status transition. Provides complete history for debugging, SLA tracking, and compliance. Both automated (cron) and manual (staff) status changes are logged.';


--
-- Name: COLUMN order_status_log.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_status_log.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN order_status_log.order_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_status_log.order_id IS 'Foreign key to orders.order_id. ON DELETE CASCADE.';


--
-- Name: COLUMN order_status_log.assignment_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_status_log.assignment_id IS 'Foreign key to order_assignments.assignment_id. Null if status change is not assignment-specific. ON DELETE SET NULL.';


--
-- Name: COLUMN order_status_log.status; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_status_log.status IS 'The new status that was set. Same values as orders.status. Varchar(63).';


--
-- Name: COLUMN order_status_log.user_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_status_log.user_id IS 'Foreign key to users.user_id. The staff member or system user who made this change. Null for automated changes.';


--
-- Name: COLUMN order_status_log.notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_status_log.notes IS 'Free-text notes explaining the status change. E.g. ''Vendor accepted'', ''Cancelled per client request''.';


--
-- Name: COLUMN order_status_log.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_status_log.created_at IS 'Timestamp when this status change occurred.';


--
-- Name: COLUMN order_status_log.source; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_status_log.source IS 'What triggered this status change. Free text (no CHECK enum) so new automation sources can be added without a migration. Conventional values: user (manual staff action — user_id will also be set), cron, rework_trigger, broadcast_timeout, risk_flag, sla_monitor, link_checker, client_portal, vendor_portal. Null on legacy rows pre-dating this column. Enforced NOT NULL at application layer on new inserts.';


--
-- Name: order_status_log_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.order_status_log_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: order_status_log_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.order_status_log_id_seq OWNED BY public.order_status_log.id;


--
-- Name: order_tags; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.order_tags (
    id integer NOT NULL,
    order_id integer NOT NULL,
    tag character varying(255) NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: TABLE order_tags; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.order_tags IS 'Flexible tags on orders for categorization and workflow routing. Common tags: ''rush'' (expedited), ''managed'' (managed service client), ''lore'' (assigned via LORE system), ''refund'' (refund pending), ''test''. Tags are used for filtering in the fulfillment dashboard and for automated workflow rules.';


--
-- Name: COLUMN order_tags.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_tags.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN order_tags.order_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_tags.order_id IS 'Foreign key to orders.order_id. ON DELETE CASCADE.';


--
-- Name: COLUMN order_tags.tag; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_tags.tag IS 'The tag value. Free text, typically lowercase. Multiple tags per order allowed.';


--
-- Name: COLUMN order_tags.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_tags.created_at IS 'Timestamp when this tag was added.';


--
-- Name: order_tags_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.order_tags_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: order_tags_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.order_tags_id_seq OWNED BY public.order_tags.id;


--
-- Name: order_triggers; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.order_triggers (
    trigger_id integer NOT NULL,
    order_id integer NOT NULL,
    assignment_id integer,
    trigger_type character varying(40) NOT NULL,
    department character varying(20) NOT NULL,
    fired_at timestamp with time zone DEFAULT now() NOT NULL,
    status character varying(20) DEFAULT 'open'::character varying NOT NULL,
    acknowledged_by integer,
    acknowledged_at timestamp with time zone,
    resolved_at timestamp with time zone,
    resolution_notes text,
    details jsonb,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT order_triggers_department_check CHECK (((department)::text = ANY ((ARRAY['fulfillment'::character varying, 'content'::character varying, 'advocate'::character varying, 'finance'::character varying, 'ops_admin'::character varying])::text[]))),
    CONSTRAINT order_triggers_status_check CHECK (((status)::text = ANY ((ARRAY['open'::character varying, 'acknowledged'::character varying, 'resolved'::character varying, 'dismissed'::character varying])::text[])))
);


--
-- Name: TABLE order_triggers; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.order_triggers IS 'System-noticed events that need human action on a specific order or assignment. One row per event. Populated by app / cron code as conditions are detected (e.g. SLA breach, link check failure, content handoff). Filtered by department so each dashboard renders its own open queue. This is NOT the status audit trail — see order_status_log for that. This is the action queue.';


--
-- Name: COLUMN order_triggers.trigger_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_triggers.trigger_id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN order_triggers.order_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_triggers.order_id IS 'FK to orders. ON DELETE CASCADE — if an order is deleted, its triggers go with it.';


--
-- Name: COLUMN order_triggers.assignment_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_triggers.assignment_id IS 'FK to order_assignments. Nullable — some triggers (e.g. no_ps_outreach) fire before any assignment exists. ON DELETE SET NULL so reassignments do not lose trigger history.';


--
-- Name: COLUMN order_triggers.trigger_type; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_triggers.trigger_type IS 'What fired. Free-form string, not a CHECK enum — adding new trigger types is an app-side change with no migration. Conventional values include sla_breach, rework_dead_link, rework_nf_ordered_df, rework_sponsored_not_ordered, rework_client_link_not_found, rework_wrong_domain, vendor_accept_timeout, broadcast_expired, content_handoff, content_qa_failed, client_review_pending, client_review_approved, no_ps_outreach, replacement_needed, dead_link_unrestorable, debit_fired, payment_ready.';


--
-- Name: COLUMN order_triggers.department; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_triggers.department IS 'Which dashboard this trigger lands on. fulfillment / content / advocate / finance / ops_admin. Routes the trigger to the team that owns the action.';


--
-- Name: COLUMN order_triggers.fired_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_triggers.fired_at IS 'Timestamp the trigger fired. Set by whatever detected the condition.';


--
-- Name: COLUMN order_triggers.status; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_triggers.status IS 'open | acknowledged (human saw it, starting work) | resolved (action taken) | dismissed (false positive / no action needed). Department dashboards default to status=open.';


--
-- Name: COLUMN order_triggers.acknowledged_by; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_triggers.acknowledged_by IS 'FK to users — the staff member who picked up this trigger. Null while status=open.';


--
-- Name: COLUMN order_triggers.acknowledged_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_triggers.acknowledged_at IS 'Timestamp the trigger moved to acknowledged. Null until picked up.';


--
-- Name: COLUMN order_triggers.resolved_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_triggers.resolved_at IS 'Timestamp the trigger moved to resolved or dismissed. Null while open / acknowledged.';


--
-- Name: COLUMN order_triggers.resolution_notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_triggers.resolution_notes IS 'Optional free-text describing the action taken. Populated at resolution time.';


--
-- Name: COLUMN order_triggers.details; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_triggers.details IS 'Trigger-specific metadata as JSONB. Shape depends on trigger_type. Example — sla_breach: {"hours_overdue": 12}. rework_nf_ordered_df: {"expected_rel": "dofollow", "found_rel": "nofollow", "link_check_id": 12345}. no_ps_outreach: {"site_id": 3480, "site_domain": "example.com"}.';


--
-- Name: COLUMN order_triggers.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_triggers.created_at IS 'Row creation timestamp. Set automatically on INSERT.';


--
-- Name: COLUMN order_triggers.updated_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.order_triggers.updated_at IS 'Row last-modified timestamp. Maintained by application layer.';


--
-- Name: order_triggers_trigger_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.order_triggers_trigger_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: order_triggers_trigger_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.order_triggers_trigger_id_seq OWNED BY public.order_triggers.trigger_id;


--
-- Name: orders; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.orders (
    order_id integer NOT NULL,
    spp_order_id character varying(31),
    spp_line_item_id character varying(31),
    client_email character varying(255) NOT NULL,
    is_managed boolean DEFAULT false,
    service_type character varying(50),
    niche character varying(30) DEFAULT 'general'::character varying,
    site_id integer,
    target_url character varying(255),
    anchor_text character varying(255),
    client_notes text,
    retail numeric(10,2),
    status character varying(50) DEFAULT 'new'::character varying NOT NULL,
    risk_score character varying(20),
    risk_factors jsonb,
    content_url character varying(255),
    content_title character varying(255),
    word_count integer,
    final_url character varying(255),
    test_order boolean DEFAULT false,
    ms_start_date date,
    ms_accounting_code character varying(255),
    started_at timestamp with time zone,
    due_at timestamp with time zone,
    last_updated_at timestamp with time zone,
    last_background_update_at timestamp with time zone,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    pay_url character varying(255),
    risk_approved boolean DEFAULT false,
    spp_client_id character varying(31),
    spp_service_id character varying(31),
    spp_lookup_result text,
    client_id integer,
    campaign_id integer,
    client_review_upgrade boolean DEFAULT false NOT NULL,
    client_reviewed_at timestamp with time zone,
    guarantee_expires_at timestamp with time zone,
    replacement_for integer,
    dead_link_resolution character varying(30),
    content_source character varying(10),
    managed_client_id integer,
    CONSTRAINT orders_content_source_check CHECK (((content_source)::text = ANY ((ARRAY['loganix'::character varying, 'vendor'::character varying, 'client'::character varying])::text[]))),
    CONSTRAINT orders_dead_link_resolution_check CHECK (((dead_link_resolution)::text = ANY ((ARRAY['vendor_restored'::character varying, 'replaced'::character varying, 'refunded'::character varying, 'credit_issued'::character varying, 'unresolved'::character varying])::text[]))),
    CONSTRAINT orders_risk_score_check CHECK (((risk_score IS NULL) OR ((risk_score)::text = ANY ((ARRAY['low'::character varying, 'medium'::character varying, 'high'::character varying, 'critical'::character varying])::text[])))),
    CONSTRAINT orders_status_check CHECK (((status)::text = ANY ((ARRAY['new'::character varying, 'assigned'::character varying, 'approval'::character varying, 'content'::character varying, 'content_by_vendor'::character varying, 'content_by_client'::character varying, 'client_review'::character varying, 'submitted'::character varying, 'posting'::character varying, 'qa'::character varying, 'complete'::character varying, 'rework'::character varying, 'canceled'::character varying, 'refunded'::character varying])::text[])))
);


--
-- Name: TABLE orders; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.orders IS 'Central order record — one row per client line item (one link placement). This is the core business entity. An order flows through: new → assigned → content_writing → working → posted → delivered → qa → complete. Each order links to a site (where the link goes), optionally to content (Google Doc), and to one or more order_assignments (which supplier fulfills it). The spp_order_id field bridges to the legacy SPP system during migration.';


--
-- Name: COLUMN orders.order_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.order_id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN orders.spp_order_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.spp_order_id IS 'Legacy SPP (Service Provider Pro) order ID. Format: alphanumeric hash like ''2276B6AA''. Used for cross-referencing during migration. Will be deprecated once SPP is fully replaced.';


--
-- Name: COLUMN orders.spp_line_item_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.spp_line_item_id IS 'Legacy SPP line item ID within the parent order. One SPP order can have multiple line items (links). Will be deprecated.';


--
-- Name: COLUMN orders.client_email; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.client_email IS 'Email of the client who placed this order. Used to identify the customer. Not a foreign key — clients are managed in SPP during migration.';


--
-- Name: COLUMN orders.is_managed; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.is_managed IS 'Boolean. True = part of a managed campaign (dedicated account manager, custom SLA). False = self-serve order through the marketplace.';


--
-- Name: COLUMN orders.service_type; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.service_type IS 'Type of link building service. Common values: ''guest_post'' (new article), ''niche_edit'' (link in existing article), ''haro'', ''pr''. Varchar(50).';


--
-- Name: COLUMN orders.niche; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.niche IS 'Content niche for this order. Values: ''general'' (default), ''cbd'', ''crypto'', ''adult'', ''gambling'', ''payday''. Affects which suppliers can fulfill and pricing. Default: ''general''.';


--
-- Name: COLUMN orders.site_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.site_id IS 'Foreign key to sites.site_id. The target website where this link will be placed. Null if site hasn''t been matched yet.';


--
-- Name: COLUMN orders.target_url; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.target_url IS 'The client''s URL that should be linked to. This is the destination of the placed link.';


--
-- Name: COLUMN orders.anchor_text; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.anchor_text IS 'The desired anchor text for the link. E.g. ''best coffee maker'' or ''CompanyName.com''. This is what appears as the clickable text.';


--
-- Name: COLUMN orders.client_notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.client_notes IS 'Special instructions or requirements from the client. Free text.';


--
-- Name: COLUMN orders.retail; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.retail IS 'Price the client paid for this order in USD. Decimal(10,2). This is revenue — compare with order_assignments.cost for margin.';


--
-- Name: COLUMN orders.status; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.status IS 'Current order status. Three content paths share this enum:
   Path 1 (Loganix content): new -> assigned -> [approval] -> content -> [client_review] -> submitted -> posting -> qa -> complete
   Path 2 (vendor content):  new -> assigned -> [approval] -> content_by_vendor -> [client_review] -> submitted -> posting -> qa -> complete
   Path 3 (client content):  new -> assigned -> content_by_client -> approval (client link + anchor) -> submitted -> posting -> qa -> complete
   [approval] and [client_review] are conditional — skipped when site or client purchase does not require them. rework is auto-triggered from link_checks. canceled / refunded can interrupt from any state.';


--
-- Name: COLUMN orders.risk_score; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.risk_score IS 'Automated risk assessment. Values: low, medium, high, critical. Based on site metrics, anchor text analysis, and client history.';


--
-- Name: COLUMN orders.risk_factors; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.risk_factors IS 'JSONB with detailed risk breakdown. Keys may include ''anchor_risk'', ''site_quality'', ''client_history'', ''niche_risk''.';


--
-- Name: COLUMN orders.content_url; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.content_url IS 'URL to the Google Doc containing the written content for this order. Set after content is written. Format: Google Docs URL.';


--
-- Name: COLUMN orders.content_title; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.content_title IS 'Title of the content piece written for this order.';


--
-- Name: COLUMN orders.word_count; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.word_count IS 'Word count of the written content.';


--
-- Name: COLUMN orders.final_url; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.final_url IS 'The live URL where the link was actually placed. Submitted by the vendor after fulfillment. This is the deliverable — the proof that the order was completed.';


--
-- Name: COLUMN orders.test_order; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.test_order IS 'Boolean. True = internal test order, not a real client order. Excluded from reporting and billing.';


--
-- Name: COLUMN orders.ms_start_date; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.ms_start_date IS 'For managed service orders: the campaign start date.';


--
-- Name: COLUMN orders.ms_accounting_code; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.ms_accounting_code IS 'For managed service orders: the internal accounting/billing code.';


--
-- Name: COLUMN orders.started_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.started_at IS 'Timestamp when fulfillment work began (order moved past ''new'' status).';


--
-- Name: COLUMN orders.due_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.due_at IS 'Timestamp when this order is due for delivery. Calculated from TAT when assigned.';


--
-- Name: COLUMN orders.last_updated_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.last_updated_at IS 'Timestamp of last status change or significant update. Used for SLA tracking.';


--
-- Name: COLUMN orders.last_background_update_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.last_background_update_at IS 'Timestamp of last automated/cron update (e.g. link check, metric refresh). Separate from manual updates.';


--
-- Name: COLUMN orders.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.created_at IS 'Timestamp when the order was created.';


--
-- Name: COLUMN orders.updated_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.updated_at IS 'Timestamp of last modification to any field.';


--
-- Name: COLUMN orders.pay_url; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.pay_url IS 'Payment URL for vendor compensation. May link to PayPal, Payoneer, or internal payment system.';


--
-- Name: COLUMN orders.risk_approved; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.risk_approved IS 'Boolean. If risk_score is ''high'', a human must set this to true before fulfillment proceeds. Manual override for the risk gate.';


--
-- Name: COLUMN orders.spp_client_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.spp_client_id IS 'SPP client ID pulled from the SPP API for orders that originated in SPP. Null when the backfill has not yet populated this row, or the order never existed in SPP. Pairs with spp_order_id, spp_line_item_id.';


--
-- Name: COLUMN orders.spp_service_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.spp_service_id IS 'SPP service ID — which service the client bought (e.g. 81 = Shop the List). Pulled from the SPP API for SPP-originated orders. Null until backfill populates. Useful for service-level revenue breakdowns.';


--
-- Name: COLUMN orders.spp_lookup_result; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.spp_lookup_result IS 'Diagnostic trail left by the backfill lookup job — e.g. "linkrequest_vendor_order (vendor_email=...)". Captures WHY a row was recovered (or why it could not be) so the next pass can act differently. Not client-facing; purely for debugging the backfill pipeline.';


--
-- Name: COLUMN orders.client_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.client_id IS 'FK to clients. Nullable during SPP cutover while backfill runs. client_email remains as fallback until every row is resolved, then drops in a later migration.';


--
-- Name: COLUMN orders.campaign_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.campaign_id IS 'FK to campaigns. Nullable — one-off managed orders with no domain campaign keep this null. Populated when an order belongs to a tracked campaign.';


--
-- Name: COLUMN orders.client_review_upgrade; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.client_review_upgrade IS 'True when the client has paid for the client-review upgrade. When true, the content pipeline routes the order to client_review status after internal QA and waits for the client to approve in the portal before moving to submitted. When false, the order bypasses client review and moves directly from content QA to submitted.';


--
-- Name: COLUMN orders.client_reviewed_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.client_reviewed_at IS 'Timestamp the client approved the content in the portal. Null means either the upgrade was not purchased, or it was purchased but the client has not yet reviewed. Setting this auto-advances the order from client_review to submitted.';


--
-- Name: COLUMN orders.guarantee_expires_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.guarantee_expires_at IS 'Timestamp the replacement guarantee window ends. Set by the app at order completion based on client/service rules (e.g. managed clients get 6 months from completion; self-serve may get 0 or 3 months). Null means the order is not under a replacement guarantee. A dead-link event after this timestamp does not obligate replacement.';


--
-- Name: COLUMN orders.replacement_for; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.replacement_for IS 'Self-FK to orders.order_id. Populated when this order was created as a replacement for a previous order whose link went dead. Null for regular orders. Supports replacement chains (replacement of a replacement) via repeated following of this column.';


--
-- Name: COLUMN orders.dead_link_resolution; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.dead_link_resolution IS 'Client-side outcome when a link on this order went dead. Null for any order that has not had a dead-link incident. Values: vendor_restored (vendor got the link back live), replaced (new replacement order created, see replacement_for on the child), refunded (client refunded cash), credit_issued (client given account credit), unresolved (nothing actionable taken, usually because the order is outside guarantee window).';


--
-- Name: COLUMN orders.content_source; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.content_source IS 'Who is providing the content. Set at order intake. Values: loganix (we write it - routes through content pipeline), vendor (supplier writes it - their side, we monitor), client (client supplied at intake - only topic coordination needed). Drives which orders.status path the order follows (content vs content_by_vendor vs content_by_client).';


--
-- Name: COLUMN orders.managed_client_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orders.managed_client_id IS 'FK to managed_clients.managed_client_id. NULL for regular SPP-style orders (use spp_client_id for those). Populated for managed orders (spp_order_id starting with "MGP"), where the orders.client_email column is the placeholder "managedclients@loganix.com" and is not useful for identifying the actual buyer. Set by the resolve_managed_orders.py backfill, which extracts the client target URL from Google Doc titles in the "GP | Guest Posts" Drive folder and matches it against managed_clients.client_domain. When NULL on an MGP order, it means we have not yet been able to resolve the client (typically because no matching Google Doc was found, or the doc title did not contain a recognizable target URL).';


--
-- Name: orders_order_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.orders_order_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: orders_order_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.orders_order_id_seq OWNED BY public.orders.order_id;


--
-- Name: products; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.products (
    product_id integer NOT NULL,
    slug character varying(40) NOT NULL,
    name character varying(100) NOT NULL,
    display_label character varying(150) NOT NULL,
    description text,
    active boolean DEFAULT true NOT NULL,
    sort_order integer DEFAULT 0 NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT products_slug_check CHECK (((slug)::text ~ '^[a-z][a-z0-9_]*$'::text))
);


--
-- Name: TABLE products; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.products IS 'Standardized catalog of product types a site can offer (banner ad, video mention, sponsored post, newsletter mention, podcast appearance, etc.). Same shape as category_vocabulary: slug for column-safe identifier, display_label for UI. Populated and maintained by staff; the supplier portal dropdown reads active=true rows.';


--
-- Name: COLUMN products.product_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.products.product_id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN products.slug; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.products.slug IS 'Column-safe identifier. Lowercase letters, digits, underscores only; starts with a letter. Example: banner_ad, video_mention, sponsored_youtube.';


--
-- Name: COLUMN products.name; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.products.name IS 'Canonical internal name (e.g. "Banner Ad").';


--
-- Name: COLUMN products.display_label; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.products.display_label IS 'Supplier- and client-facing label (e.g. "Banner Ad — any page").';


--
-- Name: COLUMN products.description; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.products.description IS 'Optional description guiding staff when classifying supplier inputs against this product type.';


--
-- Name: COLUMN products.active; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.products.active IS 'If false, retained in history but not surfaced in new supplier-portal dropdowns or client filters.';


--
-- Name: COLUMN products.sort_order; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.products.sort_order IS 'Display ordering hint for UI. Lower sorts first.';


--
-- Name: products_product_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.products_product_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: products_product_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.products_product_id_seq OWNED BY public.products.product_id;


--
-- Name: role_permissions; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.role_permissions (
    id integer NOT NULL,
    role character varying(30) NOT NULL,
    resource character varying(30) NOT NULL,
    can_create boolean DEFAULT false,
    can_read boolean DEFAULT false,
    can_update boolean DEFAULT false,
    can_delete boolean DEFAULT false,
    row_ownership character varying(50),
    notes text
);


--
-- Name: TABLE role_permissions; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.role_permissions IS 'Default CRUD permissions per role per resource. This is the authorization matrix — when an API request comes in, the system checks: (1) user''s role, (2) look up role_permissions for that role+resource, (3) check can_create/read/update/delete for the requested action, (4) if row_ownership is set, add a WHERE clause filtering to rows the user owns. API key scopes can further restrict (never expand) these permissions. The row_ownership column is the key innovation: it defines which column to use for row-level security (e.g. "supplier_id" means vendors only see rows where supplier_id matches their own).';


--
-- Name: COLUMN role_permissions.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.role_permissions.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN role_permissions.role; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.role_permissions.role IS 'User role this permission applies to. Must match a value from users.role. Values: "super_admin", "admin", "editor", "viewer", "vendor", "writer".';


--
-- Name: COLUMN role_permissions.resource; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.role_permissions.resource IS 'The resource/table this permission controls access to. E.g. "orders", "supplier_sites", "content_docs". Maps to api_scopes.resource.';


--
-- Name: COLUMN role_permissions.can_create; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.role_permissions.can_create IS 'Can this role create new rows in this resource? Maps to POST endpoints.';


--
-- Name: COLUMN role_permissions.can_read; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.role_permissions.can_read IS 'Can this role read rows from this resource? Maps to GET endpoints.';


--
-- Name: COLUMN role_permissions.can_update; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.role_permissions.can_update IS 'Can this role update existing rows in this resource? Maps to PUT/PATCH endpoints.';


--
-- Name: COLUMN role_permissions.can_delete; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.role_permissions.can_delete IS 'Can this role delete rows from this resource? Maps to DELETE endpoints.';


--
-- Name: COLUMN role_permissions.row_ownership; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.role_permissions.row_ownership IS 'Row-level security filter column. If set, API queries add: WHERE {row_ownership} = current_user.{row_ownership}. Values: NULL (no filter, sees all rows), "supplier_id" (vendor sees own data only), "writer_id" (writer sees own data only), "user_id" (user sees own records only). The API middleware resolves the current user''s supplier_id/writer_id from the users table.';


--
-- Name: COLUMN role_permissions.notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.role_permissions.notes IS 'Human-readable explanation of this permission grant. Documents the intent.';


--
-- Name: role_permissions_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.role_permissions_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: role_permissions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.role_permissions_id_seq OWNED BY public.role_permissions.id;


--
-- Name: site_ai_categories; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.site_ai_categories (
    id integer NOT NULL,
    site_id integer NOT NULL,
    category character varying(255) NOT NULL,
    rank smallint NOT NULL,
    created_at timestamp with time zone DEFAULT now(),
    updated_at timestamp with time zone DEFAULT now()
);


--
-- Name: TABLE site_ai_categories; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.site_ai_categories IS 'AI-generated topic categories for each site. A site can have multiple categories ranked by relevance (rank 1 = primary). Used for matching sites to client orders by niche/topic. Categories are strings like ''Technology >> Cybersecurity'' with optional subcategories after ''>>''.';


--
-- Name: COLUMN site_ai_categories.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_ai_categories.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN site_ai_categories.site_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_ai_categories.site_id IS 'Foreign key to sites.site_id. ON DELETE CASCADE.';


--
-- Name: COLUMN site_ai_categories.category; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_ai_categories.category IS 'Category label, e.g. ''Health >> Nutrition'', ''Technology >> AI''. Format: ''MainCategory >> Subcategory''. AI-generated from site content analysis.';


--
-- Name: COLUMN site_ai_categories.rank; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_ai_categories.rank IS 'Relevance rank (1 = most relevant category for this site). A site typically has 1-3 categories.';


--
-- Name: COLUMN site_ai_categories.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_ai_categories.created_at IS 'Timestamp when this category was assigned.';


--
-- Name: COLUMN site_ai_categories.updated_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_ai_categories.updated_at IS 'Timestamp of last update to this category assignment.';


--
-- Name: site_ai_categories_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.site_ai_categories_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: site_ai_categories_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.site_ai_categories_id_seq OWNED BY public.site_ai_categories.id;


--
-- Name: site_embeddings; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.site_embeddings (
    site_id integer NOT NULL,
    embedding_details jsonb,
    meta_embeddings public.vector(1536),
    created_at timestamp with time zone DEFAULT now(),
    updated_at timestamp with time zone DEFAULT now()
);


--
-- Name: TABLE site_embeddings; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.site_embeddings IS 'Vector embeddings for semantic site matching. Contains a 1536-dimensional vector (OpenAI text-embedding-ada-002) representing the site''s content semantics. Used for nearest-neighbor similarity search to find sites matching client order topics. Also synced to Pinecone for fast vector search.';


--
-- Name: COLUMN site_embeddings.site_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_embeddings.site_id IS 'Primary key. Foreign key to sites.site_id. ON DELETE CASCADE. One row per site.';


--
-- Name: COLUMN site_embeddings.embedding_details; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_embeddings.embedding_details IS 'JSONB metadata about the embedding — model used, input text hash, token count. For cache invalidation and debugging.';


--
-- Name: COLUMN site_embeddings.meta_embeddings; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_embeddings.meta_embeddings IS '1536-dimensional vector (pgvector type). The actual embedding for similarity search. Query with <=> (cosine distance) or <-> (L2 distance).';


--
-- Name: COLUMN site_embeddings.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_embeddings.created_at IS 'Timestamp when embedding was first generated.';


--
-- Name: COLUMN site_embeddings.updated_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_embeddings.updated_at IS 'Timestamp of last embedding regeneration.';


--
-- Name: site_geo; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.site_geo (
    id integer NOT NULL,
    site_id integer NOT NULL,
    country_code character(2) NOT NULL,
    traffic integer NOT NULL,
    rank smallint NOT NULL,
    created_at timestamp with time zone DEFAULT now(),
    updated_at timestamp with time zone DEFAULT now(),
    region character varying(100),
    city character varying(100)
);


--
-- Name: TABLE site_geo; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.site_geo IS 'Geographic traffic breakdown by country for each site. Shows what percentage of a site''s traffic comes from each country. Used to match sites to clients targeting specific geographic markets. Data from Ahrefs traffic-by-country API.';


--
-- Name: COLUMN site_geo.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_geo.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN site_geo.site_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_geo.site_id IS 'Foreign key to sites.site_id. ON DELETE CASCADE.';


--
-- Name: COLUMN site_geo.country_code; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_geo.country_code IS 'ISO 3166-1 alpha-2 country code (e.g. ''US'', ''CA'', ''GB'', ''AU''). Always uppercase, exactly 2 characters.';


--
-- Name: COLUMN site_geo.traffic; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_geo.traffic IS 'Estimated monthly organic traffic from this country. Absolute number, not percentage.';


--
-- Name: COLUMN site_geo.rank; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_geo.rank IS 'Traffic rank for this country within this site (1 = highest traffic country).';


--
-- Name: COLUMN site_geo.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_geo.created_at IS 'Timestamp when this geo record was created.';


--
-- Name: COLUMN site_geo.updated_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_geo.updated_at IS 'Timestamp of last update.';


--
-- Name: COLUMN site_geo.region; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_geo.region IS 'Sub-national region / state / province for hyper-local targeting (e.g. "New York", "California", "Ontario"). Null when regional breakdown is not available for this site. Country_code remains the primary geographic dimension; region narrows within a country.';


--
-- Name: COLUMN site_geo.city; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_geo.city IS 'City-level targeting (e.g. "New York City", "Los Angeles", "Toronto"). Null when only country/region resolution is available. Enables filters like "New York City traffic" for clients running location-specific campaigns.';


--
-- Name: site_geo_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.site_geo_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: site_geo_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.site_geo_id_seq OWNED BY public.site_geo.id;


--
-- Name: site_keyword_rankings; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.site_keyword_rankings (
    site_id integer NOT NULL,
    keywords_3 integer,
    keywords_10 integer,
    keywords_20 integer,
    keywords_50 integer,
    keywords_100 integer,
    updated_at timestamp with time zone DEFAULT now()
);


--
-- Name: TABLE site_keyword_rankings; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.site_keyword_rankings IS 'Keyword ranking distribution for each site. Shows how many keywords rank in different position buckets (top 3, top 10, etc.). One row per site. Updated by Ahrefs cron. Used to assess site quality and search visibility.';


--
-- Name: COLUMN site_keyword_rankings.site_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_keyword_rankings.site_id IS 'Primary key. Foreign key to sites.site_id. ON DELETE CASCADE. One row per site.';


--
-- Name: COLUMN site_keyword_rankings.keywords_3; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_keyword_rankings.keywords_3 IS 'Number of keywords ranking in positions 1-3 (top of page 1).';


--
-- Name: COLUMN site_keyword_rankings.keywords_10; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_keyword_rankings.keywords_10 IS 'Number of keywords ranking in positions 1-10 (page 1).';


--
-- Name: COLUMN site_keyword_rankings.keywords_20; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_keyword_rankings.keywords_20 IS 'Number of keywords ranking in positions 1-20 (pages 1-2).';


--
-- Name: COLUMN site_keyword_rankings.keywords_50; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_keyword_rankings.keywords_50 IS 'Number of keywords ranking in positions 1-50 (pages 1-5).';


--
-- Name: COLUMN site_keyword_rankings.keywords_100; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_keyword_rankings.keywords_100 IS 'Number of keywords ranking in positions 1-100 (pages 1-10).';


--
-- Name: COLUMN site_keyword_rankings.updated_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_keyword_rankings.updated_at IS 'Timestamp of last update from Ahrefs.';


--
-- Name: site_products; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.site_products (
    site_id integer NOT NULL,
    product_id integer NOT NULL,
    notes text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: TABLE site_products; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.site_products IS 'N:N capability map between sites and product types. One row = this site supports this product. Independent of any supplier currently reselling it; populated from staff audits or AI classification of the site. supplier_site_products downstream carries the actual supplier pricing for offered products.';


--
-- Name: COLUMN site_products.site_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_products.site_id IS 'FK to sites. Part of composite PK.';


--
-- Name: COLUMN site_products.product_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_products.product_id IS 'FK to products. Part of composite PK.';


--
-- Name: COLUMN site_products.notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.site_products.notes IS 'Optional free-text notes about how this product works specifically on this site (e.g. "banner placement limited to articles over 1000 words").';


--
-- Name: sites; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.sites (
    site_id integer NOT NULL,
    domain public.citext NOT NULL,
    da integer,
    dr integer,
    rd integer,
    backlinks_total integer,
    traffic_total integer,
    keywords_total integer,
    organic_cost numeric(10,2),
    indexed_pages integer,
    as_score integer,
    toxicity_score integer,
    ai_snippet text,
    ai_description text,
    ai_overview_score numeric(5,2),
    serp_match numeric(5,2),
    guidelines_url character varying(255),
    guidelines_text text,
    no_guidelines boolean DEFAULT false,
    internal_notes text,
    on_the_list boolean DEFAULT false,
    loganix_certified boolean DEFAULT false,
    priority smallint DEFAULT 0,
    traffic_trend integer,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    approved_at timestamp with time zone,
    approved_by character varying(255),
    inactive boolean DEFAULT false NOT NULL,
    language character varying(20),
    screenshot_captured_at timestamp with time zone,
    server_country character(2)
);


--
-- Name: TABLE sites; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.sites IS 'Master catalog of all websites/domains in our system. Each site has SEO metrics (DA, DR, TF, CF, traffic, keywords), AI-generated descriptions, and editorial flags. Sites are referenced by orders (where links are placed) and by supplier_sites (who can place links on them). The domain column is the unique natural key (case-insensitive).';


--
-- Name: COLUMN sites.site_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.site_id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN sites.domain; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.domain IS 'The website domain (e.g. ''techcrunch.com''). Case-insensitive (citext). No protocol or www prefix. This is the unique natural key — one row per domain.';


--
-- Name: COLUMN sites.da; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.da IS 'Moz Domain Authority score (0-100). Higher = more authoritative. Updated by Ahrefs/Moz cron jobs.';


--
-- Name: COLUMN sites.dr; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.dr IS 'Ahrefs Domain Rating score (0-100). Measures backlink profile strength. Updated by Ahrefs cron.';


--
-- Name: COLUMN sites.rd; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.rd IS 'Referring domains count — number of unique domains linking to this site. From Ahrefs.';


--
-- Name: COLUMN sites.backlinks_total; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.backlinks_total IS 'Total number of backlinks pointing to this site. From Ahrefs.';


--
-- Name: COLUMN sites.traffic_total; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.traffic_total IS 'Estimated monthly organic search traffic. From Ahrefs. Used for pricing and quality tiers.';


--
-- Name: COLUMN sites.keywords_total; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.keywords_total IS 'Total number of keywords this site ranks for in search engines. From Ahrefs.';


--
-- Name: COLUMN sites.organic_cost; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.organic_cost IS 'Estimated dollar value of organic traffic if purchased as PPC. From Ahrefs.';


--
-- Name: COLUMN sites.indexed_pages; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.indexed_pages IS 'Number of pages indexed by Google. Low values may indicate site quality issues.';


--
-- Name: COLUMN sites.as_score; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.as_score IS 'SEMrush Authority Score (0-100). Compound metric combining backlink profile strength, organic traffic, and spam signals. More manipulation-resistant than Moz DA or Ahrefs DR. Populated by SEMrush cron. Higher = more authoritative.';


--
-- Name: COLUMN sites.toxicity_score; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.toxicity_score IS 'SEMrush Toxicity Score (0-100). Backlink-audit metric indicating how toxic / spammy the domain''s backlink profile looks. Higher = more toxic. Populated by SEMrush cron.';


--
-- Name: COLUMN sites.ai_snippet; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.ai_snippet IS 'Short AI-generated one-line description of the site''s content and niche. Used in search results and tooltips.';


--
-- Name: COLUMN sites.ai_description; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.ai_description IS 'Detailed AI-generated description of the site — topics covered, audience, content style. Used for matching sites to client orders.';


--
-- Name: COLUMN sites.ai_overview_score; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.ai_overview_score IS 'Score (0-100) indicating how frequently this site appears in Google AI Overview citations. Higher = more AI visibility. Derived from SERP analysis of the site''s ranking keywords.';


--
-- Name: COLUMN sites.serp_match; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.serp_match IS 'SERP relevance matching score (0-100). How well this site''s content matches typical client order topics. Used by the matching algorithm.';


--
-- Name: COLUMN sites.guidelines_url; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.guidelines_url IS 'URL to the publisher''s content/editorial guidelines document. May be a Google Doc or blog page.';


--
-- Name: COLUMN sites.guidelines_text; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.guidelines_text IS 'Cached plain-text copy of the publisher''s content guidelines. Stored locally so we don''t depend on external URLs.';


--
-- Name: COLUMN sites.no_guidelines; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.no_guidelines IS 'Boolean. Publisher has explicitly confirmed they have no content guidelines. Distinguishes ''no guidelines'' from ''guidelines not yet collected''.';


--
-- Name: COLUMN sites.internal_notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.internal_notes IS 'Free-text internal notes about this site. Only visible to Loganix staff, never shown to vendors/publishers/clients.';


--
-- Name: COLUMN sites.on_the_list; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.on_the_list IS 'Site is currently visible in our marketplace / client-facing inventory. Set true only after outreach approves AND the site stays in good standing. Fresh enrichment crons gate on this (plus loganix_certified) before touching the site.';


--
-- Name: COLUMN sites.loganix_certified; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.loganix_certified IS 'Site has been manually vetted and approved by the outreach team. The primary gate between "we know the domain" and "we trust it enough to enrich + sell." Crons should only refresh metrics for certified sites — uncertified sites sit in a soft purgatory with just the bare bones until outreach signs off.';


--
-- Name: COLUMN sites.priority; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.priority IS 'Internal priority ranking (0-10). Higher = we prefer to use/feature this site. Used for sorting in fulfillment matching.';


--
-- Name: COLUMN sites.traffic_trend; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.traffic_trend IS 'Traffic direction indicator. Positive = traffic growing, negative = declining, 0 = stable. Calculated from traffic_history deltas.';


--
-- Name: COLUMN sites.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.created_at IS 'Timestamp when this site was first added to our catalog.';


--
-- Name: COLUMN sites.updated_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.updated_at IS 'Timestamp of the last modification to any field on this row.';


--
-- Name: COLUMN sites.approved_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.approved_at IS 'Timestamp when the outreach team approved this site for the catalog — i.e. when loganix_certified was flipped true. Until approved, the enrichment crons should skip the site. Null = not yet approved. Useful for SLA tracking (time from first-seen to approved) and cohort analysis.';


--
-- Name: COLUMN sites.approved_by; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.approved_by IS 'Identifier of the outreach team member who approved the site — user email, user_id as string, or system identifier. Freetext for now; may migrate to a users FK once the outreach module is formalized.';


--
-- Name: COLUMN sites.inactive; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.inactive IS 'Staff-controlled kill switch. Source of truth is Skynet GPv2 — true if any of GPv2.inactive / inactive_cbd / inactive_edits is 1. Carried over on each 12h rebuild-sync. Distinct from supplier_sites.inactive, which is per-supplier listing state controlled by vendors.';


--
-- Name: COLUMN sites.language; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.language IS 'Primary language of the site content (e.g. English, French, Spanish). Auto-detected by the enrichment cron. Site-level fact — the site publishes in one language regardless of which supplier lists it. supplier_sites.site_language remains as the per-listing observation history; this column is the canonical catalog value.';


--
-- Name: COLUMN sites.screenshot_captured_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.screenshot_captured_at IS 'When the homepage screenshot was last captured for this domain. The screenshot URL is deterministic from the domain (e.g. https://cdn.loganix.com/screenshots/{domain}.png) so no URL column is stored — just the presence + freshness timestamp. Site-level because the screenshot is of the actual site, identical across all suppliers who list it. supplier_sites.screenshot_captured_at stays populated today as the per-listing observation; a future backfill migration will populate this site-level column from the most recent per-domain value and then drop the old column.';


--
-- Name: COLUMN sites.server_country; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.sites.server_country IS 'ISO 3166-1 alpha-2 country code (US, GB, DE, etc.) where the site is physically hosted. Distinct from site_geo (traffic origin). Populated by enrichment cron via WHOIS / hosting IP geolocation. Some clients require specific hosting-country placements (e.g. US-hosted sites only).';


--
-- Name: sites_site_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.sites_site_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: sites_site_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.sites_site_id_seq OWNED BY public.sites.site_id;


--
-- Name: supplier_ban_history; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.supplier_ban_history (
    id integer NOT NULL,
    supplier_id integer NOT NULL,
    reason text,
    banned_by character varying(255),
    banned_at timestamp with time zone DEFAULT now() NOT NULL,
    unbanned_at timestamp with time zone
);


--
-- Name: TABLE supplier_ban_history; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.supplier_ban_history IS 'Audit log of ban/unban actions on suppliers. Each row represents one ban event. If unbanned_at is null, the ban is still active. Used for compliance tracking and dispute resolution.';


--
-- Name: COLUMN supplier_ban_history.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_ban_history.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN supplier_ban_history.supplier_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_ban_history.supplier_id IS 'Foreign key to suppliers.supplier_id.';


--
-- Name: COLUMN supplier_ban_history.reason; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_ban_history.reason IS 'Free-text explanation of why the supplier was banned.';


--
-- Name: COLUMN supplier_ban_history.banned_by; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_ban_history.banned_by IS 'Name or email of the staff member who issued the ban.';


--
-- Name: COLUMN supplier_ban_history.banned_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_ban_history.banned_at IS 'Timestamp when the ban was enacted.';


--
-- Name: COLUMN supplier_ban_history.unbanned_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_ban_history.unbanned_at IS 'Timestamp when the ban was lifted. Null = still banned.';


--
-- Name: supplier_ban_history_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.supplier_ban_history_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: supplier_ban_history_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.supplier_ban_history_id_seq OWNED BY public.supplier_ban_history.id;


--
-- Name: supplier_invoices; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.supplier_invoices (
    id integer NOT NULL,
    supplier_id integer NOT NULL,
    payment_amount numeric(10,2) NOT NULL,
    completed_orders jsonb,
    debits_included jsonb,
    credits_included jsonb,
    approved boolean DEFAULT false,
    paid boolean DEFAULT false,
    pdf_url character varying(255),
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    paid_at timestamp with time zone,
    payment_method character varying(20),
    processed_by integer,
    payment_proof_url text,
    period_start date,
    period_end date,
    CONSTRAINT supplier_invoices_payment_method_check CHECK (((payment_method IS NULL) OR ((payment_method)::text = ANY ((ARRAY['paypal'::character varying, 'payoneer'::character varying, 'wise'::character varying, 'bank_transfer'::character varying, 'credit_card'::character varying, 'wire'::character varying, 'crypto'::character varying, 'other'::character varying])::text[]))))
);


--
-- Name: TABLE supplier_invoices; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.supplier_invoices IS 'Generated invoices for vendor/publisher payouts. Each invoice aggregates unsettled supplier_ledger entries for a billing period. Contains the total payment amount, which orders are included, and approval/payment status. Vendors can view their invoices in the vendor portal.';


--
-- Name: COLUMN supplier_invoices.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_invoices.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN supplier_invoices.supplier_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_invoices.supplier_id IS 'Foreign key to suppliers.supplier_id.';


--
-- Name: COLUMN supplier_invoices.payment_amount; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_invoices.payment_amount IS 'Total payment amount for this invoice. Sum of credits minus debits for the included entries. Decimal(10,2).';


--
-- Name: COLUMN supplier_invoices.completed_orders; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_invoices.completed_orders IS 'JSONB array of order_ids included in this invoice''s credits.';


--
-- Name: COLUMN supplier_invoices.debits_included; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_invoices.debits_included IS 'JSONB array of ledger entry IDs for debits deducted from this invoice.';


--
-- Name: COLUMN supplier_invoices.credits_included; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_invoices.credits_included IS 'JSONB array of ledger entry IDs for credits included in this invoice.';


--
-- Name: COLUMN supplier_invoices.approved; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_invoices.approved IS 'Boolean. Has this invoice been approved by management for payment?';


--
-- Name: COLUMN supplier_invoices.paid; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_invoices.paid IS 'Boolean. Has this invoice actually been paid out to the supplier?';


--
-- Name: COLUMN supplier_invoices.pdf_url; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_invoices.pdf_url IS 'URL to the generated PDF invoice document.';


--
-- Name: COLUMN supplier_invoices.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_invoices.created_at IS 'Timestamp when this invoice was generated.';


--
-- Name: COLUMN supplier_invoices.paid_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_invoices.paid_at IS 'Timestamp the invoice batch payment was actually sent. Complements the paid boolean — paid says if, paid_at says when.';


--
-- Name: COLUMN supplier_invoices.payment_method; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_invoices.payment_method IS 'Channel used to send the batch payment. paypal / wise / bank_transfer / check / crypto / other. Null until paid.';


--
-- Name: COLUMN supplier_invoices.processed_by; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_invoices.processed_by IS 'FK to users — the staff member who processed the payment run. Null until paid.';


--
-- Name: COLUMN supplier_invoices.payment_proof_url; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_invoices.payment_proof_url IS 'URL to external payment confirmation (receipt, bank statement screenshot) stored as audit evidence. Null until paid.';


--
-- Name: COLUMN supplier_invoices.period_start; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_invoices.period_start IS 'Start date of the billing cycle this invoice covers. Used for audit when a supplier disputes which orders fell in a specific period.';


--
-- Name: COLUMN supplier_invoices.period_end; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_invoices.period_end IS 'End date of the billing cycle this invoice covers.';


--
-- Name: supplier_invoices_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.supplier_invoices_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: supplier_invoices_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.supplier_invoices_id_seq OWNED BY public.supplier_invoices.id;


--
-- Name: supplier_ledger; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.supplier_ledger (
    id integer NOT NULL,
    supplier_id integer NOT NULL,
    order_id integer,
    type character varying(10) NOT NULL,
    amount numeric(10,2) NOT NULL,
    vendor_tat smallint,
    actual_tat smallint,
    rating smallint,
    notes text,
    settled boolean DEFAULT false,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    debit_reason character varying(30),
    debit_triggered_by character varying(10),
    penalty_id integer,
    CONSTRAINT supplier_ledger_debit_needs_reason CHECK ((((type)::text <> 'debit'::text) OR (debit_reason IS NOT NULL))),
    CONSTRAINT supplier_ledger_debit_needs_trigger CHECK ((((type)::text <> 'debit'::text) OR (debit_triggered_by IS NOT NULL))),
    CONSTRAINT supplier_ledger_debit_reason_check CHECK (((debit_reason)::text = ANY ((ARRAY['dead_link_unrestorable'::character varying, 'duplicate_payment'::character varying, 'late_delivery_penalty'::character varying, 'quality_penalty'::character varying, 'other'::character varying])::text[]))),
    CONSTRAINT supplier_ledger_debit_triggered_by_check CHECK (((debit_triggered_by)::text = ANY ((ARRAY['auto'::character varying, 'manual'::character varying])::text[]))),
    CONSTRAINT supplier_ledger_type_check CHECK (((type)::text = ANY (ARRAY[('credit'::character varying)::text, ('debit'::character varying)::text])))
);


--
-- Name: TABLE supplier_ledger; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.supplier_ledger IS 'Financial ledger tracking all credits and debits for vendor/publisher payments. Each completed order creates a ''credit'' entry. Penalties, adjustments, and chargebacks create ''debit'' entries. At billing time, unsettled entries are rolled into a supplier_invoice. The sum of unsettled credits minus debits equals the supplier''s outstanding balance.';


--
-- Name: COLUMN supplier_ledger.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_ledger.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN supplier_ledger.supplier_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_ledger.supplier_id IS 'Foreign key to suppliers.supplier_id. The supplier this ledger entry belongs to.';


--
-- Name: COLUMN supplier_ledger.order_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_ledger.order_id IS 'Foreign key to orders.order_id. Null for debits not tied to a specific order (e.g. general adjustments).';


--
-- Name: COLUMN supplier_ledger.type; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_ledger.type IS 'Entry type: ''credit'' (money owed to supplier, typically from completed orders) or ''debit'' (money deducted — penalties, adjustments, chargebacks). CHECK constraint enforces these values.';


--
-- Name: COLUMN supplier_ledger.amount; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_ledger.amount IS 'Dollar amount. Always positive — the type field determines direction. Decimal(10,2).';


--
-- Name: COLUMN supplier_ledger.vendor_tat; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_ledger.vendor_tat IS 'For credits: the agreed turnaround time in days for this order. Used for TAT adherence tracking.';


--
-- Name: COLUMN supplier_ledger.actual_tat; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_ledger.actual_tat IS 'For credits: actual days taken to complete. Compared with vendor_tat to calculate on-time delivery rate.';


--
-- Name: COLUMN supplier_ledger.rating; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_ledger.rating IS 'Per-order quality rating (1-5) given by QA. Feeds into the supplier''s aggregate rating.';


--
-- Name: COLUMN supplier_ledger.notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_ledger.notes IS 'Free-text notes explaining this ledger entry. E.g. ''Late delivery penalty'' or ''Completed order #1234''.';


--
-- Name: COLUMN supplier_ledger.settled; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_ledger.settled IS 'Boolean. True = this entry has been included in a paid invoice. False = pending/unsettled. Only unsettled entries are included in the next invoice.';


--
-- Name: COLUMN supplier_ledger.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_ledger.created_at IS 'Timestamp when this ledger entry was created.';


--
-- Name: COLUMN supplier_ledger.debit_reason; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_ledger.debit_reason IS 'Structured debit cause. NOT NULL when type=debit. Values: dead_link_unrestorable (vendor failed to restore a dead link within the grace period — see order_assignments.restoration_deadline), duplicate_payment (order appeared in two invoices, reversing one), late_delivery_penalty (SLA breach resulted in penalty), quality_penalty (quality grade on link_checks warranted a deduction), other (edge case; use notes for specifics). The free-text notes column stays for additional context.';


--
-- Name: COLUMN supplier_ledger.debit_triggered_by; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_ledger.debit_triggered_by IS 'Whether this debit was system-generated or manually applied. NOT NULL when type=debit. Values: auto (a cron or service inserted the row — typically driven by link_checks / SLA monitor / grace-period expiry), manual (staff entered the debit directly).';


--
-- Name: COLUMN supplier_ledger.penalty_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_ledger.penalty_id IS 'Optional FK to supplier_penalties.id. Populated when this debit was caused by a specific penalty row. Null when the debit is not penalty-driven (e.g. duplicate_payment corrections). ON DELETE SET NULL so removing the penalty history does not destroy the ledger record.';


--
-- Name: supplier_ledger_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.supplier_ledger_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: supplier_ledger_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.supplier_ledger_id_seq OWNED BY public.supplier_ledger.id;


--
-- Name: supplier_order_broadcasts; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.supplier_order_broadcasts (
    id integer NOT NULL,
    order_id integer NOT NULL,
    supplier_id integer NOT NULL,
    broadcast_at timestamp with time zone DEFAULT now() NOT NULL,
    response character varying(20) DEFAULT 'pending'::character varying NOT NULL,
    responded_at timestamp with time zone,
    stagger_position integer,
    notes text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    access_hash character varying(32) DEFAULT encode(public.gen_random_bytes(16), 'hex'::text) NOT NULL,
    expires_at timestamp with time zone,
    CONSTRAINT supplier_order_broadcasts_response_check CHECK (((response)::text = ANY ((ARRAY['pending'::character varying, 'accepted'::character varying, 'denied'::character varying, 'expired'::character varying, 'ignored'::character varying])::text[])))
);


--
-- Name: TABLE supplier_order_broadcasts; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.supplier_order_broadcasts IS 'Broadcast queue for offering an order to multiple suppliers who have the matching site in their inventory. Used when the primary assignment fails or an order needs fresh placement, rather than committing to a single vendor upfront. Each row = one supplier was offered this order. First supplier to accept wins (a matching order_assignment is then created); others get response=expired or ignored. Replaces the legacy vendor_order_requests table from Skynet.';


--
-- Name: COLUMN supplier_order_broadcasts.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_order_broadcasts.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN supplier_order_broadcasts.order_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_order_broadcasts.order_id IS 'Foreign key to orders.order_id. ON DELETE CASCADE.';


--
-- Name: COLUMN supplier_order_broadcasts.supplier_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_order_broadcasts.supplier_id IS 'Foreign key to suppliers.supplier_id. The supplier who was offered this order.';


--
-- Name: COLUMN supplier_order_broadcasts.broadcast_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_order_broadcasts.broadcast_at IS 'When the offer was sent to this supplier. For staggered dispatch, this is the actual send time (not the scheduled time).';


--
-- Name: COLUMN supplier_order_broadcasts.response; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_order_broadcasts.response IS 'Current state of this offer. Values: pending (awaiting reply), accepted (supplier took it), denied (supplier explicitly declined), expired (we withdrew or it aged out), ignored (supplier never responded within window).';


--
-- Name: COLUMN supplier_order_broadcasts.responded_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_order_broadcasts.responded_at IS 'When the supplier accepted, denied, or the offer expired. Null while pending.';


--
-- Name: COLUMN supplier_order_broadcasts.stagger_position; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_order_broadcasts.stagger_position IS 'When null, this offer was sent as part of an all-at-once broadcast. When set, this is the position in a staggered dispatch (1 = first supplier contacted, 2 = second if 1 denies/times out, etc.). Lets us preserve both dispatch modes without schema changes.';


--
-- Name: COLUMN supplier_order_broadcasts.notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_order_broadcasts.notes IS 'Optional context — why this supplier was chosen, internal notes on the offer, or the supplier''s free-text reason for denying.';


--
-- Name: COLUMN supplier_order_broadcasts.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_order_broadcasts.created_at IS 'Row insert timestamp. In practice equals broadcast_at — the row is created when the offer goes out to the supplier.';


--
-- Name: COLUMN supplier_order_broadcasts.access_hash; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_order_broadcasts.access_hash IS 'Unique per-broadcast access token (32 hex chars, 128 bits of entropy, unguessable). Used in one-click email links so a vendor can accept or decline a broadcast offer without logging into the portal — the URL carries the hash instead of the internal broadcast id. Protects against competitors scraping sequential order ids.';


--
-- Name: COLUMN supplier_order_broadcasts.expires_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_order_broadcasts.expires_at IS 'Deadline for the supplier to respond. Set by the app or cron that creates the broadcast (typically broadcast_at + some offer window). When a pending offer passes this timestamp, an expiry cron flips response to expired and fires the next-vendor step. Null on legacy rows pre-dating this column.';


--
-- Name: supplier_order_broadcasts_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.supplier_order_broadcasts_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: supplier_order_broadcasts_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.supplier_order_broadcasts_id_seq OWNED BY public.supplier_order_broadcasts.id;


--
-- Name: supplier_penalties; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.supplier_penalties (
    id integer NOT NULL,
    supplier_id integer NOT NULL,
    order_id integer,
    assignment_id integer,
    penalty_type character varying(20) NOT NULL,
    notes text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    amount numeric(10,2)
);


--
-- Name: TABLE supplier_penalties; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.supplier_penalties IS 'Penalty records for vendors/publishers. Tracks incidents like late delivery, low quality, content rejection, or policy violations. Linked to specific orders and assignments when applicable. Penalty accumulation may trigger probation or banning.';


--
-- Name: COLUMN supplier_penalties.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_penalties.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN supplier_penalties.supplier_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_penalties.supplier_id IS 'Foreign key to suppliers.supplier_id.';


--
-- Name: COLUMN supplier_penalties.order_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_penalties.order_id IS 'Foreign key to orders.order_id. Null if penalty is not tied to a specific order.';


--
-- Name: COLUMN supplier_penalties.assignment_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_penalties.assignment_id IS 'Foreign key to order_assignments.assignment_id. Null if not tied to a specific assignment.';


--
-- Name: COLUMN supplier_penalties.penalty_type; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_penalties.penalty_type IS 'Type of penalty. Values: ''late'', ''quality'', ''rejection'', ''policy'', ''cancellation'', ''other''. Varchar(20).';


--
-- Name: COLUMN supplier_penalties.notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_penalties.notes IS 'Free-text description of what happened and why the penalty was issued.';


--
-- Name: COLUMN supplier_penalties.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_penalties.created_at IS 'Timestamp when the penalty was recorded.';


--
-- Name: COLUMN supplier_penalties.amount; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_penalties.amount IS 'Intended dollar deduction for this penalty. Null if the penalty is a warning with no financial intent. The actual amount that hit the supplier''s ledger is on supplier_ledger.amount for the row where penalty_id = this.id — the two can differ if a penalty was partially waived or is still pending debit.';


--
-- Name: supplier_penalties_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.supplier_penalties_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: supplier_penalties_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.supplier_penalties_id_seq OWNED BY public.supplier_penalties.id;


--
-- Name: supplier_site_audit; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.supplier_site_audit (
    id integer NOT NULL,
    site_id integer NOT NULL,
    link_type_observed character varying(10),
    sponsored_observed boolean,
    word_count_observed integer,
    outbound_links_observed integer,
    internal_links_observed boolean,
    indexed_observed boolean,
    language_observed character varying(50),
    embeds_observed boolean,
    images_observed boolean,
    live_link_verified boolean,
    site_accessible boolean,
    ads_observed boolean,
    post_frequency_observed character varying(50),
    audited_by character varying(100),
    last_audited_at timestamp with time zone DEFAULT now() NOT NULL,
    author_byline_observed character varying(30),
    site_scope_observed character varying(20),
    local_target_observed character varying(100),
    content_type_observed character varying(30),
    anchor_types_observed text[] DEFAULT '{}'::text[],
    site_category_observed character varying(100),
    gray_niches_observed text[] DEFAULT '{}'::text[]
);


--
-- Name: TABLE supplier_site_audit; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.supplier_site_audit IS 'Staff side of the catalog — what we actually observe on the live site. Historical: one row per audit run per supplier_site listing. The most recent row is the current truth; prior rows are archive for look-back. Comparison against supplier_sites claimed values is a read-time query, not a stored flag — claims and observations both change and any cached mismatch would be stale immediately. A future architecture may move observations to a site_audit table keyed by site_id and reserve this table for listing-specific notes; not in scope here.';


--
-- Name: COLUMN supplier_site_audit.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_audit.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN supplier_site_audit.site_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_audit.site_id IS 'FK to sites.site_id. ON DELETE CASCADE. NOT unique — supplier_site_audit is append-only history with one row per audit run; sites cycle through repeated audits in a continuous loop. Current truth is the most recent row by last_audited_at.';


--
-- Name: COLUMN supplier_site_audit.link_type_observed; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_audit.link_type_observed IS 'Observed link type on the site. Values: dofollow or nofollow. Determined by checking rel attributes on outbound links in published posts. Compare with supplier_sites.link_type — mismatch is a high-priority flag.';


--
-- Name: COLUMN supplier_site_audit.sponsored_observed; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_audit.sponsored_observed IS 'Boolean. True if published posts contain sponsored/paid disclosure markers (rel=sponsored, sponsored post labels, advertorial tags). Compare with supplier_sites.marked_as_sponsored.';


--
-- Name: COLUMN supplier_site_audit.word_count_observed; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_audit.word_count_observed IS 'Average word count across recently published articles on the site. Integer. Compare with supplier_sites.word_count_minimum — if observed average is below their stated minimum, the minimum may not be enforced.';


--
-- Name: COLUMN supplier_site_audit.outbound_links_observed; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_audit.outbound_links_observed IS 'Average number of outbound links per post (links pointing to external domains). High counts may indicate a link farm. Integer.';


--
-- Name: COLUMN supplier_site_audit.internal_links_observed; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_audit.internal_links_observed IS 'Boolean. True if the publisher adds their own internal links to posts. Compare with meta key adds_internal_links.';


--
-- Name: COLUMN supplier_site_audit.indexed_observed; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_audit.indexed_observed IS 'Boolean. True if published posts on this site appear in Google search results. Checked via site: search operator. Compare with meta key indexed.';


--
-- Name: COLUMN supplier_site_audit.language_observed; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_audit.language_observed IS 'Primary language of the site content as detected by scraping. E.g. English, French, Spanish. Compare with supplier_sites.site_language.';


--
-- Name: COLUMN supplier_site_audit.embeds_observed; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_audit.embeds_observed IS 'Boolean. True if published posts contain embedded media (YouTube videos, Google Maps, iframes). Compare with meta key embeds_allowed.';


--
-- Name: COLUMN supplier_site_audit.images_observed; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_audit.images_observed IS 'Boolean. True if published posts contain images (photos, infographics, screenshots). Compare with meta key images_accepted.';


--
-- Name: COLUMN supplier_site_audit.live_link_verified; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_audit.live_link_verified IS 'Boolean. True if the example link(s) in supplier_sites.live_link are still live and returning HTTP 200. False if any are 404/dead. Null = not yet checked.';


--
-- Name: COLUMN supplier_site_audit.site_accessible; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_audit.site_accessible IS 'Boolean. True if the site homepage responds with HTTP 200. False if the site is down, DNS fails, or returns errors. Used to flag dead sites that should be deactivated.';


--
-- Name: COLUMN supplier_site_audit.ads_observed; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_audit.ads_observed IS 'Boolean. True if the site has display advertising (AdSense, banner ads, pop-ups). Heavy ad presence may indicate lower editorial quality.';


--
-- Name: COLUMN supplier_site_audit.post_frequency_observed; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_audit.post_frequency_observed IS 'How often new articles are published. Freetext description. E.g. daily, 2-3 per week, monthly, inactive (last post 6+ months ago). Dead blogs are a cancellation risk.';


--
-- Name: COLUMN supplier_site_audit.audited_by; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_audit.audited_by IS 'Who or what performed this audit. Values: spider (automated agent), staff:name (manual review by a specific person). Used for audit trail.';


--
-- Name: COLUMN supplier_site_audit.last_audited_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_audit.last_audited_at IS 'Timestamp of the most recent audit. Used to prioritize re-audits — stale audits (>30 days) should be refreshed. Default: now().';


--
-- Name: COLUMN supplier_site_audit.author_byline_observed; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_audit.author_byline_observed IS 'Pattern of author bylines observed on published posts. Values: real_name (human name visible), single_recurring (same real name on most posts), multiple_unique (many different real authors), generic_admin (admin/editor/staff), site_name (site name used as author), none (no byline). Answers is a real author visible on the post, not deep identity verification.';


--
-- Name: COLUMN supplier_site_audit.site_scope_observed; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_audit.site_scope_observed IS 'Geographic scope of the site target audience. Values: global, national, state, city, neighborhood. Used to filter inventory for clients who need locally-scoped sites. Distinct from meta.accepts_local_content which tracks whether the site accepts local content from others.';


--
-- Name: COLUMN supplier_site_audit.local_target_observed; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_audit.local_target_observed IS 'When site_scope_observed is state/city/neighborhood, names the target (e.g. Austin TX, California, Brooklyn). Empty for global/national sites.';


--
-- Name: COLUMN supplier_site_audit.content_type_observed; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_audit.content_type_observed IS 'Dominant observed content format on the site. Mirrors supplier_sites.article_style values: listicle, how_to, review, case_study, interview, pr_news, parasite_seo, infographic, roundup, mixed. Compare with what the supplier claims to accept.';


--
-- Name: COLUMN supplier_site_audit.anchor_types_observed; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_audit.anchor_types_observed IS 'Anchor text types observed in outbound links on published posts. Array of values from the same enum as supplier_sites.accepted_anchor_types: location_based, branded, naked_url, generic, partial_match, exact_match, lsi, longtail_keyword. Mismatch with accepted_anchor_types indicates the supplier is not enforcing what they claim to allow.';


--
-- Name: COLUMN supplier_site_audit.site_category_observed; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_audit.site_category_observed IS 'Staff-verified primary category for the site, editable during audit. Used to correct incorrect AI-generated tags in site_ai_categories. Human-authoritative; takes precedence over AI categorization in staff-facing UIs.';


--
-- Name: COLUMN supplier_site_audit.gray_niches_observed; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_audit.gray_niches_observed IS 'Gray / sensitive niches observed being published on the site. Array sourced dynamically from the niche values in supplier_site_pricing.niche (cbd, crypto, adult, gambling, payday, pharma) plus custom values. Compare with supplier_sites.sensitive_topics to flag undisclosed gray-niche activity.';


--
-- Name: supplier_site_audit_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.supplier_site_audit_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: supplier_site_audit_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.supplier_site_audit_id_seq OWNED BY public.supplier_site_audit.id;


--
-- Name: supplier_site_credentials; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.supplier_site_credentials (
    supplier_site_id integer NOT NULL,
    login_url text,
    username text,
    password text,
    notes text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: TABLE supplier_site_credentials; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.supplier_site_credentials IS 'Publisher-provided login credentials for sites where Loganix staff post content directly (e.g. the publisher gives us a WordPress editor account). Cleartext intentionally — staff must be able to display username/password to use them. Security boundary is ROLE-BASED ACCESS CONTROL, not encryption: REVOKE ALL FROM PUBLIC plus selective GRANTs to the fulfillment role. DB sits behind VPN + OAuth. One row per supplier_site listing; cascades on delete.';


--
-- Name: COLUMN supplier_site_credentials.supplier_site_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_credentials.supplier_site_id IS 'FK to supplier_sites. Primary key — one credential row per listing.';


--
-- Name: COLUMN supplier_site_credentials.login_url; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_credentials.login_url IS 'URL to the site admin login page (e.g. https://example.com/wp-admin). Null if the publisher did not provide one.';


--
-- Name: COLUMN supplier_site_credentials.username; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_credentials.username IS 'Admin username or email for the login.';


--
-- Name: COLUMN supplier_site_credentials.password; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_credentials.password IS 'Admin password. Cleartext by design — staff must display it to use it. Protected by table-level role grants, not encryption.';


--
-- Name: COLUMN supplier_site_credentials.notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_credentials.notes IS 'Free-text notes specific to the login (e.g. "2FA disabled", "use incognito", "account resets monthly").';


--
-- Name: supplier_site_meta; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.supplier_site_meta (
    id integer NOT NULL,
    supplier_site_id integer NOT NULL,
    meta_key character varying(100) NOT NULL,
    meta_value text
);


--
-- Name: TABLE supplier_site_meta; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.supplier_site_meta IS 'Flexible key-value metadata for supplier site listings. Stores additional site-specific settings that do not warrant dedicated columns. Keys are defined in supplier_site_meta_keys. For boolean fields, meta_value is true or false. For text fields, meta_value is freetext.';


--
-- Name: COLUMN supplier_site_meta.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_meta.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN supplier_site_meta.supplier_site_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_meta.supplier_site_id IS 'Foreign key to supplier_sites.supplier_site_id. ON DELETE CASCADE. Each meta row belongs to exactly one site listing. Query pattern: SELECT meta_key, meta_value FROM supplier_site_meta WHERE supplier_site_id = X.';


--
-- Name: COLUMN supplier_site_meta.meta_key; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_meta.meta_key IS 'The property name. Must match a key in supplier_site_meta_keys. E.g. embeds_allowed, permanent_link, accepts_competitor_links, link_insert_placement, ai_detection_tool.';


--
-- Name: COLUMN supplier_site_meta.meta_value; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_meta.meta_value IS 'The property value as text. For boolean keys (indexed, embeds_allowed, etc.): true or false. For text/textarea keys (link_insert_placement, renewable_comments, etc.): freetext content, may be multi-line. For number keys: string representation of the number. Null = not set.';


--
-- Name: supplier_site_meta_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.supplier_site_meta_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: supplier_site_meta_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.supplier_site_meta_id_seq OWNED BY public.supplier_site_meta.id;


--
-- Name: supplier_site_meta_keys; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.supplier_site_meta_keys (
    meta_key character varying(100) NOT NULL,
    label character varying(255) NOT NULL,
    input_type character varying(30) DEFAULT 'text'::character varying NOT NULL,
    sort_order smallint DEFAULT 0 NOT NULL
);


--
-- Name: TABLE supplier_site_meta_keys; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.supplier_site_meta_keys IS 'Defines the available metadata fields for supplier site listings. Acts as a form builder. Key fields include: indexed, embeds_allowed (video/map embeds), images_accepted, do_we_post, updates_after_live, permanent_link, accepts_competitor_links, link_insert_placement, accepts_foreign_links, accepts_other_languages, accepts_local_content, anchor_limit, author_bio, author_bio_details, ai_detection_tool, adds_internal_links, adds_external_links, client_link_type.';


--
-- Name: COLUMN supplier_site_meta_keys.meta_key; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_meta_keys.meta_key IS 'Primary key. The unique key name referenced by supplier_site_meta.meta_key. Current keys include: indexed, embeds_allowed, images_accepted, do_we_post, site_backend_url, site_username, site_password, updates_after_live, permanent_link, renewable, renewable_comments, accepts_competitor_links, low_metrics, low_metrics_charge, link_insert_placement, accepts_foreign_links, accepts_other_languages, accepts_local_content, anchor_limit, anchor_limit_details, author_bio, author_bio_details, ai_detection_tool, plagiarism_tools, adds_internal_links, adds_external_links, client_link_type, link_count_per_post, tenure, topic_guidelines, bio_instructions.';


--
-- Name: COLUMN supplier_site_meta_keys.label; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_meta_keys.label IS 'Human-readable label shown in the vendor portal form. E.g. Accepts Video/Map Embeds, Publisher Adds Internal Links, Client Geo Restrictions.';


--
-- Name: COLUMN supplier_site_meta_keys.input_type; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_meta_keys.input_type IS 'Form input type. Values: ''text'', ''number'', ''boolean'', ''select'', ''textarea''. Controls how the field renders in the vendor portal.';


--
-- Name: COLUMN supplier_site_meta_keys.sort_order; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_meta_keys.sort_order IS 'Display order in the vendor portal form. Lower numbers appear first. Range 10-310 in increments of 10.';


--
-- Name: supplier_site_pricing; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.supplier_site_pricing (
    id integer NOT NULL,
    supplier_site_id integer NOT NULL,
    service_type character varying(30) NOT NULL,
    niche character varying(30) DEFAULT 'general'::character varying NOT NULL,
    cost numeric(10,2) NOT NULL
);


--
-- Name: TABLE supplier_site_pricing; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.supplier_site_pricing IS 'LEGACY — pricing has moved to supplier_site_products (migration 033 phase 1). This table is kept as a read-only shadow until app code is confirmed reading from the new home; a phase 2 migration will drop it. DO NOT WRITE to this table; writes go to supplier_site_products.';


--
-- Name: COLUMN supplier_site_pricing.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_pricing.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN supplier_site_pricing.supplier_site_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_pricing.supplier_site_id IS 'Foreign key to supplier_sites.supplier_site_id. ON DELETE CASCADE. Each pricing row belongs to exactly one site listing. Query pattern: SELECT service_type, niche, cost FROM supplier_site_pricing WHERE supplier_site_id = X.';


--
-- Name: COLUMN supplier_site_pricing.service_type; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_pricing.service_type IS 'The type of link service being priced. Common values: guest_post (new article with embedded link), niche_edit (link inserted into an existing published article, also called link insert). Varchar(30).';


--
-- Name: COLUMN supplier_site_pricing.niche; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_pricing.niche IS 'The content niche for this price. Standard values: general (default base price), cbd, crypto, adult, gambling, payday, pharma. Also accepts arbitrary freetext niche names for custom categories (e.g. religion, guns, politics). Sensitive niches typically cost more. Default: general.';


--
-- Name: COLUMN supplier_site_pricing.cost; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_pricing.cost IS 'Price in USD that we pay the supplier for this service+niche combination. Decimal(10,2). This is our cost, NOT the client-facing retail price. Client retail is stored in orders.retail. The margin between cost and retail is our gross profit per placement.';


--
-- Name: supplier_site_pricing_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.supplier_site_pricing_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: supplier_site_pricing_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.supplier_site_pricing_id_seq OWNED BY public.supplier_site_pricing.id;


--
-- Name: supplier_site_products; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.supplier_site_products (
    id integer NOT NULL,
    supplier_site_id integer NOT NULL,
    name character varying(255) NOT NULL,
    price numeric(10,2),
    billing_cycle character varying(20),
    notes text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    product_id integer,
    cost numeric(10,2),
    niche character varying(30),
    CONSTRAINT supplier_site_products_billing_cycle_check CHECK (((billing_cycle)::text = ANY ((ARRAY['one-time'::character varying, 'monthly'::character varying, 'yearly'::character varying, 'permanent'::character varying])::text[])))
);


--
-- Name: TABLE supplier_site_products; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.supplier_site_products IS 'Non-link products and services offered on a specific supplier site. Covers everything beyond guest posts and link inserts — banner ads, footer links, social media posts, sponsored YouTube videos, on-air interviews, PR placements, etc. Each row is one product offering with its own pricing and billing cycle. FK to supplier_sites (products are per-site, not per-supplier).';


--
-- Name: COLUMN supplier_site_products.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_products.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN supplier_site_products.supplier_site_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_products.supplier_site_id IS 'Foreign key to supplier_sites.supplier_site_id. ON DELETE CASCADE. The site listing this product belongs to.';


--
-- Name: COLUMN supplier_site_products.name; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_products.name IS 'Product name as entered by the publisher. E.g. Banner ad (sidebar), Footer link, Social media post, Sponsored YouTube video, PR placement. Free text, not enumerated.';


--
-- Name: COLUMN supplier_site_products.price; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_products.price IS 'Price in USD for this product. Decimal(10,2). Null = price on request or negotiable.';


--
-- Name: COLUMN supplier_site_products.billing_cycle; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_products.billing_cycle IS 'How this product is billed. Values: one-time, monthly, yearly, permanent. CHECK constraint enforces these values. Null = not specified.';


--
-- Name: COLUMN supplier_site_products.notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_products.notes IS 'Free-text notes about this product. E.g. choose which page, includes 30-second spot, minimum 3 month commitment.';


--
-- Name: COLUMN supplier_site_products.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_products.created_at IS 'Timestamp when this product was first created.';


--
-- Name: COLUMN supplier_site_products.updated_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_products.updated_at IS 'Timestamp of last modification.';


--
-- Name: COLUMN supplier_site_products.product_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_products.product_id IS 'FK to products — the standardized product type this offering maps to. Nullable for transition; required on new inserts once the products catalog is seeded. The existing name column remains for the supplier-specific variant (e.g. "Sidebar banner 728x90" while product_id points at the generic banner_ad).';


--
-- Name: COLUMN supplier_site_products.cost; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_products.cost IS 'Amount we pay the supplier for this offering, in USD. Distinct from price (which is retail / what clients pay through the storefront). Populated for core services during the migration from supplier_site_pricing; optional for ancillary products that have negotiated pricing per-order.';


--
-- Name: COLUMN supplier_site_products.niche; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_site_products.niche IS 'Niche tier for niche-priced products (guest_post, niche_edit, sponsored_post). Values: general, cbd, crypto, adult, gambling, payday. Null for products with no niche dimension (banner_ad, video_mention, podcast_mention, etc.). A single (supplier_site, product, niche) combination is unique.';


--
-- Name: supplier_site_products_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.supplier_site_products_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: supplier_site_products_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.supplier_site_products_id_seq OWNED BY public.supplier_site_products.id;


--
-- Name: supplier_sites; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.supplier_sites (
    supplier_site_id integer NOT NULL,
    supplier_id integer NOT NULL,
    site_id integer NOT NULL,
    link_type character varying(10),
    tat smallint,
    content_charge numeric(10,2),
    word_count_minimum integer,
    preapprove_topic boolean DEFAULT false,
    accepted_anchor_types text,
    marked_as_sponsored boolean DEFAULT false,
    article_style character varying(255),
    guidelines_url character varying(255),
    guidelines_text text,
    live_link character varying(255),
    site_notes text,
    locked boolean DEFAULT false,
    sensitive_topics jsonb,
    inactive boolean DEFAULT false,
    starting_at numeric(10,2),
    vendor_notes text,
    our_notes text,
    favorite boolean DEFAULT false,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    review_status character varying(20) DEFAULT 'submitted'::character varying,
    ux_score smallint,
    site_language character varying(50),
    review_notes text,
    screenshot_captured_at timestamp with time zone,
    CONSTRAINT supplier_sites_link_type_check CHECK (((link_type)::text = ANY (ARRAY[('dofollow'::character varying)::text, ('nofollow'::character varying)::text])))
);


--
-- Name: TABLE supplier_sites; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.supplier_sites IS 'Inventory: which sites each vendor/publisher can place links on, with placement terms. This is the core of the supply side — each row represents one supplier offering one site. Links supplier_id to site_id with pricing terms, TAT, content rules, and editorial preferences. Vendors manage this from their portal (upload CSV or edit individually). The review_status field controls whether a listing is visible to the fulfillment team. For non-link products (banner ads, social posts, etc.), see supplier_site_products.';


--
-- Name: COLUMN supplier_sites.supplier_site_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.supplier_site_id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN supplier_sites.supplier_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.supplier_id IS 'Foreign key to suppliers.supplier_id. ON DELETE CASCADE. The vendor/publisher offering this site.';


--
-- Name: COLUMN supplier_sites.site_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.site_id IS 'Foreign key to sites.site_id. ON DELETE CASCADE. The website being offered.';


--
-- Name: COLUMN supplier_sites.link_type; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.link_type IS 'Type of link provided. Values: dofollow or nofollow. CHECK constraint enforces these values. Dofollow is more valuable for SEO. Dofollow links should NOT be marked as sponsored.';


--
-- Name: COLUMN supplier_sites.tat; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.tat IS 'Turnaround time in DAYS for this specific site listing. How many days the supplier needs to upload content once provided. Overrides suppliers.default_tat when set. Note: legacy data may contain values in weeks that need conversion (multiply by 7). Integer, not a string.';


--
-- Name: COLUMN supplier_sites.content_charge; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.content_charge IS 'Additional charge (USD) if the supplier writes the content instead of us. Null = supplier does not offer writing services for this site. For non-English sites (see site_language), writing services are required. Decimal(10,2).';


--
-- Name: COLUMN supplier_sites.word_count_minimum; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.word_count_minimum IS 'Minimum article word count the publisher requires for this site. Typical range: 500-2000. Common values: 500, 750, 1000, 1500. Null = no minimum specified. Used by the content team to set writing targets.';


--
-- Name: COLUMN supplier_sites.preapprove_topic; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.preapprove_topic IS 'Boolean. If true, the article topic must be approved by the publisher before we start writing. Adds a step to the order workflow.';


--
-- Name: COLUMN supplier_sites.accepted_anchor_types; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.accepted_anchor_types IS 'Free text describing what anchor text types the publisher accepts. Common values: location-based, branded, naked_url, generic, partial_match, exact_match, lsi, longtail_keyword. May be comma-separated. All of the above means no restrictions.';


--
-- Name: COLUMN supplier_sites.marked_as_sponsored; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.marked_as_sponsored IS 'Boolean. If true, the publisher labels posts as sponsored, paid, advertorial, branded content, or similar disclosure. Simple attribution like Contributed by does NOT count. Affects link value.';


--
-- Name: COLUMN supplier_sites.article_style; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.article_style IS 'Publisher article style preferences. Common values: normal_guest_post, listicles, how_tos, reviews, case_studies, interviews, pr_news, parasite_seo, infographic_posts, roundups. Comma-separated or free text.';


--
-- Name: COLUMN supplier_sites.guidelines_url; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.guidelines_url IS 'URL to the publishers content guidelines document. Auto-extracted from guidelines_text if a URL is detected in the submission. May also be set directly. Null = no guidelines URL provided.';


--
-- Name: COLUMN supplier_sites.guidelines_text; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.guidelines_text IS 'Full text of the publishers content guidelines as submitted. When a publisher pastes guidelines into the intake form, the raw text is stored here and any URL within it is extracted into guidelines_url. This is the source-of-truth for editorial rules on this site.';


--
-- Name: COLUMN supplier_sites.live_link; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.live_link IS 'Example URL(s) of previously published posts on this site. Must be from within the last 3 months. May contain multiple URLs separated by newlines.';


--
-- Name: COLUMN supplier_sites.site_notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.site_notes IS 'Publisher/vendor-facing notes about this site listing. Visible to the supplier in their portal. Used for general site information the supplier wants to communicate. Distinct from vendor_notes (vendor-editable workspace) and our_notes (internal only).';


--
-- Name: COLUMN supplier_sites.locked; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.locked IS 'Boolean. If true, this listing cannot be edited or deactivated by the vendor through their portal — only Loganix staff can modify it. Used for contracted placements with negotiated terms that should not be changed unilaterally. Default false.';


--
-- Name: COLUMN supplier_sites.sensitive_topics; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.sensitive_topics IS 'JSONB array of sensitive niche topics this site accepts with surcharge pricing. E.g. cbd, crypto, adult, gambling, payday, pharma. Custom topics also allowed (e.g. religion, guns, politics). NOT a blacklist — these are topics the site IS willing to accept at a premium.';


--
-- Name: COLUMN supplier_sites.inactive; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.inactive IS 'Boolean. If true, this listing is currently paused/unavailable. Vendor set it inactive but hasn''t deleted it.';


--
-- Name: COLUMN supplier_sites.starting_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.starting_at IS 'Lowest available price (USD) for this site across all service types and niches. Used for display, sorting, and filtering in the marketplace. Typically the general-niche guest_post price from supplier_site_pricing. May be stale if pricing rows change — should be recomputed. Decimal(10,2).';


--
-- Name: COLUMN supplier_sites.vendor_notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.vendor_notes IS 'Working notes from the vendor about this listing. Editable by the vendor in their portal. Not shown to clients. Distinct from site_notes (publisher-provided site info) and our_notes (internal Loganix notes).';


--
-- Name: COLUMN supplier_sites.our_notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.our_notes IS 'Internal Loganix staff notes about this listing. NOT visible to vendors or publishers — only visible in the internal fulfillment dashboard. Used for quality notes, relationship context, pricing negotiations, known issues.';


--
-- Name: COLUMN supplier_sites.favorite; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.favorite IS 'Boolean. Internal flag — fulfillment team has marked this as a preferred listing for its quality/reliability.';


--
-- Name: COLUMN supplier_sites.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.created_at IS 'Timestamp when this listing was first created.';


--
-- Name: COLUMN supplier_sites.updated_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.updated_at IS 'Timestamp of last modification.';


--
-- Name: COLUMN supplier_sites.review_status; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.review_status IS 'Staff review state for this listing. Not a forced flow — staff can approve on submission without formal review, but this field exists so the optional review pipeline can be turned on later without a schema change. Canonical values (no CHECK constraint, kept flexible): submitted (supplier just added — awaiting staff eyes), in_review (staff actively reviewing), approved (live and visible to clients), rejected (did not pass — review_notes typically emailed to the supplier), re_review (supplier updated a previously-rejected listing and resubmitted for another look).';


--
-- Name: COLUMN supplier_sites.ux_score; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.ux_score IS 'Optional 0-100 score from the legacy UX review tool. The tool itself is deprecated and not currently populating this column; field kept as a placeholder so the pipeline can be reactivated later without a schema change. Null = not scored.';


--
-- Name: COLUMN supplier_sites.site_language; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.site_language IS 'Primary language of this site content (e.g. English, French, Spanish). Auto-detected by scraping the site when the domain is first submitted. Critical for routing — non-English sites require the supplier to provide their own content because our writing team only produces English articles. Null = not yet detected.';


--
-- Name: COLUMN supplier_sites.review_notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.review_notes IS 'Staff review notes on the listing. Typical uses: rejection reasons (emailed to the supplier so they can address the issues before resubmitting), interim findings while in review, or approval conditions. Freetext; nullable.';


--
-- Name: COLUMN supplier_sites.screenshot_captured_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.supplier_sites.screenshot_captured_at IS 'When a homepage screenshot was last captured for this listing. Null = no screenshot on file. The screenshot URL is deterministic from the domain (e.g. https://cdn.loganix.com/screenshots/{domain}.png) so no URL column is stored — just the presence + freshness timestamp. The screenshot/review pipeline is optional; when enabled, cron updates this timestamp after capture and UI gates thumbnail rendering on it being non-null.';


--
-- Name: supplier_sites_supplier_site_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.supplier_sites_supplier_site_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: supplier_sites_supplier_site_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.supplier_sites_supplier_site_id_seq OWNED BY public.supplier_sites.supplier_site_id;


--
-- Name: suppliers; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.suppliers (
    supplier_id integer NOT NULL,
    supplier_type character varying(20) NOT NULL,
    email public.citext NOT NULL,
    alt_email character varying(255),
    first_name character varying(255),
    last_name character varying(255),
    phone character varying(255),
    country character varying(255),
    company_name character varying(255),
    alt_contacts jsonb,
    payment_method character varying(50),
    payment_details text,
    credit_balance numeric(10,2) DEFAULT 0,
    prepayments numeric(10,2) DEFAULT 0,
    invoice_number integer DEFAULT 0,
    monthly_billing character varying(15),
    global_discount numeric(10,2),
    referral character varying(255),
    reliability integer,
    rating numeric(10,2),
    rating_details jsonb,
    contact_type character varying(255),
    provides_content character varying(255),
    default_tat smallint,
    ban_expires_at timestamp with time zone,
    probated boolean DEFAULT false,
    stl_v2_approved boolean DEFAULT false,
    consent_communication boolean DEFAULT false,
    privacy_consent boolean DEFAULT false,
    terms_conditions boolean DEFAULT false,
    last_comms_at timestamp with time zone,
    last_list_update_at timestamp with time zone,
    last_login_at timestamp with time zone,
    notes text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    debit_balance numeric(10,2) DEFAULT 0 NOT NULL,
    holiday_discount numeric(10,2),
    holiday_discount_start date,
    holiday_discount_end date,
    banned_at timestamp with time zone,
    auth_provider character varying(30),
    auth_subject character varying(255),
    portal_prefs jsonb DEFAULT '{}'::jsonb NOT NULL,
    CONSTRAINT suppliers_payment_method_check CHECK (((payment_method IS NULL) OR ((payment_method)::text = ANY ((ARRAY['paypal'::character varying, 'payoneer'::character varying, 'wise'::character varying, 'bank_transfer'::character varying, 'credit_card'::character varying, 'wire'::character varying, 'crypto'::character varying, 'other'::character varying])::text[])))),
    CONSTRAINT suppliers_supplier_type_check CHECK (((supplier_type)::text = ANY (ARRAY[('publisher'::character varying)::text, ('vendor'::character varying)::text])))
);


--
-- Name: TABLE suppliers; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.suppliers IS 'Vendors and publishers who fulfill link placement orders. Two types: ''publisher'' (owns 1-5 sites directly and posts content themselves) and ''vendor'' (resells access to many sites, acts as intermediary). This table stores their profile, contact info, payment details, quality ratings, and account status. Vendors/publishers log into their portal using their email. The supplier_id is referenced by order_assignments (who fulfills each order) and supplier_sites (their inventory).';


--
-- Name: COLUMN suppliers.supplier_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.supplier_id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN suppliers.supplier_type; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.supplier_type IS 'Either ''publisher'' (owns sites directly, typically 1-5 sites) or ''vendor'' (resells access to many sites, acts as intermediary). CHECK constraint enforces these two values.';


--
-- Name: COLUMN suppliers.email; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.email IS 'Primary email address (citext, case-insensitive). Used for portal login authentication and as the unique identifier for the supplier. Must be unique.';


--
-- Name: COLUMN suppliers.alt_email; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.alt_email IS 'Secondary/backup email address.';


--
-- Name: COLUMN suppliers.first_name; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.first_name IS 'Contact first name.';


--
-- Name: COLUMN suppliers.last_name; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.last_name IS 'Contact last name.';


--
-- Name: COLUMN suppliers.phone; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.phone IS 'Phone number. No format enforcement — stored as provided.';


--
-- Name: COLUMN suppliers.country; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.country IS 'Country of residence/operation. Free text, not ISO code.';


--
-- Name: COLUMN suppliers.company_name; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.company_name IS 'Business or company name, if applicable.';


--
-- Name: COLUMN suppliers.alt_contacts; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.alt_contacts IS 'JSONB object with additional contact methods. Keys may include ''skype'', ''telegram'', ''whatsapp'', ''discord'', etc.';


--
-- Name: COLUMN suppliers.payment_method; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.payment_method IS 'Standing payment channel preference on the supplier profile. One of: paypal / payoneer / wise / bank_transfer / credit_card / wire / crypto / other. Used by both flows: batched invoices (Flow 1, regular vendors paid 15th and 30th) and per-order direct publisher payments (Flow 2). Set once at onboarding; overridable per-payment if a supplier gives one-off instructions.';


--
-- Name: COLUMN suppliers.payment_details; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.payment_details IS 'PayPal email / Wise account / bank routing info / crypto address — the destination for payments. SENSITIVE: must be encrypted at rest via app-layer Fernet or equivalent before writing. Never log this column. Access restricted to payroll / finance roles. Actual encryption is an application-layer concern; this column exists as the storage slot only.';


--
-- Name: COLUMN suppliers.credit_balance; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.credit_balance IS 'Outstanding credit balance owed to this supplier. Accumulates as orders are completed (credits in supplier_ledger). Decreases when invoices are paid. Decimal(10,2).';


--
-- Name: COLUMN suppliers.prepayments; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.prepayments IS 'Prepaid balance — some suppliers deposit money upfront. Decimal(10,2).';


--
-- Name: COLUMN suppliers.invoice_number; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.invoice_number IS 'Counter for the next invoice number. Auto-incremented each time an invoice is generated.';


--
-- Name: COLUMN suppliers.monthly_billing; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.monthly_billing IS 'Billing cycle preference. Values: ''1st-14th'', ''15th-end'', or null for default.';


--
-- Name: COLUMN suppliers.global_discount; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.global_discount IS 'Blanket discount percentage applied to all orders for this supplier. Decimal(10,2). E.g. 10.00 = 10% discount.';


--
-- Name: COLUMN suppliers.referral; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.referral IS 'How this supplier found us or who referred them. Free text for tracking acquisition source.';


--
-- Name: COLUMN suppliers.reliability; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.reliability IS 'Reliability score (0-100) calculated from historical delivery performance — on-time rate, acceptance rate, cancellation rate.';


--
-- Name: COLUMN suppliers.rating; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.rating IS 'Overall quality rating (0-100) based on link check grades, content quality, and feedback. Decimal(10,2).';


--
-- Name: COLUMN suppliers.rating_details; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.rating_details IS 'JSONB breakdown of rating components. Keys may include ''quality_avg'', ''tat_adherence'', ''acceptance_rate'', ''cancellation_rate''.';


--
-- Name: COLUMN suppliers.contact_type; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.contact_type IS 'Primary communication channel preference (e.g. ''email'', ''slack'', ''whatsapp'').';


--
-- Name: COLUMN suppliers.provides_content; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.provides_content IS 'Whether this supplier provides their own written content. Values: ''yes'', ''no'', ''sometimes''. Affects order workflow — if no, we must assign a writer.';


--
-- Name: COLUMN suppliers.default_tat; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.default_tat IS 'Default turnaround time in days. Used as fallback when a specific supplier_site doesn''t have a TAT set.';


--
-- Name: COLUMN suppliers.ban_expires_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.ban_expires_at IS 'When the current ban automatically expires. Null (when banned_at is also set) means indefinite — staff must clear banned_at to lift. Set in the future for a timed suspension (e.g. two-week ban).';


--
-- Name: COLUMN suppliers.probated; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.probated IS 'Boolean. If true, supplier is on probation — monitored more closely, may have order volume limits.';


--
-- Name: COLUMN suppliers.stl_v2_approved; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.stl_v2_approved IS 'Boolean. Supplier has been vetted and approved for the Shop The List v2 marketplace. Quality gate for premium features.';


--
-- Name: COLUMN suppliers.consent_communication; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.consent_communication IS 'Boolean. Supplier opted in to marketing/update communications.';


--
-- Name: COLUMN suppliers.privacy_consent; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.privacy_consent IS 'Boolean. Supplier accepted the privacy policy.';


--
-- Name: COLUMN suppliers.terms_conditions; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.terms_conditions IS 'Boolean. Supplier accepted terms and conditions.';


--
-- Name: COLUMN suppliers.last_comms_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.last_comms_at IS 'Timestamp of last communication (email, message) with this supplier. Used to flag inactive suppliers.';


--
-- Name: COLUMN suppliers.last_list_update_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.last_list_update_at IS 'Timestamp when supplier last updated their site inventory (supplier_sites). Crons can remind suppliers with stale lists.';


--
-- Name: COLUMN suppliers.last_login_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.last_login_at IS 'Timestamp of last vendor portal login.';


--
-- Name: COLUMN suppliers.notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.notes IS 'Free-text internal notes about this supplier. Only visible to Loganix staff.';


--
-- Name: COLUMN suppliers.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.created_at IS 'Timestamp when this supplier account was created.';


--
-- Name: COLUMN suppliers.updated_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.updated_at IS 'Timestamp of last modification to this supplier record.';


--
-- Name: COLUMN suppliers.debit_balance; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.debit_balance IS 'Outstanding debit owed BY the supplier TO us — accrues from penalties, chargebacks, overpayments. Auto-updated from supplier_ledger debit entries; can be manually adjusted.';


--
-- Name: COLUMN suppliers.holiday_discount; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.holiday_discount IS 'Seasonal blanket discount percentage applied to all orders during the holiday window.';


--
-- Name: COLUMN suppliers.holiday_discount_start; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.holiday_discount_start IS 'Start date of the holiday discount window.';


--
-- Name: COLUMN suppliers.holiday_discount_end; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.holiday_discount_end IS 'End date of the holiday discount window.';


--
-- Name: COLUMN suppliers.banned_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.banned_at IS 'Timestamp when the current ban was issued. Null = supplier is not currently banned. A supplier is banned right now if banned_at IS NOT NULL AND (ban_expires_at IS NULL OR ban_expires_at > now()). Historical bans live in supplier_ban_history; this column tracks the active ban only.';


--
-- Name: COLUMN suppliers.auth_provider; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.auth_provider IS 'OAuth provider the supplier uses to log into the supplier portal (planned, not yet built, on a separate domain from ops.loganix.com). Values: google, facebook, linkedin, github, or null until first login. Staff auth is independent — they use the users table.';


--
-- Name: COLUMN suppliers.auth_subject; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.auth_subject IS 'The OAuth providers stable unique subject identifier for this supplier (e.g. Google sub claim). Combined with auth_provider forms the unique login identity — a supplier can rotate their email without losing access as long as auth_subject stays the same. Nullable until first login.';


--
-- Name: COLUMN suppliers.portal_prefs; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.suppliers.portal_prefs IS 'Per-supplier portal UI preferences as flexible JSONB. Examples: {"onboarding_tour_dismissed": true, "publishers_tour_dismissed": true, "default_inventory_view": "table", "timezone": "America/Los_Angeles"}. Extend keys freely without schema migrations. Used by the supplier portal (not ops.loganix.com) to remember per-user UI state across sessions.';


--
-- Name: suppliers_supplier_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.suppliers_supplier_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: suppliers_supplier_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.suppliers_supplier_id_seq OWNED BY public.suppliers.supplier_id;


--
-- Name: traffic_history; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.traffic_history (
    id integer NOT NULL,
    site_id integer NOT NULL,
    metric character varying(15) NOT NULL,
    value integer NOT NULL,
    recorded_at timestamp with time zone NOT NULL
);


--
-- Name: TABLE traffic_history; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.traffic_history IS 'Historical time-series of site metrics. Stores periodic snapshots of traffic, keywords, DR, DA, and other metrics for trend analysis. One row per site + metric + timestamp. This is the largest table in the system (~44.6M rows from legacy data). Used to calculate traffic_trend on the sites table and to show metric history charts.';


--
-- Name: COLUMN traffic_history.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.traffic_history.id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN traffic_history.site_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.traffic_history.site_id IS 'Foreign key to sites.site_id. ON DELETE CASCADE.';


--
-- Name: COLUMN traffic_history.metric; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.traffic_history.metric IS 'The metric name being tracked. Values: ''traffic'', ''keywords'', ''dr'', ''da'', ''tf'', ''cf'', ''rd'', ''backlinks'', ''organic_cost''. Stored as varchar(15).';


--
-- Name: COLUMN traffic_history.value; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.traffic_history.value IS 'The metric value at the recorded timestamp. Integer — for decimal metrics like organic_cost, stored as cents or rounded.';


--
-- Name: COLUMN traffic_history.recorded_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.traffic_history.recorded_at IS 'Timestamp when this metric snapshot was taken. Typically daily or weekly depending on the cron schedule.';


--
-- Name: traffic_history_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.traffic_history_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: traffic_history_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.traffic_history_id_seq OWNED BY public.traffic_history.id;


--
-- Name: users; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.users (
    user_id integer NOT NULL,
    email public.citext NOT NULL,
    display_name character varying(255),
    role character varying(30) DEFAULT 'viewer'::character varying NOT NULL,
    oauth_provider character varying(30),
    oauth_id character varying(255),
    writer_id integer,
    supplier_id integer,
    permissions_override jsonb,
    banned boolean DEFAULT false,
    last_login_at timestamp with time zone,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    notification_preferences jsonb
);


--
-- Name: TABLE users; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.users IS 'Internal users with roles and OAuth login. This is the authentication and authorization table for the new unified platform. Users log in via OAuth (Google, Facebook, LinkedIn, GitHub). Roles control access: super_admin, admin, editor, viewer, vendor, writer. Users may be linked to a writer or supplier profile for cross-system identity.';


--
-- Name: COLUMN users.user_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.users.user_id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN users.email; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.users.email IS 'User''s email (citext, case-insensitive). Used as the unique login identifier. @loganix.com emails get automatic staff role detection.';


--
-- Name: COLUMN users.display_name; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.users.display_name IS 'User''s display name shown in the UI.';


--
-- Name: COLUMN users.role; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.users.role IS 'Access role. Values: ''super_admin'' (full access), ''admin'' (management access), ''editor'' (content team), ''viewer'' (read-only), ''vendor'' (supplier portal), ''writer'' (content portal). Default: ''viewer''.';


--
-- Name: COLUMN users.oauth_provider; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.users.oauth_provider IS 'OAuth provider used for login. Values: ''google'', ''facebook'', ''linkedin'', ''github''. Varchar(30).';


--
-- Name: COLUMN users.oauth_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.users.oauth_id IS 'Unique ID from the OAuth provider. Used to match OAuth callbacks to user records.';


--
-- Name: COLUMN users.writer_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.users.writer_id IS 'Foreign key to writers.writer_id. Links this user account to a writer profile. Null for non-writer users.';


--
-- Name: COLUMN users.supplier_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.users.supplier_id IS 'Foreign key to suppliers.supplier_id. Links this user account to a vendor/publisher profile. Null for non-supplier users.';


--
-- Name: COLUMN users.permissions_override; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.users.permissions_override IS 'JSONB object for per-user permission overrides beyond what the role grants. E.g. {''can_approve_invoices'': true}. Null = no overrides, use role defaults.';


--
-- Name: COLUMN users.banned; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.users.banned IS 'Boolean. If true, user cannot log in.';


--
-- Name: COLUMN users.last_login_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.users.last_login_at IS 'Timestamp of last successful login.';


--
-- Name: COLUMN users.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.users.created_at IS 'Timestamp when this user account was created.';


--
-- Name: COLUMN users.updated_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.users.updated_at IS 'Timestamp of last profile modification.';


--
-- Name: COLUMN users.notification_preferences; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.users.notification_preferences IS 'JSONB object with notification settings. Keys may include ''email_digest'', ''slack_mentions'', ''order_updates''. Null = use system defaults.';


--
-- Name: users_user_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.users_user_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: users_user_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.users_user_id_seq OWNED BY public.users.user_id;


--
-- Name: v_active_supplier_bans; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_active_supplier_bans AS
 SELECT supplier_id,
    email,
    company_name,
    banned_at,
    ban_expires_at
   FROM public.suppliers
  WHERE ((banned_at IS NOT NULL) AND ((ban_expires_at IS NULL) OR (ban_expires_at > now())));


--
-- Name: VIEW v_active_supplier_bans; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON VIEW public.v_active_supplier_bans IS 'Convenience view of suppliers currently banned (banned_at set, no expiry or expiry in future). Use this instead of the raw suppliers.banned_at + ban_expires_at CASE logic when you just want "who is currently banned." Updates live with the underlying table; no refresh needed.';


--
-- Name: v_order_assignments_sla; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_order_assignments_sla AS
 SELECT assignment_id,
    order_id,
    supplier_id,
    status,
    content_handed_at,
    sla_threshold_hours,
    (content_handed_at + ((sla_threshold_hours)::double precision * '01:00:00'::interval)) AS sla_breach_at,
    ((now() > (content_handed_at + ((sla_threshold_hours)::double precision * '01:00:00'::interval))) AND (completed_at IS NULL)) AS is_breached
   FROM public.order_assignments
  WHERE ((content_handed_at IS NOT NULL) AND (sla_threshold_hours IS NOT NULL));


--
-- Name: VIEW v_order_assignments_sla; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON VIEW public.v_order_assignments_sla IS 'Read-side SLA view. Overdue dashboard query: SELECT * FROM v_order_assignments_sla WHERE is_breached = true. Computed on read rather than stored (Postgres requires generation expressions to be immutable, and interval arithmetic is not).';


--
-- Name: writers; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.writers (
    writer_id integer NOT NULL,
    name character varying(255) NOT NULL,
    paypal_email character varying(255),
    contact_email character varying(255),
    niche character varying(255),
    interests text,
    negative_interests text,
    max_articles_per_week integer,
    cost_per_word numeric(10,4),
    timezone character varying(50),
    working_hours text,
    banned boolean DEFAULT false,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    claimed boolean DEFAULT false,
    niche_qualifications character varying(30)[] DEFAULT '{}'::character varying[],
    topic_specializations character varying(50)[] DEFAULT '{}'::character varying[],
    current_load integer DEFAULT 0 NOT NULL,
    availability_status character varying(20) DEFAULT 'active'::character varying NOT NULL,
    CONSTRAINT writers_availability_status_check CHECK (((availability_status)::text = ANY ((ARRAY['active'::character varying, 'on_leave'::character varying, 'paused'::character varying, 'banned'::character varying])::text[])))
);


--
-- Name: TABLE writers; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.writers IS 'Content writers and their profiles. Writers are assigned to create content for orders that require written articles. Each writer has a niche specialization, rate, capacity, and preferences. Writers log in through the content portal to view and manage their assignments.';


--
-- Name: COLUMN writers.writer_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.writers.writer_id IS 'Auto-incrementing primary key.';


--
-- Name: COLUMN writers.name; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.writers.name IS 'Writer''s display name.';


--
-- Name: COLUMN writers.paypal_email; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.writers.paypal_email IS 'PayPal email for writer payments.';


--
-- Name: COLUMN writers.contact_email; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.writers.contact_email IS 'Email for communication. May differ from PayPal email.';


--
-- Name: COLUMN writers.niche; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.writers.niche IS 'Primary writing niche/specialization. E.g. ''technology'', ''health'', ''finance''. Used for matching writers to orders.';


--
-- Name: COLUMN writers.interests; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.writers.interests IS 'Topics the writer enjoys writing about. Free text. Used for better matching.';


--
-- Name: COLUMN writers.negative_interests; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.writers.negative_interests IS 'Topics the writer will NOT write about. Free text. Used as exclusion filter.';


--
-- Name: COLUMN writers.max_articles_per_week; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.writers.max_articles_per_week IS 'Weekly capacity — maximum articles this writer can handle.';


--
-- Name: COLUMN writers.cost_per_word; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.writers.cost_per_word IS 'Payment rate per word. Decimal(10,4). E.g. 0.0500 = $0.05/word. Multiply by word_count for total payment.';


--
-- Name: COLUMN writers.timezone; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.writers.timezone IS 'Writer''s timezone. E.g. ''America/New_York'', ''Asia/Manila''. Used for deadline calculations.';


--
-- Name: COLUMN writers.working_hours; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.writers.working_hours IS 'Preferred working hours. Free text. E.g. ''9am-5pm EST''.';


--
-- Name: COLUMN writers.banned; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.writers.banned IS 'Boolean. If true, writer cannot receive new assignments.';


--
-- Name: COLUMN writers.created_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.writers.created_at IS 'Timestamp when writer profile was created.';


--
-- Name: COLUMN writers.updated_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.writers.updated_at IS 'Timestamp of last profile update.';


--
-- Name: COLUMN writers.claimed; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.writers.claimed IS 'Boolean. Writer has claimed/verified their profile. Unclaimed profiles were typically imported in bulk and the writer has not yet logged in.';


--
-- Name: COLUMN writers.niche_qualifications; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.writers.niche_qualifications IS 'Array of niches this writer is cleared to write for. Mirrors orders.niche values: general, cbd, crypto, adult, gambling, payday. Structured replacement for the free-text niche column (which stays for transition). Auto-assignment filter: writer.niche_qualifications @> ARRAY[order.niche].';


--
-- Name: COLUMN writers.topic_specializations; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.writers.topic_specializations IS 'Array of topic tags the writer specializes in (e.g. {health, fitness, b2b_saas}). Structured replacement for the free-text interests column. Used by auto-assignment to prefer writers whose specializations match the order topic.';


--
-- Name: COLUMN writers.current_load; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.writers.current_load IS 'Running count of active in-flight assignments this week. Incremented on assignment, decremented on delivery. Auto-assignment skips writers where current_load >= max_articles_per_week.';


--
-- Name: COLUMN writers.availability_status; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.writers.availability_status IS 'active (accepting assignments), on_leave (temporary unavailable, do not assign), paused (quality hold, do not assign until review), banned (permanent). Extends the banned boolean to cover all availability states.';


--
-- Name: writers_writer_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.writers_writer_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: writers_writer_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.writers_writer_id_seq OWNED BY public.writers.writer_id;


--
-- Name: activity_log id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.activity_log ALTER COLUMN id SET DEFAULT nextval('public.activity_log_id_seq'::regclass);


--
-- Name: ai_overviews id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.ai_overviews ALTER COLUMN id SET DEFAULT nextval('public.ai_overviews_id_seq'::regclass);


--
-- Name: api_keys id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.api_keys ALTER COLUMN id SET DEFAULT nextval('public.api_keys_id_seq'::regclass);


--
-- Name: api_request_log id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.api_request_log ALTER COLUMN id SET DEFAULT nextval('public.api_request_log_id_seq'::regclass);


--
-- Name: atlas_bug_events id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.atlas_bug_events ALTER COLUMN id SET DEFAULT nextval('public.atlas_bug_events_id_seq'::regclass);


--
-- Name: atlas_bug_reports id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.atlas_bug_reports ALTER COLUMN id SET DEFAULT nextval('public.atlas_bug_reports_id_seq'::regclass);


--
-- Name: atlas_query_log id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.atlas_query_log ALTER COLUMN id SET DEFAULT nextval('public.atlas_query_log_id_seq'::regclass);


--
-- Name: campaigns campaign_id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.campaigns ALTER COLUMN campaign_id SET DEFAULT nextval('public.campaigns_campaign_id_seq'::regclass);


--
-- Name: category_vocabulary category_id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.category_vocabulary ALTER COLUMN category_id SET DEFAULT nextval('public.category_vocabulary_category_id_seq'::regclass);


--
-- Name: clients client_id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.clients ALTER COLUMN client_id SET DEFAULT nextval('public.clients_client_id_seq'::regclass);


--
-- Name: content_activity_log id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.content_activity_log ALTER COLUMN id SET DEFAULT nextval('public.content_activity_log_id_seq'::regclass);


--
-- Name: content_assignments assignment_id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.content_assignments ALTER COLUMN assignment_id SET DEFAULT nextval('public.content_assignments_assignment_id_seq'::regclass);


--
-- Name: content_docs doc_id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.content_docs ALTER COLUMN doc_id SET DEFAULT nextval('public.content_docs_doc_id_seq'::regclass);


--
-- Name: content_notes note_id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.content_notes ALTER COLUMN note_id SET DEFAULT nextval('public.content_notes_note_id_seq'::regclass);


--
-- Name: cron_executions id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.cron_executions ALTER COLUMN id SET DEFAULT nextval('public.cron_executions_id_seq'::regclass);


--
-- Name: cron_runs id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.cron_runs ALTER COLUMN id SET DEFAULT nextval('public.cron_runs_id_seq'::regclass);


--
-- Name: directus_activity id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_activity ALTER COLUMN id SET DEFAULT nextval('public.directus_activity_id_seq'::regclass);


--
-- Name: directus_fields id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_fields ALTER COLUMN id SET DEFAULT nextval('public.directus_fields_id_seq'::regclass);


--
-- Name: directus_notifications id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_notifications ALTER COLUMN id SET DEFAULT nextval('public.directus_notifications_id_seq'::regclass);


--
-- Name: directus_permissions id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_permissions ALTER COLUMN id SET DEFAULT nextval('public.directus_permissions_id_seq'::regclass);


--
-- Name: directus_presets id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_presets ALTER COLUMN id SET DEFAULT nextval('public.directus_presets_id_seq'::regclass);


--
-- Name: directus_relations id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_relations ALTER COLUMN id SET DEFAULT nextval('public.directus_relations_id_seq'::regclass);


--
-- Name: directus_revisions id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_revisions ALTER COLUMN id SET DEFAULT nextval('public.directus_revisions_id_seq'::regclass);


--
-- Name: directus_settings id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_settings ALTER COLUMN id SET DEFAULT nextval('public.directus_settings_id_seq'::regclass);


--
-- Name: directus_webhooks id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_webhooks ALTER COLUMN id SET DEFAULT nextval('public.directus_webhooks_id_seq'::regclass);


--
-- Name: link_checks id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.link_checks ALTER COLUMN id SET DEFAULT nextval('public.link_checks_id_seq'::regclass);


--
-- Name: managed_clients managed_client_id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.managed_clients ALTER COLUMN managed_client_id SET DEFAULT nextval('public.managed_clients_managed_client_id_seq'::regclass);


--
-- Name: order_assignments assignment_id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_assignments ALTER COLUMN assignment_id SET DEFAULT nextval('public.order_assignments_assignment_id_seq'::regclass);


--
-- Name: order_attachments id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_attachments ALTER COLUMN id SET DEFAULT nextval('public.order_attachments_id_seq'::regclass);


--
-- Name: order_cancellation_reasons id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_cancellation_reasons ALTER COLUMN id SET DEFAULT nextval('public.order_cancellation_reasons_id_seq'::regclass);


--
-- Name: order_messages id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_messages ALTER COLUMN id SET DEFAULT nextval('public.order_messages_id_seq'::regclass);


--
-- Name: order_status_log id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_status_log ALTER COLUMN id SET DEFAULT nextval('public.order_status_log_id_seq'::regclass);


--
-- Name: order_tags id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_tags ALTER COLUMN id SET DEFAULT nextval('public.order_tags_id_seq'::regclass);


--
-- Name: order_triggers trigger_id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_triggers ALTER COLUMN trigger_id SET DEFAULT nextval('public.order_triggers_trigger_id_seq'::regclass);


--
-- Name: orders order_id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.orders ALTER COLUMN order_id SET DEFAULT nextval('public.orders_order_id_seq'::regclass);


--
-- Name: products product_id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.products ALTER COLUMN product_id SET DEFAULT nextval('public.products_product_id_seq'::regclass);


--
-- Name: role_permissions id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.role_permissions ALTER COLUMN id SET DEFAULT nextval('public.role_permissions_id_seq'::regclass);


--
-- Name: site_ai_categories id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.site_ai_categories ALTER COLUMN id SET DEFAULT nextval('public.site_ai_categories_id_seq'::regclass);


--
-- Name: site_geo id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.site_geo ALTER COLUMN id SET DEFAULT nextval('public.site_geo_id_seq'::regclass);


--
-- Name: sites site_id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.sites ALTER COLUMN site_id SET DEFAULT nextval('public.sites_site_id_seq'::regclass);


--
-- Name: supplier_ban_history id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_ban_history ALTER COLUMN id SET DEFAULT nextval('public.supplier_ban_history_id_seq'::regclass);


--
-- Name: supplier_invoices id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_invoices ALTER COLUMN id SET DEFAULT nextval('public.supplier_invoices_id_seq'::regclass);


--
-- Name: supplier_ledger id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_ledger ALTER COLUMN id SET DEFAULT nextval('public.supplier_ledger_id_seq'::regclass);


--
-- Name: supplier_order_broadcasts id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_order_broadcasts ALTER COLUMN id SET DEFAULT nextval('public.supplier_order_broadcasts_id_seq'::regclass);


--
-- Name: supplier_penalties id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_penalties ALTER COLUMN id SET DEFAULT nextval('public.supplier_penalties_id_seq'::regclass);


--
-- Name: supplier_site_audit id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_site_audit ALTER COLUMN id SET DEFAULT nextval('public.supplier_site_audit_id_seq'::regclass);


--
-- Name: supplier_site_meta id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_site_meta ALTER COLUMN id SET DEFAULT nextval('public.supplier_site_meta_id_seq'::regclass);


--
-- Name: supplier_site_pricing id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_site_pricing ALTER COLUMN id SET DEFAULT nextval('public.supplier_site_pricing_id_seq'::regclass);


--
-- Name: supplier_site_products id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_site_products ALTER COLUMN id SET DEFAULT nextval('public.supplier_site_products_id_seq'::regclass);


--
-- Name: supplier_sites supplier_site_id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_sites ALTER COLUMN supplier_site_id SET DEFAULT nextval('public.supplier_sites_supplier_site_id_seq'::regclass);


--
-- Name: suppliers supplier_id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.suppliers ALTER COLUMN supplier_id SET DEFAULT nextval('public.suppliers_supplier_id_seq'::regclass);


--
-- Name: traffic_history id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.traffic_history ALTER COLUMN id SET DEFAULT nextval('public.traffic_history_id_seq'::regclass);


--
-- Name: users user_id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.users ALTER COLUMN user_id SET DEFAULT nextval('public.users_user_id_seq'::regclass);


--
-- Name: writers writer_id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.writers ALTER COLUMN writer_id SET DEFAULT nextval('public.writers_writer_id_seq'::regclass);


--
-- Name: activity_log activity_log_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.activity_log
    ADD CONSTRAINT activity_log_pkey PRIMARY KEY (id);


--
-- Name: ai_overviews ai_overviews_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.ai_overviews
    ADD CONSTRAINT ai_overviews_pkey PRIMARY KEY (id);


--
-- Name: ai_overviews ai_overviews_site_id_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.ai_overviews
    ADD CONSTRAINT ai_overviews_site_id_key UNIQUE (site_id);


--
-- Name: api_keys api_keys_key_hash_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.api_keys
    ADD CONSTRAINT api_keys_key_hash_key UNIQUE (key_hash);


--
-- Name: api_keys api_keys_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.api_keys
    ADD CONSTRAINT api_keys_pkey PRIMARY KEY (id);


--
-- Name: api_request_log api_request_log_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.api_request_log
    ADD CONSTRAINT api_request_log_pkey PRIMARY KEY (id);


--
-- Name: api_scopes api_scopes_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.api_scopes
    ADD CONSTRAINT api_scopes_pkey PRIMARY KEY (scope);


--
-- Name: atlas_bug_events atlas_bug_events_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.atlas_bug_events
    ADD CONSTRAINT atlas_bug_events_pkey PRIMARY KEY (id);


--
-- Name: atlas_bug_reports atlas_bug_reports_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.atlas_bug_reports
    ADD CONSTRAINT atlas_bug_reports_pkey PRIMARY KEY (id);


--
-- Name: atlas_query_log atlas_query_log_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.atlas_query_log
    ADD CONSTRAINT atlas_query_log_pkey PRIMARY KEY (id);


--
-- Name: atlas_user_roles atlas_user_roles_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.atlas_user_roles
    ADD CONSTRAINT atlas_user_roles_pkey PRIMARY KEY (email, role);


--
-- Name: campaigns campaigns_client_id_base_domain_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.campaigns
    ADD CONSTRAINT campaigns_client_id_base_domain_key UNIQUE (client_id, base_domain) DEFERRABLE INITIALLY DEFERRED;


--
-- Name: campaigns campaigns_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.campaigns
    ADD CONSTRAINT campaigns_pkey PRIMARY KEY (campaign_id);


--
-- Name: category_vocabulary category_vocabulary_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.category_vocabulary
    ADD CONSTRAINT category_vocabulary_pkey PRIMARY KEY (category_id);


--
-- Name: category_vocabulary category_vocabulary_slug_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.category_vocabulary
    ADD CONSTRAINT category_vocabulary_slug_key UNIQUE (slug);


--
-- Name: clients clients_email_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.clients
    ADD CONSTRAINT clients_email_key UNIQUE (email);


--
-- Name: clients clients_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.clients
    ADD CONSTRAINT clients_pkey PRIMARY KEY (client_id);


--
-- Name: content_activity_log content_activity_log_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.content_activity_log
    ADD CONSTRAINT content_activity_log_pkey PRIMARY KEY (id);


--
-- Name: content_assignments content_assignments_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.content_assignments
    ADD CONSTRAINT content_assignments_pkey PRIMARY KEY (assignment_id);


--
-- Name: content_docs content_docs_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.content_docs
    ADD CONSTRAINT content_docs_pkey PRIMARY KEY (doc_id);


--
-- Name: content_notes content_notes_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.content_notes
    ADD CONSTRAINT content_notes_pkey PRIMARY KEY (note_id);


--
-- Name: cron_executions cron_executions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.cron_executions
    ADD CONSTRAINT cron_executions_pkey PRIMARY KEY (id);


--
-- Name: cron_jobs cron_jobs_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.cron_jobs
    ADD CONSTRAINT cron_jobs_pkey PRIMARY KEY (cron_name);


--
-- Name: cron_runs cron_runs_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.cron_runs
    ADD CONSTRAINT cron_runs_pkey PRIMARY KEY (id);


--
-- Name: cron_runs cron_runs_site_id_cron_name_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.cron_runs
    ADD CONSTRAINT cron_runs_site_id_cron_name_key UNIQUE (site_id, cron_name);


--
-- Name: directus_access directus_access_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_access
    ADD CONSTRAINT directus_access_pkey PRIMARY KEY (id);


--
-- Name: directus_activity directus_activity_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_activity
    ADD CONSTRAINT directus_activity_pkey PRIMARY KEY (id);


--
-- Name: directus_collections directus_collections_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_collections
    ADD CONSTRAINT directus_collections_pkey PRIMARY KEY (collection);


--
-- Name: directus_comments directus_comments_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_comments
    ADD CONSTRAINT directus_comments_pkey PRIMARY KEY (id);


--
-- Name: directus_dashboards directus_dashboards_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_dashboards
    ADD CONSTRAINT directus_dashboards_pkey PRIMARY KEY (id);


--
-- Name: directus_extensions directus_extensions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_extensions
    ADD CONSTRAINT directus_extensions_pkey PRIMARY KEY (id);


--
-- Name: directus_fields directus_fields_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_fields
    ADD CONSTRAINT directus_fields_pkey PRIMARY KEY (id);


--
-- Name: directus_files directus_files_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_files
    ADD CONSTRAINT directus_files_pkey PRIMARY KEY (id);


--
-- Name: directus_flows directus_flows_operation_unique; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_flows
    ADD CONSTRAINT directus_flows_operation_unique UNIQUE (operation);


--
-- Name: directus_flows directus_flows_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_flows
    ADD CONSTRAINT directus_flows_pkey PRIMARY KEY (id);


--
-- Name: directus_folders directus_folders_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_folders
    ADD CONSTRAINT directus_folders_pkey PRIMARY KEY (id);


--
-- Name: directus_migrations directus_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_migrations
    ADD CONSTRAINT directus_migrations_pkey PRIMARY KEY (version);


--
-- Name: directus_notifications directus_notifications_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_notifications
    ADD CONSTRAINT directus_notifications_pkey PRIMARY KEY (id);


--
-- Name: directus_operations directus_operations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_operations
    ADD CONSTRAINT directus_operations_pkey PRIMARY KEY (id);


--
-- Name: directus_operations directus_operations_reject_unique; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_operations
    ADD CONSTRAINT directus_operations_reject_unique UNIQUE (reject);


--
-- Name: directus_operations directus_operations_resolve_unique; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_operations
    ADD CONSTRAINT directus_operations_resolve_unique UNIQUE (resolve);


--
-- Name: directus_panels directus_panels_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_panels
    ADD CONSTRAINT directus_panels_pkey PRIMARY KEY (id);


--
-- Name: directus_permissions directus_permissions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_permissions
    ADD CONSTRAINT directus_permissions_pkey PRIMARY KEY (id);


--
-- Name: directus_policies directus_policies_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_policies
    ADD CONSTRAINT directus_policies_pkey PRIMARY KEY (id);


--
-- Name: directus_presets directus_presets_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_presets
    ADD CONSTRAINT directus_presets_pkey PRIMARY KEY (id);


--
-- Name: directus_relations directus_relations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_relations
    ADD CONSTRAINT directus_relations_pkey PRIMARY KEY (id);


--
-- Name: directus_revisions directus_revisions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_revisions
    ADD CONSTRAINT directus_revisions_pkey PRIMARY KEY (id);


--
-- Name: directus_roles directus_roles_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_roles
    ADD CONSTRAINT directus_roles_pkey PRIMARY KEY (id);


--
-- Name: directus_sessions directus_sessions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_sessions
    ADD CONSTRAINT directus_sessions_pkey PRIMARY KEY (token);


--
-- Name: directus_settings directus_settings_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_settings
    ADD CONSTRAINT directus_settings_pkey PRIMARY KEY (id);


--
-- Name: directus_shares directus_shares_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_shares
    ADD CONSTRAINT directus_shares_pkey PRIMARY KEY (id);


--
-- Name: directus_translations directus_translations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_translations
    ADD CONSTRAINT directus_translations_pkey PRIMARY KEY (id);


--
-- Name: directus_users directus_users_email_unique; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_users
    ADD CONSTRAINT directus_users_email_unique UNIQUE (email);


--
-- Name: directus_users directus_users_external_identifier_unique; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_users
    ADD CONSTRAINT directus_users_external_identifier_unique UNIQUE (external_identifier);


--
-- Name: directus_users directus_users_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_users
    ADD CONSTRAINT directus_users_pkey PRIMARY KEY (id);


--
-- Name: directus_users directus_users_token_unique; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_users
    ADD CONSTRAINT directus_users_token_unique UNIQUE (token);


--
-- Name: directus_versions directus_versions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_versions
    ADD CONSTRAINT directus_versions_pkey PRIMARY KEY (id);


--
-- Name: directus_webhooks directus_webhooks_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_webhooks
    ADD CONSTRAINT directus_webhooks_pkey PRIMARY KEY (id);


--
-- Name: link_checks link_checks_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.link_checks
    ADD CONSTRAINT link_checks_pkey PRIMARY KEY (id);


--
-- Name: supplier_site_audit link_type_observed_enum; Type: CHECK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE public.supplier_site_audit
    ADD CONSTRAINT link_type_observed_enum CHECK (((link_type_observed IS NULL) OR ((link_type_observed)::text = ANY ((ARRAY['dofollow'::character varying, 'nofollow'::character varying, 'sponsored'::character varying])::text[])))) NOT VALID;


--
-- Name: managed_clients managed_clients_client_name_unique; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.managed_clients
    ADD CONSTRAINT managed_clients_client_name_unique UNIQUE (client_name);


--
-- Name: managed_clients managed_clients_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.managed_clients
    ADD CONSTRAINT managed_clients_pkey PRIMARY KEY (managed_client_id);


--
-- Name: order_assignments order_assignments_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_assignments
    ADD CONSTRAINT order_assignments_pkey PRIMARY KEY (assignment_id);


--
-- Name: order_attachments order_attachments_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_attachments
    ADD CONSTRAINT order_attachments_pkey PRIMARY KEY (id);


--
-- Name: order_cancellation_reasons order_cancellation_reasons_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_cancellation_reasons
    ADD CONSTRAINT order_cancellation_reasons_pkey PRIMARY KEY (id);


--
-- Name: order_messages order_messages_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_messages
    ADD CONSTRAINT order_messages_pkey PRIMARY KEY (id);


--
-- Name: order_status_log order_status_log_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_status_log
    ADD CONSTRAINT order_status_log_pkey PRIMARY KEY (id);


--
-- Name: order_tags order_tags_order_id_tag_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_tags
    ADD CONSTRAINT order_tags_order_id_tag_key UNIQUE (order_id, tag);


--
-- Name: order_tags order_tags_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_tags
    ADD CONSTRAINT order_tags_pkey PRIMARY KEY (id);


--
-- Name: order_triggers order_triggers_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_triggers
    ADD CONSTRAINT order_triggers_pkey PRIMARY KEY (trigger_id);


--
-- Name: orders orders_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.orders
    ADD CONSTRAINT orders_pkey PRIMARY KEY (order_id);


--
-- Name: supplier_site_audit outbound_links_observed_range; Type: CHECK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE public.supplier_site_audit
    ADD CONSTRAINT outbound_links_observed_range CHECK (((outbound_links_observed IS NULL) OR ((outbound_links_observed >= 0) AND (outbound_links_observed <= 1000)))) NOT VALID;


--
-- Name: products products_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.products
    ADD CONSTRAINT products_pkey PRIMARY KEY (product_id);


--
-- Name: products products_slug_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.products
    ADD CONSTRAINT products_slug_key UNIQUE (slug);


--
-- Name: role_permissions role_permissions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.role_permissions
    ADD CONSTRAINT role_permissions_pkey PRIMARY KEY (id);


--
-- Name: role_permissions role_permissions_role_resource_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.role_permissions
    ADD CONSTRAINT role_permissions_role_resource_key UNIQUE (role, resource);


--
-- Name: site_ai_categories site_ai_categories_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.site_ai_categories
    ADD CONSTRAINT site_ai_categories_pkey PRIMARY KEY (id);


--
-- Name: site_ai_categories site_ai_categories_site_id_rank_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.site_ai_categories
    ADD CONSTRAINT site_ai_categories_site_id_rank_key UNIQUE (site_id, rank);


--
-- Name: site_embeddings site_embeddings_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.site_embeddings
    ADD CONSTRAINT site_embeddings_pkey PRIMARY KEY (site_id);


--
-- Name: site_geo site_geo_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.site_geo
    ADD CONSTRAINT site_geo_pkey PRIMARY KEY (id);


--
-- Name: site_geo site_geo_site_id_country_code_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.site_geo
    ADD CONSTRAINT site_geo_site_id_country_code_key UNIQUE (site_id, country_code);


--
-- Name: site_keyword_rankings site_keyword_rankings_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.site_keyword_rankings
    ADD CONSTRAINT site_keyword_rankings_pkey PRIMARY KEY (site_id);


--
-- Name: site_products site_products_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.site_products
    ADD CONSTRAINT site_products_pkey PRIMARY KEY (site_id, product_id);


--
-- Name: sites sites_domain_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.sites
    ADD CONSTRAINT sites_domain_key UNIQUE (domain);


--
-- Name: sites sites_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.sites
    ADD CONSTRAINT sites_pkey PRIMARY KEY (site_id);


--
-- Name: supplier_ban_history supplier_ban_history_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_ban_history
    ADD CONSTRAINT supplier_ban_history_pkey PRIMARY KEY (id);


--
-- Name: supplier_invoices supplier_invoices_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_invoices
    ADD CONSTRAINT supplier_invoices_pkey PRIMARY KEY (id);


--
-- Name: supplier_ledger supplier_ledger_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_ledger
    ADD CONSTRAINT supplier_ledger_pkey PRIMARY KEY (id);


--
-- Name: supplier_order_broadcasts supplier_order_broadcasts_access_hash_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_order_broadcasts
    ADD CONSTRAINT supplier_order_broadcasts_access_hash_key UNIQUE (access_hash);


--
-- Name: supplier_order_broadcasts supplier_order_broadcasts_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_order_broadcasts
    ADD CONSTRAINT supplier_order_broadcasts_pkey PRIMARY KEY (id);


--
-- Name: supplier_penalties supplier_penalties_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_penalties
    ADD CONSTRAINT supplier_penalties_pkey PRIMARY KEY (id);


--
-- Name: supplier_site_audit supplier_site_audit_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_site_audit
    ADD CONSTRAINT supplier_site_audit_pkey PRIMARY KEY (id);


--
-- Name: supplier_site_credentials supplier_site_credentials_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_site_credentials
    ADD CONSTRAINT supplier_site_credentials_pkey PRIMARY KEY (supplier_site_id);


--
-- Name: supplier_site_meta_keys supplier_site_meta_keys_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_site_meta_keys
    ADD CONSTRAINT supplier_site_meta_keys_pkey PRIMARY KEY (meta_key);


--
-- Name: supplier_site_meta supplier_site_meta_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_site_meta
    ADD CONSTRAINT supplier_site_meta_pkey PRIMARY KEY (id);


--
-- Name: supplier_site_meta supplier_site_meta_supplier_site_id_meta_key_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_site_meta
    ADD CONSTRAINT supplier_site_meta_supplier_site_id_meta_key_key UNIQUE (supplier_site_id, meta_key);


--
-- Name: supplier_site_pricing supplier_site_pricing_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_site_pricing
    ADD CONSTRAINT supplier_site_pricing_pkey PRIMARY KEY (id);


--
-- Name: supplier_site_pricing supplier_site_pricing_supplier_site_id_service_type_niche_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_site_pricing
    ADD CONSTRAINT supplier_site_pricing_supplier_site_id_service_type_niche_key UNIQUE (supplier_site_id, service_type, niche);


--
-- Name: supplier_site_products supplier_site_products_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_site_products
    ADD CONSTRAINT supplier_site_products_pkey PRIMARY KEY (id);


--
-- Name: supplier_sites supplier_sites_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_sites
    ADD CONSTRAINT supplier_sites_pkey PRIMARY KEY (supplier_site_id);


--
-- Name: supplier_sites supplier_sites_supplier_id_site_id_link_type_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_sites
    ADD CONSTRAINT supplier_sites_supplier_id_site_id_link_type_key UNIQUE (supplier_id, site_id, link_type);


--
-- Name: suppliers suppliers_email_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.suppliers
    ADD CONSTRAINT suppliers_email_key UNIQUE (email);


--
-- Name: suppliers suppliers_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.suppliers
    ADD CONSTRAINT suppliers_pkey PRIMARY KEY (supplier_id);


--
-- Name: traffic_history traffic_history_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.traffic_history
    ADD CONSTRAINT traffic_history_pkey PRIMARY KEY (id);


--
-- Name: traffic_history traffic_history_site_id_metric_recorded_at_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.traffic_history
    ADD CONSTRAINT traffic_history_site_id_metric_recorded_at_key UNIQUE (site_id, metric, recorded_at);


--
-- Name: users users_email_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.users
    ADD CONSTRAINT users_email_key UNIQUE (email);


--
-- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.users
    ADD CONSTRAINT users_pkey PRIMARY KEY (user_id);


--
-- Name: supplier_site_audit word_count_observed_range; Type: CHECK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE public.supplier_site_audit
    ADD CONSTRAINT word_count_observed_range CHECK (((word_count_observed IS NULL) OR ((word_count_observed >= 0) AND (word_count_observed <= 100000)))) NOT VALID;


--
-- Name: writers writers_name_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.writers
    ADD CONSTRAINT writers_name_key UNIQUE (name);


--
-- Name: writers writers_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.writers
    ADD CONSTRAINT writers_pkey PRIMARY KEY (writer_id);


--
-- Name: campaigns_base_domain_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX campaigns_base_domain_idx ON public.campaigns USING btree (base_domain);


--
-- Name: campaigns_client_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX campaigns_client_idx ON public.campaigns USING btree (client_id);


--
-- Name: campaigns_status_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX campaigns_status_idx ON public.campaigns USING btree (status) WHERE ((status)::text = 'active'::text);


--
-- Name: category_vocabulary_active_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX category_vocabulary_active_idx ON public.category_vocabulary USING btree (sort_order, slug) WHERE (active = true);


--
-- Name: clients_account_manager_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX clients_account_manager_idx ON public.clients USING btree (account_manager_id);


--
-- Name: clients_client_type_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX clients_client_type_idx ON public.clients USING btree (client_type);


--
-- Name: content_activity_log_writer_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX content_activity_log_writer_idx ON public.content_activity_log USING btree (writer_id) WHERE (writer_id IS NOT NULL);


--
-- Name: content_assignments_due_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX content_assignments_due_idx ON public.content_assignments USING btree (due_at) WHERE (((status)::text = ANY ((ARRAY['assigned'::character varying, 'writing'::character varying, 'revision_requested'::character varying])::text[])) AND (delivered_at IS NULL));


--
-- Name: content_docs_repurpose_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX content_docs_repurpose_idx ON public.content_docs USING btree (repurpose_status) WHERE (repurpose_available = true);


--
-- Name: content_notes_doc_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX content_notes_doc_idx ON public.content_notes USING btree (doc_id, created_at DESC);


--
-- Name: directus_activity_timestamp_index; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX directus_activity_timestamp_index ON public.directus_activity USING btree ("timestamp");


--
-- Name: directus_revisions_parent_index; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX directus_revisions_parent_index ON public.directus_revisions USING btree (parent);


--
-- Name: idx_activity_action; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_activity_action ON public.activity_log USING btree (action);


--
-- Name: idx_activity_actor; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_activity_actor ON public.activity_log USING btree (actor);


--
-- Name: idx_activity_created; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_activity_created ON public.activity_log USING btree (created_at);


--
-- Name: idx_activity_entity; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_activity_entity ON public.activity_log USING btree (entity_type, entity_id);


--
-- Name: idx_api_keys_key_hash; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_api_keys_key_hash ON public.api_keys USING btree (key_hash);


--
-- Name: idx_api_keys_prefix; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_api_keys_prefix ON public.api_keys USING btree (key_prefix);


--
-- Name: idx_api_keys_user_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_api_keys_user_id ON public.api_keys USING btree (user_id);


--
-- Name: idx_api_request_log_created; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_api_request_log_created ON public.api_request_log USING btree (created_at);


--
-- Name: idx_api_request_log_key; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_api_request_log_key ON public.api_request_log USING btree (api_key_id);


--
-- Name: idx_api_request_log_path; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_api_request_log_path ON public.api_request_log USING btree (path);


--
-- Name: idx_api_request_log_user; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_api_request_log_user ON public.api_request_log USING btree (user_id);


--
-- Name: idx_assignments_current; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_assignments_current ON public.order_assignments USING btree (is_current) WHERE (is_current = true);


--
-- Name: idx_assignments_hash; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_assignments_hash ON public.order_assignments USING btree (vendor_hash);


--
-- Name: idx_assignments_order; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_assignments_order ON public.order_assignments USING btree (order_id);


--
-- Name: idx_assignments_supplier; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_assignments_supplier ON public.order_assignments USING btree (supplier_id);


--
-- Name: idx_atlas_bug_events_bug; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_atlas_bug_events_bug ON public.atlas_bug_events USING btree (bug_id, created_at);


--
-- Name: idx_atlas_bug_reports_reporter; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_atlas_bug_reports_reporter ON public.atlas_bug_reports USING btree (reporter_email);


--
-- Name: idx_atlas_bug_reports_status; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_atlas_bug_reports_status ON public.atlas_bug_reports USING btree (status, created_at DESC);


--
-- Name: idx_atlas_query_log_err; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_atlas_query_log_err ON public.atlas_query_log USING btree (ts DESC) WHERE (error IS NOT NULL);


--
-- Name: idx_atlas_query_log_table; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_atlas_query_log_table ON public.atlas_query_log USING btree (table_name) WHERE (table_name IS NOT NULL);


--
-- Name: idx_atlas_query_log_tool; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_atlas_query_log_tool ON public.atlas_query_log USING btree (tool);


--
-- Name: idx_atlas_query_log_ts; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_atlas_query_log_ts ON public.atlas_query_log USING btree (ts DESC);


--
-- Name: idx_atlas_user_roles_last_seen; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_atlas_user_roles_last_seen ON public.atlas_user_roles USING btree (last_seen_at DESC NULLS LAST);


--
-- Name: idx_atlas_user_roles_role; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_atlas_user_roles_role ON public.atlas_user_roles USING btree (role);


--
-- Name: idx_ban_supplier; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_ban_supplier ON public.supplier_ban_history USING btree (supplier_id);


--
-- Name: idx_broadcasts_order; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_broadcasts_order ON public.supplier_order_broadcasts USING btree (order_id);


--
-- Name: idx_broadcasts_pending; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_broadcasts_pending ON public.supplier_order_broadcasts USING btree (response) WHERE ((response)::text = 'pending'::text);


--
-- Name: idx_broadcasts_supplier; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_broadcasts_supplier ON public.supplier_order_broadcasts USING btree (supplier_id);


--
-- Name: idx_cancel_reasons_assignment; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_cancel_reasons_assignment ON public.order_cancellation_reasons USING btree (assignment_id);


--
-- Name: idx_cancel_reasons_order; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_cancel_reasons_order ON public.order_cancellation_reasons USING btree (order_id);


--
-- Name: idx_content_activity_created; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_content_activity_created ON public.content_activity_log USING btree (created_at);


--
-- Name: idx_content_activity_doc; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_content_activity_doc ON public.content_activity_log USING btree (doc_id);


--
-- Name: idx_content_activity_order; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_content_activity_order ON public.content_activity_log USING btree (order_id);


--
-- Name: idx_content_assignments_current; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_content_assignments_current ON public.content_assignments USING btree (is_current) WHERE (is_current = true);


--
-- Name: idx_content_assignments_doc; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_content_assignments_doc ON public.content_assignments USING btree (doc_id);


--
-- Name: idx_content_assignments_writer; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_content_assignments_writer ON public.content_assignments USING btree (writer_id);


--
-- Name: idx_content_docs_archived; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_content_docs_archived ON public.content_docs USING btree (archived);


--
-- Name: idx_content_docs_order; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_content_docs_order ON public.content_docs USING btree (order_id);


--
-- Name: idx_content_docs_status; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_content_docs_status ON public.content_docs USING btree (status);


--
-- Name: idx_cron_executions_cron; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_cron_executions_cron ON public.cron_executions USING btree (cron_name);


--
-- Name: idx_cron_executions_started; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_cron_executions_started ON public.cron_executions USING btree (started_at);


--
-- Name: idx_cron_executions_status; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_cron_executions_status ON public.cron_executions USING btree (status) WHERE ((status)::text = ANY ((ARRAY['failed'::character varying, 'timeout'::character varying])::text[]));


--
-- Name: idx_cron_runs_name; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_cron_runs_name ON public.cron_runs USING btree (cron_name, last_run_at);


--
-- Name: idx_invoices_supplier; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_invoices_supplier ON public.supplier_invoices USING btree (supplier_id);


--
-- Name: idx_ledger_order; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_ledger_order ON public.supplier_ledger USING btree (order_id);


--
-- Name: idx_ledger_settled; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_ledger_settled ON public.supplier_ledger USING btree (settled);


--
-- Name: idx_ledger_supplier; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_ledger_supplier ON public.supplier_ledger USING btree (supplier_id);


--
-- Name: idx_link_checks_date; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_link_checks_date ON public.link_checks USING btree (checked_at);


--
-- Name: idx_link_checks_order; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_link_checks_order ON public.link_checks USING btree (order_id);


--
-- Name: idx_managed_clients_client_domain; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_managed_clients_client_domain ON public.managed_clients USING btree (lower(client_domain)) WHERE (client_domain IS NOT NULL);


--
-- Name: idx_order_attachments_order; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_order_attachments_order ON public.order_attachments USING btree (order_id);


--
-- Name: idx_order_messages_order; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_order_messages_order ON public.order_messages USING btree (order_id);


--
-- Name: idx_order_tags_order; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_order_tags_order ON public.order_tags USING btree (order_id);


--
-- Name: idx_orders_client; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_orders_client ON public.orders USING btree (client_email);


--
-- Name: idx_orders_due; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_orders_due ON public.orders USING btree (due_at);


--
-- Name: idx_orders_managed_client_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_orders_managed_client_id ON public.orders USING btree (managed_client_id) WHERE (managed_client_id IS NOT NULL);


--
-- Name: idx_orders_site; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_orders_site ON public.orders USING btree (site_id);


--
-- Name: idx_orders_spp; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_orders_spp ON public.orders USING btree (spp_order_id);


--
-- Name: idx_orders_spp_client_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_orders_spp_client_id ON public.orders USING btree (spp_client_id) WHERE (spp_client_id IS NOT NULL);


--
-- Name: idx_orders_spp_service_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_orders_spp_service_id ON public.orders USING btree (spp_service_id) WHERE (spp_service_id IS NOT NULL);


--
-- Name: idx_orders_status; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_orders_status ON public.orders USING btree (status);


--
-- Name: idx_orders_status_due; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_orders_status_due ON public.orders USING btree (status, due_at);


--
-- Name: idx_role_permissions_role; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_role_permissions_role ON public.role_permissions USING btree (role);


--
-- Name: idx_sites_approved_at; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_sites_approved_at ON public.sites USING btree (approved_at) WHERE (approved_at IS NOT NULL);


--
-- Name: idx_sites_da; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_sites_da ON public.sites USING btree (da);


--
-- Name: idx_sites_dr; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_sites_dr ON public.sites USING btree (dr);


--
-- Name: idx_sites_on_the_list; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_sites_on_the_list ON public.sites USING btree (on_the_list);


--
-- Name: idx_sites_priority; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_sites_priority ON public.sites USING btree (priority);


--
-- Name: idx_sites_traffic; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_sites_traffic ON public.sites USING btree (traffic_total);


--
-- Name: idx_status_log_created; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_status_log_created ON public.order_status_log USING btree (created_at);


--
-- Name: idx_status_log_order; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_status_log_order ON public.order_status_log USING btree (order_id);


--
-- Name: idx_supplier_penalties_order; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_supplier_penalties_order ON public.supplier_penalties USING btree (order_id);


--
-- Name: idx_supplier_penalties_supplier; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_supplier_penalties_supplier ON public.supplier_penalties USING btree (supplier_id);


--
-- Name: idx_supplier_site_audit_site; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_supplier_site_audit_site ON public.supplier_site_audit USING btree (site_id);


--
-- Name: idx_supplier_site_pricing_type; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_supplier_site_pricing_type ON public.supplier_site_pricing USING btree (service_type, niche);


--
-- Name: idx_supplier_site_products_site; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_supplier_site_products_site ON public.supplier_site_products USING btree (supplier_site_id);


--
-- Name: idx_supplier_sites_locked; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_supplier_sites_locked ON public.supplier_sites USING btree (locked);


--
-- Name: idx_supplier_sites_site; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_supplier_sites_site ON public.supplier_sites USING btree (site_id);


--
-- Name: idx_supplier_sites_supplier; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_supplier_sites_supplier ON public.supplier_sites USING btree (supplier_id);


--
-- Name: idx_suppliers_auth_identity; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX idx_suppliers_auth_identity ON public.suppliers USING btree (auth_provider, auth_subject) WHERE (auth_subject IS NOT NULL);


--
-- Name: idx_suppliers_type; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_suppliers_type ON public.suppliers USING btree (supplier_type);


--
-- Name: idx_traffic_history_site_metric; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_traffic_history_site_metric ON public.traffic_history USING btree (site_id, metric, recorded_at DESC);


--
-- Name: idx_users_oauth; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_users_oauth ON public.users USING btree (oauth_provider, oauth_id);


--
-- Name: idx_users_role; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_users_role ON public.users USING btree (role);


--
-- Name: link_checks_assignment_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX link_checks_assignment_idx ON public.link_checks USING btree (assignment_id);


--
-- Name: order_assignments_sla_clock_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX order_assignments_sla_clock_idx ON public.order_assignments USING btree (content_handed_at, sla_threshold_hours) WHERE (((status)::text = ANY ((ARRAY['accepted'::character varying, 'working'::character varying])::text[])) AND (completed_at IS NULL));


--
-- Name: order_triggers_assignment_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX order_triggers_assignment_idx ON public.order_triggers USING btree (assignment_id);


--
-- Name: order_triggers_open_by_dept_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX order_triggers_open_by_dept_idx ON public.order_triggers USING btree (department, fired_at DESC) WHERE ((status)::text = 'open'::text);


--
-- Name: order_triggers_order_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX order_triggers_order_idx ON public.order_triggers USING btree (order_id);


--
-- Name: order_triggers_type_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX order_triggers_type_idx ON public.order_triggers USING btree (trigger_type);


--
-- Name: orders_campaign_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX orders_campaign_idx ON public.orders USING btree (campaign_id);


--
-- Name: orders_client_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX orders_client_idx ON public.orders USING btree (client_id);


--
-- Name: orders_guarantee_active_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX orders_guarantee_active_idx ON public.orders USING btree (guarantee_expires_at) WHERE (guarantee_expires_at IS NOT NULL);


--
-- Name: orders_replacement_for_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX orders_replacement_for_idx ON public.orders USING btree (replacement_for) WHERE (replacement_for IS NOT NULL);


--
-- Name: products_active_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX products_active_idx ON public.products USING btree (sort_order, slug) WHERE (active = true);


--
-- Name: site_geo_city_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX site_geo_city_idx ON public.site_geo USING btree (city) WHERE (city IS NOT NULL);


--
-- Name: site_geo_region_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX site_geo_region_idx ON public.site_geo USING btree (region) WHERE (region IS NOT NULL);


--
-- Name: site_products_product_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX site_products_product_idx ON public.site_products USING btree (product_id);


--
-- Name: sites_language_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX sites_language_idx ON public.sites USING btree (language) WHERE (language IS NOT NULL);


--
-- Name: sites_server_country_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX sites_server_country_idx ON public.sites USING btree (server_country) WHERE (server_country IS NOT NULL);


--
-- Name: supplier_ledger_penalty_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX supplier_ledger_penalty_idx ON public.supplier_ledger USING btree (penalty_id) WHERE (penalty_id IS NOT NULL);


--
-- Name: supplier_order_broadcasts_pending_expired_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX supplier_order_broadcasts_pending_expired_idx ON public.supplier_order_broadcasts USING btree (expires_at) WHERE ((response)::text = 'pending'::text);


--
-- Name: supplier_site_audit_latest_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX supplier_site_audit_latest_idx ON public.supplier_site_audit USING btree (site_id, last_audited_at DESC);


--
-- Name: supplier_site_products_niche_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX supplier_site_products_niche_idx ON public.supplier_site_products USING btree (niche) WHERE (niche IS NOT NULL);


--
-- Name: supplier_site_products_product_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX supplier_site_products_product_idx ON public.supplier_site_products USING btree (product_id) WHERE (product_id IS NOT NULL);


--
-- Name: supplier_site_products_unique_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX supplier_site_products_unique_idx ON public.supplier_site_products USING btree (supplier_site_id, product_id, COALESCE(niche, '__none__'::character varying));


--
-- Name: supplier_site_audit supplier_site_audit_set_actor; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER supplier_site_audit_set_actor BEFORE INSERT ON public.supplier_site_audit FOR EACH ROW EXECUTE FUNCTION public.set_supplier_site_audit_actor();


--
-- Name: ai_overviews ai_overviews_site_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.ai_overviews
    ADD CONSTRAINT ai_overviews_site_id_fkey FOREIGN KEY (site_id) REFERENCES public.sites(site_id) ON DELETE CASCADE;


--
-- Name: api_keys api_keys_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.api_keys
    ADD CONSTRAINT api_keys_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(user_id) ON DELETE CASCADE;


--
-- Name: api_request_log api_request_log_api_key_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.api_request_log
    ADD CONSTRAINT api_request_log_api_key_id_fkey FOREIGN KEY (api_key_id) REFERENCES public.api_keys(id) ON DELETE SET NULL;


--
-- Name: api_request_log api_request_log_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.api_request_log
    ADD CONSTRAINT api_request_log_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(user_id) ON DELETE SET NULL;


--
-- Name: atlas_bug_events atlas_bug_events_bug_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.atlas_bug_events
    ADD CONSTRAINT atlas_bug_events_bug_id_fkey FOREIGN KEY (bug_id) REFERENCES public.atlas_bug_reports(id) ON DELETE CASCADE;


--
-- Name: campaigns campaigns_client_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.campaigns
    ADD CONSTRAINT campaigns_client_id_fkey FOREIGN KEY (client_id) REFERENCES public.clients(client_id);


--
-- Name: clients clients_account_manager_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.clients
    ADD CONSTRAINT clients_account_manager_id_fkey FOREIGN KEY (account_manager_id) REFERENCES public.users(user_id);


--
-- Name: content_activity_log content_activity_log_doc_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.content_activity_log
    ADD CONSTRAINT content_activity_log_doc_id_fkey FOREIGN KEY (doc_id) REFERENCES public.content_docs(doc_id) ON DELETE SET NULL;


--
-- Name: content_activity_log content_activity_log_order_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.content_activity_log
    ADD CONSTRAINT content_activity_log_order_id_fkey FOREIGN KEY (order_id) REFERENCES public.orders(order_id) ON DELETE SET NULL;


--
-- Name: content_activity_log content_activity_log_writer_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.content_activity_log
    ADD CONSTRAINT content_activity_log_writer_id_fkey FOREIGN KEY (writer_id) REFERENCES public.writers(writer_id) ON DELETE SET NULL;


--
-- Name: content_assignments content_assignments_doc_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.content_assignments
    ADD CONSTRAINT content_assignments_doc_id_fkey FOREIGN KEY (doc_id) REFERENCES public.content_docs(doc_id) ON DELETE CASCADE;


--
-- Name: content_assignments content_assignments_reviewer_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.content_assignments
    ADD CONSTRAINT content_assignments_reviewer_user_id_fkey FOREIGN KEY (reviewer_user_id) REFERENCES public.users(user_id) ON DELETE SET NULL;


--
-- Name: content_assignments content_assignments_writer_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.content_assignments
    ADD CONSTRAINT content_assignments_writer_id_fkey FOREIGN KEY (writer_id) REFERENCES public.writers(writer_id);


--
-- Name: content_docs content_docs_order_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.content_docs
    ADD CONSTRAINT content_docs_order_id_fkey FOREIGN KEY (order_id) REFERENCES public.orders(order_id) ON DELETE CASCADE;


--
-- Name: content_docs content_docs_qa_approved_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.content_docs
    ADD CONSTRAINT content_docs_qa_approved_by_fkey FOREIGN KEY (qa_approved_by) REFERENCES public.users(user_id) ON DELETE SET NULL;


--
-- Name: content_notes content_notes_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.content_notes
    ADD CONSTRAINT content_notes_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(user_id);


--
-- Name: content_notes content_notes_doc_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.content_notes
    ADD CONSTRAINT content_notes_doc_id_fkey FOREIGN KEY (doc_id) REFERENCES public.content_docs(doc_id) ON DELETE CASCADE;


--
-- Name: cron_executions cron_executions_cron_name_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.cron_executions
    ADD CONSTRAINT cron_executions_cron_name_fkey FOREIGN KEY (cron_name) REFERENCES public.cron_jobs(cron_name) ON DELETE CASCADE;


--
-- Name: cron_runs cron_runs_site_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.cron_runs
    ADD CONSTRAINT cron_runs_site_id_fkey FOREIGN KEY (site_id) REFERENCES public.sites(site_id) ON DELETE CASCADE;


--
-- Name: directus_access directus_access_policy_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_access
    ADD CONSTRAINT directus_access_policy_foreign FOREIGN KEY (policy) REFERENCES public.directus_policies(id) ON DELETE CASCADE;


--
-- Name: directus_access directus_access_role_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_access
    ADD CONSTRAINT directus_access_role_foreign FOREIGN KEY (role) REFERENCES public.directus_roles(id) ON DELETE CASCADE;


--
-- Name: directus_access directus_access_user_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_access
    ADD CONSTRAINT directus_access_user_foreign FOREIGN KEY ("user") REFERENCES public.directus_users(id) ON DELETE CASCADE;


--
-- Name: directus_collections directus_collections_group_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_collections
    ADD CONSTRAINT directus_collections_group_foreign FOREIGN KEY ("group") REFERENCES public.directus_collections(collection);


--
-- Name: directus_comments directus_comments_user_created_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_comments
    ADD CONSTRAINT directus_comments_user_created_foreign FOREIGN KEY (user_created) REFERENCES public.directus_users(id) ON DELETE SET NULL;


--
-- Name: directus_comments directus_comments_user_updated_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_comments
    ADD CONSTRAINT directus_comments_user_updated_foreign FOREIGN KEY (user_updated) REFERENCES public.directus_users(id);


--
-- Name: directus_dashboards directus_dashboards_user_created_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_dashboards
    ADD CONSTRAINT directus_dashboards_user_created_foreign FOREIGN KEY (user_created) REFERENCES public.directus_users(id) ON DELETE SET NULL;


--
-- Name: directus_files directus_files_folder_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_files
    ADD CONSTRAINT directus_files_folder_foreign FOREIGN KEY (folder) REFERENCES public.directus_folders(id) ON DELETE SET NULL;


--
-- Name: directus_files directus_files_modified_by_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_files
    ADD CONSTRAINT directus_files_modified_by_foreign FOREIGN KEY (modified_by) REFERENCES public.directus_users(id);


--
-- Name: directus_files directus_files_uploaded_by_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_files
    ADD CONSTRAINT directus_files_uploaded_by_foreign FOREIGN KEY (uploaded_by) REFERENCES public.directus_users(id);


--
-- Name: directus_flows directus_flows_user_created_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_flows
    ADD CONSTRAINT directus_flows_user_created_foreign FOREIGN KEY (user_created) REFERENCES public.directus_users(id) ON DELETE SET NULL;


--
-- Name: directus_folders directus_folders_parent_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_folders
    ADD CONSTRAINT directus_folders_parent_foreign FOREIGN KEY (parent) REFERENCES public.directus_folders(id);


--
-- Name: directus_notifications directus_notifications_recipient_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_notifications
    ADD CONSTRAINT directus_notifications_recipient_foreign FOREIGN KEY (recipient) REFERENCES public.directus_users(id) ON DELETE CASCADE;


--
-- Name: directus_notifications directus_notifications_sender_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_notifications
    ADD CONSTRAINT directus_notifications_sender_foreign FOREIGN KEY (sender) REFERENCES public.directus_users(id);


--
-- Name: directus_operations directus_operations_flow_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_operations
    ADD CONSTRAINT directus_operations_flow_foreign FOREIGN KEY (flow) REFERENCES public.directus_flows(id) ON DELETE CASCADE;


--
-- Name: directus_operations directus_operations_reject_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_operations
    ADD CONSTRAINT directus_operations_reject_foreign FOREIGN KEY (reject) REFERENCES public.directus_operations(id);


--
-- Name: directus_operations directus_operations_resolve_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_operations
    ADD CONSTRAINT directus_operations_resolve_foreign FOREIGN KEY (resolve) REFERENCES public.directus_operations(id);


--
-- Name: directus_operations directus_operations_user_created_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_operations
    ADD CONSTRAINT directus_operations_user_created_foreign FOREIGN KEY (user_created) REFERENCES public.directus_users(id) ON DELETE SET NULL;


--
-- Name: directus_panels directus_panels_dashboard_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_panels
    ADD CONSTRAINT directus_panels_dashboard_foreign FOREIGN KEY (dashboard) REFERENCES public.directus_dashboards(id) ON DELETE CASCADE;


--
-- Name: directus_panels directus_panels_user_created_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_panels
    ADD CONSTRAINT directus_panels_user_created_foreign FOREIGN KEY (user_created) REFERENCES public.directus_users(id) ON DELETE SET NULL;


--
-- Name: directus_permissions directus_permissions_policy_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_permissions
    ADD CONSTRAINT directus_permissions_policy_foreign FOREIGN KEY (policy) REFERENCES public.directus_policies(id) ON DELETE CASCADE;


--
-- Name: directus_presets directus_presets_role_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_presets
    ADD CONSTRAINT directus_presets_role_foreign FOREIGN KEY (role) REFERENCES public.directus_roles(id) ON DELETE CASCADE;


--
-- Name: directus_presets directus_presets_user_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_presets
    ADD CONSTRAINT directus_presets_user_foreign FOREIGN KEY ("user") REFERENCES public.directus_users(id) ON DELETE CASCADE;


--
-- Name: directus_revisions directus_revisions_activity_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_revisions
    ADD CONSTRAINT directus_revisions_activity_foreign FOREIGN KEY (activity) REFERENCES public.directus_activity(id) ON DELETE CASCADE;


--
-- Name: directus_revisions directus_revisions_parent_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_revisions
    ADD CONSTRAINT directus_revisions_parent_foreign FOREIGN KEY (parent) REFERENCES public.directus_revisions(id);


--
-- Name: directus_revisions directus_revisions_version_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_revisions
    ADD CONSTRAINT directus_revisions_version_foreign FOREIGN KEY (version) REFERENCES public.directus_versions(id) ON DELETE CASCADE;


--
-- Name: directus_roles directus_roles_parent_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_roles
    ADD CONSTRAINT directus_roles_parent_foreign FOREIGN KEY (parent) REFERENCES public.directus_roles(id);


--
-- Name: directus_sessions directus_sessions_share_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_sessions
    ADD CONSTRAINT directus_sessions_share_foreign FOREIGN KEY (share) REFERENCES public.directus_shares(id) ON DELETE CASCADE;


--
-- Name: directus_sessions directus_sessions_user_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_sessions
    ADD CONSTRAINT directus_sessions_user_foreign FOREIGN KEY ("user") REFERENCES public.directus_users(id) ON DELETE CASCADE;


--
-- Name: directus_settings directus_settings_project_logo_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_settings
    ADD CONSTRAINT directus_settings_project_logo_foreign FOREIGN KEY (project_logo) REFERENCES public.directus_files(id);


--
-- Name: directus_settings directus_settings_public_background_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_settings
    ADD CONSTRAINT directus_settings_public_background_foreign FOREIGN KEY (public_background) REFERENCES public.directus_files(id);


--
-- Name: directus_settings directus_settings_public_favicon_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_settings
    ADD CONSTRAINT directus_settings_public_favicon_foreign FOREIGN KEY (public_favicon) REFERENCES public.directus_files(id);


--
-- Name: directus_settings directus_settings_public_foreground_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_settings
    ADD CONSTRAINT directus_settings_public_foreground_foreign FOREIGN KEY (public_foreground) REFERENCES public.directus_files(id);


--
-- Name: directus_settings directus_settings_public_registration_role_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_settings
    ADD CONSTRAINT directus_settings_public_registration_role_foreign FOREIGN KEY (public_registration_role) REFERENCES public.directus_roles(id) ON DELETE SET NULL;


--
-- Name: directus_settings directus_settings_storage_default_folder_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_settings
    ADD CONSTRAINT directus_settings_storage_default_folder_foreign FOREIGN KEY (storage_default_folder) REFERENCES public.directus_folders(id) ON DELETE SET NULL;


--
-- Name: directus_shares directus_shares_collection_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_shares
    ADD CONSTRAINT directus_shares_collection_foreign FOREIGN KEY (collection) REFERENCES public.directus_collections(collection) ON DELETE CASCADE;


--
-- Name: directus_shares directus_shares_role_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_shares
    ADD CONSTRAINT directus_shares_role_foreign FOREIGN KEY (role) REFERENCES public.directus_roles(id) ON DELETE CASCADE;


--
-- Name: directus_shares directus_shares_user_created_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_shares
    ADD CONSTRAINT directus_shares_user_created_foreign FOREIGN KEY (user_created) REFERENCES public.directus_users(id) ON DELETE SET NULL;


--
-- Name: directus_users directus_users_role_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_users
    ADD CONSTRAINT directus_users_role_foreign FOREIGN KEY (role) REFERENCES public.directus_roles(id) ON DELETE SET NULL;


--
-- Name: directus_versions directus_versions_collection_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_versions
    ADD CONSTRAINT directus_versions_collection_foreign FOREIGN KEY (collection) REFERENCES public.directus_collections(collection) ON DELETE CASCADE;


--
-- Name: directus_versions directus_versions_user_created_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_versions
    ADD CONSTRAINT directus_versions_user_created_foreign FOREIGN KEY (user_created) REFERENCES public.directus_users(id) ON DELETE SET NULL;


--
-- Name: directus_versions directus_versions_user_updated_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_versions
    ADD CONSTRAINT directus_versions_user_updated_foreign FOREIGN KEY (user_updated) REFERENCES public.directus_users(id);


--
-- Name: directus_webhooks directus_webhooks_migrated_flow_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.directus_webhooks
    ADD CONSTRAINT directus_webhooks_migrated_flow_foreign FOREIGN KEY (migrated_flow) REFERENCES public.directus_flows(id) ON DELETE SET NULL;


--
-- Name: link_checks link_checks_assignment_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.link_checks
    ADD CONSTRAINT link_checks_assignment_id_fkey FOREIGN KEY (assignment_id) REFERENCES public.order_assignments(assignment_id) ON DELETE SET NULL;


--
-- Name: link_checks link_checks_order_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.link_checks
    ADD CONSTRAINT link_checks_order_id_fkey FOREIGN KEY (order_id) REFERENCES public.orders(order_id) ON DELETE CASCADE;


--
-- Name: order_assignments order_assignments_invoice_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_assignments
    ADD CONSTRAINT order_assignments_invoice_id_fkey FOREIGN KEY (invoice_id) REFERENCES public.supplier_invoices(id);


--
-- Name: order_assignments order_assignments_order_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_assignments
    ADD CONSTRAINT order_assignments_order_id_fkey FOREIGN KEY (order_id) REFERENCES public.orders(order_id) ON DELETE CASCADE;


--
-- Name: order_assignments order_assignments_supplier_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_assignments
    ADD CONSTRAINT order_assignments_supplier_id_fkey FOREIGN KEY (supplier_id) REFERENCES public.suppliers(supplier_id);


--
-- Name: order_attachments order_attachments_assignment_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_attachments
    ADD CONSTRAINT order_attachments_assignment_id_fkey FOREIGN KEY (assignment_id) REFERENCES public.order_assignments(assignment_id);


--
-- Name: order_attachments order_attachments_order_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_attachments
    ADD CONSTRAINT order_attachments_order_id_fkey FOREIGN KEY (order_id) REFERENCES public.orders(order_id) ON DELETE CASCADE;


--
-- Name: order_attachments order_attachments_uploader_client_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_attachments
    ADD CONSTRAINT order_attachments_uploader_client_id_fkey FOREIGN KEY (uploader_client_id) REFERENCES public.clients(client_id);


--
-- Name: order_attachments order_attachments_uploader_supplier_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_attachments
    ADD CONSTRAINT order_attachments_uploader_supplier_id_fkey FOREIGN KEY (uploader_supplier_id) REFERENCES public.suppliers(supplier_id);


--
-- Name: order_attachments order_attachments_uploader_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_attachments
    ADD CONSTRAINT order_attachments_uploader_user_id_fkey FOREIGN KEY (uploader_user_id) REFERENCES public.users(user_id);


--
-- Name: order_cancellation_reasons order_cancellation_reasons_assignment_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_cancellation_reasons
    ADD CONSTRAINT order_cancellation_reasons_assignment_id_fkey FOREIGN KEY (assignment_id) REFERENCES public.order_assignments(assignment_id) ON DELETE SET NULL;


--
-- Name: order_cancellation_reasons order_cancellation_reasons_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_cancellation_reasons
    ADD CONSTRAINT order_cancellation_reasons_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(user_id) ON DELETE SET NULL;


--
-- Name: order_cancellation_reasons order_cancellation_reasons_order_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_cancellation_reasons
    ADD CONSTRAINT order_cancellation_reasons_order_id_fkey FOREIGN KEY (order_id) REFERENCES public.orders(order_id) ON DELETE CASCADE;


--
-- Name: order_messages order_messages_order_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_messages
    ADD CONSTRAINT order_messages_order_id_fkey FOREIGN KEY (order_id) REFERENCES public.orders(order_id) ON DELETE CASCADE;


--
-- Name: order_status_log order_status_log_assignment_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_status_log
    ADD CONSTRAINT order_status_log_assignment_id_fkey FOREIGN KEY (assignment_id) REFERENCES public.order_assignments(assignment_id) ON DELETE SET NULL;


--
-- Name: order_status_log order_status_log_order_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_status_log
    ADD CONSTRAINT order_status_log_order_id_fkey FOREIGN KEY (order_id) REFERENCES public.orders(order_id) ON DELETE CASCADE;


--
-- Name: order_status_log order_status_log_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_status_log
    ADD CONSTRAINT order_status_log_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(user_id) ON DELETE SET NULL;


--
-- Name: order_tags order_tags_order_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_tags
    ADD CONSTRAINT order_tags_order_id_fkey FOREIGN KEY (order_id) REFERENCES public.orders(order_id) ON DELETE CASCADE;


--
-- Name: order_triggers order_triggers_acknowledged_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_triggers
    ADD CONSTRAINT order_triggers_acknowledged_by_fkey FOREIGN KEY (acknowledged_by) REFERENCES public.users(user_id);


--
-- Name: order_triggers order_triggers_assignment_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_triggers
    ADD CONSTRAINT order_triggers_assignment_id_fkey FOREIGN KEY (assignment_id) REFERENCES public.order_assignments(assignment_id) ON DELETE SET NULL;


--
-- Name: order_triggers order_triggers_order_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.order_triggers
    ADD CONSTRAINT order_triggers_order_id_fkey FOREIGN KEY (order_id) REFERENCES public.orders(order_id) ON DELETE CASCADE;


--
-- Name: orders orders_campaign_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.orders
    ADD CONSTRAINT orders_campaign_id_fkey FOREIGN KEY (campaign_id) REFERENCES public.campaigns(campaign_id);


--
-- Name: orders orders_client_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.orders
    ADD CONSTRAINT orders_client_id_fkey FOREIGN KEY (client_id) REFERENCES public.clients(client_id);


--
-- Name: orders orders_managed_client_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.orders
    ADD CONSTRAINT orders_managed_client_id_fkey FOREIGN KEY (managed_client_id) REFERENCES public.managed_clients(managed_client_id);


--
-- Name: orders orders_replacement_for_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.orders
    ADD CONSTRAINT orders_replacement_for_fkey FOREIGN KEY (replacement_for) REFERENCES public.orders(order_id);


--
-- Name: orders orders_site_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.orders
    ADD CONSTRAINT orders_site_id_fkey FOREIGN KEY (site_id) REFERENCES public.sites(site_id);


--
-- Name: site_ai_categories site_ai_categories_site_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.site_ai_categories
    ADD CONSTRAINT site_ai_categories_site_id_fkey FOREIGN KEY (site_id) REFERENCES public.sites(site_id) ON DELETE CASCADE;


--
-- Name: site_embeddings site_embeddings_site_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.site_embeddings
    ADD CONSTRAINT site_embeddings_site_id_fkey FOREIGN KEY (site_id) REFERENCES public.sites(site_id) ON DELETE CASCADE;


--
-- Name: site_geo site_geo_site_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.site_geo
    ADD CONSTRAINT site_geo_site_id_fkey FOREIGN KEY (site_id) REFERENCES public.sites(site_id) ON DELETE CASCADE;


--
-- Name: site_keyword_rankings site_keyword_rankings_site_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.site_keyword_rankings
    ADD CONSTRAINT site_keyword_rankings_site_id_fkey FOREIGN KEY (site_id) REFERENCES public.sites(site_id) ON DELETE CASCADE;


--
-- Name: site_products site_products_product_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.site_products
    ADD CONSTRAINT site_products_product_id_fkey FOREIGN KEY (product_id) REFERENCES public.products(product_id) ON DELETE CASCADE;


--
-- Name: site_products site_products_site_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.site_products
    ADD CONSTRAINT site_products_site_id_fkey FOREIGN KEY (site_id) REFERENCES public.sites(site_id) ON DELETE CASCADE;


--
-- Name: supplier_ban_history supplier_ban_history_supplier_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_ban_history
    ADD CONSTRAINT supplier_ban_history_supplier_id_fkey FOREIGN KEY (supplier_id) REFERENCES public.suppliers(supplier_id);


--
-- Name: supplier_invoices supplier_invoices_processed_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_invoices
    ADD CONSTRAINT supplier_invoices_processed_by_fkey FOREIGN KEY (processed_by) REFERENCES public.users(user_id);


--
-- Name: supplier_invoices supplier_invoices_supplier_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_invoices
    ADD CONSTRAINT supplier_invoices_supplier_id_fkey FOREIGN KEY (supplier_id) REFERENCES public.suppliers(supplier_id);


--
-- Name: supplier_ledger supplier_ledger_order_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_ledger
    ADD CONSTRAINT supplier_ledger_order_id_fkey FOREIGN KEY (order_id) REFERENCES public.orders(order_id);


--
-- Name: supplier_ledger supplier_ledger_penalty_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_ledger
    ADD CONSTRAINT supplier_ledger_penalty_id_fkey FOREIGN KEY (penalty_id) REFERENCES public.supplier_penalties(id) ON DELETE SET NULL;


--
-- Name: supplier_ledger supplier_ledger_supplier_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_ledger
    ADD CONSTRAINT supplier_ledger_supplier_id_fkey FOREIGN KEY (supplier_id) REFERENCES public.suppliers(supplier_id);


--
-- Name: supplier_order_broadcasts supplier_order_broadcasts_order_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_order_broadcasts
    ADD CONSTRAINT supplier_order_broadcasts_order_id_fkey FOREIGN KEY (order_id) REFERENCES public.orders(order_id) ON DELETE CASCADE;


--
-- Name: supplier_order_broadcasts supplier_order_broadcasts_supplier_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_order_broadcasts
    ADD CONSTRAINT supplier_order_broadcasts_supplier_id_fkey FOREIGN KEY (supplier_id) REFERENCES public.suppliers(supplier_id) ON DELETE CASCADE;


--
-- Name: supplier_penalties supplier_penalties_assignment_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_penalties
    ADD CONSTRAINT supplier_penalties_assignment_id_fkey FOREIGN KEY (assignment_id) REFERENCES public.order_assignments(assignment_id);


--
-- Name: supplier_penalties supplier_penalties_order_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_penalties
    ADD CONSTRAINT supplier_penalties_order_id_fkey FOREIGN KEY (order_id) REFERENCES public.orders(order_id);


--
-- Name: supplier_penalties supplier_penalties_supplier_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_penalties
    ADD CONSTRAINT supplier_penalties_supplier_id_fkey FOREIGN KEY (supplier_id) REFERENCES public.suppliers(supplier_id);


--
-- Name: supplier_site_audit supplier_site_audit_site_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_site_audit
    ADD CONSTRAINT supplier_site_audit_site_id_fkey FOREIGN KEY (site_id) REFERENCES public.sites(site_id) ON DELETE CASCADE;


--
-- Name: supplier_site_credentials supplier_site_credentials_supplier_site_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_site_credentials
    ADD CONSTRAINT supplier_site_credentials_supplier_site_id_fkey FOREIGN KEY (supplier_site_id) REFERENCES public.supplier_sites(supplier_site_id) ON DELETE CASCADE;


--
-- Name: supplier_site_meta supplier_site_meta_supplier_site_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_site_meta
    ADD CONSTRAINT supplier_site_meta_supplier_site_id_fkey FOREIGN KEY (supplier_site_id) REFERENCES public.supplier_sites(supplier_site_id) ON DELETE CASCADE;


--
-- Name: supplier_site_pricing supplier_site_pricing_supplier_site_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_site_pricing
    ADD CONSTRAINT supplier_site_pricing_supplier_site_id_fkey FOREIGN KEY (supplier_site_id) REFERENCES public.supplier_sites(supplier_site_id) ON DELETE CASCADE;


--
-- Name: supplier_site_products supplier_site_products_product_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_site_products
    ADD CONSTRAINT supplier_site_products_product_id_fkey FOREIGN KEY (product_id) REFERENCES public.products(product_id) ON DELETE SET NULL;


--
-- Name: supplier_site_products supplier_site_products_supplier_site_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_site_products
    ADD CONSTRAINT supplier_site_products_supplier_site_id_fkey FOREIGN KEY (supplier_site_id) REFERENCES public.supplier_sites(supplier_site_id) ON DELETE CASCADE;


--
-- Name: supplier_sites supplier_sites_site_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_sites
    ADD CONSTRAINT supplier_sites_site_id_fkey FOREIGN KEY (site_id) REFERENCES public.sites(site_id) ON DELETE CASCADE;


--
-- Name: supplier_sites supplier_sites_supplier_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.supplier_sites
    ADD CONSTRAINT supplier_sites_supplier_id_fkey FOREIGN KEY (supplier_id) REFERENCES public.suppliers(supplier_id) ON DELETE CASCADE;


--
-- Name: traffic_history traffic_history_site_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.traffic_history
    ADD CONSTRAINT traffic_history_site_id_fkey FOREIGN KEY (site_id) REFERENCES public.sites(site_id) ON DELETE CASCADE;


--
-- Name: users users_supplier_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.users
    ADD CONSTRAINT users_supplier_id_fkey FOREIGN KEY (supplier_id) REFERENCES public.suppliers(supplier_id);


--
-- Name: users users_writer_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.users
    ADD CONSTRAINT users_writer_id_fkey FOREIGN KEY (writer_id) REFERENCES public.writers(writer_id);


--
--

\unrestrict JB82uK0VLDvnlLfNV5slgltXKvuSbMlatGwnho8h5H6tIyux4rud7W97E3Ke9mH

