prop/lib/microwaveprop_web/controllers/api/v1/contact_controller.ex
Graham McIntire fd976b0cd5
fix: resolve 391 Credo issues across codebase
- Add jump_credo_checks ~> 0.4 with all 20 checks enabled
- Fix all standard Credo issues: 139 @spec (113 done, 26 remain),
  4 refactoring, 3 alias usage, 9 System.cmd env, 5 unsafe_to_atom,
  2 max line length, 9 assert_receive timeout
- Fix 170+ jump_credo_checks warnings:
  - 117 TopLevelAliasImportRequire: move nested alias/import to module top
  - 32 UseObanProWorker: switch to Oban.Pro.Worker
  - 4 DoctestIExExamples: add doctests / create test file
  - ~20 WeakAssertion: strengthen type-check assertions
  - Various ConditionalAssertion, AssertReceiveTimeout fixes
- Exclude vendor/ from Credo analysis
- Remaining: 175 warnings (mostly opinionated WeakAssertion,
  AvoidSocketAssignsInTest), 26 @spec annotations
2026-06-12 13:51:32 -05:00

107 lines
3.1 KiB
Elixir

defmodule MicrowavepropWeb.Api.V1.ContactController do
@moduledoc "Read + create contacts (QSOs)."
use Phoenix.Controller, formats: [:json]
alias Microwaveprop.Accounts.Scope
alias Microwaveprop.Radio
alias Microwaveprop.Radio.Contact
alias Microwaveprop.Repo
alias MicrowavepropWeb.Api.ErrorJSON
alias MicrowavepropWeb.Api.V1.ContactJSON
plug :accepts, ["json"]
action_fallback MicrowavepropWeb.Api.FallbackController
@max_per_page 200
@spec index(Plug.Conn.t(), map()) :: Plug.Conn.t()
def index(conn, params) do
page = params |> Map.get("page", "1") |> parse_int(1)
per_page = params |> Map.get("per_page", "50") |> parse_int(50) |> min(@max_per_page) |> max(1)
search = params["search"]
viewer = conn.assigns[:current_api_user]
%{entries: entries, total_entries: total, total_pages: total_pages} =
Radio.list_contacts(
page: page,
per_page: per_page,
search: search,
scope: Scope.for_user(viewer)
)
json(
conn,
ContactJSON.index_paginated(%{
contacts: entries,
viewer: viewer,
page: page,
per_page: per_page,
total_entries: total,
total_pages: total_pages
})
)
end
@spec show(Plug.Conn.t(), map()) :: Plug.Conn.t()
def show(conn, %{"id" => id}) do
viewer = conn.assigns[:current_api_user]
case Repo.get(Contact, id) do
nil ->
ErrorJSON.send_problem(conn, 404, "not_found", "Contact not found.")
%Contact{private: true, user_id: owner_id} = contact ->
if owner_id && viewer && viewer.id == owner_id do
json(conn, ContactJSON.show(%{contact: contact, viewer: viewer}))
else
ErrorJSON.send_problem(conn, 404, "not_found", "Contact not found.")
end
%Contact{} = contact ->
json(conn, ContactJSON.show(%{contact: contact, viewer: viewer}))
end
end
@spec create(Plug.Conn.t(), map()) :: Plug.Conn.t()
def create(conn, params) do
user = conn.assigns.current_api_user
attrs =
params
|> Map.take(
~w(station1 station2 qso_timestamp mode band grid1 grid2 user_declared_prop_mode height1_ft height2_ft private notes)
)
|> Map.put_new("submitter_email", user.email)
case Radio.create_contact(attrs, user.id) do
{:ok, contact} ->
conn
|> put_status(:created)
|> json(ContactJSON.show(%{contact: contact, viewer: user}))
{:error, %Ecto.Changeset{} = changeset} ->
ErrorJSON.send_changeset(conn, changeset)
{:error, :duplicate, existing} ->
conn
|> put_status(:conflict)
|> json(%{
type: "about:blank",
title: "duplicate_contact",
status: 409,
detail: "An equivalent contact already exists.",
existing: ContactJSON.show(%{contact: existing, viewer: user}).data
})
end
end
defp parse_int(value, fallback) when is_binary(value) do
case Integer.parse(value) do
{n, ""} when n >= 1 -> n
_ -> fallback
end
end
defp parse_int(_, fallback), do: fallback
end