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
48 lines
1.2 KiB
Elixir
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
|