/
hit_controller.ex
209 lines (171 loc) · 6.38 KB
/
hit_controller.ex
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
defmodule HitsWeb.HitController do
use HitsWeb, :controller
# use Phoenix.Channel
# import Ecto.Query
alias Hits.{Hit, Repository, User, Useragent}
use Params
@doc """
Schema validator.
The possible URL and query parameters are defined here and checked for validity.
The possible values are fetched from https://shields.io/endpoint
"""
defparams schema_validator %{
user!: :string,
repository!: :string,
style: [field: Ecto.Enum, values: [plastic: "plastic", flat: "flat", flatSquare: "flat-square", forTheBadge: "for-the-badge", social: "social"], default: :flat],
color: [field: :string, default: "lightgrey"],
show: [field: :string, default: nil],
}
def index(conn, %{"user" => user, "repository" => repository} = params) do
# Schema validation
# Check https://github.com/vic/params#usage
schema = schema_validator(params)
params = Params.data(schema)
params_map = Params.to_map(schema)
if schema.valid? and String.ends_with?(repository, ".svg") do
if user_valid?(user) and repository_valid?(repository) do
# insert hit
{_user_schema, _useragent_schema, repo} = insert_hit(conn, user, repository)
count =
if params.show == "unique" do
Hit.count_unique_hits(repo.id)
else
Hit.count_hits(repo.id)
end
# Send hit to connected clients via channel github.com/dwyl/hits/issues/79
HitsWeb.Endpoint.broadcast("hit:lobby", "hit", %{
"user" => user,
"repo" => repository,
"count" => count
})
# Render badge or json
if Content.get_accept_header(conn) =~ "json" do
render_json(conn, count, params)
else
render_badge(conn, count, params.style)
end
else
# Render badge or json
if Content.get_accept_header(conn) =~ "json" do
render_invalid_json(conn)
else
render_invalid_badge(conn)
end
end
else
if Content.get_accept_header(conn) =~ "json" do
render_invalid_json(conn)
else
if user_valid?(user) and repository_valid?(repository) do
render(conn, "index.html", params_map)
else
redirect(conn, to: "/error/#{user}/#{repository}")
end
end
end
end
@doc """
insert_hit/3 inserts user, useragent, repository and the
hit entry which link the useragent to the repository
## Parameters
- conn: Map the standard Plug.Conn info see: hexdocs.pm/plug/Plug.Conn.html
- username: The Github user
- repository: The Github repository
- filter_count: define filter for count result
Returns tuple {user, useragent, repository}.
"""
def insert_hit(conn, username, repository) do
useragent = Hits.get_user_agent_string(conn)
# remote_ip comes in as a Tuple {192, 168, 1, 42} >> 192.168.1.42 (dot quad)
ip = Hits.get_user_ip_address(conn)
# TODO: perform IP Geolocation lookup here so we can insert lat/lon for map!
# insert the useragent:
useragent = Useragent.insert(%{"name" => useragent, "ip" => ip})
# insert the user:
user = User.insert(%{"name" => username})
# strip ".svg" from repo name and insert:
repository_name = repository |> String.split(".svg") |> List.first()
repository =
Ecto.build_assoc(user, :repositories)
|> Ecto.Changeset.change()
# link useragent to repository to create hit entry
|> Ecto.Changeset.put_assoc(:useragents, [useragent])
|> Repository.insert(%{"name" => repository_name})
{user, useragent, repository}
end
@doc """
render_json/1 outputs an encoded json related to a badge.
## Parameters
- conn: Map the standard Plug.Conn info see: hexdocs.pm/plug/Plug.Conn.html
Returns an encoded json that can be used with `shields.io` URL.
See https://shields.io/endpoint
"""
def render_json(conn, count, params) do
json_response = %{
"schemaVersion" => "1",
"label" => "hits",
"style" => params.style,
"message" => count,
"color" => params.color
}
json(conn, json_response)
end
@doc """
render_invalid_json/1 outputs an encoded json related to an invalid badge.
## Parameters
- conn: Map the standard Plug.Conn info see: hexdocs.pm/plug/Plug.Conn.html
Returns an encoded json that can be used with `shields.io` URL.
See https://shields.io/endpoint
"""
def render_invalid_json(conn) do
json_response = %{
"schemaVersion" => "1",
"label" => "hits",
"message" => "invalid url",
}
json(conn, json_response)
end
@doc """
render_badge/2 renders the badge for the url requested in conn
## Parameters
- conn: Map the standard Plug.Conn info see: hexdocs.pm/plug/Plug.Conn.html
- count: Number the view/hit count to be displayed in the badge.
Returns Http response to end-user's browser with the svg (XML) of the badge.
"""
def render_badge(conn, count, style) do
conn
|> put_resp_content_type("image/svg+xml")
|> send_resp(200, Hits.make_badge(count, style))
end
def render_invalid_badge(conn) do
conn
|> put_resp_content_type("image/svg+xml")
|> send_resp(404, Hits.svg_invalid_badge())
end
@doc """
edgecase/2 handles the case where people did not follow the instructions
for creating their badge ... 🙄 see: https://github.com/dwyl/hits/issues/67
## Parameters
- conn: Map the standard Plug.Conn info see: hexdocs.pm/plug/Plug.Conn.html
- params: the url path params %{"etc", "user", "repository"}
Invokes the index function if ".svg" is present else returns "bad badge"
"""
def edgecase(conn, %{"repository" => repository} = params) do
# note: we ignore the "etc" portion of the url which is usually
# just the person's username ... see: github.com/dwyl/hits/issues/67
# we cannot help you so you get a 404!
if repository =~ ".svg" do
index(conn, params)
else
conn
|> put_resp_content_type("image/svg+xml")
|> send_resp(404, Hits.make_badge(404, params["style"]))
end
end
# see issue https://github.com/dwyl/hits/issues/154
# alphanumeric follow by one or zeor "-" or just alphanumerics
defp user_valid?(user), do: String.match?(user, ~r/^([[:alnum:]]+-)*[[:alnum:]]+$/)
# ^[[:alnum:]-_.]+$ means the name is composed of one or multiple alphanumeric character
# or "-_." characters
defp repository_valid?(repo), do: String.match?(repo, ~r/^[[:alnum:]-_.]+$/)
end