towerops/lib/towerops/gaiia/network_site.ex
Graham McIntire 3cd2303b6f
add gaiia integration stages 1-2: sync engine and entity mapping UI
Stage 1 - Foundation:
- GraphQL client with Relay cursor pagination and rate limiting
- 4 Ecto schemas: accounts, network sites, inventory items, billing subscriptions
- Gaiia context with upsert operations preserving user mappings
- Sync module orchestrating full organization sync
- Oban cron worker running every 15 minutes
- Migration creating 4 tables with org-scoped unique constraints

Stage 2 - Entity Mapping:
- GaiiaMappingLive with two tabs (Network Sites / Inventory Items)
- Match suggestion engine (IP/name matching with confidence levels)
- Inline search and link/unlink UI following Preseem devices pattern
- Filter by all/mapped/unmapped with URL-driven state
- Amber suggestion badges for potential matches
2026-02-13 10:31:32 -06:00

48 lines
1.2 KiB
Elixir

defmodule Towerops.Gaiia.NetworkSite do
@moduledoc """
Schema for network sites (towers/POPs) synced from Gaiia API.
"""
use Ecto.Schema
import Ecto.Changeset
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "gaiia_network_sites" do
field :gaiia_id, :string
field :name, :string
field :address, :map
field :ip_blocks, {:array, :string}, default: []
field :account_count, :integer
field :total_mrr, :decimal
field :raw_data, :map
belongs_to :organization, Towerops.Organizations.Organization
belongs_to :site, Towerops.Sites.Site
timestamps(type: :utc_datetime)
end
def changeset(network_site, attrs) do
network_site
|> cast(attrs, [
:organization_id,
:gaiia_id,
:name,
:address,
:ip_blocks,
:account_count,
:total_mrr,
:site_id,
:raw_data
])
|> validate_required([:organization_id, :gaiia_id])
|> unique_constraint([:organization_id, :gaiia_id],
error_key: :gaiia_id,
message: "has already been taken"
)
|> foreign_key_constraint(:organization_id)
|> foreign_key_constraint(:site_id)
end
end