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 Hexpm.Repository.Package do
Go to line 2
  use Hexpm.Schema
Go to line 3
  import Ecto.Query, only: [from: 2]
Go to line 5
  @derive {HexpmWeb.Stale, assocs: [:releases, :owners, :downloads]}
Go to line 6
  @derive {Phoenix.Param, key: :name}
Go to line 8
  schema "packages" do
Go to line 9
    field :name, :string
Go to line 10
    field :docs_updated_at, :utc_datetime_usec
Go to line 11
    field :latest_version, Hexpm.Version, virtual: true
Go to line 12
    timestamps()
Go to line 14
    belongs_to :repository, Repository
Go to line 15
    has_many :releases, Release
Go to line 16
    has_many :package_owners, PackageOwner
Go to line 17
    has_many :owners, through: [:package_owners, :user]
Go to line 18
    has_many :downloads, PackageDownload
Go to line 19
    has_many :package_reports, PackageReport
Go to line 20
    embeds_one :meta, PackageMetadata, on_replace: :delete
Go to line 23
  @elixir_names ~w(eex elixir elixirc ex_unit iex logger mix)
Go to line 24
  @tool_names ~w(erlang typer to_erl run_erl escript erlc erl epmd dialyzer ct_run rebar rebar3 hex hexpm mix_hex)
