Meet SourceLevel, an All-in-one Data & Analytics for Engineering Teams

SourceLevel provides metrics and insights by collecting data from many sources such as GitHub and GitLab. Our product brings visibility over every corner of the delivery pipeline in a Data & Analytics Solution for Engineering Teams.

Get started for free
Go to line 1
defmodule HexpmWeb.ControllerHelpers do
Go to line 2
  import Plug.Conn
Go to line 3
  import Phoenix.Controller
Go to line 5
  alias Hexpm.Accounts.{Auth, Organizations}
Go to line 6
  alias Hexpm.Repository.{Packages, Releases, Repositories}
Go to line 7
  alias HexpmWeb.Router.Helpers, as: Routes
Go to line 9
  @max_cache_age 60
Go to line 11
  # TODO: check privacy settings
Go to line 12
  def cache(conn, control, vary) do
Go to line 14
    |> maybe_put_resp_header("cache-control", parse_control(control))
Go to line 15
    |> maybe_put_resp_header("vary", parse_vary(vary))
Go to line 18
  def api_cache(conn, privacy) do
Go to line 19
    control = [logged_in_privacy(conn, privacy), "max-age": @max_cache_age]
Go to line 20
    vary = ["accept", "accept-encoding"]
Go to line 21
    cache(conn, control, vary)
Go to line 24
  defp logged_in_privacy(conn, :logged_in) do
Go to line 25
    if conn.assigns.current_user, do: :private, else: :public
Go to line 28
  defp logged_in_privacy(_conn, other) do
Go to line 32
  defp parse_vary(nil), do: nil
Go to line 33
  defp parse_vary(vary), do: Enum.map_join(vary, ", ", &"#{&1}")
Go to line 35
  defp parse_control(nil), do: nil
Go to line 37
  defp parse_control(control) do
