├── test ├── test_helper.exs ├── shady_urls_web │ ├── views │ │ ├── page_view_test.exs │ │ ├── layout_view_test.exs │ │ └── error_view_test.exs │ └── controllers │ │ └── page_controller_test.exs └── support │ └── conn_case.ex ├── phoenix_static_buildpack.config ├── elixir_buildpack.config ├── lib ├── shady_urls_web │ ├── templates │ │ ├── layout │ │ │ ├── redirect.html.heex │ │ │ ├── app.html.heex │ │ │ └── root.html.heex │ │ └── page │ │ │ ├── result.html.heex │ │ │ ├── redirect.html.heex │ │ │ └── index.html.heex │ ├── views │ │ ├── page_view.ex │ │ ├── layout_view.ex │ │ ├── error_view.ex │ │ └── error_helpers.ex │ ├── controllers │ │ └── page_controller.ex │ ├── router.ex │ ├── telemetry.ex │ └── endpoint.ex ├── shady_urls.ex ├── shady_urls │ ├── database.ex │ ├── application.ex │ └── generator.ex └── shady_urls_web.ex ├── priv └── static │ ├── favicon.ico │ ├── robots.txt.gz │ ├── favicon-a8ca4e3a2bb8fea46a9ee9e102e7d3eb.ico │ ├── robots-c9e52225d69203eceb967fc6fad66420.txt.gz │ ├── robots.txt │ └── robots-c9e52225d69203eceb967fc6fad66420.txt ├── .formatter.exs ├── config ├── test.exs ├── config.exs ├── prod.exs ├── runtime.exs └── dev.exs ├── README.md ├── .gitignore ├── mix.exs └── mix.lock /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /phoenix_static_buildpack.config: -------------------------------------------------------------------------------- 1 | node_version=16.14.0 -------------------------------------------------------------------------------- /elixir_buildpack.config: -------------------------------------------------------------------------------- 1 | elixir_version=1.14.0 2 | erlang_version=25.0.4 -------------------------------------------------------------------------------- /lib/shady_urls_web/templates/layout/redirect.html.heex: -------------------------------------------------------------------------------- 1 | <%= @inner_content %> -------------------------------------------------------------------------------- /priv/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirkavrba/shady-urls/HEAD/priv/static/favicon.ico -------------------------------------------------------------------------------- /priv/static/robots.txt.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirkavrba/shady-urls/HEAD/priv/static/robots.txt.gz -------------------------------------------------------------------------------- /lib/shady_urls_web/views/page_view.ex: -------------------------------------------------------------------------------- 1 | defmodule ShadyUrlsWeb.PageView do 2 | use ShadyUrlsWeb, :view 3 | end 4 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | import_deps: [:phoenix], 3 | inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /lib/shady_urls_web/templates/layout/app.html.heex: -------------------------------------------------------------------------------- 1 |
2 | <%= @inner_content %> 3 |
4 | -------------------------------------------------------------------------------- /test/shady_urls_web/views/page_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ShadyUrlsWeb.PageViewTest do 2 | use ShadyUrlsWeb.ConnCase, async: true 3 | end 4 | -------------------------------------------------------------------------------- /priv/static/favicon-a8ca4e3a2bb8fea46a9ee9e102e7d3eb.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirkavrba/shady-urls/HEAD/priv/static/favicon-a8ca4e3a2bb8fea46a9ee9e102e7d3eb.ico -------------------------------------------------------------------------------- /priv/static/robots-c9e52225d69203eceb967fc6fad66420.txt.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirkavrba/shady-urls/HEAD/priv/static/robots-c9e52225d69203eceb967fc6fad66420.txt.gz -------------------------------------------------------------------------------- /priv/static/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | User-agent: * 5 | Disallow: / 6 | -------------------------------------------------------------------------------- /priv/static/robots-c9e52225d69203eceb967fc6fad66420.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | User-agent: * 5 | Disallow: / 6 | -------------------------------------------------------------------------------- /test/shady_urls_web/controllers/page_controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ShadyUrlsWeb.PageControllerTest do 2 | use ShadyUrlsWeb.ConnCase 3 | 4 | test "GET /", %{conn: conn} do 5 | conn = get(conn, "/") 6 | assert html_response(conn, 200) =~ "Welcome to Phoenix!" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/shady_urls.ex: -------------------------------------------------------------------------------- 1 | defmodule ShadyUrls do 2 | @moduledoc """ 3 | ShadyUrls keeps the contexts that define your domain 4 | and business logic. 5 | 6 | Contexts are also responsible for managing your data, regardless 7 | if it comes from the database, an external API or others. 8 | """ 9 | end 10 | -------------------------------------------------------------------------------- /lib/shady_urls_web/views/layout_view.ex: -------------------------------------------------------------------------------- 1 | defmodule ShadyUrlsWeb.LayoutView do 2 | use ShadyUrlsWeb, :view 3 | 4 | # Phoenix LiveDashboard is available only in development by default, 5 | # so we instruct Elixir to not warn if the dashboard route is missing. 6 | @compile {:no_warn_undefined, {Routes, :live_dashboard_path, 2}} 7 | end 8 | -------------------------------------------------------------------------------- /test/shady_urls_web/views/layout_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ShadyUrlsWeb.LayoutViewTest do 2 | use ShadyUrlsWeb.ConnCase, async: true 3 | 4 | # When testing helpers, you may want to import Phoenix.HTML and 5 | # use functions such as safe_to_string() to convert the helper 6 | # result into an HTML string. 7 | # import Phoenix.HTML 8 | end 9 | -------------------------------------------------------------------------------- /lib/shady_urls_web/templates/page/result.html.heex: -------------------------------------------------------------------------------- 1 |

