diff --git a/.formatter.exs b/.formatter.exs index ef8840c..d2cda26 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,6 +1,4 @@ +# Used by "mix format" [ - import_deps: [:ecto, :ecto_sql, :phoenix], - subdirectories: ["priv/*/migrations"], - plugins: [Phoenix.LiveView.HTMLFormatter], - inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}", "priv/*/seeds.exs"] + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] ] diff --git a/.gitignore b/.gitignore index 80c2ddf..6af327e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ # The directory Mix downloads your dependencies sources to. /deps/ -# Where 3rd-party dependencies like ExDoc output generated docs. +# Where third-party dependencies like ExDoc output generated docs. /doc/ # Ignore .fetch files in case you like to edit your project deps locally. @@ -19,21 +19,20 @@ erl_crash.dump # Also ignore archive artifacts (built via "mix archive.build"). *.ez -# Temporary files, for example, from tests. -/tmp/ - # Ignore package tarball (built via "mix hex.build"). botchini-*.tar +# Temporary files for e.g. tests +/tmp + # Ignore assets that are produced by build tools. /priv/static/assets/ # Ignore digested assets cache. /priv/static/cache_manifest.json -# In case you use Node.js/npm, you want to ignore these. -npm-debug.log -/assets/node_modules/ +# Secrets +config/*.secret.exs -# Ignore secret env vars -/config/dev.secret.exs +# ElixirLS +.elixir_ls diff --git a/assets/css/app.css b/assets/css/app.css index 378c8f9..76fcadc 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -1,5 +1,3 @@ @import "tailwindcss/base"; @import "tailwindcss/components"; @import "tailwindcss/utilities"; - -/* This file is for your main application CSS */ diff --git a/assets/favicon.ico b/assets/favicon.ico new file mode 100644 index 0000000..73de524 Binary files /dev/null and b/assets/favicon.ico differ diff --git a/assets/images/twitch_example.png b/assets/images/twitch_example.png new file mode 100644 index 0000000..83a4794 Binary files /dev/null and b/assets/images/twitch_example.png differ diff --git a/assets/images/youtube_example.png b/assets/images/youtube_example.png new file mode 100644 index 0000000..ad0ff78 Binary files /dev/null and b/assets/images/youtube_example.png differ diff --git a/assets/js/app.js b/assets/js/app.js index d5e278a..bf203ba 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -1,3 +1,6 @@ +// We import the CSS which is extracted to its own file by esbuild. +// Remove this line if you add a your own CSS build pipeline (e.g postcss). + // If you want to use Phoenix channels, run `mix help phx.gen.channel` // to get started and then uncomment the line below. // import "./user_socket.js" @@ -23,15 +26,12 @@ import {LiveSocket} from "phoenix_live_view" import topbar from "../vendor/topbar" let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") -let liveSocket = new LiveSocket("/live", Socket, { - longPollFallbackMs: 2500, - params: {_csrf_token: csrfToken} -}) +let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}}) // Show progress bar on live navigation and form submits topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"}) -window.addEventListener("phx:page-loading-start", _info => topbar.show(300)) -window.addEventListener("phx:page-loading-stop", _info => topbar.hide()) +window.addEventListener("phx:page-loading-start", info => topbar.show()) +window.addEventListener("phx:page-loading-stop", info => topbar.hide()) // connect if there are any LiveViews on the page liveSocket.connect() diff --git a/assets/robots.txt b/assets/robots.txt new file mode 100644 index 0000000..26e06b5 --- /dev/null +++ b/assets/robots.txt @@ -0,0 +1,5 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file +# +# To ban all spiders from the entire site uncomment the next two lines: +# User-agent: * +# Disallow: / diff --git a/assets/tailwind.config.js b/assets/tailwind.config.js index 3a48447..4bf4581 100644 --- a/assets/tailwind.config.js +++ b/assets/tailwind.config.js @@ -1,80 +1,24 @@ +const colors = require("tailwindcss/colors"); + // See the Tailwind configuration guide for advanced usage // https://tailwindcss.com/docs/configuration - -const colors = require("tailwindcss/colors") -const plugin = require("tailwindcss/plugin") -const fs = require("fs") -const path = require("path") - module.exports = { darkMode: "class", content: [ - "./js/**/*.js", - "../lib/botchini_web.ex", - "../lib/botchini_web/**/*.*ex" + './js/**/*.js', + '../lib/*_web.ex', + '../lib/*_web/**/*.*ex', + "../deps/petal_components/**/*.*ex", ], theme: { extend: { colors: { - brand: "#FD4F00", - primary: colors.blue, secondary: colors.pink, - } + }, }, }, plugins: [ - require("@tailwindcss/forms"), - // Allows prefixing tailwind classes with LiveView classes to add rules - // only when LiveView classes are applied, for example: - // - //
- // - plugin(({addVariant}) => addVariant("phx-no-feedback", [".phx-no-feedback&", ".phx-no-feedback &"])), - plugin(({addVariant}) => addVariant("phx-click-loading", [".phx-click-loading&", ".phx-click-loading &"])), - plugin(({addVariant}) => addVariant("phx-submit-loading", [".phx-submit-loading&", ".phx-submit-loading &"])), - plugin(({addVariant}) => addVariant("phx-change-loading", [".phx-change-loading&", ".phx-change-loading &"])), - - // Embeds Heroicons (https://heroicons.com) into your app.css bundle - // See your `CoreComponents.icon/1` for more information. - // - plugin(function({matchComponents, theme}) { - let iconsDir = path.join(__dirname, "../deps/heroicons/optimized") - let values = {} - let icons = [ - ["", "/24/outline"], - ["-solid", "/24/solid"], - ["-mini", "/20/solid"], - ["-micro", "/16/solid"] - ] - icons.forEach(([suffix, dir]) => { - fs.readdirSync(path.join(iconsDir, dir)).forEach(file => { - let name = path.basename(file, ".svg") + suffix - values[name] = {name, fullPath: path.join(iconsDir, dir, file)} - }) - }) - matchComponents({ - "hero": ({name, fullPath}) => { - let content = fs.readFileSync(fullPath).toString().replace(/\r?\n|\r/g, "") - let size = theme("spacing.6") - if (name.endsWith("-mini")) { - size = theme("spacing.5") - } else if (name.endsWith("-micro")) { - size = theme("spacing.4") - } - return { - [`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`, - "-webkit-mask": `var(--hero-${name})`, - "mask": `var(--hero-${name})`, - "mask-repeat": "no-repeat", - "background-color": "currentColor", - "vertical-align": "middle", - "display": "inline-block", - "width": size, - "height": size - } - } - }, {values}) - }) + require('@tailwindcss/forms') ] } diff --git a/assets/vendor/topbar.js b/assets/vendor/topbar.js index 4195727..1f62209 100644 --- a/assets/vendor/topbar.js +++ b/assets/vendor/topbar.js @@ -1,6 +1,6 @@ /** * @license MIT - * topbar 2.0.0, 2023-02-04 + * topbar 1.0.0, 2021-01-06 * https://buunguyen.github.io/topbar * Copyright (c) 2021 Buu Nguyen */ @@ -35,11 +35,10 @@ })(); var canvas, + progressTimerId, + fadeTimerId, currentProgress, showing, - progressTimerId = null, - fadeTimerId = null, - delayTimerId = null, addEvent = function (elem, type, handler) { if (elem.addEventListener) elem.addEventListener(type, handler, false); else if (elem.attachEvent) elem.attachEvent("on" + type, handler); @@ -96,26 +95,21 @@ for (var key in opts) if (options.hasOwnProperty(key)) options[key] = opts[key]; }, - show: function (delay) { + show: function () { if (showing) return; - if (delay) { - if (delayTimerId) return; - delayTimerId = setTimeout(() => topbar.show(), delay); - } else { - showing = true; - if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId); - if (!canvas) createCanvas(); - canvas.style.opacity = 1; - canvas.style.display = "block"; - topbar.progress(0); - if (options.autoRun) { - (function loop() { - progressTimerId = window.requestAnimationFrame(loop); - topbar.progress( - "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2) - ); - })(); - } + showing = true; + if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId); + if (!canvas) createCanvas(); + canvas.style.opacity = 1; + canvas.style.display = "block"; + topbar.progress(0); + if (options.autoRun) { + (function loop() { + progressTimerId = window.requestAnimationFrame(loop); + topbar.progress( + "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2) + ); + })(); } }, progress: function (to) { @@ -131,8 +125,6 @@ return currentProgress; }, hide: function () { - clearTimeout(delayTimerId); - delayTimerId = null; if (!showing) return; showing = false; if (progressTimerId != null) { diff --git a/config/config.exs b/config/config.exs index a71c503..e2455be 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,10 +1,3 @@ -# This file is responsible for configuring your application -# and its dependencies with the aid of the Config module. -# -# This configuration file is loaded before any dependency and -# is restricted to this project. - -# General application configuration import Config config :botchini, @@ -25,37 +18,31 @@ config :botchini, Botchini.Scheduler, # Configures the endpoint config :botchini, BotchiniWeb.Endpoint, url: [host: "localhost"], - adapter: Bandit.PhoenixAdapter, - render_errors: [ - formats: [html: BotchiniWeb.ErrorHTML, json: BotchiniWeb.ErrorJSON], - layout: false - ], + render_errors: [view: BotchiniWeb.ErrorView, accepts: ~w(html json), layout: false], pubsub_server: Botchini.PubSub, - live_view: [signing_salt: "yFQUW8gj"] - -# Configures the mailer -# -# By default it uses the "Local" adapter which stores the emails -# locally. You can see the emails in your browser, at "/dev/mailbox". -# -# For production it's recommended to configure a different adapter -# at the `config/runtime.exs`. -config :botchini, Botchini.Mailer, adapter: Swoosh.Adapters.Local + live_view: [signing_salt: "ZIYuYInA"] # Configure esbuild (the version is required) config :esbuild, - version: "0.17.11", - botchini: [ + version: "0.14.29", + default: [ args: ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*), cd: Path.expand("../assets", __DIR__), env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} ] -# Configure tailwind (the version is required) +# Configures Elixir's Logger +config :logger, :console, + format: "$time $metadata[$level] $message\n", + metadata: [:request_id, :interaction_data, :guild_id, :channel_id, :user_id] + +# Use Jason for JSON parsing in Phoenix +config :phoenix, :json_library, Jason + config :tailwind, - version: "3.4.0", - botchini: [ + version: "3.1.2", + default: [ args: ~w( --config=tailwind.config.js --input=css/app.css @@ -64,14 +51,4 @@ config :tailwind, cd: Path.expand("../assets", __DIR__) ] -# Configures Elixir's Logger -config :logger, :console, - format: "$time $metadata[$level] $message\n", - metadata: [:request_id, :interaction_data, :guild_id, :channel_id, :user_id] - -# Use Jason for JSON parsing in Phoenix -config :phoenix, :json_library, Jason - -# Import environment specific config. This must remain at the bottom -# of this file so it overrides the configuration defined above. -import_config "#{config_env()}.exs" +import_config "#{Mix.env()}.exs" diff --git a/config/runtime.exs b/config/runtime.exs index a33379d..2602075 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -28,13 +28,13 @@ if config_env() == :prod do For example: ecto://USER:PASS@HOST/DATABASE """ - maybe_ipv6 = if System.get_env("ECTO_IPV6") in ~w(true 1), do: [:inet6], else: [] + maybe_ipv6 = if System.get_env("ECTO_IPV6"), do: [:inet6], else: [] config :botchini, Botchini.Repo, - # ssl: true, + ssl: false, url: database_url, - pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"), - socket_options: maybe_ipv6 + socket_options: maybe_ipv6, + pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") # The secret key base is used to sign/encrypt cookies and other secrets. # A default value is used in config/dev.exs and config/test.exs but you @@ -51,14 +51,12 @@ if config_env() == :prod do host = System.get_env("PHX_HOST") || "example.com" port = String.to_integer(System.get_env("PORT") || "4000") - config :botchini, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY") - config :botchini, BotchiniWeb.Endpoint, url: [host: host, port: 443, scheme: "https"], http: [ # Enable IPv6 and bind on all interfaces. # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. - # See the documentation on https://hexdocs.pm/bandit/Bandit.html#t:options/0 + # See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html # for details about using IPv6 vs IPv4 and loopback vs public addresses. ip: {0, 0, 0, 0, 0, 0, 0, 0}, port: port diff --git a/lib/botchini/application.ex b/lib/botchini/application.ex index 948c5a7..e2efef9 100644 --- a/lib/botchini/application.ex +++ b/lib/botchini/application.ex @@ -9,19 +9,16 @@ defmodule Botchini.Application do def start(_type, _args) do children = [ - BotchiniWeb.Telemetry, Botchini.Repo, Botchini.Cache, Botchini.Scheduler, - Botchini.Services.Twitch.AuthMiddleware, - {DNSCluster, query: Application.get_env(:botchini, :dns_cluster_query) || :ignore}, + # Start the Telemetry supervisor + BotchiniWeb.Telemetry, + # Start the PubSub system {Phoenix.PubSub, name: Botchini.PubSub}, - # Start the Finch HTTP client for sending emails - {Finch, name: Botchini.Finch}, - # Start a worker by calling: Botchini.Worker.start_link(arg) - # {Botchini.Worker, arg}, - # Start to serve requests, typically the last entry - BotchiniWeb.Endpoint + BotchiniWeb.Endpoint, + # Twitch auth middleware + Botchini.Services.Twitch.AuthMiddleware ] |> start_nostrum(Application.fetch_env!(:botchini, :environment)) @@ -33,12 +30,4 @@ defmodule Botchini.Application do defp start_nostrum(children, :test), do: children defp start_nostrum(children, _env), do: children ++ [BotchiniDiscord.Consumer] - - # Tell Phoenix to update the endpoint configuration - # whenever the application is updated. - @impl true - def config_change(changed, _new, removed) do - BotchiniWeb.Endpoint.config_change(changed, removed) - :ok - end end diff --git a/lib/botchini/mailer.ex b/lib/botchini/mailer.ex deleted file mode 100644 index e833ac8..0000000 --- a/lib/botchini/mailer.ex +++ /dev/null @@ -1,3 +0,0 @@ -defmodule Botchini.Mailer do - use Swoosh.Mailer, otp_app: :botchini -end diff --git a/lib/botchini_web.ex b/lib/botchini_web.ex index 5d6be46..5cd0e02 100644 --- a/lib/botchini_web.ex +++ b/lib/botchini_web.ex @@ -1,60 +1,53 @@ defmodule BotchiniWeb do @moduledoc """ The entrypoint for defining your web interface, such - as controllers, components, channels, and so on. + as controllers, views, channels and so on. This can be used in your application as: use BotchiniWeb, :controller - use BotchiniWeb, :html + use BotchiniWeb, :view - The definitions below will be executed for every controller, - component, etc, so keep them short and clean, focused + The definitions below will be executed for every view, + controller, etc, so keep them short and clean, focused on imports, uses and aliases. Do NOT define functions inside the quoted expressions - below. Instead, define additional modules and import - those modules here. + below. Instead, define any helper function in modules + and import those modules here. """ - def static_paths, do: ~w(assets fonts images favicon.ico robots.txt) - - def router do + def controller do quote do - use Phoenix.Router, helpers: false + use Phoenix.Controller, namespace: BotchiniWeb - # Import common connection and controller functions to use in pipelines import Plug.Conn - import Phoenix.Controller - import Phoenix.LiveView.Router - end - end - - def channel do - quote do - use Phoenix.Channel + import BotchiniWeb.Gettext + alias BotchiniWeb.Router.Helpers, as: Routes end end - def controller do + def view do quote do - use Phoenix.Controller, - formats: [:html, :json], - layouts: [html: BotchiniWeb.Layouts] + use Phoenix.View, + root: "lib/botchini_web/templates", + namespace: BotchiniWeb - import Plug.Conn - import BotchiniWeb.Gettext + # Import convenience functions from controllers + import Phoenix.Controller, + only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1] - unquote(verified_routes()) + # Include shared imports and aliases for views + unquote(view_helpers()) end end def live_view do quote do use Phoenix.LiveView, - layout: {BotchiniWeb.Layouts, :app} + layout: {BotchiniWeb.LayoutView, "live.html"} - unquote(html_helpers()) + unquote(view_helpers()) end end @@ -62,45 +55,52 @@ defmodule BotchiniWeb do quote do use Phoenix.LiveComponent - unquote(html_helpers()) + unquote(view_helpers()) end end - def html do + def component do quote do use Phoenix.Component - # Import convenience functions from controllers - import Phoenix.Controller, - only: [get_csrf_token: 0, view_module: 1, view_template: 1] - - # Include general helpers for rendering HTML - unquote(html_helpers()) + unquote(view_helpers()) end end - defp html_helpers do + def router do quote do - # HTML escaping functionality - import Phoenix.HTML - # Core UI components and translation - import BotchiniWeb.CoreComponents - import BotchiniWeb.Gettext + use Phoenix.Router - # Shortcut for generating JS commands - alias Phoenix.LiveView.JS + import Plug.Conn + import Phoenix.Controller + import Phoenix.LiveView.Router + end + end - # Routes generation with the ~p sigil - unquote(verified_routes()) + def channel do + quote do + use Phoenix.Channel + import BotchiniWeb.Gettext end end - def verified_routes do + defp view_helpers do quote do - use Phoenix.VerifiedRoutes, - endpoint: BotchiniWeb.Endpoint, - router: BotchiniWeb.Router, - statics: BotchiniWeb.static_paths() + # Use all HTML functionality (forms, tags, etc) + use Phoenix.HTML + + # Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc) + import Phoenix.LiveView.Helpers + + # Import basic rendering functionality (render, render_layout, etc) + import Phoenix.View + + import BotchiniWeb.ErrorHelpers + import BotchiniWeb.Gettext + alias BotchiniWeb.Router.Helpers, as: Routes + + # Imports petal components + use PetalComponents end end diff --git a/lib/botchini_web/components/core_components.ex b/lib/botchini_web/components/core_components.ex deleted file mode 100644 index dbb013e..0000000 --- a/lib/botchini_web/components/core_components.ex +++ /dev/null @@ -1,677 +0,0 @@ -defmodule BotchiniWeb.CoreComponents do - @moduledoc """ - Provides core UI components. - - At first glance, this module may seem daunting, but its goal is to provide - core building blocks for your application, such as modals, tables, and - forms. The components consist mostly of markup and are well-documented - with doc strings and declarative assigns. You may customize and style - them in any way you want, based on your application growth and needs. - - The default components use Tailwind CSS, a utility-first CSS framework. - See the [Tailwind CSS documentation](https://tailwindcss.com) to learn - how to customize them or feel free to swap in another framework altogether. - - Icons are provided by [heroicons](https://heroicons.com). See `icon/1` for usage. - """ - use Phoenix.Component - - alias Phoenix.HTML.Form - alias Phoenix.LiveView.JS - - import BotchiniWeb.Gettext - - @doc """ - Renders a modal. - - ## Examples - - <.modal id="confirm-modal"> - This is a modal. - - - JS commands may be passed to the `:on_cancel` to configure - the closing/cancel event, for example: - - <.modal id="confirm" on_cancel={JS.navigate(~p"/posts")}> - This is another modal. - - - """ - attr :id, :string, required: true - attr :show, :boolean, default: false - attr :on_cancel, JS, default: %JS{} - slot :inner_block, required: true - - def modal(assigns) do - ~H""" - - """ - end - - def input(%{type: "select"} = assigns) do - ~H""" -
- <.label for={@id}><%= @label %> - - <.error :for={msg <- @errors}><%= msg %> -
- """ - end - - def input(%{type: "textarea"} = assigns) do - ~H""" -
- <.label for={@id}><%= @label %> - - <.error :for={msg <- @errors}><%= msg %> -
- """ - end - - # All other inputs text, datetime-local, url, password, etc. are handled here... - def input(assigns) do - ~H""" -
- <.label for={@id}><%= @label %> - - <.error :for={msg <- @errors}><%= msg %> -
- """ - end - - @doc """ - Renders a label. - """ - attr :for, :string, default: nil - slot :inner_block, required: true - - def label(assigns) do - ~H""" - - """ - end - - @doc """ - Generates a generic error message. - """ - slot :inner_block, required: true - - def error(assigns) do - ~H""" -