Go to line 25
  @otp_names ~w(
Go to line 26
    appmon asn1 common_test compiler cosEvent cosEventDomain cosFileTransfer
Go to line 27
    cosNotification cosProperty cosTime cosTransactions crypto debugger
Go to line 28
    dialyzer diameter edoc eldap erl_docgen erl_interface erts et eunit gs hipe
Go to line 29
    ic inets jinterface kernel Makefile megaco mnesia observer odbc orber
Go to line 30
    os_mon ose otp_mibs parsetools percept pman public_key reltool runtime_tools
Go to line 31
    sasl snmp ssh ssl stdlib syntax_tools test_server toolbar tools tv typer
Go to line 32
    webtool wx xmerl
Go to line 34
  @inets_names ~w(ftp tftp httpc httpd)
Go to line 35
  @app_names ~w(toucan exla nx live_book axon torchx net http net_http mint_pool)
Go to line 36
  @windows_names ~w(
Go to line 37
    nul con prn aux com1 com2 com3 com4 com5 com6 com7 com8 com9 lpt1 lpt2
Go to line 38
    lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9
Go to line 41
  # Backwards compatible for tests, fixed in Hex at 2017-07-29
Go to line 42
  if Mix.env() == :hex do
Go to line 43
    @generic_names []
Go to line 45
    @generic_names ~w(package organization www)
Go to line 48
  @reserved_names Enum.concat([
Go to line 49
                    @elixir_names,
Go to line 50
                    @otp_names,
Go to line 51
                    @inets_names,
Go to line 52
                    @tool_names,
Go to line 53
                    @app_names,
Go to line 54
                    @windows_names,
Go to line 55
                    @generic_names
Go to line 58
  def build(repository, user, params) do
Go to line 59
    package =
Go to line 60
      build_assoc(repository, :packages)
Go to line 61
      |> Map.put(:repository, repository)
Go to line 64
    |> cast(params, ~w(name)a)
Go to line 65
    |> unique_constraint(:name, name: "packages_repository_id__lower_name_index")
Go to line 66
    |> validate_required(:name)
Go to line 67
    |> validate_length(:name, min: 2)
Go to line 68
    |> validate_format(:name, ~r"^[a-z]\w*$")
Go to line 69
    |> validate_exclusion(:name, @reserved_names)
Go to line 70
    |> cast_embed(:meta, with: &PackageMetadata.changeset(&1, &2, package), required: true)
Go to line 71
    |> put_first_owner(user, repository)
Go to line 74
  @spec delete({map, any} | %{__struct__: atom | %{__changeset__: any}}) :: Ecto.Changeset.t()
Go to line 75
  def delete(package) do
Go to line 76
    foreign_key_constraint(
Go to line 77
      change(package),
Go to line 79
      name: "requirements_dependency_id_fkey",
Go to line 80
      message: "you cannot delete this package because other packages depend on it"
Go to line 84
  defp put_first_owner(changeset, user, repository) do
Go to line 85
    if repository.public do
Go to line 86
      put_assoc(changeset, :package_owners, [%PackageOwner{user_id: user.id}])
Go to line 88
      changeset
Go to line 92
  def update(package, params) do
Go to line 93
    cast(package, params, [])
Go to line 94
    |> cast_embed(:meta, with: &PackageMetadata.changeset(&1, &2, package), required: true)
Go to line 95
    |> validate_metadata_name()
Go to line 98
  def package_owner(package, user, level \\ "maintainer") do
Go to line 99
    levels = PackageOwner.level_or_higher(level)
Go to line 102
      po in PackageOwner,
Go to line 103
      left_join: ou in OrganizationUser,
Go to line 104
      on: ou.organization_id == ^package.repository.organization_id,
Go to line 105
      where: ou.user_id == ^user.id or ^package.repository.public,
Go to line 106
      where: po.package_id == ^package.id,
Go to line 107
      where: po.user_id == ^user.id,
Go to line 108
      where: po.level in ^levels,
Go to line 109
      select: count(po.id) >= 1
Go to line 113
  def organization_owner(package, user, level \\ "maintainer") do
Go to line 114
    role = PackageOwner.level_to_organization_role(level)
Go to line 115
    roles = Organization.role_or_higher(role)
Go to line 118
      po in PackageOwner,
Go to line 119
      join: u in assoc(po, :user),
Go to line 120
      join: ou in OrganizationUser,
Go to line 121
      on: u.organization_id == ou.organization_id,
Go to line 122
      where: po.package_id == ^package.id,
Go to line 123
      where: ou.user_id == ^user.id,
Go to line 124
      where: ou.role in ^roles,
Go to line 125
      select: count(po.id) >= 1
Go to line 129
  def all(repositories, page, count, search, sort, fields) do
Go to line 131
      p in assoc(repositories, :packages),
Go to line 132
      join: r in assoc(p, :repository),
Go to line 133
      preload: :downloads
Go to line 135
    |> sort(sort)
Go to line 136
    |> Hexpm.Utils.paginate(page, count)
Go to line 137
    |> search(search)
Go to line 138
    |> fields(fields)
Go to line 141
  def recent(repository, count) do
Go to line 143
      p in assoc(repository, :packages),
Go to line 144
      order_by: [desc: p.inserted_at],
Go to line 145
      limit: ^count,
Go to line 146
      select: {p.name, p.inserted_at, p.meta}
Go to line 150
  def count() do
Go to line 151
    from(p in Package, select: count(p.id))
Go to line 154
  def count(repositories, search) do
Go to line 156
      p in assoc(repositories, :packages),
Go to line 157
      join: r in assoc(p, :repository),
Go to line 158
      select: count(p.id)
Go to line 160
    |> search(search)
Go to line 163
  defp validate_metadata_name(changeset) do
Go to line 164
    name = get_field(changeset, :name)
Go to line 165
    meta_name = changeset.params["meta"]["name"]
Go to line 167
    if !meta_name || name == meta_name do
Go to line 170
      add_error(changeset, :name, "metadata does not match package name")
Go to line 174
  defp fields(query, nil) do
Go to line 178
  defp fields(query, fields) do
Go to line 179
    from(p in query, select: ^fields)
Go to line 182
  defmacrop description_query(p, search) do
Go to line 185
        "to_tsvector('english', regexp_replace((?->'description')::text, '/', ' ')) @@ to_tsquery('english', ?)",
Go to line 186
        unquote(p).meta,
Go to line 187
        ^unquote(search)
Go to line 192
  defmacrop name_query(p, search) do
Go to line 194
      ilike(fragment("?::text", unquote(p).name), ^unquote(search))
Go to line 198
  defp search(query, nil) do
Go to line 202
  defp search(query, {:letter, letter}) do
Go to line 203
    search = letter <> "%"
Go to line 204
    from(p in query, where: name_query(p, search))
Go to line 207
  defp search(query, search) when is_binary(search) do
Go to line 208
    case parse_search(search) do
Go to line 209
      {:ok, params} ->
Go to line 210
        Enum.reduce(params, query, fn {k, v}, q -> search_param(k, v, q) end)
Go to line 213
        basic_search(query, search)
Go to line 217
  defp basic_search(query, search) do
Go to line 218
    {repository, package} = name_search(search)
Go to line 219
    description = description_search(search)
Go to line 221
    if repository do
Go to line 223
        [p, r] in query,
Go to line 225
          (name_query(p, package) and name_query(r, repository)) or
Go to line 226
            description_query(p, description)
Go to line 229
      from(p in query, where: name_query(p, package) or description_query(p, description))
Go to line 233
  # TODO: add repository param
Go to line 234
  defp search_param("name", search, query) do
Go to line 235
    case String.split(search, "/", parts: 2) do
Go to line 236
      [repository, package] ->
Go to line 238
          [p, r] in query,
Go to line 239
          where: name_query(p, extra_name_search(package)),
Go to line 240
          where: name_query(r, extra_name_search(repository))
Go to line 244
        search = extra_name_search(search)
Go to line 245
        from(p in query, where: name_query(p, search))
Go to line 249
  defp search_param("description", search, query) do
Go to line 250
    search = description_search(search)
Go to line 251
    from(p in query, where: description_query(p, search))
Go to line 254
  defp search_param("extra", search, query) do
Go to line 255
    [value | keys] =
Go to line 257
      |> String.split(",")
Go to line 258
      |> Enum.reverse()
Go to line 260
    extra = extra_map(keys, extra_value(value))
Go to line 262
    from(p in query, where: fragment("?->'extra' @> ?", p.meta, ^extra))
Go to line 265
  defp search_param("depends", search, query) do
Go to line 266
    case String.split(search, ":", parts: 2) do
Go to line 267
      [repository, package] ->
Go to line 269
          p in query,
Go to line 270
          join: pd in Hexpm.Repository.PackageDependant,
Go to line 271
          on: p.id == pd.dependant_id,
Go to line 272
          where: pd.name == ^package,
Go to line 273
          where: pd.repo == ^repository
Go to line 278
          p in query,
Go to line 279
          join: pd in Hexpm.Repository.PackageDependant,
Go to line 280
          on: p.id == pd.dependant_id,
Go to line 281
          where: pd.name == ^search
Go to line 286
  defp search_param(_, _, query) do
Go to line 290
  defp extra_value(<<"[", value::binary>>) do
Go to line 292
    |> String.trim_trailing("]")
Go to line 293
    |> String.split(",")
Go to line 294
    |> Enum.map(&try_integer/1)
Go to line 297
  defp extra_value(value), do: try_integer(value)
Go to line 299
  defp try_integer(string) do
Go to line 300
    case Integer.parse(string) do
Go to line 301
      {int, ""} -> int
Go to line 302
      _ -> string
Go to line 306
  defp extra_map([], m), do: m
Go to line 308
  defp extra_map([h | t], m) do
Go to line 309
    extra_map(t, %{h => m})
Go to line 312
  defp like_search(search, :contains), do: "%" <> search <> "%"
Go to line 313
  defp like_search(search, :equals), do: search
Go to line 315
  defp escape_search(search) do
Go to line 316
    String.replace(search, ~r"(%|_|\\)"u, "\\\\\\1")
Go to line 319
  defp name_search(search) do
Go to line 320
    case String.split(search, "/", parts: 2) do
Go to line 321
      [repository, package] ->
Go to line 322
        {do_name_search(repository), do_name_search(package)}
Go to line 325
        {nil, do_name_search(search)}
Go to line 329
  defp do_name_search(search) do
Go to line 331
    |> escape_search()
Go to line 332
    |> like_search(search_filter(search))
Go to line 335
  defp search_filter(search) do
Go to line 336
    if String.length(search) >= 3 do
Go to line 343
  defp description_search(search) do
Go to line 345
    |> String.replace(~r/\//u, " ")
Go to line 346
    |> String.replace(~r/[^\w\s]/u, "")
Go to line 347
    |> String.trim()
Go to line 348
    |> String.replace(~r"\s+"u, " | ")
Go to line 351
  def extra_name_search(search) do
Go to line 353
    |> escape_search()
Go to line 354
    |> String.replace(~r/(^\*)|(\*$)/u, "%")
Go to line 357
  defp sort(query, :name) do
Go to line 358
    from(p in query, order_by: p.name)
Go to line 361
  defp sort(query, :inserted_at) do
Go to line 362
    from(p in query, order_by: [desc: p.inserted_at])
Go to line 365
  defp sort(query, :updated_at) do
Go to line 366
    from(p in query, order_by: [desc: p.updated_at])
Go to line 369
  defp sort(query, :recently_published) do
Go to line 371
      p in query,
Go to line 372
      join: r in Release,
Go to line 373
      on: p.id == r.package_id,
Go to line 374
      group_by: p.id,
Go to line 375
      order_by: [desc: max(r.inserted_at), desc: p.id]
Go to line 379
  defp sort(query, :total_downloads) do
Go to line 381
      p in query,
Go to line 382
      left_join: d in PackageDownload,
Go to line 383
      on: p.id == d.package_id and (d.view == "all" or is_nil(d.view)),
Go to line 384
      order_by: [fragment("? DESC NULLS LAST", d.downloads)]
Go to line 388
  defp sort(query, :recent_downloads) do
Go to line 390
      p in query,
Go to line 391
      left_join: d in PackageDownload,
Go to line 392
      on: p.id == d.package_id and (d.view == "recent" or is_nil(d.view)),
Go to line 393
      order_by: [fragment("? DESC NULLS LAST", d.downloads)]
Go to line 397
  defp sort(query, nil) do
Go to line 401
  defp parse_search(search) do
Go to line 403
    |> String.trim_leading()
Go to line 404
    |> parse_params([])
Go to line 407
  defp parse_params("", params), do: {:ok, Enum.reverse(params)}
Go to line 409
  defp parse_params(tail, params) do
Go to line 410
    with {:ok, key, tail} <- parse_key(tail),
Go to line 411
         {:ok, value, tail} <- parse_value(tail) do
Go to line 412
      parse_params(tail, [{key, value} | params])
Go to line 414
      _ -> :error
Go to line 418
  defp parse_key(string) do
Go to line 419
    with [k, tail] when k != "" <- String.split(string, ":", parts: 2) do
Go to line 420
      {:ok, k, String.trim_leading(tail)}
Go to line 424
  defp parse_value(string) do
Go to line 425
    case string do
Go to line 426
      "\"" <> rest ->
Go to line 427
        with [v, tail] <- String.split(rest, "\"", parts: 2) do
Go to line 428
          {:ok, v, String.trim_leading(tail)}
Go to line 432
        case String.split(string, " ", parts: 2) do
Go to line 433
          [value] -> {:ok, value, ""}
Go to line 434
          [value, tail] -> {:ok, value, String.trim_leading(tail)}