Link generated!

2 | 3 |
4 |
5 | <%= @original %> → 6 |
7 |
8 |
9 | 10 | Make another link shady -------------------------------------------------------------------------------- /lib/shady_urls_web/templates/page/redirect.html.heex: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %{} end, name: __MODULE__) 6 | end 7 | 8 | @spec insert_redirect(String.t(), String.t(), boolean()) :: no_return() 9 | def insert_redirect(path, redirect, preview) when is_binary(path) and is_binary(redirect) and is_boolean(preview) do 10 | Agent.update(__MODULE__, fn db -> Map.put(db, path, {redirect, preview}) end) 11 | end 12 | 13 | @spec lookup_redirect(String.t()) :: {:ok, String.t(), boolean()} | :not_found 14 | def lookup_redirect(path) when is_binary(path) do 15 | result = Agent.get(__MODULE__, fn db -> db[path] end) 16 | 17 | case result do 18 | nil -> :not_found 19 | {redirect, preview} -> {:ok, redirect, preview} 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/shady_urls_web/templates/page/index.html.heex: -------------------------------------------------------------------------------- 1 | . 2 |
3 | 4 | 5 |
6 |
7 |
8 |
9 | 10 |
11 | 15 |
16 | 17 |
18 |
-------------------------------------------------------------------------------- /lib/shady_urls_web/views/error_helpers.ex: -------------------------------------------------------------------------------- 1 | defmodule ShadyUrlsWeb.ErrorHelpers do 2 | @moduledoc """ 3 | Conveniences for translating and building error messages. 4 | """ 5 | 6 | use Phoenix.HTML 7 | 8 | @doc """ 9 | Generates tag for inlined form input errors. 10 | """ 11 | def error_tag(form, field) do 12 | Enum.map(Keyword.get_values(form.errors, field), fn error -> 13 | content_tag(:span, translate_error(error), 14 | class: "invalid-feedback", 15 | phx_feedback_for: input_name(form, field) 16 | ) 17 | end) 18 | end 19 | 20 | @doc """ 21 | Translates an error message. 22 | """ 23 | def translate_error({msg, opts}) do 24 | # Because the error messages we show in our forms and APIs 25 | # are defined inside Ecto, we need to translate them dynamically. 26 | Enum.reduce(opts, msg, fn {key, value}, acc -> 27 | String.replace(acc, "%{#{key}}", fn _ -> to_string(value) end) 28 | end) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | shady_urls-*.tar 24 | 25 | # Ignore assets that are produced by build tools. 26 | /priv/static/assets/ 27 | 28 | # Ignore digested assets cache. 29 | /priv/static/cache_manifest.json 30 | 31 | # In case you use Node.js/npm, you want to ignore these. 32 | npm-debug.log 33 | /assets/node_modules/ 34 | 35 | # Mnesia persistence files 36 | .mnesia -------------------------------------------------------------------------------- /lib/shady_urls/application.ex: -------------------------------------------------------------------------------- 1 | defmodule ShadyUrls.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | 8 | @impl true 9 | def start(_type, _args) do 10 | children = [ 11 | # Start the Telemetry supervisor 12 | ShadyUrlsWeb.Telemetry, 13 | # Start the PubSub system 14 | {Phoenix.PubSub, name: ShadyUrls.PubSub}, 15 | # Start the Endpoint (http/https) 16 | ShadyUrlsWeb.Endpoint, 17 | # Start a worker by calling: ShadyUrls.Worker.start_link(arg) 18 | # {ShadyUrls.Worker, arg}, 19 | ShadyUrls.Database 20 | ] 21 | 22 | # See https://hexdocs.pm/elixir/Supervisor.html 23 | # for other strategies and supported options 24 | opts = [strategy: :one_for_one, name: ShadyUrls.Supervisor] 25 | Supervisor.start_link(children, opts) 26 | end 27 | 28 | # Tell Phoenix to update the endpoint configuration 29 | # whenever the application is updated. 30 | @impl true 31 | def config_change(changed, _new, removed) do 32 | ShadyUrlsWeb.Endpoint.config_change(changed, removed) 33 | :ok 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/support/conn_case.ex: -------------------------------------------------------------------------------- 1 | defmodule ShadyUrlsWeb.ConnCase do 2 | @moduledoc """ 3 | This module defines the test case to be used by 4 | tests that require setting up a connection. 5 | 6 | Such tests rely on `Phoenix.ConnTest` and also 7 | import other functionality to make it easier 8 | to build common data structures and query the data layer. 9 | 10 | Finally, if the test case interacts with the database, 11 | we enable the SQL sandbox, so changes done to the database 12 | are reverted at the end of every test. If you are using 13 | PostgreSQL, you can even run database tests asynchronously 14 | by setting `use ShadyUrlsWeb.ConnCase, async: true`, although 15 | this option is not recommended for other databases. 16 | """ 17 | 18 | use ExUnit.CaseTemplate 19 | 20 | using do 21 | quote do 22 | # Import conveniences for testing with connections 23 | import Plug.Conn 24 | import Phoenix.ConnTest 25 | import ShadyUrlsWeb.ConnCase 26 | 27 | alias ShadyUrlsWeb.Router.Helpers, as: Routes 28 | 29 | # The default endpoint for testing 30 | @endpoint ShadyUrlsWeb.Endpoint 31 | end 32 | end 33 | 34 | setup _tags do 35 | {:ok, conn: Phoenix.ConnTest.build_conn()} 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/shady_urls_web/templates/layout/root.html.heex: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= live_title_tag assigns[:page_title] || "Shady URLs" %> 9 | 10 | 11 | 12 | 13 |
14 |