- <.icon name="hero-exclamation-circle-mini" class="mt-0.5 h-5 w-5 flex-none" /> - <%= render_slot(@inner_block) %> -

- """ - end - - @doc """ - Renders a header with title. - """ - attr :class, :string, default: nil - - slot :inner_block, required: true - slot :subtitle - slot :actions - - def header(assigns) do - ~H""" -
-
-

- <%= render_slot(@inner_block) %> -

-

- <%= render_slot(@subtitle) %> -

-
-
<%= render_slot(@actions) %>
-
- """ - end - - @doc ~S""" - Renders a table with generic styling. - - ## Examples - - <.table id="users" rows={@users}> - <:col :let={user} label="id"><%= user.id %> - <:col :let={user} label="username"><%= user.username %> - - """ - attr :id, :string, required: true - attr :rows, :list, required: true - attr :row_id, :any, default: nil, doc: "the function for generating the row id" - attr :row_click, :any, default: nil, doc: "the function for handling phx-click on each row" - - attr :row_item, :any, - default: &Function.identity/1, - doc: "the function for mapping each row before calling the :col and :action slots" - - slot :col, required: true do - attr :label, :string - end - - slot :action, doc: "the slot for showing user actions in the last table column" - - def table(assigns) do - assigns = - with %{rows: %Phoenix.LiveView.LiveStream{}} <- assigns do - assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end) - end - - ~H""" -
- - - - - - - - - - - - - -
<%= col[:label] %> - <%= gettext("Actions") %> -
-
- - - <%= render_slot(col, @row_item.(row)) %> - -
-
-
- - - <%= render_slot(action, @row_item.(row)) %> - -
-
-
- """ - end - - @doc """ - Renders a data list. - - ## Examples - - <.list> - <:item title="Title"><%= @post.title %> - <:item title="Views"><%= @post.views %> - - """ - slot :item, required: true do - attr :title, :string, required: true - end - - def list(assigns) do - ~H""" -
-
-
-
<%= item.title %>
-
<%= render_slot(item) %>
-
-
-
- """ - end - - @doc """ - Renders a back navigation link. - - ## Examples - - <.back navigate={~p"/posts"}>Back to posts - """ - attr :navigate, :any, required: true - slot :inner_block, required: true - - def back(assigns) do - ~H""" -
- <.link - navigate={@navigate} - class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700" - > - <.icon name="hero-arrow-left-solid" class="h-3 w-3" /> - <%= render_slot(@inner_block) %> - -
- """ - end - - @doc """ - Renders a [Heroicon](https://heroicons.com). - - Heroicons come in three styles – outline, solid, and mini. - By default, the outline style is used, but solid and mini may - be applied by using the `-solid` and `-mini` suffix. - - You can customize the size and colors of the icons by setting - width, height, and background color classes. - - Icons are extracted from the `deps/heroicons` directory and bundled within - your compiled app.css by the plugin in your `assets/tailwind.config.js`. - - ## Examples - - <.icon name="hero-x-mark-solid" /> - <.icon name="hero-arrow-path" class="ml-1 w-3 h-3 animate-spin" /> - """ - attr :name, :string, required: true - attr :class, :string, default: nil - - def icon(%{name: "hero-" <> _} = assigns) do - ~H""" - - """ - end - - ## JS Commands - - def show(js \\ %JS{}, selector) do - JS.show(js, - to: selector, - transition: - {"transition-all transform ease-out duration-300", - "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95", - "opacity-100 translate-y-0 sm:scale-100"} - ) - end - - def hide(js \\ %JS{}, selector) do - JS.hide(js, - to: selector, - time: 200, - transition: - {"transition-all transform ease-in duration-200", - "opacity-100 translate-y-0 sm:scale-100", - "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"} - ) - end - - def show_modal(js \\ %JS{}, id) when is_binary(id) do - js - |> JS.show(to: "##{id}") - |> JS.show( - to: "##{id}-bg", - transition: {"transition-all transform ease-out duration-300", "opacity-0", "opacity-100"} - ) - |> show("##{id}-container") - |> JS.add_class("overflow-hidden", to: "body") - |> JS.focus_first(to: "##{id}-content") - end - - def hide_modal(js \\ %JS{}, id) do - js - |> JS.hide( - to: "##{id}-bg", - transition: {"transition-all transform ease-in duration-200", "opacity-100", "opacity-0"} - ) - |> hide("##{id}-container") - |> JS.hide(to: "##{id}", transition: {"block", "block", "hidden"}) - |> JS.remove_class("overflow-hidden", to: "body") - |> JS.pop_focus() - end - - @doc """ - Translates an error message using gettext. - """ - def translate_error({msg, opts}) do - # When using gettext, we typically pass the strings we want - # to translate as a static argument: - # - # # Translate the number of files with plural rules - # dngettext("errors", "1 file", "%{count} files", count) - # - # However the error messages in our forms and APIs are generated - # dynamically, so we need to translate them by calling Gettext - # with our gettext backend as first argument. Translations are - # available in the errors.po file (as we use the "errors" domain). - if count = opts[:count] do - Gettext.dngettext(BotchiniWeb.Gettext, "errors", msg, msg, count, opts) - else - Gettext.dgettext(BotchiniWeb.Gettext, "errors", msg, opts) - end - end - - @doc """ - Translates the errors for a field from a keyword list of errors. - """ - def translate_errors(errors, field) when is_list(errors) do - for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts}) - end -end diff --git a/lib/botchini_web/components/icons.ex b/lib/botchini_web/components/icons.ex new file mode 100644 index 0000000..635bb90 --- /dev/null +++ b/lib/botchini_web/components/icons.ex @@ -0,0 +1,15 @@ +defmodule BotchiniWeb.Components.Icons do + @moduledoc """ + Component for rendering a button + """ + + use Phoenix.Component + + def github(assigns) do + ~H""" + + + + """ + end +end diff --git a/lib/botchini_web/components/layouts.ex b/lib/botchini_web/components/layouts.ex deleted file mode 100644 index 2f4b1fd..0000000 --- a/lib/botchini_web/components/layouts.ex +++ /dev/null @@ -1,5 +0,0 @@ -defmodule BotchiniWeb.Layouts do - use BotchiniWeb, :html - - embed_templates "layouts/*" -end diff --git a/lib/botchini_web/components/layouts/app.html.heex b/lib/botchini_web/components/layouts/app.html.heex deleted file mode 100644 index 424d1b0..0000000 --- a/lib/botchini_web/components/layouts/app.html.heex +++ /dev/null @@ -1,44 +0,0 @@ -
- -
- -
- <%= @inner_content %> -
diff --git a/lib/botchini_web/components/layouts/root.html.heex b/lib/botchini_web/components/layouts/root.html.heex deleted file mode 100644 index 4845405..0000000 --- a/lib/botchini_web/components/layouts/root.html.heex +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - <.live_title suffix=" · Phoenix Framework"> - <%= assigns[:page_title] || "Botchini" %> - - - - - - - <%= @inner_content %> - - diff --git a/lib/botchini_web/controllers/error_html.ex b/lib/botchini_web/controllers/error_html.ex deleted file mode 100644 index 744e241..0000000 --- a/lib/botchini_web/controllers/error_html.ex +++ /dev/null @@ -1,19 +0,0 @@ -defmodule BotchiniWeb.ErrorHTML do - use BotchiniWeb, :html - - # If you want to customize your error pages, - # uncomment the embed_templates/1 call below - # and add pages to the error directory: - # - # * lib/botchini_web/controllers/error_html/404.html.heex - # * lib/botchini_web/controllers/error_html/500.html.heex - # - # embed_templates "error_html/*" - - # The default is to render a plain text page based on - # the template name. For example, "404.html" becomes - # "Not Found". - def render(template, _assigns) do - Phoenix.Controller.status_message_from_template(template) - end -end diff --git a/lib/botchini_web/controllers/error_json.ex b/lib/botchini_web/controllers/error_json.ex deleted file mode 100644 index 56f4194..0000000 --- a/lib/botchini_web/controllers/error_json.ex +++ /dev/null @@ -1,15 +0,0 @@ -defmodule BotchiniWeb.ErrorJSON do - # If you want to customize a particular status code, - # you may add your own clauses, such as: - # - # def render("500.json", _assigns) do - # %{errors: %{detail: "Internal Server Error"}} - # end - - # By default, Phoenix returns the status message from - # the template name. For example, "404.json" becomes - # "Not Found". - def render(template, _assigns) do - %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}} - end -end diff --git a/lib/botchini_web/controllers/page_controller.ex b/lib/botchini_web/controllers/page_controller.ex index 496ec8e..300c58d 100644 --- a/lib/botchini_web/controllers/page_controller.ex +++ b/lib/botchini_web/controllers/page_controller.ex @@ -14,7 +14,7 @@ defmodule BotchiniWeb.PageController do |> Plug.Conn.assign(:total_servers, total_servers) |> Plug.Conn.assign(:total_streams, total_creators) |> Plug.Conn.assign(:invite_bot_url, generate_bot_url()) - |> render(:index) + |> render("index.html") end defp generate_bot_url do diff --git a/lib/botchini_web/controllers/page_html.ex b/lib/botchini_web/controllers/page_html.ex deleted file mode 100644 index 6e0e7a6..0000000 --- a/lib/botchini_web/controllers/page_html.ex +++ /dev/null @@ -1,5 +0,0 @@ -defmodule BotchiniWeb.PageHTML do - use BotchiniWeb, :html - - embed_templates "page_html/*" -end diff --git a/lib/botchini_web/controllers/page_html/index.html.heex b/lib/botchini_web/controllers/page_html/index.html.heex deleted file mode 100644 index 0398055..0000000 --- a/lib/botchini_web/controllers/page_html/index.html.heex +++ /dev/null @@ -1,39 +0,0 @@ -
-

- Botchini -

-

- Get notified on Discord - whenever your favorite creators goes live! -

-
- -
- - -
- -
-
-
- <%= @total_servers %> -
- -
- <%= @total_streams %> -
- -

Discord servers

- -

Creators being followed

-
-
- - - Add it to your server - diff --git a/lib/botchini_web/controllers/api/status_controller.ex b/lib/botchini_web/controllers/status_controller.ex similarity index 68% rename from lib/botchini_web/controllers/api/status_controller.ex rename to lib/botchini_web/controllers/status_controller.ex index 5edb5f7..ff2c024 100644 --- a/lib/botchini_web/controllers/api/status_controller.ex +++ b/lib/botchini_web/controllers/status_controller.ex @@ -1,4 +1,4 @@ -defmodule BotchiniWeb.Api.StatusController do +defmodule BotchiniWeb.StatusController do use BotchiniWeb, :controller def index(conn, _params) do diff --git a/lib/botchini_web/controllers/api/twitch_controller.ex b/lib/botchini_web/controllers/twitch_controller.ex similarity index 93% rename from lib/botchini_web/controllers/api/twitch_controller.ex rename to lib/botchini_web/controllers/twitch_controller.ex index 9ec8378..31b34df 100644 --- a/lib/botchini_web/controllers/api/twitch_controller.ex +++ b/lib/botchini_web/controllers/twitch_controller.ex @@ -1,4 +1,4 @@ -defmodule BotchiniWeb.Api.TwitchController do +defmodule BotchiniWeb.TwitchController do use BotchiniWeb, :controller require Logger @@ -8,7 +8,7 @@ defmodule BotchiniWeb.Api.TwitchController do @spec callback(Plug.Conn.t(), any) :: Plug.Conn.t() def callback(conn, _params) do - case request_valid?(conn, Application.fetch_env!(:botchini, :environment)) do + case is_request_valid?(conn, Application.fetch_env!(:botchini, :environment)) do false -> conn |> put_status(:not_found) @@ -20,9 +20,9 @@ defmodule BotchiniWeb.Api.TwitchController do end end - defp request_valid?(_conn, :dev), do: true + defp is_request_valid?(_conn, :dev), do: true - defp request_valid?(conn, _) do + defp is_request_valid?(conn, _) do message_id = get_header(conn, "twitch-eventsub-message-id") message_timestamp = get_header(conn, "twitch-eventsub-message-timestamp") [body] = Map.get(conn.assigns, :raw_body) diff --git a/lib/botchini_web/controllers/api/youtube_controller.ex b/lib/botchini_web/controllers/youtube_controller.ex similarity index 93% rename from lib/botchini_web/controllers/api/youtube_controller.ex rename to lib/botchini_web/controllers/youtube_controller.ex index ee3b278..5ba0234 100644 --- a/lib/botchini_web/controllers/api/youtube_controller.ex +++ b/lib/botchini_web/controllers/youtube_controller.ex @@ -1,4 +1,4 @@ -defmodule BotchiniWeb.Api.YoutubeController do +defmodule BotchiniWeb.YoutubeController do use BotchiniWeb, :controller require Logger @@ -18,7 +18,7 @@ defmodule BotchiniWeb.Api.YoutubeController do @spec notification(Plug.Conn.t(), any) :: Plug.Conn.t() def notification(conn, _params) do - case request_valid?(conn, Application.fetch_env!(:botchini, :environment)) do + case is_request_valid?(conn, Application.fetch_env!(:botchini, :environment)) do false -> conn |> put_status(:not_found) @@ -29,9 +29,9 @@ defmodule BotchiniWeb.Api.YoutubeController do end end - defp request_valid?(_conn, :dev), do: true + defp is_request_valid?(_conn, :dev), do: true - defp request_valid?(conn, _) do + defp is_request_valid?(conn, _) do [raw_body] = Map.get(conn.assigns, :raw_body) webhook_secret = Application.fetch_env!(:botchini, :youtube_webhook_secret) diff --git a/lib/botchini_web/endpoint.ex b/lib/botchini_web/endpoint.ex index 879353f..bf32d0b 100644 --- a/lib/botchini_web/endpoint.ex +++ b/lib/botchini_web/endpoint.ex @@ -7,47 +7,48 @@ defmodule BotchiniWeb.Endpoint do @session_options [ store: :cookie, key: "_botchini_key", - signing_salt: "W7YPA8pN", - same_site: "Lax" + signing_salt: "rDA/NW0y" ] - socket "/live", Phoenix.LiveView.Socket, - websocket: [connect_info: [session: @session_options]], - longpoll: [connect_info: [session: @session_options]] + socket("/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]) # Serve at "/" the static files from "priv/static" directory. # # You should set gzip to true if you are running phx.digest # when deploying your static files in production. - plug Plug.Static, + plug(Plug.Static, at: "/", from: :botchini, gzip: false, - only: BotchiniWeb.static_paths() + only: ~w(assets fonts images favicon.ico robots.txt) + ) # Code reloading can be explicitly enabled under the # :code_reloader configuration of your endpoint. if code_reloading? do - socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket - plug Phoenix.LiveReloader - plug Phoenix.CodeReloader - plug Phoenix.Ecto.CheckRepoStatus, otp_app: :botchini + socket("/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket) + plug(Phoenix.LiveReloader) + plug(Phoenix.CodeReloader) + plug(Phoenix.Ecto.CheckRepoStatus, otp_app: :botchini) end - plug Phoenix.LiveDashboard.RequestLogger, + plug(Phoenix.LiveDashboard.RequestLogger, param_key: "request_logger", cookie_key: "request_logger" + ) - plug Plug.RequestId - plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] + plug(Plug.RequestId) + plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint]) - plug Plug.Parsers, - parsers: [:urlencoded, :multipart, :json], + plug(Plug.Parsers, + parsers: [:urlencoded, :multipart, :json, Plug.Parsers.XML], pass: ["*/*"], + body_reader: {BotchiniWeb.CacheBodyReader, :read_body, []}, json_decoder: Phoenix.json_library() + ) - plug Plug.MethodOverride - plug Plug.Head - plug Plug.Session, @session_options - plug BotchiniWeb.Router + plug(Plug.MethodOverride) + plug(Plug.Head) + plug(Plug.Session, @session_options) + plug(BotchiniWeb.Router) end diff --git a/lib/botchini_web/plugs/body_reader.ex b/lib/botchini_web/plugs/body_reader.ex new file mode 100644 index 0000000..94369df --- /dev/null +++ b/lib/botchini_web/plugs/body_reader.ex @@ -0,0 +1,12 @@ +defmodule BotchiniWeb.CacheBodyReader do + @moduledoc """ + Plug for saving raw body on the connection + """ + + @spec read_body(Plug.Conn.t(), keyword()) :: {:ok, binary, Plug.Conn.t()} + def read_body(conn, opts) do + {:ok, body, conn} = Plug.Conn.read_body(conn, opts) + conn = update_in(conn.assigns[:raw_body], &[body | &1 || []]) + {:ok, body, conn} + end +end diff --git a/lib/botchini_web/plugs/xml.ex b/lib/botchini_web/plugs/xml.ex new file mode 100644 index 0000000..0797722 --- /dev/null +++ b/lib/botchini_web/plugs/xml.ex @@ -0,0 +1,27 @@ +defmodule Plug.Parsers.XML do + @moduledoc """ + Plug for decoding XML requests + """ + + @behaviour Plug.Parsers + import Plug.Conn + + def init(opts) do + opts + end + + def parse(conn, _, "atom+xml", _headers, opts) do + {:ok, body, conn} = read_body(conn, opts) + conn = update_in(conn.assigns[:raw_body], &[body | &1 || []]) + + decode({:ok, body, conn}) + end + + def parse(conn, _type, _subtype, _headers, _opts) do + {:next, conn} + end + + defp decode({:ok, body, conn}) do + {:ok, XmlToMap.naive_map(body), conn} + end +end diff --git a/lib/botchini_web/router.ex b/lib/botchini_web/router.ex index 961e07a..3d196ad 100644 --- a/lib/botchini_web/router.ex +++ b/lib/botchini_web/router.ex @@ -2,62 +2,58 @@ defmodule BotchiniWeb.Router do use BotchiniWeb, :router pipeline :browser do - plug :accepts, ["html"] - plug :fetch_session - plug :fetch_live_flash - plug :put_root_layout, html: {BotchiniWeb.Layouts, :root} - plug :protect_from_forgery - plug :put_secure_browser_headers + plug(:accepts, ["html"]) + plug(:fetch_session) + plug(:fetch_live_flash) + plug(:put_root_layout, {BotchiniWeb.LayoutView, :root}) + plug(:protect_from_forgery) + plug(:put_secure_browser_headers) end pipeline :api do - plug :accepts, ["json"] + plug(:accepts, ["json"]) end - # pipeline :xml_api do - # plug :accepts, ["xml"] - # end + pipeline :xml_api do + plug(:accepts, ["xml"]) + end scope "/", BotchiniWeb do - pipe_through :browser + pipe_through(:browser) - get "/", PageController, :index + get("/", PageController, :index) end + # Other scopes may use custom stacks. scope "/api", BotchiniWeb do - pipe_through :api + pipe_through(:api) - get "/status", Api.StatusController, :index + get("/status", StatusController, :index) - post "/twitch/webhooks/callback", Api.TwitchController, :callback - get "/youtube/webhooks/callback", Api.YoutubeController, :challenge + post("/twitch/webhooks/callback", TwitchController, :callback) + get("/youtube/webhooks/callback", YoutubeController, :challenge) end - # scope "/api", BotchiniWeb do - # pipe_through :xml_api + scope "/api", BotchiniWeb do + pipe_through(:xml_api) - # post "/youtube/webhooks/callback", YoutubeController, :notification - # end + post("/youtube/webhooks/callback", YoutubeController, :notification) + end - # Other scopes may use custom stacks. - # scope "/api", BotchiniWeb do - # pipe_through :api - # end - - # Enable LiveDashboard and Swoosh mailbox preview in development - if Application.compile_env(:botchini, :dev_routes) do - # If you want to use the LiveDashboard in production, you should put - # it behind authentication and allow only admins to access it. - # If your application does not have an admins-only section yet, - # you can use Plug.BasicAuth to set up some basic authentication - # as long as you are also using SSL (which you should anyway). + # Enables LiveDashboard only for development + # + # If you want to use the LiveDashboard in production, you should put + # it behind authentication and allow only admins to access it. + # If your application does not have an admins-only section yet, + # you can use Plug.BasicAuth to set up some basic authentication + # as long as you are also using SSL (which you should anyway). + if Mix.env() in [:dev, :test] do import Phoenix.LiveDashboard.Router - scope "/dev" do - pipe_through :browser + scope "/" do + pipe_through(:browser) - live_dashboard "/dashboard", metrics: BotchiniWeb.Telemetry - forward "/mailbox", Plug.Swoosh.MailboxPreview + live_dashboard("/dashboard", metrics: BotchiniWeb.Telemetry) end end end diff --git a/lib/botchini_web/telemetry.ex b/lib/botchini_web/telemetry.ex index 74f1813..7880096 100644 --- a/lib/botchini_web/telemetry.ex +++ b/lib/botchini_web/telemetry.ex @@ -1,4 +1,8 @@ defmodule BotchiniWeb.Telemetry do + @moduledoc """ + Handles Phoenix telemetry + """ + use Supervisor import Telemetry.Metrics @@ -22,34 +26,13 @@ defmodule BotchiniWeb.Telemetry do def metrics do [ # Phoenix Metrics - summary("phoenix.endpoint.start.system_time", - unit: {:native, :millisecond} - ), summary("phoenix.endpoint.stop.duration", unit: {:native, :millisecond} ), - summary("phoenix.router_dispatch.start.system_time", - tags: [:route], - unit: {:native, :millisecond} - ), - summary("phoenix.router_dispatch.exception.duration", - tags: [:route], - unit: {:native, :millisecond} - ), summary("phoenix.router_dispatch.stop.duration", tags: [:route], unit: {:native, :millisecond} ), - summary("phoenix.socket_connected.duration", - unit: {:native, :millisecond} - ), - summary("phoenix.channel_joined.duration", - unit: {:native, :millisecond} - ), - summary("phoenix.channel_handled_in.duration", - tags: [:event], - unit: {:native, :millisecond} - ), # Database Metrics summary("botchini.repo.query.total_time", diff --git a/lib/botchini_web/templates/layout/app.html.heex b/lib/botchini_web/templates/layout/app.html.heex new file mode 100644 index 0000000..e45e47a --- /dev/null +++ b/lib/botchini_web/templates/layout/app.html.heex @@ -0,0 +1,23 @@ +
+
+ <.container max_width="lg" class=""> +
+ <.link link_type="live_patch" to="/" class="tracking-wider uppercase"> + <.h3 class="!mb-0">Botchini + + + +
+ +
+ + <%= @inner_content %> +
diff --git a/lib/botchini_web/templates/layout/live.html.heex b/lib/botchini_web/templates/layout/live.html.heex new file mode 100644 index 0000000..c6a5daf --- /dev/null +++ b/lib/botchini_web/templates/layout/live.html.heex @@ -0,0 +1,8 @@ +<.container class="my-10"> + <.alert color="info" class="mb-5" label={live_flash(@flash, :info)} phx-click="lv:clear-flash" phx-value-key="info" /> + + <.alert color="danger" class="mb-5" label={live_flash(@flash, :error)} phx-click="lv:clear-flash" + phx-value-key="error" /> + + <%= @inner_content %> + diff --git a/lib/botchini_web/templates/layout/root.html.heex b/lib/botchini_web/templates/layout/root.html.heex new file mode 100644 index 0000000..fcfa48b --- /dev/null +++ b/lib/botchini_web/templates/layout/root.html.heex @@ -0,0 +1,18 @@ + + + + + + + + + <%= live_title_tag assigns[:page_title] || "Hello" , suffix: " · Botchini" %> + + + + + + <%= @inner_content %> + + + diff --git a/lib/botchini_web/templates/page/index.html.heex b/lib/botchini_web/templates/page/index.html.heex new file mode 100644 index 0000000..34ad86b --- /dev/null +++ b/lib/botchini_web/templates/page/index.html.heex @@ -0,0 +1,30 @@ +
+
+ <.h1 class="">Botchini + <.h3 class="">Get notified on Discord whenever your favorite creators goes live! +
+ +
+ + +
+ +
+
+
+ <%= @total_servers %> +
+ +
+ <%= @total_streams %> +
+ +

