222 lines
7.4 KiB
Elixir
222 lines
7.4 KiB
Elixir
defmodule ShootingEventPhxWeb.ApiController do
|
|
use ShootingEventPhxWeb, :controller
|
|
alias ShootingEventPhx.{Auth, Events}
|
|
alias ShootingEventPhx.Auth.SessionStore
|
|
alias ShootingEventPhx.Domain.{EventData, Settings, Stages, StateService}
|
|
alias ShootingEventPhx.AI.Gemini
|
|
|
|
def health(conn, _params) do
|
|
json(conn, %{"status" => "ok"})
|
|
end
|
|
|
|
def state(conn, _params) do
|
|
state = StateService.read_state(Auth.admin_request?(conn))
|
|
json(conn, state)
|
|
end
|
|
|
|
def admin_state(conn, _params) do
|
|
json(conn, StateService.read_state(true))
|
|
end
|
|
|
|
def admin_login(conn, params) do
|
|
username = Map.get(params, "username", "")
|
|
password = Map.get(params, "password", "")
|
|
|
|
if Auth.verify_admin(username, password) do
|
|
{:ok, token, expires_at} = SessionStore.create_token()
|
|
|
|
json(conn, %{
|
|
"token" => token,
|
|
"expiresAt" => DateTime.to_iso8601(expires_at),
|
|
"username" => Auth.admin_user()
|
|
})
|
|
else
|
|
error(conn, :unauthorized, "invalid credentials")
|
|
end
|
|
end
|
|
|
|
def admin_logout(conn, _params) do
|
|
case Auth.bearer_token(conn) do
|
|
nil -> :ok
|
|
token -> SessionStore.delete_token(token)
|
|
end
|
|
|
|
json(conn, %{"ok" => true})
|
|
end
|
|
|
|
def update_settings(conn, params) do
|
|
case Settings.update_settings(params) do
|
|
:ok ->
|
|
Events.broadcast()
|
|
json(conn, StateService.read_state(true))
|
|
|
|
{:error, message} ->
|
|
error(conn, :bad_request, message)
|
|
end
|
|
end
|
|
|
|
def create_player(conn, params) do
|
|
case EventData.create_player(params) do
|
|
:ok ->
|
|
Events.broadcast()
|
|
conn |> put_status(:created) |> json(StateService.read_state(true))
|
|
|
|
{:error, :bad_request, message} ->
|
|
error(conn, :bad_request, message)
|
|
|
|
{:error, _, message} ->
|
|
error(conn, :internal_server_error, message)
|
|
end
|
|
end
|
|
|
|
def update_player(conn, %{"id" => id} = params) do
|
|
with {player_id, ""} <- Integer.parse(to_string(id)),
|
|
result <- EventData.update_player(player_id, params),
|
|
:ok <- result do
|
|
Events.broadcast()
|
|
json(conn, StateService.read_state(true))
|
|
else
|
|
:error -> error(conn, :bad_request, "invalid player id")
|
|
{:error, :bad_request, message} -> error(conn, :bad_request, message)
|
|
{:error, :not_found, message} -> error(conn, :not_found, message)
|
|
{:error, _, message} -> error(conn, :internal_server_error, message)
|
|
end
|
|
end
|
|
|
|
def delete_player(conn, %{"id" => id}) do
|
|
with {player_id, ""} <- Integer.parse(to_string(id)),
|
|
:ok <- EventData.delete_player(player_id) do
|
|
Events.broadcast()
|
|
json(conn, StateService.read_state(true))
|
|
else
|
|
:error -> error(conn, :bad_request, "invalid player id")
|
|
{:error, :not_found, message} -> error(conn, :not_found, message)
|
|
{:error, _, message} -> error(conn, :internal_server_error, message)
|
|
end
|
|
end
|
|
|
|
def auto_group_players(conn, params) do
|
|
groups = Map.get(params, "groups", [])
|
|
|
|
case EventData.auto_group_players(groups) do
|
|
:ok ->
|
|
Events.broadcast()
|
|
json(conn, StateService.read_state(true))
|
|
|
|
{:error, :bad_request, message} ->
|
|
error(conn, :bad_request, message)
|
|
|
|
{:error, _, message} ->
|
|
error(conn, :internal_server_error, message)
|
|
end
|
|
end
|
|
|
|
def update_score(conn, %{"stage" => stage, "id" => id} = params) do
|
|
with {:ok, normalized_stage} <- Stages.validate_stage(stage),
|
|
{player_id, ""} <- Integer.parse(to_string(id)),
|
|
score <- parse_score(Map.get(params, "score")),
|
|
:ok <- EventData.update_score(normalized_stage, player_id, score) do
|
|
Events.broadcast()
|
|
json(conn, StateService.read_state(true))
|
|
else
|
|
{:error, message} -> error(conn, :bad_request, message)
|
|
:error -> error(conn, :bad_request, "invalid player id")
|
|
{:invalid_score, message} -> error(conn, :bad_request, message)
|
|
{:error, :bad_request, message} -> error(conn, :bad_request, message)
|
|
{:error, _, message} -> error(conn, :internal_server_error, message)
|
|
end
|
|
end
|
|
|
|
def reset_stage(conn, %{"stage" => stage} = params) do
|
|
with {:ok, target_stages} <- Stages.resolve_reset_stages(stage),
|
|
reset_proofs <- truthy?(Map.get(params, "resetProofs")),
|
|
:ok <- EventData.reset_stage_scores(target_stages, reset_proofs) do
|
|
Events.broadcast()
|
|
json(conn, StateService.read_state(true))
|
|
else
|
|
{:error, message} -> error(conn, :bad_request, message)
|
|
{:error, _, message} -> error(conn, :internal_server_error, message)
|
|
end
|
|
end
|
|
|
|
def update_score_proof(conn, %{"stage" => stage, "id" => id} = params) do
|
|
with {:ok, normalized_stage} <- Stages.validate_stage(stage),
|
|
{player_id, ""} <- Integer.parse(to_string(id)),
|
|
image_data when is_binary(image_data) <- Map.get(params, "imageData"),
|
|
:ok <- EventData.update_score_proof(normalized_stage, player_id, image_data) do
|
|
Events.broadcast()
|
|
json(conn, StateService.read_state(true))
|
|
else
|
|
{:error, message} -> error(conn, :bad_request, message)
|
|
:error -> error(conn, :bad_request, "invalid player id")
|
|
nil -> error(conn, :bad_request, "imageData is required")
|
|
{:error, :bad_request, message} -> error(conn, :bad_request, message)
|
|
{:error, _, message} -> error(conn, :internal_server_error, message)
|
|
end
|
|
end
|
|
|
|
def delete_score_proof(conn, %{"stage" => stage, "id" => id}) do
|
|
with {:ok, normalized_stage} <- Stages.validate_stage(stage),
|
|
{player_id, ""} <- Integer.parse(to_string(id)),
|
|
:ok <- EventData.delete_score_proof(normalized_stage, player_id) do
|
|
Events.broadcast()
|
|
json(conn, StateService.read_state(true))
|
|
else
|
|
{:error, message} -> error(conn, :bad_request, message)
|
|
:error -> error(conn, :bad_request, "invalid player id")
|
|
{:error, _, message} -> error(conn, :internal_server_error, message)
|
|
end
|
|
end
|
|
|
|
def score_advice(conn, %{"stage" => stage, "id" => id}) do
|
|
with {:ok, normalized_stage} <- Stages.validate_stage(stage),
|
|
{player_id, ""} <- Integer.parse(to_string(id)),
|
|
{:ok, image_data} <- EventData.load_score_proof_image(normalized_stage, player_id),
|
|
{:ok, current_score} <- EventData.load_current_score(normalized_stage, player_id),
|
|
{:ok, advice} <-
|
|
Gemini.generate_score_advice(normalized_stage, player_id, image_data, current_score) do
|
|
json(conn, advice)
|
|
else
|
|
{:error, :not_found} -> error(conn, :bad_request, "no proof image found for this score")
|
|
{:error, :service_unavailable, message} -> error(conn, :service_unavailable, message)
|
|
{:error, :bad_gateway, message} -> error(conn, :bad_gateway, message)
|
|
{:error, message} when is_binary(message) -> error(conn, :bad_request, message)
|
|
:error -> error(conn, :bad_request, "invalid player id")
|
|
end
|
|
end
|
|
|
|
defp parse_score(value) do
|
|
cond do
|
|
is_integer(value) ->
|
|
value
|
|
|
|
is_binary(value) ->
|
|
case Integer.parse(String.trim(value)) do
|
|
{v, ""} -> v
|
|
_ -> {:invalid_score, "invalid request body"}
|
|
end
|
|
|
|
true ->
|
|
{:invalid_score, "invalid request body"}
|
|
end
|
|
end
|
|
|
|
defp truthy?(value) do
|
|
case value do
|
|
true -> true
|
|
1 -> true
|
|
"1" -> true
|
|
"true" -> true
|
|
"yes" -> true
|
|
"on" -> true
|
|
_ -> false
|
|
end
|
|
end
|
|
|
|
defp error(conn, status, message) do
|
|
conn
|
|
|> put_status(status)
|
|
|> json(%{"message" => message})
|
|
end
|
|
end
|