defmodule Towerops.Sites do @moduledoc """ The Sites context. """ import Ecto.Query alias Towerops.Agents.AgentAssignment alias Towerops.Devices.Device alias Towerops.Repo alias Towerops.Sites.Site @doc """ Returns the list of sites for an organization. """ def list_organization_sites(organization_id) do Repo.all( from(s in Site, where: s.organization_id == ^organization_id, order_by: [asc: s.name], preload: [:parent_site]) ) end @doc """ Returns the count of sites for an organization. """ def count_organization_sites(organization_id) do Repo.aggregate( from(s in Site, where: s.organization_id == ^organization_id), :count ) end @doc """ Returns the list of root sites (sites without a parent) for an organization. """ def list_root_sites(organization_id) do Repo.all( from(s in Site, where: s.organization_id == ^organization_id, where: is_nil(s.parent_site_id), order_by: [asc: s.name] ) ) end @doc """ Returns the list of child sites for a parent site. """ def list_child_sites(parent_site_id) do Repo.all(from(s in Site, where: s.parent_site_id == ^parent_site_id, order_by: [asc: s.name])) end @doc """ Gets a single site. """ def get_site!(id) do Site |> Repo.get!(id) |> Repo.preload([:parent_site, :child_sites, :device]) end @doc """ Gets a single site belonging to an organization. """ def get_organization_site!(organization_id, site_id) do Repo.one!( from(s in Site, where: s.id == ^site_id, where: s.organization_id == ^organization_id, preload: [:parent_site, :child_sites, :device] ) ) end @doc """ Creates a site. """ def create_site(attrs) do %Site{} |> Site.changeset(attrs) |> Repo.insert() end @doc """ Updates a site. """ def update_site(%Site{} = site, attrs) do site |> Site.changeset(attrs) |> Repo.update() end @doc """ Deletes a site. """ def delete_site(%Site{} = site) do Repo.delete(site) end @doc """ Returns an `%Ecto.Changeset{}` for tracking site changes. """ def change_site(%Site{} = site, attrs \\ %{}) do Site.changeset(site, attrs) end @doc """ Builds a hierarchical tree structure of sites. """ def build_site_tree(organization_id) do sites = list_organization_sites(organization_id) root_sites = Enum.filter(sites, &is_nil(&1.parent_site_id)) Enum.map(root_sites, fn root -> build_tree_node(root, sites) end) end defp build_tree_node(site, all_sites) do children = all_sites |> Enum.filter(&(&1.parent_site_id == site.id)) |> Enum.map(&build_tree_node(&1, all_sites)) %{site: site, children: children} end ## Bulk Configuration Updates @doc """ Applies site's SNMP configuration to all devices at the site. This updates all devices records to use the site's SNMP version and community string, overwriting any existing device-level configurations. Returns the number of device records updated. """ def apply_snmp_config_to_all_equipment(site_id) do site = get_site!(site_id) Repo.update_all(from(e in Device, where: e.site_id == ^site_id), set: [ snmp_version: site.snmp_version, snmp_community: site.snmp_community, updated_at: DateTime.truncate(DateTime.utc_now(), :second) ] ) end @doc """ Applies site's agent to all devices at the site. This creates/updates agent assignments for all devices at the site to use the site's agent, overwriting any existing device-level assignments. Returns the number of device records assigned. """ def apply_agent_to_all_equipment(site_id) do site = Repo.get!(Site, site_id) # device IDs for this site device_ids = Repo.all(from(e in Device, where: e.site_id == ^site_id, select: e.id)) if site.agent_token_id do # Delete existing assignments Repo.delete_all(from(a in AgentAssignment, where: a.device_id in ^device_ids)) # Create new assignments now = DateTime.truncate(DateTime.utc_now(), :second) assignments = Enum.map(device_ids, fn device_id -> %{ device_id: device_id, agent_token_id: site.agent_token_id, inserted_at: now, updated_at: now } end) {count, _} = Repo.insert_all(AgentAssignment, assignments) {count, nil} else # No agent set, delete all assignments (they will inherit from org or use cloud) Repo.delete_all(from(a in AgentAssignment, where: a.device_id in ^device_ids)) end end end