Discord servers

+ +

Creators being followed

+
+
+ + <.button color="primary" link_type="a" to={@invite_bot_url} target="_blank" rel="noopener noreferrer" + label="Add it to your server" /> +
diff --git a/lib/botchini_web/views/error_helpers.ex b/lib/botchini_web/views/error_helpers.ex new file mode 100644 index 0000000..f39435f --- /dev/null +++ b/lib/botchini_web/views/error_helpers.ex @@ -0,0 +1,47 @@ +defmodule BotchiniWeb.ErrorHelpers do + @moduledoc """ + Conveniences for translating and building error messages. + """ + + use Phoenix.HTML + + @doc """ + Generates tag for inlined form input errors. + """ + def error_tag(form, field) do + Enum.map(Keyword.get_values(form.errors, field), fn error -> + content_tag(:span, translate_error(error), + class: "invalid-feedback", + phx_feedback_for: input_name(form, field) + ) + end) + end + + @doc """ + Translates an error message using gettext. + """ + def translate_error({msg, opts}) do + # When using gettext, we typically pass the strings we want + # to translate as a static argument: + # + # # Translate "is invalid" in the "errors" domain + # dgettext("errors", "is invalid") + # + # # Translate the number of files with plural rules + # dngettext("errors", "1 file", "%{count} files", count) + # + # Because the error messages we show in our forms and APIs + # are defined inside Ecto, we need to translate them dynamically. + # This requires us to call the Gettext module passing our gettext + # backend as first argument. + # + # Note we use the "errors" domain, which means translations + # should be written to the errors.po file. The :count option is + # set by Ecto and indicates we should also apply plural rules. + if count = opts[:count] do + Gettext.dngettext(BotchiniWeb.Gettext, "errors", msg, msg, count, opts) + else + Gettext.dgettext(BotchiniWeb.Gettext, "errors", msg, opts) + end + end +end diff --git a/lib/botchini_web/views/error_view.ex b/lib/botchini_web/views/error_view.ex new file mode 100644 index 0000000..e8858de --- /dev/null +++ b/lib/botchini_web/views/error_view.ex @@ -0,0 +1,16 @@ +defmodule BotchiniWeb.ErrorView do + use BotchiniWeb, :view + + # If you want to customize a particular status code + # for a certain format, you may uncomment below. + # def render("500.html", _assigns) do + # "Internal Server Error" + # end + + # By default, Phoenix returns the status message from + # the template name. For example, "404.html" becomes + # "Not Found". + def template_not_found(template, _assigns) do + Phoenix.Controller.status_message_from_template(template) + end +end diff --git a/lib/botchini_web/views/layout_view.ex b/lib/botchini_web/views/layout_view.ex new file mode 100644 index 0000000..95377d2 --- /dev/null +++ b/lib/botchini_web/views/layout_view.ex @@ -0,0 +1,7 @@ +defmodule BotchiniWeb.LayoutView do + use BotchiniWeb, :view + + # Phoenix LiveDashboard is available only in development by default, + # so we instruct Elixir to not warn if the dashboard route is missing. + @compile {:no_warn_undefined, {Routes, :live_dashboard_path, 2}} +end diff --git a/lib/botchini_web/views/page_view.ex b/lib/botchini_web/views/page_view.ex new file mode 100644 index 0000000..c1cca11 --- /dev/null +++ b/lib/botchini_web/views/page_view.ex @@ -0,0 +1,3 @@ +defmodule BotchiniWeb.PageView do + use BotchiniWeb, :view +end diff --git a/mix.exs b/mix.exs index 6ff8faa..86f9eac 100644 --- a/mix.exs +++ b/mix.exs @@ -4,8 +4,9 @@ defmodule Botchini.MixProject do def project do [ app: :botchini, - version: "9.0.0", + version: "8.9.0", elixir: "~> 1.16.1", + build_embedded: Mix.env() == :prod, start_permanent: Mix.env() == :prod, deps: deps(), aliases: aliases(), @@ -13,9 +14,6 @@ defmodule Botchini.MixProject do ] end - # Configuration for the OTP application. - # - # Type `mix help compile.app` for more information. def application do [ mod: {Botchini.Application, []}, @@ -24,11 +22,7 @@ defmodule Botchini.MixProject do end defp extra_applications(:dev), do: extra_applications(:all) ++ [:exsync] - defp extra_applications(_all), do: [:logger, :runtime_tools] - - # Specifies which paths to compile per environment. - defp elixirc_paths(:test), do: ["lib", "test/support"] - defp elixirc_paths(_), do: ["lib"] + defp extra_applications(_all), do: [:logger, :elixir_xml_to_map] defp deps do [ @@ -36,58 +30,51 @@ defmodule Botchini.MixProject do {:nostrum, git: "https://github.com/Kraigie/nostrum.git", runtime: Mix.env() != :test}, {:cowlib, "~> 2.11", hex: :remedy_cowlib, override: true}, # Phoenix - {:phoenix, "~> 1.7.11"}, - {:phoenix_ecto, "~> 4.4"}, - {:phoenix_html, "~> 4.0"}, - {:phoenix_live_reload, "~> 1.2", only: :dev}, - {:phoenix_live_view, "~> 0.20.2"}, - {:floki, ">= 0.30.0", only: :test}, - {:phoenix_live_dashboard, "~> 0.8.3"}, - {:esbuild, "~> 0.8", runtime: Mix.env() == :dev}, - {:tailwind, "~> 0.2", runtime: Mix.env() == :dev}, - {:heroicons, - github: "tailwindlabs/heroicons", - tag: "v2.1.1", - sparse: "optimized", - app: false, - compile: false, - depth: 1}, - {:swoosh, "~> 1.5"}, - {:finch, "~> 0.13"}, + {:phoenix, "~> 1.6.16"}, + {:phoenix_html, "~> 3.3.1"}, + {:phoenix_live_dashboard, "~> 0.6"}, + {:phoenix_live_reload, "~> 1.4.1", only: :dev}, + {:phoenix_live_view, "~> 0.17.14"}, + {:floki, ">= 0.34.2", only: :test}, + {:esbuild, "~> 0.7", runtime: Mix.env() == :dev}, {:telemetry_metrics, "~> 0.6"}, {:telemetry_poller, "~> 1.0"}, - {:gettext, "~> 0.20"}, - {:jason, "~> 1.2"}, - {:dns_cluster, "~> 0.1.1"}, - {:bandit, "~> 1.2"}, + {:gettext, "~> 0.22.1"}, + {:jason, "~> 1.4.0"}, + {:plug_cowboy, "~> 2.6.1"}, + {:tailwind, "~> 0.2", runtime: Mix.env() == :dev}, + {:petal_components, "~> 0.17"}, + {:elixir_xml_to_map, "~> 2.0"}, # Ecto - {:ecto_sql, "~> 3.11.1"}, - {:postgrex, ">= 0.17.4"}, + {:phoenix_ecto, "~> 4.4"}, + {:ecto_sql, "~> 3.9.2"}, + {:postgrex, ">= 0.16.5"}, # HTTP Client - {:tesla, "~> 1.8.0"}, + {:tesla, "~> 1.6.0"}, {:gun, "~> 2.0", override: true}, - {:hackney, "~> 1.20.1"}, - {:exconstructor, "~> 1.2.10"}, + {:hackney, "~> 1.17.0"}, + {:exconstructor, "~> 1.1.0"}, # Helpers {:ink, "~> 1.0"}, {:quantum, "~> 3.0"}, # Development and testing {:exsync, "~> 0.2", only: :dev}, {:credo, "~> 1.7.0", only: [:dev, :test], runtime: false}, - {:patch, "~> 0.13.0", only: [:test]}, + {:patch, "~> 0.12.0", only: [:test]}, {:faker, "~> 0.16", only: :test} ] end defp aliases do [ - setup: ["deps.get", "ecto.setup", "assets.setup", "assets.build"], - "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], + setup: ["deps.get", "ecto.setup"], + "ecto.setup": ["ecto.create", "ecto.migrate"], "ecto.reset": ["ecto.drop", "ecto.setup"], test: ["ecto.drop --quiet", "ecto.create --quiet", "ecto.migrate --quiet", "test"], - "assets.setup": ["tailwind.install --if-missing", "esbuild.install --if-missing"], - "assets.build": ["tailwind botchini", "esbuild botchini"], "assets.deploy": ["tailwind default --minify", "esbuild default --minify", "phx.digest"] ] end + + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] end diff --git a/mix.lock b/mix.lock index 0c03ee3..3934b42 100644 --- a/mix.lock +++ b/mix.lock @@ -1,36 +1,37 @@ %{ - "bandit": {:hex, :bandit, "1.2.0", "2b5784909cc25b2514868055ff27458cdc63314514b90d86448ff91d18bece80", [:mix], [{:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "05688b883d87cc3b32991517a61e8c2ce8ee2dd6aa6eb73635426002a6661491"}, - "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, + "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, "castle": {:hex, :castle, "0.3.0", "47b1a550b2348a6d7e60e43ded1df19dca601ed21ef6f267c3dbb1b3a301fbf5", [:mix], [{:forecastle, "~> 0.1.0", [hex: :forecastle, repo: "hexpm", optional: false]}], "hexpm", "dbdc1c171520c4591101938a3d342dec70d36b7f5b102a5c138098581e35fcef"}, - "castore": {:hex, :castore, "1.0.5", "9eeebb394cc9a0f3ae56b813459f990abb0a3dedee1be6b27fdb50301930502f", [:mix], [], "hexpm", "8d7c597c3e4a64c395980882d4bca3cebb8d74197c590dc272cfd3b6a6310578"}, - "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, + "castore": {:hex, :castore, "1.0.3", "7130ba6d24c8424014194676d608cb989f62ef8039efd50ff4b3f33286d06db8", [:mix], [], "hexpm", "680ab01ef5d15b161ed6a95449fac5c6b8f60055677a8e79acf01b27baa4390b"}, + "certifi": {:hex, :certifi, "2.11.0", "5adfe37ceb8569d019f836944aeaf27f8ac391dacaf3707f570c155b7e03aaa8", [:rebar3], [], "hexpm", "9e37e0542ec3fabaa19a0734b3900dc095797fac48c40a2a9741d8ad5e3c9bb7"}, "chacha20": {:hex, :chacha20, "1.0.4", "0359d8f9a32269271044c1b471d5cf69660c362a7c61a98f73a05ef0b5d9eb9e", [:mix], [], "hexpm", "2027f5d321ae9903f1f0da7f51b0635ad6b8819bc7fe397837930a2011bc2349"}, + "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, + "cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"}, + "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"}, "cowlib": {:hex, :remedy_cowlib, "2.11.1", "7abb4d0779a7d1c655f7642dc0bd0af754951e95005dfa01b500c68fe35a5961", [:rebar3], [], "hexpm", "0b613dc308e080cb6134285f1b1b55c3873e101652e70c70010fc6651c91b130"}, - "credo": {:hex, :credo, "1.7.3", "05bb11eaf2f2b8db370ecaa6a6bda2ec49b2acd5e0418bc106b73b07128c0436", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "35ea675a094c934c22fb1dca3696f3c31f2728ae6ef5a53b5d648c11180a4535"}, + "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"}, "crontab": {:hex, :crontab, "1.1.13", "3bad04f050b9f7f1c237809e42223999c150656a6b2afbbfef597d56df2144c5", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "d67441bec989640e3afb94e123f45a2bc42d76e02988c9613885dc3d01cf7085"}, "curve25519": {:hex, :curve25519, "1.0.5", "f801179424e4012049fcfcfcda74ac04f65d0ffceeb80e7ef1d3352deb09f5bb", [:mix], [], "hexpm", "0fba3ad55bf1154d4d5fc3ae5fb91b912b77b13f0def6ccb3a5d58168ff4192d"}, - "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, + "db_connection": {:hex, :db_connection, "2.5.0", "bb6d4f30d35ded97b29fe80d8bd6f928a1912ca1ff110831edcd238a1973652c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c92d5ba26cd69ead1ff7582dbb860adeedfff39774105a4f1c92cbb654b55aa2"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, - "dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"}, - "ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"}, - "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, + "ecto": {:hex, :ecto, "3.9.6", "2f420c173efcb2e22fa4f8fc41e75e02b3c5bd4cffef12085cae5418c12e530d", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df17bc06ba6f78a7b764e4a14ef877fe5f4499332c5a105ace11fe7013b72c84"}, + "ecto_sql": {:hex, :ecto_sql, "3.9.2", "34227501abe92dba10d9c3495ab6770e75e79b836d114c41108a4bf2ce200ad5", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1eb5eeb4358fdbcd42eac11c1fbd87e3affd7904e639d77903c1358b2abd3f70"}, "ed25519": {:hex, :ed25519, "1.4.1", "479fb83c3e31987c9cad780e6aeb8f2015fb5a482618cdf2a825c9aff809afc4", [:mix], [], "hexpm", "0dacb84f3faa3d8148e81019ca35f9d8dcee13232c32c9db5c2fb8ff48c80ec7"}, + "elixir_xml_to_map": {:hex, :elixir_xml_to_map, "2.0.0", "a9b4a3e03ec7b938d463958ce857aa719627efc9af62412f7fa80497f69c1091", [:mix], [{:erlsom, "~> 1.4", [hex: :erlsom, repo: "hexpm", optional: false]}], "hexpm", "6988899f0cd5036a6dabc493ffc110cf3d6bffbf8e1b56410575f66c1bf6eb8c"}, "equivalex": {:hex, :equivalex, "1.0.3", "170d9a82ae066e0020dfe1cf7811381669565922eb3359f6c91d7e9a1124ff74", [:mix], [], "hexpm", "46fa311adb855117d36e461b9c0ad2598f72110ad17ad73d7533c78020e045fc"}, - "esbuild": {:hex, :esbuild, "0.8.1", "0cbf919f0eccb136d2eeef0df49c4acf55336de864e63594adcea3814f3edf41", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "25fc876a67c13cb0a776e7b5d7974851556baeda2085296c14ab48555ea7560f"}, - "exconstructor": {:hex, :exconstructor, "1.2.10", "72a540c89b4c5af75f88c076727c0318a8f4038df6412dcf546e8771dcac118d", [:mix], [], "hexpm", "6e504f88cc56c3a7313f0d7687c5d3454b014255867232957512a61e5f90bea7"}, - "expo": {:hex, :expo, "0.5.1", "249e826a897cac48f591deba863b26c16682b43711dd15ee86b92f25eafd96d9", [:mix], [], "hexpm", "68a4233b0658a3d12ee00d27d37d856b1ba48607e7ce20fd376958d0ba6ce92b"}, + "erlsom": {:hex, :erlsom, "1.5.1", "c8fe2babd33ff0846403f6522328b8ab676f896b793634cfe7ef181c05316c03", [:rebar3], [], "hexpm", "7965485494c5844dd127656ac40f141aadfa174839ec1be1074e7edf5b4239eb"}, + "esbuild": {:hex, :esbuild, "0.7.1", "fa0947e8c3c3c2f86c9bf7e791a0a385007ccd42b86885e8e893bdb6631f5169", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "66661cdf70b1378ee4dc16573fcee67750b59761b2605a0207c267ab9d19f13c"}, + "exconstructor": {:hex, :exconstructor, "1.1.0", "272623a7b203cb2901c20cbb92c5c3ab103cc0087ff7c881979e046043346752", [:mix], [], "hexpm", "0edd55e8352e04dabf71f35453a57650175c7d7e6af707b1d3df610e5052afe0"}, + "expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"}, "exsync": {:hex, :exsync, "0.3.0", "39ab8b3d4e5fe779a34ad930135145283ebf56069513dfdfaad4e30a04b158c7", [:mix], [{:file_system, "~> 0.2", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "2030d085a14fa5f685d53d97171a21345dddaf2b67a0927263efc6b2cd2bb09f"}, "faker": {:hex, :faker, "0.17.0", "671019d0652f63aefd8723b72167ecdb284baf7d47ad3a82a15e9b8a6df5d1fa", [:mix], [], "hexpm", "a7d4ad84a93fd25c5f5303510753789fc2433ff241bf3b4144d3f6f291658a6a"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "finch": {:hex, :finch, "0.17.0", "17d06e1d44d891d20dbd437335eebe844e2426a0cd7e3a3e220b461127c73f70", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8d014a661bb6a437263d4b5abf0bcbd3cf0deb26b1e8596f2a271d22e48934c7"}, - "floki": {:hex, :floki, "0.35.3", "0c8c6234aa71cb2b069cf801e8f8f30f8d096eb452c3dae2ccc409510ec32720", [:mix], [], "hexpm", "6d9f07f3fc76599f3b66c39f4a81ac62c8f4d9631140268db92aacad5d0e56d4"}, + "floki": {:hex, :floki, "0.34.3", "5e2dcaec5d7c228ce5b1d3501502e308b2d79eb655e4191751a1fe491c37feac", [:mix], [], "hexpm", "9577440eea5b97924b4bf3c7ea55f7b8b6dce589f9b28b096cc294a8dc342341"}, "forecastle": {:hex, :forecastle, "0.1.2", "f8dab08962c7a33010ebd39182513129f17b8814aa16fa453ddd536040882daf", [:mix], [], "hexpm", "8efaeb2e7d0fa24c605605e42562e2dbb0ffd11dc1dd99ef77d78884536ce501"}, "gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"}, - "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, + "gettext": {:hex, :gettext, "0.22.3", "c8273e78db4a0bb6fba7e9f0fd881112f349a3117f7f7c598fa18c66c888e524", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "935f23447713954a6866f1bb28c3a878c4c011e802bcd68a726f5e558e4b64bd"}, "gun": {:hex, :gun, "2.0.1", "160a9a5394800fcba41bc7e6d421295cf9a7894c2252c0678244948e3336ad73", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "a10bc8d6096b9502205022334f719cc9a08d9adcfbfc0dbee9ef31b56274a20b"}, - "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, - "heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "88ab3a0d790e6a47404cba02800a6b25d2afae50", [tag: "v2.1.1", sparse: "optimized"]}, - "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, + "hackney": {:hex, :hackney, "1.17.1", "08463f93d2cc1a03817bf28d8dae6021543f773bd436c9377047224856c4422c", [:rebar3], [{:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "~> 3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "d2cba9e3c8103ad0320623e9f1c33e8d378a15eaabe2ee8ae441898f3d35a18c"}, + "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "ink": {:hex, :ink, "1.2.1", "d42ea4753a5fe0a2103ac25aecca581196e49497f67f80b8fe0fc674c92afac6", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "2038033f35f94f3e12ec2362b9978ab1415f426f8eef2228bfcd4e465e9b05ee"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, @@ -38,36 +39,34 @@ "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, - "mint": {:hex, :mint, "1.5.2", "4805e059f96028948870d23d7783613b7e6b0e2fb4e98d720383852a760067fd", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "d77d9e9ce4eb35941907f1d3df38d8f750c357865353e21d335bdcdf6d892a02"}, - "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, - "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"}, - "nostrum": {:git, "https://github.com/Kraigie/nostrum.git", "d2daf4941927bc4452a4e79acbef4a574ce32f57", []}, + "nostrum": {:git, "https://github.com/Kraigie/nostrum.git", "08adf8b1bd3236e5d7d5b87dc3995bb5d3cfb4dd", []}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, - "patch": {:hex, :patch, "0.13.0", "da48728f9086a835956200a671210fe88f67ff48bb1f92626989886493ac2081", [:mix], [], "hexpm", "d65a840d485dfa05bf6673269b56680e7537a05050684e713de125a351b28112"}, - "phoenix": {:hex, :phoenix, "1.7.11", "1d88fc6b05ab0c735b250932c4e6e33bfa1c186f76dcf623d8dd52f07d6379c7", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "b1ec57f2e40316b306708fe59b92a16b9f6f4bf50ccfa41aa8c7feb79e0ec02a"}, - "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.3", "86e9878f833829c3f66da03d75254c155d91d72a201eb56ae83482328dc7ca93", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d36c401206f3011fefd63d04e8ef626ec8791975d9d107f9a0817d426f61ac07"}, - "phoenix_html": {:hex, :phoenix_html, "4.0.0", "4857ec2edaccd0934a923c2b0ba526c44a173c86b847e8db725172e9e51d11d6", [:mix], [], "hexpm", "cee794a052f243291d92fa3ccabcb4c29bb8d236f655fb03bcbdc3a8214b8d13"}, - "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.3", "7ff51c9b6609470f681fbea20578dede0e548302b0c8bdf338b5a753a4f045bf", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "f9470a0a8bae4f56430a23d42f977b5a6205fdba6559d76f932b876bfaec652d"}, + "patch": {:hex, :patch, "0.12.0", "2da8967d382bade20344a3e89d618bfba563b12d4ac93955468e830777f816b0", [:mix], [], "hexpm", "ffd0e9a7f2ad5054f37af84067ee88b1ad337308a1cb227e181e3967127b0235"}, + "petal_components": {:hex, :petal_components, "0.17.7", "09ee08bdf2bf6c1a043fc26b8167849cc2f14690822f783df784bb9196136202", [:mix], [{:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_ecto, "~> 4.4", [hex: :phoenix_ecto, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.17", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "8c9bc2ccf11a617bd6d8f0ebe2361716b0a6ab3bb30fd974719b54efc113db75"}, + "phoenix": {:hex, :phoenix, "1.6.16", "e5bdd18c7a06da5852a25c7befb72246de4ddc289182285f8685a40b7b5f5451", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.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", "e15989ff34f670a96b95ef6d1d25bad0d9c50df5df40b671d8f4a669e050ac39"}, + "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.2", "b21bd01fdeffcfe2fab49e4942aa938b6d3e89e93a480d4aee58085560a0bc0d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "70242edd4601d50b69273b057ecf7b684644c19ee750989fd555625ae4ce8f5d"}, + "phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"}, + "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"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [: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", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"}, - "phoenix_live_view": {:hex, :phoenix_live_view, "0.20.4", "0dc21e89dbf5b1f3a69090a92d1a2724bfa951d5cbccff6c5b318e12eac107e3", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d8930c9c79dd25775646874abdf3d8d24356b88d58fa14f637c8e3418d36bce3"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "0.17.14", "5ec615d4d61bf9d4755f158bd6c80372b715533fe6d6219e12d74fb5eedbeac1", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.0 or ~> 1.7.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", "afeb6ba43ce329a6f7fc1c9acdfc6d3039995345f025febb7f409a92f6faebd3"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, - "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, - "plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"}, - "plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.3", "32de561eefcefa951aead30a1f94f1b5f0379bc9e340bb5c667f65f1edfa4326", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "16f4b6588a4152f3cc057b9d0c0ba7e82ee23afa65543da535313ad8d25d8e2c"}, + "phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"}, + "plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [: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", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, "poly1305": {:hex, :poly1305, "1.0.4", "7cdc8961a0a6e00a764835918cdb8ade868044026df8ef5d718708ea6cc06611", [:mix], [{:chacha20, "~> 1.0", [hex: :chacha20, repo: "hexpm", optional: false]}, {:equivalex, "~> 1.0", [hex: :equivalex, repo: "hexpm", optional: false]}], "hexpm", "e14e684661a5195e149b3139db4a1693579d4659d65bba115a307529c47dbc3b"}, - "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, + "postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"}, "quantum": {:hex, :quantum, "3.5.0", "8d2c5ba68c55991e8975aca368e3ab844ba01f4b87c4185a7403280e2c99cf34", [:mix], [{:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}, {:gen_stage, "~> 0.14 or ~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_registry, "~> 0.2", [hex: :telemetry_registry, repo: "hexpm", optional: false]}], "hexpm", "cab737d1d9779f43cb1d701f46dd05ea58146fd96238d91c9e0da662c1982bb6"}, + "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, + "remix": {:hex, :remix, "0.0.2", "f06115659d8ede8d725fae1708920ef73353a1b39efe6a232d2a38b1f2902109", [:mix], [], "hexpm", "5f5555646ed4fca83fab8620735150aa0bc408c5a17a70d28cfa7086bc6f497c"}, "salsa20": {:hex, :salsa20, "1.0.4", "404cbea1fa8e68a41bcc834c0a2571ac175580fec01cc38cc70c0fb9ffc87e9b", [:mix], [], "hexpm", "745ddcd8cfa563ddb0fd61e7ce48d5146279a2cf7834e1da8441b369fdc58ac6"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, - "swoosh": {:hex, :swoosh, "1.15.1", "9a8f45b24efc51ce3b04680324828c50fbd9733b9cd798abdcf9b51f5b4667a3", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.4 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aa4e794bd39529f3d7e9cfd406ff1bbc422b9120e2a7ad38dfdf9b469d0c91f6"}, - "tailwind": {:hex, :tailwind, "0.2.2", "9e27288b568ede1d88517e8c61259bc214a12d7eed271e102db4c93fcca9b2cd", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "ccfb5025179ea307f7f899d1bb3905cd0ac9f687ed77feebc8f67bdca78565c4"}, + "tailwind": {:hex, :tailwind, "0.2.1", "83d8eadbe71a8e8f67861fe7f8d51658ecfb258387123afe4d9dc194eddc36b0", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "e8a13f6107c95f73e58ed1b4221744e1eb5a093cd1da244432067e19c8c9a277"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, - "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"}, + "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"}, "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, "telemetry_registry": {:hex, :telemetry_registry, "0.3.1", "14a3319a7d9027bdbff7ebcacf1a438f5f5c903057b93aee484cca26f05bdcba", [:mix, :rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6d0ca77b691cf854ed074b459a93b87f4c7f5512f8f7743c635ca83da81f939e"}, - "tesla": {:hex, :tesla, "1.8.0", "d511a4f5c5e42538d97eef7c40ec4f3e44effdc5068206f42ed859e09e51d1fd", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "10501f360cd926a309501287470372af1a6e1cbed0f43949203a4c13300bc79f"}, - "thousand_island": {:hex, :thousand_island, "1.3.2", "bc27f9afba6e1a676dd36507d42e429935a142cf5ee69b8e3f90bff1383943cd", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0e085b93012cd1057b378fce40cbfbf381ff6d957a382bfdd5eca1a98eec2535"}, + "tesla": {:hex, :tesla, "1.6.1", "42c9b88a988dcb6f929560890902dd06a81aa320311887aeeab0406581b5e567", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "a17a9aa6c14e44ad9940a143290d44fdcc49eb2ff0bf2e9a0ddf62e6be491f59"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, - "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, - "websock_adapter": {:hex, :websock_adapter, "0.5.5", "9dfeee8269b27e958a65b3e235b7e447769f66b5b5925385f5a569269164a210", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "4b977ba4a01918acbf77045ff88de7f6972c2a009213c515a445c48f224ffce9"}, } diff --git a/priv/gettext/errors.pot b/priv/gettext/errors.pot index eef2de2..39a220b 100644 --- a/priv/gettext/errors.pot +++ b/priv/gettext/errors.pot @@ -7,6 +7,7 @@ ## Run `mix gettext.extract` to bring this file up to ## date. Leave `msgstr`s empty as changing them here has no ## effect: edit them in PO (`.po`) files instead. + ## From Ecto.Changeset.cast/4 msgid "can't be blank" msgstr "" @@ -47,23 +48,13 @@ msgid "are still associated with this entry" msgstr "" ## From Ecto.Changeset.validate_length/3 -msgid "should have %{count} item(s)" -msgid_plural "should have %{count} item(s)" -msgstr[0] "" -msgstr[1] "" - msgid "should be %{count} character(s)" msgid_plural "should be %{count} character(s)" msgstr[0] "" msgstr[1] "" -msgid "should be %{count} byte(s)" -msgid_plural "should be %{count} byte(s)" -msgstr[0] "" -msgstr[1] "" - -msgid "should have at least %{count} item(s)" -msgid_plural "should have at least %{count} item(s)" +msgid "should have %{count} item(s)" +msgid_plural "should have %{count} item(s)" msgstr[0] "" msgstr[1] "" @@ -72,13 +63,8 @@ msgid_plural "should be at least %{count} character(s)" msgstr[0] "" msgstr[1] "" -msgid "should be at least %{count} byte(s)" -msgid_plural "should be at least %{count} byte(s)" -msgstr[0] "" -msgstr[1] "" - -msgid "should have at most %{count} item(s)" -msgid_plural "should have at most %{count} item(s)" +msgid "should have at least %{count} item(s)" +msgid_plural "should have at least %{count} item(s)" msgstr[0] "" msgstr[1] "" @@ -87,8 +73,8 @@ msgid_plural "should be at most %{count} character(s)" msgstr[0] "" msgstr[1] "" -msgid "should be at most %{count} byte(s)" -msgid_plural "should be at most %{count} byte(s)" +msgid "should have at most %{count} item(s)" +msgid_plural "should have at most %{count} item(s)" msgstr[0] "" msgstr[1] "" diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs deleted file mode 100644 index 147c55c..0000000 --- a/priv/repo/seeds.exs +++ /dev/null @@ -1,11 +0,0 @@ -# Script for populating the database. You can run it as: -# -# mix run priv/repo/seeds.exs -# -# Inside the script, you can read and write to any of your -# repositories directly: -# -# Botchini.Repo.insert!(%Botchini.SomeSchema{}) -# -# We recommend using the bang functions (`insert!`, `update!` -# and so on) as they will fail if something goes wrong. diff --git a/priv/static/favicon.ico b/priv/static/favicon.ico index 7f372bf..73de524 100644 Binary files a/priv/static/favicon.ico and b/priv/static/favicon.ico differ diff --git a/priv/static/images/logo.svg b/priv/static/images/logo.svg deleted file mode 100644 index 9f26bab..0000000 --- a/priv/static/images/logo.svg +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/test/botchini_web/controllers/error_html_test.exs b/test/botchini_web/controllers/error_html_test.exs deleted file mode 100644 index 9f9dbd8..0000000 --- a/test/botchini_web/controllers/error_html_test.exs +++ /dev/null @@ -1,14 +0,0 @@ -defmodule BotchiniWeb.ErrorHTMLTest do - use BotchiniWeb.ConnCase, async: true - - # Bring render_to_string/4 for testing custom views - import Phoenix.Template - - test "renders 404.html" do - assert render_to_string(BotchiniWeb.ErrorHTML, "404", "html", []) == "Not Found" - end - - test "renders 500.html" do - assert render_to_string(BotchiniWeb.ErrorHTML, "500", "html", []) == "Internal Server Error" - end -end diff --git a/test/botchini_web/controllers/error_json_test.exs b/test/botchini_web/controllers/error_json_test.exs deleted file mode 100644 index aabf96c..0000000 --- a/test/botchini_web/controllers/error_json_test.exs +++ /dev/null @@ -1,12 +0,0 @@ -defmodule BotchiniWeb.ErrorJSONTest do - use BotchiniWeb.ConnCase, async: true - - test "renders 404" do - assert BotchiniWeb.ErrorJSON.render("404.json", %{}) == %{errors: %{detail: "Not Found"}} - end - - test "renders 500" do - assert BotchiniWeb.ErrorJSON.render("500.json", %{}) == - %{errors: %{detail: "Internal Server Error"}} - end -end diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex deleted file mode 100644 index d1d0d00..0000000 --- a/test/support/conn_case.ex +++ /dev/null @@ -1,38 +0,0 @@ -defmodule BotchiniWeb.ConnCase do - @moduledoc """ - This module defines the test case to be used by - tests that require setting up a connection. - - Such tests rely on `Phoenix.ConnTest` and also - import other functionality to make it easier - to build common data structures and query the data layer. - - Finally, if the test case interacts with the database, - we enable the SQL sandbox, so changes done to the database - are reverted at the end of every test. If you are using - PostgreSQL, you can even run database tests asynchronously - by setting `use BotchiniWeb.ConnCase, async: true`, although - this option is not recommended for other databases. - """ - - use ExUnit.CaseTemplate - - using do - quote do - # The default endpoint for testing - @endpoint BotchiniWeb.Endpoint - - use BotchiniWeb, :verified_routes - - # Import conveniences for testing with connections - import Plug.Conn - import Phoenix.ConnTest - import BotchiniWeb.ConnCase - end - end - - setup tags do - Botchini.DataCase.setup_sandbox(tags) - {:ok, conn: Phoenix.ConnTest.build_conn()} - end -end diff --git a/test/support/data_case.ex b/test/support/data_case.ex index c384d4a..7f9c01c 100644 --- a/test/support/data_case.ex +++ b/test/support/data_case.ex @@ -1,17 +1,6 @@ defmodule Botchini.DataCase do @moduledoc """ - This module defines the setup for tests requiring - access to the application's data layer. - - You may define functions here to be used as helpers in - your tests. - - Finally, if the test case interacts with the database, - we enable the SQL sandbox, so changes done to the database - are reverted at the end of every test. If you are using - PostgreSQL, you can even run database tests asynchronously - by setting `use Botchini.DataCase, async: true`, although - this option is not recommended for other databases. + Case for tests that use the database """ use ExUnit.CaseTemplate @@ -24,44 +13,14 @@ defmodule Botchini.DataCase do using do quote do - alias Botchini.Repo - import Ecto - import Ecto.Changeset - import Ecto.Query + import Ecto.{Changeset, Query} + import Botchini.DataCase + alias Botchini.Repo end end - setup tags do - Botchini.DataCase.setup_sandbox(tags) - :ok - end - - @doc """ - Sets up the sandbox based on the test tags. - """ - def setup_sandbox(tags) do - pid = Sandbox.start_owner!(Botchini.Repo, shared: not tags[:async]) - on_exit(fn -> Sandbox.stop_owner(pid) end) - end - - @doc """ - A helper that transforms changeset errors into a map of messages. - - assert {:error, changeset} = Accounts.create_user(%{password: "short"}) - assert "password is too short" in errors_on(changeset).password - assert %{password: ["password is too short"]} = errors_on(changeset) - - """ - def errors_on(changeset) do - Ecto.Changeset.traverse_errors(changeset, fn {message, opts} -> - Regex.replace(~r"%{(\w+)}", message, fn _, key -> - opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string() - end) - end) - end - setup tags do :ok = Sandbox.checkout(Repo)