Go to line 38
    Enum.map_join(control, ", ", fn
Go to line 39
      atom when is_atom(atom) -> "#{atom}"
Go to line 40
      {key, value} -> "#{key}=#{value}"
Go to line 44
  defp maybe_put_resp_header(conn, _header, nil), do: conn
Go to line 45
  defp maybe_put_resp_header(conn, header, value), do: put_resp_header(conn, header, value)
Go to line 47
  def render_error(conn, status, assigns \\ []) do
Go to line 49
    |> put_status(status)
Go to line 50
    |> put_layout(false)
Go to line 51
    |> put_view(HexpmWeb.ErrorView)
Go to line 52
    |> render(:"#{status}", assigns)
Go to line 53
    |> halt()
Go to line 56
  def validation_failed(conn, %Ecto.Changeset{} = changeset) do
Go to line 57
    errors = translate_errors(changeset)
Go to line 58
    render_error(conn, 422, errors: errors)
Go to line 61
  def validation_failed(conn, errors) do
Go to line 62
    render_error(conn, 422, errors: errors)
Go to line 65
  def translate_errors(changeset) do
Go to line 66
    Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
Go to line 67
      case {message, Keyword.fetch(opts, :type)} do
Go to line 68
        {"is invalid", {:ok, type}} -> type_error(type)
Go to line 69
        _ -> interpolate_errors(message, opts)
Go to line 72
    |> normalize_errors()
Go to line 75
  defp interpolate_errors(message, opts) do
Go to line 76
    Enum.reduce(opts, message, fn {key, value}, message ->
Go to line 77
      pattern = "%{#{key}}"
Go to line 79
      if String.contains?(message, pattern) do
Go to line 80
        if String.Chars.impl_for(value) do
Go to line 81
          String.replace(message, pattern, to_string(value))
Go to line 83
          raise "Unable to translate error: #{inspect({message, opts})}"
Go to line 91
  defp type_error(type), do: "expected type #{pretty_type(type)}"
Go to line 93
  defp pretty_type({:array, type}), do: "list(#{pretty_type(type)})"
Go to line 94
  defp pretty_type({:map, type}), do: "map(#{pretty_type(type)})"
Go to line 95
  defp pretty_type(type), do: type |> inspect() |> String.trim_leading(":")
Go to line 97
  # Since Changeset.traverse_errors returns `{field: [err], ...}`
Go to line 98
  # but Hex client expects `{field: err1, ...}` we normalize to the latter.
Go to line 99
  defp normalize_errors(errors) do
Go to line 100
    Enum.flat_map(errors, &normalize_key_value/1)
Go to line 101
    |> Map.new()
Go to line 104
  defp normalize_key_value({key, value}) do
Go to line 105
    case value do
Go to line 106
      _ when value == %{} ->
Go to line 109
      [%{} | _] = value ->
Go to line 110
        value = Enum.reduce(value, %{}, &Map.merge(&2, normalize_errors(&1)))
Go to line 111
        [{key, value}]
Go to line 116
      value when is_map(value) ->
Go to line 117
        [{key, normalize_errors(value)}]
Go to line 119
      [value | _] ->
Go to line 120
        [{key, value}]
Go to line 124
  def not_found(conn) do
Go to line 125
    render_error(conn, 404)
Go to line 128
  def when_stale(conn, entities, opts \\ [], fun) do
Go to line 129
    etag = etag(entities)
Go to line 130
    modified = if Keyword.get(opts, :modified, true), do: last_modified(entities)
Go to line 134
      |> put_etag(etag)
Go to line 135
      |> put_last_modified(modified)
Go to line 137
    if fresh?(conn, etag: etag, modified: modified) do
Go to line 138
      send_resp(conn, 304, "")
Go to line 140
      fun.(conn)
Go to line 144
  defp put_etag(conn, nil) do
Go to line 148
  defp put_etag(conn, etag) do
Go to line 149
    put_resp_header(conn, "etag", etag)
Go to line 152
  defp put_last_modified(conn, nil) do
Go to line 156
  defp put_last_modified(conn, modified) do
Go to line 157
    put_resp_header(conn, "last-modified", :cowboy_clock.rfc1123(modified))
Go to line 160
  defp fresh?(conn, opts) do
Go to line 161
    not expired?(conn, opts)
Go to line 164
  defp expired?(conn, opts) do
Go to line 165
    modified_since = List.first(get_req_header(conn, "if-modified-since"))
Go to line 166
    none_match = List.first(get_req_header(conn, "if-none-match"))
Go to line 168
    if modified_since || none_match do
Go to line 169
      modified_since?(modified_since, opts[:modified]) or none_match?(none_match, opts[:etag])
Go to line 175
  defp modified_since?(header, last_modified) do
Go to line 176
    if header && last_modified do
Go to line 177
      modified_since = :httpd_util.convert_request_date(String.to_charlist(header))
Go to line 178
      modified_since = :calendar.datetime_to_gregorian_seconds(modified_since)
Go to line 179
      last_modified = :calendar.datetime_to_gregorian_seconds(last_modified)
Go to line 180
      last_modified > modified_since
Go to line 186
  defp none_match?(none_match, etag) do
Go to line 187
    if none_match && etag do
Go to line 188
      none_match = Plug.Conn.Utils.list(none_match)
Go to line 189
      etag not in none_match and "*" not in none_match
Go to line 195
  defp etag(schemas) do
Go to line 198
      |> List.wrap()
Go to line 199
      |> Enum.map(&HexpmWeb.Stale.etag/1)
Go to line 200
      |> List.flatten()
Go to line 201
      |> :erlang.term_to_binary()
Go to line 203
    :crypto.hash(:md5, binary)
Go to line 204
    |> Base.encode16(case: :lower)
Go to line 207
  def last_modified(schemas) do
Go to line 209
    |> List.wrap()
Go to line 210
    |> Enum.map(&HexpmWeb.Stale.last_modified/1)
Go to line 211
    |> List.flatten()
Go to line 212
    |> Enum.reject(&is_nil/1)
Go to line 213
    |> Enum.map(&time_to_erl/1)
Go to line 214
    |> Enum.max()
Go to line 217
  defp time_to_erl(%NaiveDateTime{} = datetime), do: NaiveDateTime.to_erl(datetime)
Go to line 218
  defp time_to_erl(%DateTime{} = datetime), do: NaiveDateTime.to_erl(datetime)
Go to line 219
  defp time_to_erl(%Date{} = date), do: {Date.to_erl(date), {0, 0, 0}}
Go to line 221
  def fetch_repository(conn, _opts) do
Go to line 222
    if param = conn.params["repository"] do
Go to line 223
      if repository = Repositories.get(param, [:organization]) do
Go to line 225
        |> assign(:repository, repository)
Go to line 226
        |> assign(:organization, repository.organization)
Go to line 229
        |> HexpmWeb.AuthHelpers.forbidden("account not authorized for this action")
Go to line 234
      |> assign(:repository, nil)
Go to line 235
      |> assign(:organization, nil)
Go to line 239
  def fetch_organization(conn, _opts) do
Go to line 240
    if param = conn.params["organization"] do
Go to line 241
      if organization = Organizations.get(param) do
Go to line 242
        assign(conn, :organization, organization)
Go to line 245
        |> HexpmWeb.AuthHelpers.forbidden("account not authorized for this action")
Go to line 249
      assign(conn, :organization, nil)
Go to line 253
  def maybe_fetch_package(conn, _opts) do
Go to line 254
    repository = Repositories.get(conn.params["repository"], [:organization])
Go to line 255
    package = repository && Packages.get(repository, conn.params["name"])
Go to line 258
    |> assign(:repository, repository)
Go to line 259
    |> assign(:package, package)
Go to line 260
    |> assign(:organization, repository && repository.organization)
Go to line 263
  def fetch_release(conn, _opts) do
Go to line 264
    case Version.parse(conn.params["version"]) do
Go to line 265
      {:ok, version} ->
Go to line 266
        repository = Repositories.get(conn.params["repository"], [:organization])
Go to line 267
        package = repository && Packages.get(repository, conn.params["name"])
Go to line 268
        release = package && Releases.get(package, version)
Go to line 270
        if release do
Go to line 272
          |> assign(:repository, repository)
Go to line 273
          |> assign(:package, package)
Go to line 274
          |> assign(:release, release)
Go to line 275
          |> assign(:organization, repository && repository.organization)
Go to line 278
          |> not_found()
Go to line 283
        render_error(conn, 400, message: "invalid version: #{conn.params["version"]}")
Go to line 287
  def maybe_fetch_release(conn, _opts) do
Go to line 288
    case Version.parse(conn.params["version"]) do
Go to line 289
      {:ok, version} ->
Go to line 290
        repository = Repositories.get(conn.params["repository"], [:organization])
Go to line 291
        package = repository && Packages.get(repository, conn.params["name"])
Go to line 292
        release = package && Releases.get(package, version)
Go to line 295
        |> assign(:repository, repository)
Go to line 296
        |> assign(:package, package)
Go to line 297
        |> assign(:release, release)
Go to line 298
        |> assign(:organization, repository && repository.organization)
Go to line 301
        render_error(conn, 400, message: "invalid version: #{conn.params["version"]}")
Go to line 305
  def required_params(conn, required_param_names) do
Go to line 306
    remaining = required_param_names -- Map.keys(conn.params)
Go to line 308
    if remaining == [] do
Go to line 311
      names = Enum.map_join(remaining, ", ", &inspect/1)
Go to line 312
      message = "missing required parameters: #{names}"
Go to line 313
      render_error(conn, 400, message: message)
Go to line 317
  def authorize(conn, opts) do
Go to line 318
    HexpmWeb.AuthHelpers.authorized(conn, opts)
Go to line 321
  def maybe_authorize(conn, opts) do
Go to line 322
    HexpmWeb.AuthHelpers.maybe_authorized(conn, opts)
Go to line 325
  def audit_data(conn) do
Go to line 326
    user_or_organization = conn.assigns.current_user || conn.assigns.current_organization
Go to line 327
    {user_or_organization, conn.assigns.user_agent}
Go to line 330
  def password_auth(username, password) do
Go to line 331
    case Auth.password_auth(username, password) do
Go to line 332
      {:ok, %{user: user, email: email}} ->
Go to line 333
        if email.verified,
Go to line 334
          do: {:ok, user},
Go to line 335
          else: {:error, :unconfirmed}
Go to line 338
        {:error, :wrong}
Go to line 342
  def auth_error_message(:wrong), do: "Invalid username, email or password."
Go to line 344
  def auth_error_message(:unconfirmed),
Go to line 345
    do: "Email has not been verified yet. You can resend the verification email below."
Go to line 347
  def password_breached_message(conn, _opts) do
Go to line 348
    # docs_path + anchor #password-security
Go to line 349
    "The password you provided has previously been breached. " <>
Go to line 350
      "To increase your security, please change your password." <>
Go to line 351
      "<br /><a class=\"small\" href=\"#{Routes.docs_path(conn, :faq)}#password-security\">" <>
Go to line 352
      "Learn more about our password security.</a>"
Go to line 355
  def requires_login(conn, _opts) do
Go to line 356
    if logged_in?(conn) do
Go to line 359
      redirect(conn, to: Routes.login_path(conn, :show, return: conn.request_path))
Go to line 364
  def logged_in?(conn) do
Go to line 365
    !!conn.assigns[:current_user]
Go to line 368
  def nillify_params(conn, keys) do
Go to line 370
      Enum.reduce(keys, conn.params, fn key, params ->
Go to line 371
        case Map.fetch(conn.params, key) do
Go to line 372
          {:ok, value} -> Map.put(params, key, scrub_param(value))
Go to line 373
          :error -> params
Go to line 377
    %{conn | params: params}
Go to line 380
  defp scrub_param(%{__struct__: mod} = struct) when is_atom(mod) do
Go to line 384
  defp scrub_param(%{} = param) do
Go to line 385
    Enum.reduce(param, %{}, fn {k, v}, acc ->
Go to line 386
      Map.put(acc, k, scrub_param(v))
Go to line 390
  defp scrub_param(param) when is_list(param) do
Go to line 391
    Enum.map(param, &scrub_param/1)
Go to line 394
  defp scrub_param(param) do
Go to line 395
    if scrub?(param), do: nil, else: param
Go to line 398
  defp scrub?(" " <> rest), do: scrub?(rest)
Go to line 399
  defp scrub?(""), do: true
Go to line 400
  defp scrub?(_), do: false