File size: 4,764 Bytes
8da2481
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
defmodule PlausibleWeb.Api.Internal.AnnotationsController do
  @moduledoc """
  Internal API controller for annotations.
  """
  use Plausible
  use PlausibleWeb, :controller
  use PlausibleWeb.Plugs.ErrorHandler
  alias Plausible.Annotations
  alias Plausible.ChangesetHelpers
  alias Plausible.Stats.{ApiQueryParser, QueryPeriod}
  alias PlausibleWeb.Api.Helpers, as: H

  def index(conn, params) do
    user = conn.assigns[:current_user]
    site = conn.assigns.site
    site_role = conn.assigns.site_role

    with {:ok, input_date_range} <- parse_input_date_range(params),
         {:ok, relative_date} <- parse_relative_date(params) do
      now = DateTime.utc_now(:second)

      range_in_site_tz =
        QueryPeriod.build_range_for_site(input_date_range, site, relative_date, now)

      case Annotations.get_all_for_site(site, site_role, user, range_in_site_tz) do
        {:ok, result} ->
          json(conn, result)

        {:error, :not_enough_permissions} ->
          json(conn, "Not enough permissions to get annotations")
      end
    else
      {:error, message} -> H.bad_request(conn, message)
    end
  end

  defp parse_input_date_range(%{
         "date_range_start" => start,
         "date_range_end" => end_
       })
       when is_binary(start) and is_binary(end_) do
    case ApiQueryParser.parse_input_date_range([start, end_]) do
      {:ok, parsed} -> {:ok, parsed}
      {:error, %{message: message}} -> {:error, message}
    end
  end

  defp parse_input_date_range(%{"date_range" => "realtime"}), do: {:ok, :realtime}
  defp parse_input_date_range(%{"date_range" => "realtime_30m"}), do: {:ok, :realtime_30m}

  defp parse_input_date_range(%{"date_range" => date_range}) do
    case ApiQueryParser.parse_input_date_range(date_range) do
      {:ok, parsed} -> {:ok, parsed}
      {:error, %{message: message}} -> {:error, message}
    end
  end

  defp parse_input_date_range(_), do: {:error, "Required 'date_range' parameter missing"}

  defp parse_relative_date(%{"relative_date" => date}) when is_binary(date) do
    case Date.from_iso8601(date) do
      {:ok, date} -> {:ok, date}
      _ -> {:error, "Failed to convert '#{date}' to date"}
    end
  end

  defp parse_relative_date(_), do: {:ok, nil}

  def create(conn, params) do
    user = conn.assigns[:current_user]
    site = conn.assigns.site
    site_role = conn.assigns.site_role

    case Annotations.insert_one(user, site, site_role, params) do
      {:error, :not_enough_permissions} ->
        H.not_enough_permissions(conn, "Not enough permissions to create annotation")

      {:error, :annotations_limit_reached} ->
        H.not_enough_permissions(conn, "Annotations limit reached")

      {:error, {:invalid_annotation, errors}} when is_list(errors) ->
        conn
        |> put_status(400)
        |> json(%{
          error: ChangesetHelpers.serialize_first_error(errors)
        })

      {:ok, annotation} ->
        json(conn, annotation)
    end
  end

  def update(conn, params) do
    user = conn.assigns[:current_user]
    site = conn.assigns.site
    site_role = conn.assigns.site_role
    annotation_id = normalize_annotation_id_param(params["annotation_id"])

    case Annotations.update_one(user, site, site_role, annotation_id, params) do
      {:error, :not_enough_permissions} ->
        H.not_enough_permissions(conn, "Not enough permissions to edit annotation")

      {:error, :annotation_not_found} ->
        annotation_not_found(conn, params["annotation_id"])

      {:error, {:invalid_annotation, errors}} when is_list(errors) ->
        conn
        |> put_status(400)
        |> json(%{
          error: ChangesetHelpers.serialize_first_error(errors)
        })

      {:ok, annotation} ->
        json(conn, annotation)
    end
  end

  def delete(conn, params) do
    user = conn.assigns[:current_user]
    site = conn.assigns.site
    site_role = conn.assigns.site_role
    annotation_id = normalize_annotation_id_param(params["annotation_id"])

    case Annotations.delete_one(user, site, site_role, annotation_id) do
      {:error, :not_enough_permissions} ->
        H.not_enough_permissions(conn, "Not enough permissions to delete annotation")

      {:error, :annotation_not_found} ->
        annotation_not_found(conn, params["annotation_id"])

      {:ok, annotation} ->
        json(conn, annotation)
    end
  end

  @spec normalize_annotation_id_param(any()) :: nil | pos_integer()
  defp normalize_annotation_id_param(input) do
    case Integer.parse(input) do
      {int_value, ""} when int_value > 0 -> int_value
      _ -> nil
    end
  end

  defp annotation_not_found(%Plug.Conn{} = conn, annotation_id_param) do
    H.not_found(conn, "Annotation not found with ID #{inspect(annotation_id_param)}")
  end
end