towerops/lib/towerops/integrations/integration.ex
Graham McIntie db40a3e5b7 feat: show human-readable sync status messages for integrations
- Added last_sync_message field to integrations schema
- Gaiia sync: shows item counts on success, friendly error on failure
- Preseem sync: shows AP/device counts on success, friendly error on failure
- Error messages translated from technical errors to user-friendly text
- Message displayed on integration card next to status badge
- Exposed in REST API response
2026-02-14 14:11:02 -06:00

56 lines
1.6 KiB
Elixir

defmodule Towerops.Integrations.Integration do
@moduledoc """
Schema for per-organization third-party integrations.
Each organization can have one integration per provider. Credentials
are encrypted at rest using Cloak AES-256-GCM.
"""
use Ecto.Schema
import Ecto.Changeset
alias Towerops.Encrypted
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
@valid_providers ~w(preseem gaiia pagerduty netbox)
@valid_sync_statuses ~w(never success partial failed)
schema "integrations" do
field :provider, :string
field :enabled, :boolean, default: false
field :credentials, Encrypted.Map
field :sync_interval_minutes, :integer, default: 10
field :last_synced_at, :utc_datetime
field :last_sync_status, :string, default: "never"
field :last_sync_message, :string
belongs_to :organization, Towerops.Organizations.Organization
timestamps(type: :utc_datetime)
end
def changeset(integration, attrs) do
integration
|> cast(attrs, [
:organization_id,
:provider,
:enabled,
:credentials,
:sync_interval_minutes,
:last_synced_at,
:last_sync_status,
:last_sync_message
])
|> validate_required([:organization_id, :provider])
|> validate_inclusion(:provider, @valid_providers)
|> validate_inclusion(:last_sync_status, @valid_sync_statuses)
|> validate_number(:sync_interval_minutes, greater_than: 0)
|> unique_constraint([:organization_id, :provider],
error_key: :provider,
message: "has already been taken"
)
|> foreign_key_constraint(:organization_id)
end
end