Pixelex.Sites (Pixelex v0.1.0)

Copy Markdown View Source

Per-tenant configuration, cached in ETS.

A site owns its domain, the event names a browser is allowed to send, its ad-platform credentials, and its retention window. All of it lives in pixelex_sites rather than in application config, because a library cannot know a tenant's event names at compile time — that was the one thing wrong with every version of this code that came before it.

Why the cache is not optional

POST /px/e runs the allowlist check on every single event. Reading a row per event would put the busiest query in the system on the request path of the cheapest operation in it. Sites change rarely, so a read-through ETS cache with a TTL is the whole design; refresh/1 invalidates on write.

The allowlist

An unauthenticated endpoint that writes whatever string it is handed is a table anyone on the internet can fill, and the damage is not the disk — it is that every number on the dashboard becomes a claim that cannot be defended. So the server decides what exists. An unknown name is dropped silently and the endpoint answers 204 either way: telling a scanner which names are real is free help.

allow_any_event: true turns the allowlist off, for sites that only ever emit events from trusted server-side code.

Summary

Functions

May this site record an event with this name from a browser?

A site declared in application config rather than the database.

Fetch a site, from cache when warm.

Create or update a site. Invalidates the cache.

Drop a site from the cache, so the next read reloads it.

Types

t()

@type t() :: %Pixelex.Sites{
  allow_any_event: term(),
  allowed_events: term(),
  destinations: term(),
  domain: term(),
  id: term(),
  retention_days: term()
}

Functions

allowed_event?(arg1, name)

@spec allowed_event?(t() | nil, String.t()) :: boolean()

May this site record an event with this name from a browser?

configured(site_id)

@spec configured(String.t()) :: t() | nil

A site declared in application config rather than the database.

config :pixelex,
  sites: %{
    "shop" => [
      domain: "shop.test",
      allowed_events: ~w(book_click call_click order_started)
    ]
  }

Most applications have exactly one site, and making them run a migration, write an admin screen and insert a row before a single page view is counted is a bad first five minutes. Config is checked first: it is explicit, it costs no query, and it is version-controlled alongside the data-track attributes whose names it lists.

The table is for the multi-tenant case, where sites are created by users at runtime and cannot be known when the release is built.

get(site_id)

@spec get(String.t()) :: t() | nil

Fetch a site, from cache when warm.

Returns nil for an unknown id — the caller drops the event. An auto-provisioning default would mean a typo in a tracking snippet silently creates a tenant.

put(attrs)

@spec put(map()) :: {:ok, t()} | {:error, term()}

Create or update a site. Invalidates the cache.

refresh(site_id)

@spec refresh(String.t()) :: :ok

Drop a site from the cache, so the next read reloads it.