Shady URLs

15 |
16 | 17 | Please note that this is just a fun side project and that your redirects can stop working at any time (I store them in memory using elixir agents lmao) 18 | 19 |
20 |
21 | <%= @inner_content %> 22 | 23 | 24 | -------------------------------------------------------------------------------- /lib/shady_urls_web/controllers/page_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule ShadyUrlsWeb.PageController do 2 | use ShadyUrlsWeb, :controller 3 | 4 | alias ShadyUrls.Database 5 | alias ShadyUrls.Generator 6 | 7 | @spec index(Plug.Conn.t(), any) :: Plug.Conn.t() 8 | def index(conn, _params) do 9 | render(conn, "index.html") 10 | end 11 | 12 | @spec handle(Plug.Conn.t(), map) :: Plug.Conn.t() 13 | def handle(conn, %{ "path" => path }) do 14 | case Database.lookup_redirect(path) do 15 | :not_found -> redirect(conn, to: Routes.page_path(ShadyUrlsWeb.Endpoint, :index)) 16 | {:ok, url, true} -> redirect(conn, external: url) 17 | {:ok, url, false} -> 18 | conn 19 | |> put_root_layout({ ShadyUrlsWeb.LayoutView, "redirect.html"}) 20 | |> assign(:redirect, url) 21 | |> render("redirect.html") 22 | end 23 | end 24 | 25 | @spec generate(Plug.Conn.t(), map) :: Plug.Conn.t() 26 | def generate(conn, %{ "url" => url, "preview" => preview }) do 27 | url = Generator.normalize_url(url) 28 | path = Generator.generate_shady_url(url) 29 | link = Routes.page_url(ShadyUrlsWeb.Endpoint, :handle, path) 30 | preview = (preview == "true") 31 | 32 | Database.insert_redirect(path, url, preview) 33 | 34 | conn 35 | |> assign(:original, url) 36 | |> assign(:generated, link) 37 | |> render("result.html") 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Config module. 3 | # 4 | # This configuration file is loaded before any dependency and 5 | # is restricted to this project. 6 | 7 | # General application configuration 8 | import Config 9 | 10 | # Configures the endpoint 11 | config :shady_urls, ShadyUrlsWeb.Endpoint, 12 | url: [host: "localhost"], 13 | render_errors: [view: ShadyUrlsWeb.ErrorView, accepts: ~w(html json), layout: false], 14 | pubsub_server: ShadyUrls.PubSub, 15 | live_view: [signing_salt: "uRdT7CCC"] 16 | 17 | # Configure esbuild (the version is required) 18 | config :esbuild, 19 | version: "0.14.29", 20 | default: [ 21 | args: 22 | ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*), 23 | cd: Path.expand("../assets", __DIR__), 24 | env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} 25 | ] 26 | 27 | # Configures Elixir's Logger 28 | config :logger, :console, 29 | format: "$time $metadata[$level] $message\n", 30 | metadata: [:request_id] 31 | 32 | # Use Jason for JSON parsing in Phoenix 33 | config :phoenix, :json_library, Jason 34 | 35 | # Import environment specific config. This must remain at the bottom 36 | # of this file so it overrides the configuration defined above. 37 | import_config "#{config_env()}.exs" 38 | -------------------------------------------------------------------------------- /lib/shady_urls_web/router.ex: -------------------------------------------------------------------------------- 1 | defmodule ShadyUrlsWeb.Router do 2 | use ShadyUrlsWeb, :router 3 | 4 | pipeline :browser do 5 | plug :accepts, ["html"] 6 | plug :fetch_session 7 | plug :fetch_live_flash 8 | plug :put_root_layout, {ShadyUrlsWeb.LayoutView, :root} 9 | plug :protect_from_forgery 10 | plug :put_secure_browser_headers 11 | end 12 | 13 | pipeline :api do 14 | plug :accepts, ["json"] 15 | end 16 | 17 | scope "/", ShadyUrlsWeb do 18 | pipe_through :browser 19 | 20 | get "/", PageController, :index 21 | get "/:path", PageController, :handle 22 | 23 | post "/generate", PageController, :generate 24 | end 25 | 26 | # Other scopes may use custom stacks. 27 | # scope "/api", ShadyUrlsWeb do 28 | # pipe_through :api 29 | # end 30 | 31 | # Enables LiveDashboard only for development 32 | # 33 | # If you want to use the LiveDashboard in production, you should put 34 | # it behind authentication and allow only admins to access it. 35 | # If your application does not have an admins-only section yet, 36 | # you can use Plug.BasicAuth to set up some basic authentication 37 | # as long as you are also using SSL (which you should anyway). 38 | if Mix.env() in [:dev, :test] do 39 | import Phoenix.LiveDashboard.Router 40 | 41 | scope "/" do 42 | pipe_through :browser 43 | 44 | live_dashboard "/dashboard", metrics: ShadyUrlsWeb.Telemetry 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/shady_urls_web/telemetry.ex: -------------------------------------------------------------------------------- 1 | defmodule ShadyUrlsWeb.Telemetry do 2 | use Supervisor 3 | import Telemetry.Metrics 4 | 5 | def start_link(arg) do 6 | Supervisor.start_link(__MODULE__, arg, name: __MODULE__) 7 | end 8 | 9 | @impl true 10 | def init(_arg) do 11 | children = [ 12 | # Telemetry poller will execute the given period measurements 13 | # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics 14 | {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} 15 | # Add reporters as children of your supervision tree. 16 | # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()} 17 | ] 18 | 19 | Supervisor.init(children, strategy: :one_for_one) 20 | end 21 | 22 | def metrics do 23 | [ 24 | # Phoenix Metrics 25 | summary("phoenix.endpoint.stop.duration", 26 | unit: {:native, :millisecond} 27 | ), 28 | summary("phoenix.router_dispatch.stop.duration", 29 | tags: [:route], 30 | unit: {:native, :millisecond} 31 | ), 32 | 33 | # VM Metrics 34 | summary("vm.memory.total", unit: {:byte, :kilobyte}), 35 | summary("vm.total_run_queue_lengths.total"), 36 | summary("vm.total_run_queue_lengths.cpu"), 37 | summary("vm.total_run_queue_lengths.io") 38 | ] 39 | end 40 | 41 | defp periodic_measurements do 42 | [ 43 | # A module, function and arguments to be invoked periodically. 44 | # This function must call :telemetry.execute/3 and a metric must be added above. 45 | # {ShadyUrlsWeb, :count_users, []} 46 | ] 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/shady_urls_web/endpoint.ex: -------------------------------------------------------------------------------- 1 | defmodule ShadyUrlsWeb.Endpoint do 2 | use Phoenix.Endpoint, otp_app: :shady_urls 3 | 4 | # The session will be stored in the cookie and signed, 5 | # this means its contents can be read but not tampered with. 6 | # Set :encryption_salt if you would also like to encrypt it. 7 | @session_options [ 8 | store: :cookie, 9 | key: "_shady_urls_key", 10 | signing_salt: "vK9FtGOy" 11 | ] 12 | 13 | socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]] 14 | 15 | # Serve at "/" the static files from "priv/static" directory. 16 | # 17 | # You should set gzip to true if you are running phx.digest 18 | # when deploying your static files in production. 19 | plug Plug.Static, 20 | at: "/", 21 | from: :shady_urls, 22 | gzip: false, 23 | only: ~w(assets fonts images favicon.ico robots.txt) 24 | 25 | # Code reloading can be explicitly enabled under the 26 | # :code_reloader configuration of your endpoint. 27 | if code_reloading? do 28 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket 29 | plug Phoenix.LiveReloader 30 | plug Phoenix.CodeReloader 31 | end 32 | 33 | plug Phoenix.LiveDashboard.RequestLogger, 34 | param_key: "request_logger", 35 | cookie_key: "request_logger" 36 | 37 | plug Plug.RequestId 38 | plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] 39 | 40 | plug Plug.Parsers, 41 | parsers: [:urlencoded, :multipart, :json], 42 | pass: ["*/*"], 43 | json_decoder: Phoenix.json_library() 44 | 45 | plug Plug.MethodOverride 46 | plug Plug.Head 47 | plug Plug.Session, @session_options 48 | plug ShadyUrlsWeb.Router 49 | end 50 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule ShadyUrls.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :shady_urls, 7 | version: "0.1.0", 8 | elixir: "~> 1.12", 9 | elixirc_paths: elixirc_paths(Mix.env()), 10 | compilers: Mix.compilers(), 11 | start_permanent: Mix.env() == :prod, 12 | aliases: aliases(), 13 | deps: deps() 14 | ] 15 | end 16 | 17 | # Configuration for the OTP application. 18 | # 19 | # Type `mix help compile.app` for more information. 20 | def application do 21 | [ 22 | mod: {ShadyUrls.Application, []}, 23 | extra_applications: [:logger, :runtime_tools] 24 | ] 25 | end 26 | 27 | # Specifies which paths to compile per environment. 28 | defp elixirc_paths(:test), do: ["lib", "test/support"] 29 | defp elixirc_paths(_), do: ["lib"] 30 | 31 | # Specifies your project dependencies. 32 | # 33 | # Type `mix help deps` for examples and options. 34 | defp deps do 35 | [ 36 | {:phoenix, "~> 1.6.11"}, 37 | {:phoenix_html, "~> 3.0"}, 38 | {:phoenix_live_reload, "~> 1.2", only: :dev}, 39 | {:phoenix_live_view, "~> 0.17.5"}, 40 | {:floki, ">= 0.30.0", only: :test}, 41 | {:phoenix_live_dashboard, "~> 0.6"}, 42 | {:esbuild, "~> 0.4", runtime: Mix.env() == :dev}, 43 | {:telemetry_metrics, "~> 0.6"}, 44 | {:telemetry_poller, "~> 1.0"}, 45 | {:jason, "~> 1.2"}, 46 | {:plug_cowboy, "~> 2.5"} 47 | ] 48 | end 49 | 50 | # Aliases are shortcuts or tasks specific to the current project. 51 | # For example, to install project dependencies and perform other setup tasks, run: 52 | # 53 | # $ mix setup 54 | # 55 | # See the documentation for `Mix` for more info on aliases. 56 | defp aliases do 57 | [ 58 | setup: ["deps.get"], 59 | "assets.deploy": ["esbuild default --minify", "phx.digest"] 60 | ] 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /config/prod.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # For production, don't forget to configure the url host 4 | # to something meaningful, Phoenix uses this information 5 | # when generating URLs. 6 | # 7 | # Note we also include the path to a cache manifest 8 | # containing the digested version of static files. This 9 | # manifest is generated by the `mix phx.digest` task, 10 | # which you should run after static files are built and 11 | # before starting your production server. 12 | config :shady_urls, ShadyUrlsWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json" 13 | 14 | # Do not print debug messages in production 15 | config :logger, level: :info 16 | 17 | # ## SSL Support 18 | # 19 | # To get SSL working, you will need to add the `https` key 20 | # to the previous section and set your `:url` port to 443: 21 | # 22 | config :shady_urls, ShadyUrlsWeb.Endpoint, 23 | force_ssl: [rewrite_on: [:x_forwarded_proto]] 24 | # https: [ 25 | # ..., 26 | # port: 443, 27 | # cipher_suite: :strong, 28 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), 29 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH") 30 | # ] 31 | # 32 | # The `cipher_suite` is set to `:strong` to support only the 33 | # latest and more secure SSL ciphers. This means old browsers 34 | # and clients may not be supported. You can set it to 35 | # `:compatible` for wider support. 36 | # 37 | # `:keyfile` and `:certfile` expect an absolute path to the key 38 | # and cert in disk or a relative path inside priv, for example 39 | # "priv/ssl/server.key". For all supported SSL configuration 40 | # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 41 | # 42 | # We also recommend setting `force_ssl` in your endpoint, ensuring 43 | # no data is ever sent via http, always redirecting to https: 44 | # 45 | # config :shady_urls, ShadyUrlsWeb.Endpoint, 46 | # force_ssl: [hsts: true] 47 | # 48 | # Check `Plug.SSL` for all available options in `force_ssl`. 49 | -------------------------------------------------------------------------------- /config/runtime.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # config/runtime.exs is executed for all environments, including 4 | # during releases. It is executed after compilation and before the 5 | # system starts, so it is typically used to load production configuration 6 | # and secrets from environment variables or elsewhere. Do not define 7 | # any compile-time configuration in here, as it won't be applied. 8 | # The block below contains prod specific runtime configuration. 9 | 10 | # ## Using releases 11 | # 12 | # If you use `mix release`, you need to explicitly enable the server 13 | # by passing the PHX_SERVER=true when you start it: 14 | # 15 | # PHX_SERVER=true bin/shady_urls start 16 | # 17 | # Alternatively, you can use `mix phx.gen.release` to generate a `bin/server` 18 | # script that automatically sets the env var above. 19 | if System.get_env("PHX_SERVER") do 20 | config :shady_urls, ShadyUrlsWeb.Endpoint, server: true 21 | end 22 | 23 | if config_env() == :prod do 24 | # The secret key base is used to sign/encrypt cookies and other secrets. 25 | # A default value is used in config/dev.exs and config/test.exs but you 26 | # want to use a different value for prod and you most likely don't want 27 | # to check this value into version control, so we use an environment 28 | # variable instead. 29 | secret_key_base = 30 | System.get_env("SECRET_KEY_BASE") || 31 | raise """ 32 | environment variable SECRET_KEY_BASE is missing. 33 | You can generate one by calling: mix phx.gen.secret 34 | """ 35 | 36 | host = System.get_env("PHX_HOST") || "0x4a69726b612.xyz" 37 | port = String.to_integer(System.get_env("PORT") || "4000") 38 | 39 | config :shady_urls, ShadyUrlsWeb.Endpoint, 40 | url: [host: host, port: 443, scheme: "https"], 41 | http: [ 42 | # Enable IPv6 and bind on all interfaces. 43 | # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. 44 | # See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html 45 | # for details about using IPv6 vs IPv4 and loopback vs public addresses. 46 | ip: {0, 0, 0, 0, 0, 0, 0, 0}, 47 | port: port 48 | ], 49 | secret_key_base: secret_key_base 50 | end 51 | -------------------------------------------------------------------------------- /config/dev.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # For development, we disable any cache and enable 4 | # debugging and code reloading. 5 | # 6 | # The watchers configuration can be used to run external 7 | # watchers to your application. For example, we use it 8 | # with esbuild to bundle .js and .css sources. 9 | config :shady_urls, ShadyUrlsWeb.Endpoint, 10 | # Binding to loopback ipv4 address prevents access from other machines. 11 | # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. 12 | http: [ip: {127, 0, 0, 1}, port: 4000], 13 | check_origin: false, 14 | code_reloader: true, 15 | debug_errors: true, 16 | secret_key_base: "4RWs10jQ2tzMobJ/ElFqent+Y7h+nzt7sIdxO2+DYTmGFGbrAbjh+RjQCmfGep8d", 17 | watchers: [] 18 | 19 | # ## SSL Support 20 | # 21 | # In order to use HTTPS in development, a self-signed 22 | # certificate can be generated by running the following 23 | # Mix task: 24 | # 25 | # mix phx.gen.cert 26 | # 27 | # Note that this task requires Erlang/OTP 20 or later. 28 | # Run `mix help phx.gen.cert` for more information. 29 | # 30 | # The `http:` config above can be replaced with: 31 | # 32 | # https: [ 33 | # port: 4001, 34 | # cipher_suite: :strong, 35 | # keyfile: "priv/cert/selfsigned_key.pem", 36 | # certfile: "priv/cert/selfsigned.pem" 37 | # ], 38 | # 39 | # If desired, both `http:` and `https:` keys can be 40 | # configured to run both http and https servers on 41 | # different ports. 42 | 43 | # Watch static and templates for browser reloading. 44 | config :shady_urls, ShadyUrlsWeb.Endpoint, 45 | live_reload: [ 46 | patterns: [ 47 | ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$", 48 | ~r"lib/shady_urls_web/(live|views)/.*(ex)$", 49 | ~r"lib/shady_urls_web/templates/.*(eex)$" 50 | ] 51 | ] 52 | 53 | # Do not include metadata nor timestamps in development logs 54 | config :logger, :console, format: "[$level] $message\n" 55 | 56 | # Set a higher stacktrace during development. Avoid configuring such 57 | # in production as building large stacktraces may be expensive. 58 | config :phoenix, :stacktrace_depth, 20 59 | 60 | # Initialize plugs at runtime for faster development compilation 61 | config :phoenix, :plug_init_mode, :runtime 62 | -------------------------------------------------------------------------------- /lib/shady_urls_web.ex: -------------------------------------------------------------------------------- 1 | defmodule ShadyUrlsWeb do 2 | @moduledoc """ 3 | The entrypoint for defining your web interface, such 4 | as controllers, views, channels and so on. 5 | 6 | This can be used in your application as: 7 | 8 | use ShadyUrlsWeb, :controller 9 | use ShadyUrlsWeb, :view 10 | 11 | The definitions below will be executed for every view, 12 | controller, etc, so keep them short and clean, focused 13 | on imports, uses and aliases. 14 | 15 | Do NOT define functions inside the quoted expressions 16 | below. Instead, define any helper function in modules 17 | and import those modules here. 18 | """ 19 | 20 | def controller do 21 | quote do 22 | use Phoenix.Controller, namespace: ShadyUrlsWeb 23 | 24 | import Plug.Conn 25 | alias ShadyUrlsWeb.Router.Helpers, as: Routes 26 | end 27 | end 28 | 29 | def view do 30 | quote do 31 | use Phoenix.View, 32 | root: "lib/shady_urls_web/templates", 33 | namespace: ShadyUrlsWeb 34 | 35 | # Import convenience functions from controllers 36 | import Phoenix.Controller, 37 | only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1] 38 | 39 | # Include shared imports and aliases for views 40 | unquote(view_helpers()) 41 | end 42 | end 43 | 44 | def live_view do 45 | quote do 46 | use Phoenix.LiveView, 47 | layout: {ShadyUrlsWeb.LayoutView, "live.html"} 48 | 49 | unquote(view_helpers()) 50 | end 51 | end 52 | 53 | def live_component do 54 | quote do 55 | use Phoenix.LiveComponent 56 | 57 | unquote(view_helpers()) 58 | end 59 | end 60 | 61 | def component do 62 | quote do 63 | use Phoenix.Component 64 | 65 | unquote(view_helpers()) 66 | end 67 | end 68 | 69 | def router do 70 | quote do 71 | use Phoenix.Router 72 | 73 | import Plug.Conn 74 | import Phoenix.Controller 75 | import Phoenix.LiveView.Router 76 | end 77 | end 78 | 79 | def channel do 80 | quote do 81 | use Phoenix.Channel 82 | end 83 | end 84 | 85 | defp view_helpers do 86 | quote do 87 | # Use all HTML functionality (forms, tags, etc) 88 | use Phoenix.HTML 89 | 90 | # Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc) 91 | import Phoenix.LiveView.Helpers 92 | 93 | # Import basic rendering functionality (render, render_layout, etc) 94 | import Phoenix.View 95 | 96 | import ShadyUrlsWeb.ErrorHelpers 97 | alias ShadyUrlsWeb.Router.Helpers, as: Routes 98 | end 99 | end 100 | 101 | @doc """ 102 | When used, dispatch to the appropriate controller/view/etc. 103 | """ 104 | defmacro __using__(which) when is_atom(which) do 105 | apply(__MODULE__, which, []) 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /lib/shady_urls/generator.ex: -------------------------------------------------------------------------------- 1 | defmodule ShadyUrls.Generator do 2 | 3 | @words [ 4 | # Thanks to http://urlify.io/ and https://verylegit.link for providing inspiration <3 5 | "facebook.com", "amazon.com", "paypal.com", "Win a free iPad", "ISIS registration form", "Bush did 9 11", "trojan", 6 | "Trojan.Win32", "Genocide", "KKK", "Ku Klux Klan", "Heroin", "Cocaine", "Meth", "Weed", "Download", "Free", 7 | "Hentai", "Porn", "weeb", "Twitter Hack", "Facebook Hack", "Crypto", "Bitcoin", "Stolen credit cards", 8 | "Phishing", "White Power", "Tentacle fun time", "Windows Crack", "Free Money", "Webhost000", "Invoice", 9 | "Java Exploit", "Warez", "Cracked", "Botnet", "Dark Net", "xxx", "Adolf will rise", "IP Stealer", 10 | "Token grabber", "Discord hack", "Leaked nudes", "OnlyFans", "License key", "Secret conspiracy", 11 | "Illegal substances", "Midget porn", "Furry", "420", "Hot moms nearby", "Hot single women", 12 | "Penis enlargement pills", "Tinder", "~home", "Click Here", "FBI", "CIA", "NSA", "Donald Trump", 13 | "Android hack", "Malware", "Keylogger", "Root", "Win $100 000 000", "Hidden service", "Tor", 14 | "Onion", "Terrorism", "Yiff", "Tesla", "e621.net", "nhentai.net", "Trial version", "Try not to cum", "Challenge", 15 | "CSGO cheats", "Minecraft", "Minecraft cheat", "Free Minecraft", "Nitro", "Free Nitro", "Steam gift cards", 16 | "Google Play gift cards", "Netflix credentials", "Roblox", "Robux", "Free Robux", "Hacked Roblox Client", 17 | "Hacked Minecraft Client", "Send Nudes" 18 | ] 19 | 20 | @extensions [ 21 | "pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", 22 | "txt", "bin", "png", "jpg", "gif", "webp", "mp4", "mp3", "ics" 23 | ] 24 | 25 | @ending_extensions [ 26 | "zip", "rar", "exe", "msi", "jar", "bat", "7z", "ps1" 27 | ] 28 | 29 | @spec normalize_url(String.t()) :: String.t() 30 | def normalize_url(source_url) when is_binary(source_url) do 31 | url = source_url 32 | |> String.trim() 33 | |> String.trim("/") 34 | |> String.replace(~r/^https?:\/\//, "") 35 | 36 | "https://#{url}" 37 | end 38 | 39 | @spec generate_shady_url(String.t()) :: String.t() 40 | def generate_shady_url(source_url) when is_binary(source_url) do 41 | # Seed the PRNG based off the provided source url 42 | :rand.seed(:default, :erlang.phash2(source_url)) 43 | 44 | words = select_shady_words() 45 | extension = Enum.random(@extensions) 46 | ending_extension = Enum.random(@ending_extensions) 47 | 48 | path = words 49 | |> Enum.map(fn word -> String.downcase(word) end) 50 | |> Enum.map(fn word -> String.replace(word, ~r/\s+/, "_") end) 51 | |> Enum.map(&generate_random_suffix/1) 52 | |> Enum.join("-") 53 | 54 | "#{path}.#{extension}.#{ending_extension}" 55 | end 56 | 57 | @spec select_shady_words() :: [String.t()] 58 | defp select_shady_words() do 59 | @words 60 | |> Enum.shuffle() 61 | |> Enum.take(4 + :rand.uniform(2)) 62 | |> Enum.to_list() 63 | end 64 | 65 | @spec generate_random_suffix(String.t()) :: String.t() 66 | defp generate_random_suffix(word) when is_binary(word) do 67 | length = 2 + :rand.uniform(2) 68 | suffix = for _ <- 0..length, into: "", do: <> 69 | 70 | word <> "-" <> suffix 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "amnesia": {:hex, :amnesia, "0.2.8", "81199a1c4c8db886cfb8ea159f746d5ffdb188bee96cb944f63bdb4465b09fa0", [:mix], [{:exquisite, "~> 0.1.7", [hex: :exquisite, repo: "hexpm", optional: false]}], "hexpm", "6037898c974457809ffa1f9a74cccab8f48c99b206aee9b3fc7bb3af73b06b14"}, 3 | "castore": {:hex, :castore, "0.1.18", "deb5b9ab02400561b6f5708f3e7660fc35ca2d51bfc6a940d2f513f89c2975fc", [:mix], [], "hexpm", "61bbaf6452b782ef80b33cdb45701afbcf0a918a45ebe7e73f1130d661e66a06"}, 4 | "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"}, 5 | "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, 6 | "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"}, 7 | "esbuild": {:hex, :esbuild, "0.5.0", "d5bb08ff049d7880ee3609ed5c4b864bd2f46445ea40b16b4acead724fb4c4a3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "f183a0b332d963c4cfaf585477695ea59eef9a6f2204fdd0efa00e099694ffe5"}, 8 | "exquisite": {:hex, :exquisite, "0.1.10", "e3ca4f8b812696a40a6da3bcd4e9861cef879ee2eb239d5485b0f96885fc9fba", [:mix], [], "hexpm", "0af9319851f1e21dd4adab812c82247f05e11cc19820fa17cc74afd696e2313c"}, 9 | "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, 10 | "floki": {:hex, :floki, "0.33.1", "f20f1eb471e726342b45ccb68edb9486729e7df94da403936ea94a794f072781", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "461035fd125f13fdf30f243c85a0b1e50afbec876cbf1ceefe6fddd2e6d712c6"}, 11 | "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, 12 | "jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"}, 13 | "memento": {:hex, :memento, "0.3.2", "38cfc8ff9bcb1adff7cbd0f3b78a762636b86dff764729d1c82d0464c539bdd0", [:mix], [], "hexpm", "25cf691a98a0cb70262f4a7543c04bab24648cb2041d937eb64154a8d6f8012b"}, 14 | "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"}, 15 | "phoenix": {:hex, :phoenix, "1.6.11", "29f3c0fd12fa1fc4d4b05e341578e55bc78d96ea83a022587a7e276884d397e4", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1664e34f80c25ea4918fbadd957f491225ef601c0e00b4e644b1a772864bfbc2"}, 16 | "phoenix_html": {:hex, :phoenix_html, "3.2.0", "1c1219d4b6cb22ac72f12f73dc5fad6c7563104d083f711c3fcd8551a1f4ae11", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "36ec97ba56d25c0136ef1992c37957e4246b649d620958a1f9fa86165f8bc54f"}, 17 | "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.6.5", "1495bb014be12c9a9252eca04b9af54246f6b5c1e4cd1f30210cd00ec540cf8e", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.3", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.17.7", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "ef4fa50dd78364409039c99cf6f98ab5209b4c5f8796c17f4db118324f0db852"}, 18 | "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"}, 19 | "phoenix_live_view": {:hex, :phoenix_live_view, "0.17.11", "205f6aa5405648c76f2abcd57716f42fc07d8f21dd8ea7b262dd12b324b50c95", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7177791944b7f90ed18f5935a6a5f07f760b36f7b3bdfb9d28c57440a3c43f99"}, 20 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, 21 | "phoenix_view": {:hex, :phoenix_view, "1.1.2", "1b82764a065fb41051637872c7bd07ed2fdb6f5c3bd89684d4dca6e10115c95a", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "7ae90ad27b09091266f6adbb61e1d2516a7c3d7062c6789d46a7554ec40f3a56"}, 22 | "plug": {:hex, :plug, "1.13.6", "187beb6b67c6cec50503e940f0434ea4692b19384d47e5fdfd701e93cadb4cc2", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02b9c6b9955bce92c829f31d6284bf53c591ca63c4fb9ff81dfd0418667a34ff"}, 23 | "plug_cowboy": {:hex, :plug_cowboy, "2.5.2", "62894ccd601cf9597e2c23911ff12798a8a18d237e9739f58a6b04e4988899fe", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ea6e87f774c8608d60c8d34022a7d073bd7680a0a013f049fc62bf35efea1044"}, 24 | "plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"}, 25 | "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, 26 | "tailwind": {:hex, :tailwind, "0.1.8", "3762defebc8e328fb19ff1afb8c37723e53b52be5ca74f0b8d0a02d1f3f432cf", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "40061d1bf2c0505c6b87be7a3ed05243fc10f6e1af4bac3336db8358bc84d4cc"}, 27 | "telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"}, 28 | "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"}, 29 | "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, 30 | } 31 | --------------------------------------------------------------------------------