From a1a568b7f7fbfa5c4ea53daff54eeff1f96c7a94 Mon Sep 17 00:00:00 2001 From: Kieran Date: Tue, 7 May 2024 12:16:57 -0700 Subject: [PATCH] [Enhancement] Add history table to homepage (And TZ support) (#229) * Added basic history table to homepage * Improved homepage history query * Set table to use 24h time * Added timezone support * Updated README to reflect new TZ stuff --- README.md | 5 + config/config.exs | 3 +- config/runtime.exs | 3 +- lib/pinchflat/media/media_query.ex | 8 ++ .../custom_components/text_components.ex | 13 +++ .../controllers/pages/page_controller.ex | 8 +- .../pages/page_html/history_table_live.ex | 110 ++++++++++++++++++ .../pages/page_html/home.html.heex | 10 +- mix.exs | 1 + mix.lock | 11 ++ 10 files changed, 165 insertions(+), 7 deletions(-) create mode 100644 lib/pinchflat_web/controllers/pages/page_html/history_table_live.ex diff --git a/README.md b/README.md index a4a1ab97..8ad9b6ac 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,9 @@ version: '3' services: pinchflat: image: keglin/pinchflat:latest + environment: + # Set the timezone to your local timezone + - TZ=America/New_York ports: - '8945:8945' volumes: @@ -110,7 +113,9 @@ services: ```bash # Be sure to replace /host/path/to/config and /host/path/to/downloads below with # the paths to the directories you created in step 1 +# Be sure to replace America/New_York with your local timezone docker run \ + -e TZ=America/New_York \ -p 8945:8945 \ -v /host/path/to/config:/config \ -v /host/path/to/downloads:/downloads \ diff --git a/config/config.exs b/config/config.exs index b4060886..ad5febea 100644 --- a/config/config.exs +++ b/config/config.exs @@ -25,7 +25,8 @@ config :pinchflat, basic_auth_username: "", basic_auth_password: "", expose_feed_endpoints: false, - file_watcher_poll_interval: 1000 + file_watcher_poll_interval: 1000, + timezone: "UTC" config :pinchflat, Pinchflat.Repo, journal_mode: :wal, diff --git a/config/runtime.exs b/config/runtime.exs index df022fd4..ca409ad5 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -63,7 +63,8 @@ if config_env() == :prod do extras_directory: extras_path, tmpfile_directory: Path.join([System.tmp_dir!(), "pinchflat", "data"]), dns_cluster_query: System.get_env("DNS_CLUSTER_QUERY"), - expose_feed_endpoints: expose_feed_endpoints + expose_feed_endpoints: expose_feed_endpoints, + timezone: System.get_env("TIMEZONE") || System.get_env("TZ") || "UTC" config :pinchflat, Pinchflat.Repo, database: db_path, diff --git a/lib/pinchflat/media/media_query.ex b/lib/pinchflat/media/media_query.ex index 53e2c351..3fe57d47 100644 --- a/lib/pinchflat/media/media_query.ex +++ b/lib/pinchflat/media/media_query.ex @@ -21,6 +21,8 @@ defmodule Pinchflat.Media.MediaQuery do # Suffixes: # - _for - the arg passed is an association record + # NOTE: that dyanmic query approach kinda rocked - should refactor in future + def new do MediaItem end @@ -172,6 +174,12 @@ defmodule Pinchflat.Media.MediaQuery do |> matching_source_title_regex() end + def where_pending_or_downloaded(query) do + query + |> where_pending_download() + |> or_where([mi], not is_nil(mi.media_downloaded_at)) + end + defp require_assoc(query, identifier) do if has_named_binding?(query, identifier) do query diff --git a/lib/pinchflat_web/components/custom_components/text_components.ex b/lib/pinchflat_web/components/custom_components/text_components.ex index 1a577f5d..69ac0509 100644 --- a/lib/pinchflat_web/components/custom_components/text_components.ex +++ b/lib/pinchflat_web/components/custom_components/text_components.ex @@ -78,4 +78,17 @@ defmodule PinchflatWeb.CustomComponents.TextComponents do <%= @text %> """ end + + @doc """ + Renders a UTC datetime in the specified format and timezone + """ + attr :datetime, :any, required: true + attr :format, :string, default: "%Y-%m-%d %H:%M:%S" + attr :timezone, :string, default: Application.compile_env(:pinchflat, :timezone) + + def datetime_in_zone(assigns) do + ~H""" + + """ + end end diff --git a/lib/pinchflat_web/controllers/pages/page_controller.ex b/lib/pinchflat_web/controllers/pages/page_controller.ex index ef1b1547..b66a96bc 100644 --- a/lib/pinchflat_web/controllers/pages/page_controller.ex +++ b/lib/pinchflat_web/controllers/pages/page_controller.ex @@ -1,10 +1,9 @@ defmodule PinchflatWeb.Pages.PageController do - alias Pinchflat.Media.MediaItem use PinchflatWeb, :controller alias Pinchflat.Repo alias Pinchflat.Sources.Source - alias Pinchflat.Media.MediaItem + alias Pinchflat.Media.MediaQuery alias Pinchflat.Profiles.MediaProfile def home(conn, params) do @@ -25,7 +24,10 @@ defmodule PinchflatWeb.Pages.PageController do |> render(:home, media_profile_count: Repo.aggregate(MediaProfile, :count, :id), source_count: Repo.aggregate(Source, :count, :id), - media_item_count: Repo.aggregate(MediaItem, :count, :id) + media_item_count: + MediaQuery.new() + |> MediaQuery.with_media_downloaded_at() + |> Repo.aggregate(:count, :id) ) end diff --git a/lib/pinchflat_web/controllers/pages/page_html/history_table_live.ex b/lib/pinchflat_web/controllers/pages/page_html/history_table_live.ex new file mode 100644 index 00000000..10a81189 --- /dev/null +++ b/lib/pinchflat_web/controllers/pages/page_html/history_table_live.ex @@ -0,0 +1,110 @@ +defmodule Pinchflat.Pages.HistoryTableLive do + use PinchflatWeb, :live_view + import Ecto.Query, warn: false + + alias Pinchflat.Repo + alias Pinchflat.Media.MediaQuery + alias Pinchflat.Utils.NumberUtils + alias PinchflatWeb.CustomComponents.TextComponents + + @limit 10 + + def render(%{records: []} = assigns) do + ~H""" +
+ <.icon_button icon_name="hero-arrow-path" class="h-10 w-10" phx-click="reload_page" /> +

Nothing Here!

+
+ """ + end + + def render(assigns) do + ~H""" +
+ + <.icon_button icon_name="hero-arrow-path" class="h-10 w-10" phx-click="reload_page" tooltip="Refresh" /> + Showing <%= length(@records) %> of <%= @total_record_count %> + +
+ <.table rows={@records} table_class="text-white"> + <:col :let={media_item} label="Title"> + <.subtle_link href={~p"/sources/#{media_item.source_id}/media/#{media_item}"}> + <%= StringUtils.truncate(media_item.title, 35) %> + + + <:col :let={media_item} label="Upload Date"> + <%= media_item.upload_date %> + + <:col :let={media_item} label="Indexed At"> + <%= format_datetime(media_item.inserted_at) %> + + <:col :let={media_item} label="Downloaded At"> + <%= format_datetime(media_item.media_downloaded_at) %> + + <:col :let={media_item} label="Source"> + <.subtle_link href={~p"/sources/#{media_item.source_id}"}> + <%= StringUtils.truncate(media_item.source.custom_name, 35) %> + + + +
+
+ <.live_pagination_controls page_number={@page} total_pages={@total_pages} /> +
+
+ """ + end + + def mount(_params, _session, socket) do + page = 1 + base_query = generate_base_query() + pagination_attrs = fetch_pagination_attributes(base_query, page) + + {:ok, assign(socket, Map.merge(pagination_attrs, %{base_query: base_query}))} + end + + def handle_event("page_change", %{"direction" => direction}, %{assigns: assigns} = socket) do + direction = if direction == "inc", do: 1, else: -1 + new_page = assigns.page + direction + new_assigns = fetch_pagination_attributes(assigns.base_query, new_page) + + {:noreply, assign(socket, new_assigns)} + end + + def handle_event("reload_page", _params, %{assigns: assigns} = socket) do + new_assigns = fetch_pagination_attributes(assigns.base_query, assigns.page) + + {:noreply, assign(socket, new_assigns)} + end + + defp fetch_pagination_attributes(base_query, page) do + total_record_count = Repo.aggregate(base_query, :count, :id) + total_pages = max(ceil(total_record_count / @limit), 1) + page = NumberUtils.clamp(page, 1, total_pages) + records = fetch_records(base_query, page) + + %{page: page, total_pages: total_pages, records: records, total_record_count: total_record_count} + end + + defp fetch_records(base_query, page) do + offset = (page - 1) * @limit + + base_query + |> limit(^@limit) + |> offset(^offset) + |> Repo.all() + |> Repo.preload(:source) + end + + defp generate_base_query do + MediaQuery.new() + |> MediaQuery.where_pending_or_downloaded() + |> order_by(desc: :id) + end + + defp format_datetime(nil), do: "" + + defp format_datetime(datetime) do + TextComponents.datetime_in_zone(%{datetime: datetime, format: "%Y-%m-%d %H:%M"}) + end +end diff --git a/lib/pinchflat_web/controllers/pages/page_html/home.html.heex b/lib/pinchflat_web/controllers/pages/page_html/home.html.heex index 88f5ca9c..d018bcb8 100644 --- a/lib/pinchflat_web/controllers/pages/page_html/home.html.heex +++ b/lib/pinchflat_web/controllers/pages/page_html/home.html.heex @@ -17,11 +17,17 @@
- Media Item(s) + Downloaded Media

<%= @media_item_count %>

- I know this page isn't super useful yet, but give it time :) + + +
+ History +
+ <%= live_render(@conn, Pinchflat.Pages.HistoryTableLive) %> +
diff --git a/mix.exs b/mix.exs index 302548ac..454581d8 100644 --- a/mix.exs +++ b/mix.exs @@ -68,6 +68,7 @@ defmodule Pinchflat.MixProject do {:plug_cowboy, "~> 2.5"}, {:oban, "~> 2.16"}, {:nimble_parsec, "~> 1.4"}, + {:timex, "~> 3.0"}, {:mox, "~> 1.0", only: :test}, {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, {:credo_naming, "~> 2.1", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index 650eab10..7fe77ff7 100644 --- a/mix.lock +++ b/mix.lock @@ -2,6 +2,8 @@ "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "castore": {:hex, :castore, "1.0.5", "9eeebb394cc9a0f3ae56b813459f990abb0a3dedee1be6b27fdb50301930502f", [:mix], [], "hexpm", "8d7c597c3e4a64c395980882d4bca3cebb8d74197c590dc272cfd3b6a6310578"}, "cc_precompiler": {:hex, :cc_precompiler, "0.1.9", "e8d3364f310da6ce6463c3dd20cf90ae7bbecbf6c5203b98bf9b48035592649b", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "9dcab3d0f3038621f1601f13539e7a9ee99843862e66ad62827b0c42b2f58a54"}, + "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, + "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "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, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"}, @@ -24,15 +26,20 @@ "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.2", "87f8c75ed8654b9635b311774308b2760b47e9a579dabf2e4d5f1e1d42c39e0b", [:mix], [], "hexpm", "6b05289a8e9eac475f644f09c2e4ba7e19201fd002b89c28c1293e7bd16773d9"}, "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, + "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"}, "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, + "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, + "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, "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"}, "mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"}, "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"}, "oban": {:hex, :oban, "2.17.3", "ddfd5710aadcd550d2e174c8d73ce5f1865601418cf54a91775f20443fb832b7", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "452eada8bfe0d0fefd0740ab5fa8cf3ef6c375df0b4a3c3805d179022a04738a"}, + "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "phoenix": {:hex, :phoenix, "1.7.10", "02189140a61b2ce85bb633a9b6fd02dff705a5f1596869547aeb2b2b95edd729", [: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.6", [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", "cf784932e010fd736d656d7fead6a584a4498efefe5b8227e9f383bf15bb79d0"}, "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, "3.3.3", "380b8fb45912b5638d2f1d925a3771b4516b9a78587249cabe394e0a5d579dc9", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "923ebe6fec6e2e3b3e569dfbdc6560de932cd54b000ada0208b5f45024bdd76c"}, @@ -46,12 +53,16 @@ "plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "swoosh": {:hex, :swoosh, "1.14.4", "94e9dba91f7695a10f49b0172c4a4cb658ef24abef7e8140394521b7f3bbb2d4", [:mix], [{: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", "081c5a590e4ba85cc89baddf7b2beecf6c13f7f84a958f1cd969290815f0f026"}, "table_rex": {:hex, :table_rex, "4.0.0", "3c613a68ebdc6d4d1e731bc973c233500974ec3993c99fcdabb210407b90959b", [:mix], [], "hexpm", "c35c4d5612ca49ebb0344ea10387da4d2afe278387d4019e4d8111e815df8f55"}, "tailwind": {:hex, :tailwind, "0.2.2", "9e27288b568ede1d88517e8c61259bc214a12d7eed271e102db4c93fcca9b2cd", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "ccfb5025179ea307f7f899d1bb3905cd0ac9f687ed77feebc8f67bdca78565c4"}, "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_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, + "timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"}, + "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, + "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"}, }