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"""
-
-
-
-
-
- <.focus_wrap
- id={"#{@id}-container"}
- phx-window-keydown={JS.exec("data-cancel", to: "##{@id}")}
- phx-key="escape"
- phx-click-away={JS.exec("data-cancel", to: "##{@id}")}
- class="shadow-zinc-700/10 ring-zinc-700/10 relative hidden rounded-2xl bg-white p-14 shadow-lg ring-1 transition"
- >
-
-
- <.icon name="hero-x-mark-solid" class="h-5 w-5" />
-
-
-
- <%= render_slot(@inner_block) %>
-
-
-
-
-
-
- """
- end
-
- @doc """
- Renders flash notices.
-
- ## Examples
-
- <.flash kind={:info} flash={@flash} />
- <.flash kind={:info} phx-mounted={show("#flash")}>Welcome Back!
- """
- attr :id, :string, doc: "the optional id of flash container"
- attr :flash, :map, default: %{}, doc: "the map of flash messages to display"
- attr :title, :string, default: nil
- attr :kind, :atom, values: [:info, :error], doc: "used for styling and flash lookup"
- attr :rest, :global, doc: "the arbitrary HTML attributes to add to the flash container"
-
- slot :inner_block, doc: "the optional inner block that renders the flash message"
-
- def flash(assigns) do
- assigns = assign_new(assigns, :id, fn -> "flash-#{assigns.kind}" end)
-
- ~H"""
-
hide("##{@id}")}
- role="alert"
- class={[
- "fixed top-2 right-2 mr-2 w-80 sm:w-96 z-50 rounded-lg p-3 ring-1",
- @kind == :info && "bg-emerald-50 text-emerald-800 ring-emerald-500 fill-cyan-900",
- @kind == :error && "bg-rose-50 text-rose-900 shadow-md ring-rose-500 fill-rose-900"
- ]}
- {@rest}
- >
-
- <.icon :if={@kind == :info} name="hero-information-circle-mini" class="h-4 w-4" />
- <.icon :if={@kind == :error} name="hero-exclamation-circle-mini" class="h-4 w-4" />
- <%= @title %>
-
-
<%= msg %>
-
- <.icon name="hero-x-mark-solid" class="h-5 w-5 opacity-40 group-hover:opacity-70" />
-
-
- """
- end
-
- @doc """
- Shows the flash group with standard titles and content.
-
- ## Examples
-
- <.flash_group flash={@flash} />
- """
- attr :flash, :map, required: true, doc: "the map of flash messages"
- attr :id, :string, default: "flash-group", doc: "the optional id of flash container"
-
- def flash_group(assigns) do
- ~H"""
-
- <.flash kind={:info} title={gettext("Success!")} flash={@flash} />
- <.flash kind={:error} title={gettext("Error!")} flash={@flash} />
- <.flash
- id="client-error"
- kind={:error}
- title={gettext("We can't find the internet")}
- phx-disconnected={show(".phx-client-error #client-error")}
- phx-connected={hide("#client-error")}
- hidden
- >
- <%= gettext("Attempting to reconnect") %>
- <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
-
-
- <.flash
- id="server-error"
- kind={:error}
- title={gettext("Something went wrong!")}
- phx-disconnected={show(".phx-server-error #server-error")}
- phx-connected={hide("#server-error")}
- hidden
- >
- <%= gettext("Hang in there while we get back on track") %>
- <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
-
-
- """
- end
-
- @doc """
- Renders a simple form.
-
- ## Examples
-
- <.simple_form for={@form} phx-change="validate" phx-submit="save">
- <.input field={@form[:email]} label="Email"/>
- <.input field={@form[:username]} label="Username" />
- <:actions>
- <.button>Save
-
-
- """
- attr :for, :any, required: true, doc: "the datastructure for the form"
- attr :as, :any, default: nil, doc: "the server side parameter to collect all input under"
-
- attr :rest, :global,
- include: ~w(autocomplete name rel action enctype method novalidate target multipart),
- doc: "the arbitrary HTML attributes to apply to the form tag"
-
- slot :inner_block, required: true
- slot :actions, doc: "the slot for form actions, such as a submit button"
-
- def simple_form(assigns) do
- ~H"""
- <.form :let={f} for={@for} as={@as} {@rest}>
-
- <%= render_slot(@inner_block, f) %>
-
- <%= render_slot(action, f) %>
-
-
-
- """
- end
-
- @doc """
- Renders a button.
-
- ## Examples
-
- <.button>Send!
- <.button phx-click="go" class="ml-2">Send!
- """
- attr :type, :string, default: nil
- attr :class, :string, default: nil
- attr :rest, :global, include: ~w(disabled form name value)
-
- slot :inner_block, required: true
-
- def button(assigns) do
- ~H"""
-
- <%= render_slot(@inner_block) %>
-
- """
- end
-
- @doc """
- Renders an input with label and error messages.
-
- A `Phoenix.HTML.FormField` may be passed as argument,
- which is used to retrieve the input name, id, and values.
- Otherwise all attributes may be passed explicitly.
-
- ## Types
-
- This function accepts all HTML input types, considering that:
-
- * You may also set `type="select"` to render a `
` tag
-
- * `type="checkbox"` is used exclusively to render boolean values
-
- * For live file uploads, see `Phoenix.Component.live_file_input/1`
-
- See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input
- for more information.
-
- ## Examples
-
- <.input field={@form[:email]} type="email" />
- <.input name="my-input" errors={["oh no!"]} />
- """
- attr :id, :any, default: nil
- attr :name, :any
- attr :label, :string, default: nil
- attr :value, :any
-
- attr :type, :string,
- default: "text",
- values: ~w(checkbox color date datetime-local email file hidden month number password
- range radio search select tel text textarea time url week)
-
- attr :field, Phoenix.HTML.FormField,
- doc: "a form field struct retrieved from the form, for example: @form[:email]"
-
- attr :errors, :list, default: []
- attr :checked, :boolean, doc: "the checked flag for checkbox inputs"
- attr :prompt, :string, default: nil, doc: "the prompt for select inputs"
- attr :options, :list, doc: "the options to pass to Phoenix.HTML.Form.options_for_select/2"
- attr :multiple, :boolean, default: false, doc: "the multiple flag for select inputs"
-
- attr :rest, :global,
- include: ~w(accept autocomplete capture cols disabled form list max maxlength min minlength
- multiple pattern placeholder readonly required rows size step)
-
- slot :inner_block
-
- def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
- assigns
- |> assign(field: nil, id: assigns.id || field.id)
- |> assign(:errors, Enum.map(field.errors, &translate_error(&1)))
- |> assign_new(:name, fn -> if assigns.multiple, do: field.name <> "[]", else: field.name end)
- |> assign_new(:value, fn -> field.value end)
- |> input()
- end
-
- def input(%{type: "checkbox"} = assigns) do
- assigns =
- assign_new(assigns, :checked, fn ->
- Form.normalize_value("checkbox", assigns[:value])
- end)
-
- ~H"""
-
-
-
-
- <%= @label %>
-
- <.error :for={msg <- @errors}><%= msg %>
-
- """
- end
-
- def input(%{type: "select"} = assigns) do
- ~H"""
-
- <.label for={@id}><%= @label %>
-
- <%= @prompt %>
- <%= Form.options_for_select(@options, @value) %>
-
- <.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"""
-
- <%= render_slot(@inner_block) %>
-
- """
- 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"""
-
- """
- 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 @@
-
-
-
- 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
+
+
+
+ <.link to={@github_url} target="_blank" rel="noopener noreferrer" class="flex items-center gap-1">
+
+ GitHub
+
+
+ <.button color="white" variant="outline" link_type="a" to={@invite_bot_url} target="_blank"
+ rel="noopener noreferrer" label="Invite" />
+
+
+
+
+
+ <%= @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)