← back to Patternbank Archive

sql/001_schema.sql

108 lines

-- patternbank_archive schema
-- Source: https://patternbank.com (commercial pattern marketplace)
-- Internal-use only. See LICENSE_NOTES.md for compliance record.
-- Do NOT expose any of this data on a public route.

CREATE EXTENSION IF NOT EXISTS pg_trgm;

CREATE TABLE IF NOT EXISTS designers (
  designer_slug           TEXT PRIMARY KEY,             -- patternbank.com/<slug>
  display_name            TEXT,
  profile_url             TEXT,
  bio                     TEXT,
  country                 TEXT,
  pattern_count_remote    INT,                          -- self-reported on profile
  raw_payload             JSONB,
  ingested_at             TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at              TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS patterns (
  pattern_id              TEXT PRIMARY KEY,             -- patternbank's own id from URL
  slug                    TEXT NOT NULL,
  title                   TEXT,
  designer_slug           TEXT REFERENCES designers(designer_slug),
  designer_display_name   TEXT,
  source_url              TEXT NOT NULL,
  category                TEXT,                         -- e.g. floral, abstract, animal
  subcategory             TEXT,
  tags                    TEXT[],
  colors_hex              TEXT[],                       -- swatches surfaced on PDP
  description             TEXT,
  license_label           TEXT,                         -- "Premium", "Studio", "Free", etc.
  price_usd               NUMERIC,
  is_seamless             BOOLEAN,
  added_at_remote         TIMESTAMPTZ,
  -- compliance / provenance
  source                  TEXT NOT NULL DEFAULT 'patternbank-headless',
  owned_by_steve          BOOLEAN NOT NULL DEFAULT FALSE,
  image_license           TEXT NOT NULL DEFAULT 'unknown_third_party',
  ok_for_commercial_use   BOOLEAN NOT NULL DEFAULT FALSE,
  ok_for_ai_training      BOOLEAN NOT NULL DEFAULT FALSE,   -- robots Content-Signal=ai-train:no
  -- forensic
  raw_payload             JSONB NOT NULL,
  ingested_at             TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at              TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  ingest_run_id           TEXT
);

CREATE INDEX IF NOT EXISTS idx_patterns_designer    ON patterns (designer_slug);
CREATE INDEX IF NOT EXISTS idx_patterns_category    ON patterns (category);
CREATE INDEX IF NOT EXISTS idx_patterns_tags_gin    ON patterns USING GIN (tags);
CREATE INDEX IF NOT EXISTS idx_patterns_colors_gin  ON patterns USING GIN (colors_hex);
CREATE INDEX IF NOT EXISTS idx_patterns_title_trgm  ON patterns USING GIN (title gin_trgm_ops);

CREATE TABLE IF NOT EXISTS pattern_images (
  image_id                BIGSERIAL PRIMARY KEY,
  pattern_id              TEXT NOT NULL REFERENCES patterns(pattern_id) ON DELETE CASCADE,
  position                INT,                          -- 0 = primary tile, 1+ = applied/sample views
  remote_url              TEXT NOT NULL,
  local_path              TEXT,                         -- data/images/<id>/<n>.jpg if downloaded
  width                   INT,
  height                  INT,
  bytes                   BIGINT,
  sha256                  TEXT,                         -- dedupe + integrity
  content_type            TEXT,
  is_thumbnail            BOOLEAN DEFAULT FALSE,
  ingested_at             TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_images_pattern ON pattern_images (pattern_id);
CREATE INDEX IF NOT EXISTS idx_images_sha     ON pattern_images (sha256);

CREATE TABLE IF NOT EXISTS categories (
  category_slug           TEXT PRIMARY KEY,
  parent_slug             TEXT REFERENCES categories(category_slug),
  display_name            TEXT,
  url                     TEXT,
  pattern_count_remote    INT,
  ingested_at             TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS ingest_runs (
  run_id                  TEXT PRIMARY KEY,
  started_at              TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  finished_at             TIMESTAMPTZ,
  mode                    TEXT,                         -- 'discover' | 'pdp' | 'images'
  pages_fetched           INT NOT NULL DEFAULT 0,
  patterns_seen           INT NOT NULL DEFAULT 0,
  patterns_upserted       INT NOT NULL DEFAULT 0,
  images_upserted         INT NOT NULL DEFAULT 0,
  images_downloaded       INT NOT NULL DEFAULT 0,
  bytes_downloaded        BIGINT NOT NULL DEFAULT 0,
  errors                  INT NOT NULL DEFAULT 0,
  notes                   TEXT
);

CREATE TABLE IF NOT EXISTS crawl_queue (
  url                     TEXT PRIMARY KEY,
  kind                    TEXT NOT NULL,                -- 'list' | 'pdp' | 'image'
  priority                INT NOT NULL DEFAULT 100,
  attempts                INT NOT NULL DEFAULT 0,
  last_attempt_at         TIMESTAMPTZ,
  last_status             INT,
  last_error              TEXT,
  done                    BOOLEAN NOT NULL DEFAULT FALSE,
  enqueued_at             TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_queue_pending ON crawl_queue (kind, done, priority) WHERE done = FALSE;