defmodule ToweropsWeb.Api.V1.ProfilesController do @moduledoc """ API controller for managing device SNMP profiles. All endpoints require API token authentication. """ use ToweropsWeb, :controller require Logger @doc """ POST /api/v1/profiles/import Imports device profiles from JSON data exported via `mix export_profiles`. **Requires superuser API token** - Only API tokens created by superuser accounts can import profiles. Request body: [ { "os": "epmp", "detection": { ... }, # YAML detection data "discovery": { ... } # YAML discovery data (optional) }, ... ] Response: { "status": "queued", "job_id": "uuid", "profile_count": 15, "message": "Import job queued successfully..." } Example: curl -X POST https://towerops.net/api/v1/profiles/import \\ -H "Authorization: Bearer YOUR_SUPERUSER_TOKEN" \\ -H "Content-Type: application/json" \\ -d @profiles.json """ def import_bulk(conn, %{"_json" => profiles}) when is_list(profiles) do # Check if user is a superuser user = conn.assigns[:current_user] if !user || !user.is_superuser do Logger.warning("Profile import attempted by non-superuser: #{inspect(user && user.email)}") conn |> put_status(:forbidden) |> json(%{error: "Superuser access required. Only API tokens created by superusers can import profiles."}) else Logger.info("Queueing import for #{length(profiles)} device profiles via API (user: #{user.email})") do_import_bulk(conn, profiles) end end def import_bulk(conn, _params) do conn |> put_status(:bad_request) |> json(%{error: "Invalid request format. Expected array of profile objects."}) end # Private functions defp do_import_bulk(conn, profiles) do # Generate unique job ID for tracking job_id = Ecto.UUID.generate() # Enqueue background job case Exq.enqueue(Exq, "maintenance", Towerops.Workers.ProfileImportWorker, [ profiles, job_id ]) do {:ok, _job_jid} -> Logger.info("Profile import job #{job_id} queued successfully") json(conn, %{ status: "queued", job_id: job_id, profile_count: length(profiles), message: "Import job queued successfully. #{length(profiles)} profiles will be imported in the background." }) {:error, reason} -> Logger.error("Failed to queue profile import: #{inspect(reason)}") conn |> put_status(:internal_server_error) |> json(%{error: "Failed to queue import job", reason: inspect(reason)}) end end end