defmodule ToweropsWeb.GraphQL.Schema do @moduledoc """ GraphQL schema for the TowerOps API. Provides a complete GraphQL interface for managing devices, sites, alerts, agents, organization settings, members, integrations, and activity feeds. Queries that require an organization scope accept an optional `organization_id` argument for mobile authentication. API token users can omit it (org comes from token). """ use Absinthe.Schema alias ToweropsWeb.GraphQL.Middleware.OrganizationScope import_types(ToweropsWeb.GraphQL.Types.Common) import_types(ToweropsWeb.GraphQL.Types.Device) import_types(ToweropsWeb.GraphQL.Types.Site) import_types(ToweropsWeb.GraphQL.Types.Alert) import_types(ToweropsWeb.GraphQL.Types.Agent) import_types(ToweropsWeb.GraphQL.Types.Organization) import_types(ToweropsWeb.GraphQL.Types.Member) import_types(ToweropsWeb.GraphQL.Types.Integration) import_types(ToweropsWeb.GraphQL.Types.Activity) import_types(ToweropsWeb.GraphQL.Types.Schedule) import_types(ToweropsWeb.GraphQL.Types.EscalationPolicy) import_types(ToweropsWeb.GraphQL.Types.MaintenanceWindow) import_types(ToweropsWeb.GraphQL.Types.Check) import_types(ToweropsWeb.GraphQL.Types.TimeSeries) query do # User's organizations (no org scope needed — for mobile org picker) field :my_organizations, list_of(:organization) do resolve(&ToweropsWeb.GraphQL.Resolvers.Organization.list_mine/3) end # Devices field :devices, list_of(:device) do arg(:organization_id, :id) arg(:site_id, :id) arg(:status, :string) arg(:limit, :integer, default_value: 100) middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.Device.list/3) end field :device, :device do arg(:organization_id, :id) arg(:id, non_null(:id)) middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.Device.get/3) end # Sites field :sites, list_of(:site) do arg(:organization_id, :id) middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.Site.list/3) end field :site, :site do arg(:organization_id, :id) arg(:id, non_null(:id)) middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.Site.get/3) end # Alerts field :alerts, list_of(:alert) do arg(:organization_id, :id) arg(:status, :string) arg(:device_id, :id) arg(:limit, :integer, default_value: 100) middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.Alert.list/3) end field :alert, :alert do arg(:organization_id, :id) arg(:id, non_null(:id)) middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.Alert.get/3) end # Service Checks field :checks, list_of(:check) do arg(:organization_id, :id) arg(:device_id, :id) arg(:check_type, :string) middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.Check.list/3) end field :check, :check do arg(:organization_id, :id) arg(:id, non_null(:id)) middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.Check.get/3) end # Agents field :agents, list_of(:agent) do arg(:organization_id, :id) middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.Agent.list/3) end field :agent, :agent do arg(:organization_id, :id) arg(:id, non_null(:id)) middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.Agent.get/3) end # Organization field :organization, :organization do arg(:organization_id, :id) middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.Organization.get/3) end # Members field :members, list_of(:member) do arg(:organization_id, :id) middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.Member.list/3) end # Integrations field :integrations, list_of(:integration) do arg(:organization_id, :id) middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.Integration.list/3) end # Activity feed field :activity, list_of(:activity_item) do arg(:organization_id, :id) arg(:limit, :integer, default_value: 50) arg(:type, :string) middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.Activity.list/3) end # Device metrics field :device_metrics, list_of(:metric_point) do arg(:organization_id, :id) arg(:device_id, non_null(:id)) arg(:sensor_type, :string) arg(:time_range, :string, default_value: "24h") middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.Device.metrics/3) end # Device interfaces field :device_interfaces, list_of(:interface) do arg(:organization_id, :id) arg(:device_id, non_null(:id)) middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.Device.interfaces/3) end # Time-series queries field :device_sensors, list_of(:sensor) do arg(:organization_id, :id) arg(:device_id, non_null(:id)) middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.TimeSeries.device_sensors/3) end field :sensor_readings, list_of(:sensor_reading) do arg(:organization_id, :id) arg(:sensor_id, non_null(:id)) arg(:time_range, :string, default_value: "24h") middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.TimeSeries.sensor_readings/3) end field :interface_traffic, list_of(:interface_traffic_point) do arg(:organization_id, :id) arg(:interface_id, non_null(:id)) arg(:time_range, :string, default_value: "24h") middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.TimeSeries.interface_traffic/3) end field :check_results, list_of(:check_result_point) do arg(:organization_id, :id) arg(:check_id, non_null(:id)) arg(:time_range, :string, default_value: "1h") middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.TimeSeries.check_results/3) end # Schedules field :schedules, list_of(:schedule) do arg(:organization_id, :id) middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.Schedule.list/3) end field :schedule, :schedule do arg(:organization_id, :id) arg(:id, non_null(:id)) middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.Schedule.get/3) end field :on_call, :on_call_result do arg(:organization_id, :id) arg(:schedule_id, non_null(:id)) middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.Schedule.on_call/3) end # Escalation Policies field :escalation_policies, list_of(:escalation_policy) do arg(:organization_id, :id) middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.EscalationPolicy.list/3) end field :escalation_policy, :escalation_policy do arg(:organization_id, :id) arg(:id, non_null(:id)) middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.EscalationPolicy.get/3) end # Maintenance Windows field :maintenance_windows, list_of(:maintenance_window) do arg(:organization_id, :id) arg(:filter, :string) middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.MaintenanceWindow.list/3) end field :maintenance_window, :maintenance_window do arg(:organization_id, :id) arg(:id, non_null(:id)) middleware(OrganizationScope) resolve(&ToweropsWeb.GraphQL.Resolvers.MaintenanceWindow.get/3) end end mutation do # Device CRUD field :create_device, :device do arg(:input, non_null(:device_input)) resolve(&ToweropsWeb.GraphQL.Resolvers.Device.create/3) end field :update_device, :device do arg(:id, non_null(:id)) arg(:input, non_null(:device_input)) resolve(&ToweropsWeb.GraphQL.Resolvers.Device.update/3) end field :delete_device, :delete_result do arg(:id, non_null(:id)) resolve(&ToweropsWeb.GraphQL.Resolvers.Device.delete/3) end # Site CRUD field :create_site, :site do arg(:input, non_null(:site_input)) resolve(&ToweropsWeb.GraphQL.Resolvers.Site.create/3) end field :update_site, :site do arg(:id, non_null(:id)) arg(:input, non_null(:site_input)) resolve(&ToweropsWeb.GraphQL.Resolvers.Site.update/3) end field :delete_site, :delete_result do arg(:id, non_null(:id)) resolve(&ToweropsWeb.GraphQL.Resolvers.Site.delete/3) end # Check CRUD field :create_check, :check do arg(:input, non_null(:check_input)) resolve(&ToweropsWeb.GraphQL.Resolvers.Check.create/3) end field :update_check, :check do arg(:id, non_null(:id)) arg(:input, non_null(:check_input)) resolve(&ToweropsWeb.GraphQL.Resolvers.Check.update/3) end field :delete_check, :delete_result do arg(:id, non_null(:id)) resolve(&ToweropsWeb.GraphQL.Resolvers.Check.delete/3) end # Alert actions field :acknowledge_alert, :alert do arg(:id, non_null(:id)) resolve(&ToweropsWeb.GraphQL.Resolvers.Alert.acknowledge/3) end field :resolve_alert, :alert do arg(:id, non_null(:id)) resolve(&ToweropsWeb.GraphQL.Resolvers.Alert.resolve_alert/3) end # Agent management field :create_agent, :agent_with_token do arg(:name, non_null(:string)) resolve(&ToweropsWeb.GraphQL.Resolvers.Agent.create/3) end field :delete_agent, :delete_result do arg(:id, non_null(:id)) resolve(&ToweropsWeb.GraphQL.Resolvers.Agent.delete/3) end # Organization settings field :update_organization, :organization do arg(:input, non_null(:organization_input)) resolve(&ToweropsWeb.GraphQL.Resolvers.Organization.update/3) end # Member management field :send_invitation, :invitation do arg(:email, non_null(:string)) arg(:role, :string, default_value: "technician") resolve(&ToweropsWeb.GraphQL.Resolvers.Member.invite/3) end field :cancel_invitation, :delete_result do arg(:id, non_null(:id)) resolve(&ToweropsWeb.GraphQL.Resolvers.Member.cancel_invitation/3) end field :remove_member, :delete_result do arg(:id, non_null(:id)) resolve(&ToweropsWeb.GraphQL.Resolvers.Member.remove/3) end field :update_member_role, :member do arg(:id, non_null(:id)) arg(:role, non_null(:string)) resolve(&ToweropsWeb.GraphQL.Resolvers.Member.update_role/3) end # Integrations field :create_integration, :integration do arg(:input, non_null(:integration_input)) resolve(&ToweropsWeb.GraphQL.Resolvers.Integration.create/3) end field :update_integration, :integration do arg(:id, non_null(:id)) arg(:input, non_null(:integration_input)) resolve(&ToweropsWeb.GraphQL.Resolvers.Integration.update/3) end field :delete_integration, :delete_result do arg(:id, non_null(:id)) resolve(&ToweropsWeb.GraphQL.Resolvers.Integration.delete/3) end field :test_integration, :test_result do arg(:id, non_null(:id)) resolve(&ToweropsWeb.GraphQL.Resolvers.Integration.test_connection/3) end # Schedule CRUD field :create_schedule, :schedule do arg(:input, non_null(:schedule_input)) resolve(&ToweropsWeb.GraphQL.Resolvers.Schedule.create/3) end field :update_schedule, :schedule do arg(:id, non_null(:id)) arg(:input, non_null(:schedule_input)) resolve(&ToweropsWeb.GraphQL.Resolvers.Schedule.update/3) end field :delete_schedule, :delete_result do arg(:id, non_null(:id)) resolve(&ToweropsWeb.GraphQL.Resolvers.Schedule.delete/3) end # Layer CRUD field :create_layer, :layer do arg(:schedule_id, non_null(:id)) arg(:input, non_null(:layer_input)) resolve(&ToweropsWeb.GraphQL.Resolvers.Schedule.create_layer/3) end field :update_layer, :layer do arg(:id, non_null(:id)) arg(:input, non_null(:layer_input)) resolve(&ToweropsWeb.GraphQL.Resolvers.Schedule.update_layer/3) end field :delete_layer, :delete_result do arg(:id, non_null(:id)) resolve(&ToweropsWeb.GraphQL.Resolvers.Schedule.delete_layer/3) end # Layer member management field :add_layer_member, :layer_member do arg(:layer_id, non_null(:id)) arg(:user_id, non_null(:id)) arg(:position, non_null(:integer)) resolve(&ToweropsWeb.GraphQL.Resolvers.Schedule.add_member/3) end field :remove_layer_member, :delete_result do arg(:id, non_null(:id)) resolve(&ToweropsWeb.GraphQL.Resolvers.Schedule.remove_member/3) end # Override management field :create_override, :override do arg(:schedule_id, non_null(:id)) arg(:input, non_null(:override_input)) resolve(&ToweropsWeb.GraphQL.Resolvers.Schedule.create_override/3) end field :delete_override, :delete_result do arg(:id, non_null(:id)) resolve(&ToweropsWeb.GraphQL.Resolvers.Schedule.delete_override/3) end # Escalation Policy CRUD field :create_escalation_policy, :escalation_policy do arg(:input, non_null(:escalation_policy_input)) resolve(&ToweropsWeb.GraphQL.Resolvers.EscalationPolicy.create/3) end field :update_escalation_policy, :escalation_policy do arg(:id, non_null(:id)) arg(:input, non_null(:escalation_policy_input)) resolve(&ToweropsWeb.GraphQL.Resolvers.EscalationPolicy.update/3) end field :delete_escalation_policy, :delete_result do arg(:id, non_null(:id)) resolve(&ToweropsWeb.GraphQL.Resolvers.EscalationPolicy.delete/3) end # Escalation Rule CRUD field :create_escalation_rule, :escalation_rule do arg(:escalation_policy_id, non_null(:id)) arg(:input, non_null(:escalation_rule_input)) resolve(&ToweropsWeb.GraphQL.Resolvers.EscalationPolicy.create_rule/3) end field :update_escalation_rule, :escalation_rule do arg(:id, non_null(:id)) arg(:input, non_null(:escalation_rule_input)) resolve(&ToweropsWeb.GraphQL.Resolvers.EscalationPolicy.update_rule/3) end field :delete_escalation_rule, :delete_result do arg(:id, non_null(:id)) resolve(&ToweropsWeb.GraphQL.Resolvers.EscalationPolicy.delete_rule/3) end # Escalation Target management field :create_escalation_target, :escalation_target do arg(:escalation_rule_id, non_null(:id)) arg(:input, non_null(:escalation_target_input)) resolve(&ToweropsWeb.GraphQL.Resolvers.EscalationPolicy.create_target/3) end field :delete_escalation_target, :delete_result do arg(:id, non_null(:id)) resolve(&ToweropsWeb.GraphQL.Resolvers.EscalationPolicy.delete_target/3) end # Maintenance Window CRUD field :create_maintenance_window, :maintenance_window do arg(:input, non_null(:maintenance_window_input)) resolve(&ToweropsWeb.GraphQL.Resolvers.MaintenanceWindow.create/3) end field :update_maintenance_window, :maintenance_window do arg(:id, non_null(:id)) arg(:input, non_null(:maintenance_window_input)) resolve(&ToweropsWeb.GraphQL.Resolvers.MaintenanceWindow.update/3) end field :delete_maintenance_window, :delete_result do arg(:id, non_null(:id)) resolve(&ToweropsWeb.GraphQL.Resolvers.MaintenanceWindow.delete/3) end end subscription do field :device_status_changed, :device_status_event do arg(:organization_id, :id) arg(:device_id, :id) config(fn args, resolution -> org_id = resolve_subscription_org_id(args, resolution) if org_id do topic = if args[:device_id], do: "gql:device_status:#{org_id}:#{args.device_id}", else: "gql:device_status:#{org_id}" {:ok, topic: topic} else {:error, "organization_id is required"} end end) end field :alert_event, :alert_event do arg(:organization_id, :id) config(fn args, resolution -> org_id = resolve_subscription_org_id(args, resolution) if org_id do {:ok, topic: "gql:alerts:#{org_id}"} else {:error, "organization_id is required"} end end) end field :sensor_readings_updated, :sensor_readings_event do arg(:organization_id, :id) arg(:device_id, non_null(:id)) config(fn args, resolution -> org_id = resolve_subscription_org_id(args, resolution) if org_id do {:ok, topic: "gql:sensor_readings:#{org_id}:#{args.device_id}"} else {:error, "organization_id is required"} end end) end end # Resolves org_id from context (API token) or args (mobile token) defp resolve_subscription_org_id(args, %{context: %{organization_id: org_id}}) do args[:organization_id] || org_id end defp resolve_subscription_org_id(%{organization_id: org_id}, %{context: %{user: user}}) when is_binary(org_id) do if Towerops.Organizations.user_has_access?(user.id, org_id), do: org_id end defp resolve_subscription_org_id(_args, _resolution), do: nil end