diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index bbcbbe7d..b27a530d 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -8,7 +8,7 @@ assignees: '' --- **Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] +A clear and concise description of what the problem is. Example: I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 743cc74d..4a1d6b8d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,18 +3,22 @@ # See https://docs.github.com/en/code-security/dependabot/dependabot-alerts/configuring-dependabot-alerts version: 2 updates: - # Updates Elixir dependencies - - package-ecosystem: mix - directory: server/ + # Updates GHA dependencies + - package-ecosystem: github-actions + directory: / schedule: interval: weekly day: monday time: "05:00" timezone: America/Los_Angeles - # Will only open a maximum of 3 PRs + groups: + actions: + patterns: + - "*" open-pull-requests-limit: 3 - # Updates GHA dependencies - - package-ecosystem: github-actions + + # Updates Docker dependencies + - package-ecosystem: docker directory: / schedule: interval: weekly @@ -22,11 +26,23 @@ updates: time: "05:00" timezone: America/Los_Angeles open-pull-requests-limit: 3 + + # Updates Elixir dependencies + - package-ecosystem: mix + directory: server/ + schedule: + interval: weekly + day: monday + time: "05:00" + timezone: America/Los_Angeles + # Will only open a maximum of 3 PRs + open-pull-requests-limit: 3 + # Updates JavaScript dependencies - package-ecosystem: npm directory: /ui schedule: - interval: daily + interval: weekly time: "05:00" timezone: America/Los_Angeles open-pull-requests-limit: 3 @@ -69,17 +85,8 @@ updates: misc: patterns: - "*" - # Open individual PRs for the following packages exclude-patterns: + # Open individual PRs for the following packages - "typescript" - "phoenix" - "@types/node" - # Updates Docker dependencies - - package-ecosystem: docker - directory: / - schedule: - interval: weekly - day: monday - time: "05:00" - timezone: America/Los_Angeles - open-pull-requests-limit: 3 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 82bde338..54c62941 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -13,7 +13,7 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + - uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0 with: egress-policy: audit diff --git a/.github/workflows/fast-forward.yaml b/.github/workflows/fast-forward.yaml index 7f598362..44045ffe 100644 --- a/.github/workflows/fast-forward.yaml +++ b/.github/workflows/fast-forward.yaml @@ -20,7 +20,7 @@ jobs: issues: write steps: - - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + - uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0 with: egress-policy: audit diff --git a/.github/workflows/heroku.yaml b/.github/workflows/heroku.yaml index b57b1f08..67b00aeb 100644 --- a/.github/workflows/heroku.yaml +++ b/.github/workflows/heroku.yaml @@ -17,7 +17,7 @@ jobs: || (contains(github.event.comment.body, '/refresh-heroku-status') && github.event.issue.pull_request) }} steps: - - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + - uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0 with: egress-policy: audit @@ -45,7 +45,7 @@ jobs: # Check that the deployed app returns successful HTTP response. steps: - - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + - uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0 with: egress-policy: audit diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 2032dbb5..ddd1abed 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -31,7 +31,7 @@ jobs: # actions: read steps: - - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + - uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0 with: egress-policy: audit @@ -41,7 +41,7 @@ jobs: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 + uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 with: results_file: results.sarif results_format: sarif @@ -63,7 +63,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@97a0fba1372883ab732affbe8f94b823f91727db # v3.pre.node20 + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: SARIF file path: results.sarif @@ -72,6 +72,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 + uses: github/codeql-action/upload-sarif@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 with: sarif_file: results.sarif diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ace2f45e..99db1012 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,10 +11,10 @@ We recommend following these steps if you'd like to contribute to the repo. 0. Check out the [Trello board](https://trello.com/b/hRFh7Sc1/orcasite-development) for an overview of features being worked on. - This [public roadmap](https://trello.com/b/wBg0qhss/orcasound-roadmap) has a higher level view. -1. Fork the Orcasite repo -2. Develop on a [feature branch](https://www.atlassian.com/git/tutorials/comparing-workflows/feature-branch-workflow) +1. Fork the Orcasite repo. +2. Develop on a [feature branch](https://www.atlassian.com/git/tutorials/comparing-workflows/feature-branch-workflow). 3. Submit a PR (don't review your own)! - - To maintain a consistent style, we recommend running [Prettier](https://github.com/prettier/prettier) on js, and `mix format` before submission + - To maintain a consistent style, we recommend running [Prettier](https://github.com/prettier/prettier) on js, and `mix format` before submission. 4. Once a PR is merged, we can throw it up onto the dev server to see how things look! ### Questions? @@ -51,6 +51,6 @@ If you're new to Elixir (or JS and React), that's no problem! Here are some reso - [Apollo GraphQL](https://www.apollographql.com/docs/react/) - Javascript library for GraphQL ### Example codebases -Some of these are convenient for reference +Some of these are convenient for reference: -- [Evercam](https://github.com/evercam/evercam-server/) - Good example of supervision trees \ No newline at end of file +- [Evercam](https://github.com/evercam/evercam-server/) - Good example of supervision trees diff --git a/README.md b/README.md index aef06084..33db8b8a 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ Once everything finishes starting up, you'll be able to access the UI at [`http: ### UI -The new version (v3) is currently under development, rapidly changing, and has no tests yet +The new version (v3) is currently under development, rapidly changing, and has no tests yet. ## Deployment diff --git a/server/config/config.exs b/server/config/config.exs index ee550f99..8f451f84 100644 --- a/server/config/config.exs +++ b/server/config/config.exs @@ -73,13 +73,11 @@ config :orcasite, OrcasiteWeb.Auth.AuthAccessPipeline, error_handler: OrcasiteWeb.Auth.AuthErrorHandler config :orcasite, :ecto_repos, [Orcasite.Repo] -config :orcasite, :ash_apis, [Orcasite.Notifications, Orcasite.Accounts, Orcasite.Radio] +config :orcasite, :ash_domains, [Orcasite.Notifications, Orcasite.Accounts, Orcasite.Radio] config :orcasite, :ash_uuid, migration_default?: true -config :ash, :use_all_identities_in_manage_relationship?, false config :ash, :custom_types, geometry: Orcasite.Types.Geometry config :ash_graphql, :default_managed_relationship_type_name_template, :action_name config :ash_graphql, :json_type, :json -config :ash, :utc_datetime_type, :datetime config :mime, :types, %{ "application/vnd.api+json" => ["json"] @@ -93,7 +91,7 @@ config :orcasite, Oban, repo: Orcasite.Repo, # 7 day job retention plugins: [{Oban.Plugins.Pruner, max_age: 7 * 24 * 60 * 60}], - queues: [default: 10, email: 10, feed_segments: 10] + queues: [default: 10, email: 10, feeds: 10] config :spark, :formatter, remove_parens?: true, diff --git a/server/lib/orcasite/accounts/accounts.ex b/server/lib/orcasite/accounts/accounts.ex index eeb32773..139316d4 100644 --- a/server/lib/orcasite/accounts/accounts.ex +++ b/server/lib/orcasite/accounts/accounts.ex @@ -1,8 +1,9 @@ defmodule Orcasite.Accounts do - use Ash.Api, extensions: [AshAdmin.Api, AshGraphql.Api] + use Ash.Domain, extensions: [AshAdmin.Domain, AshGraphql.Domain] resources do - registry Orcasite.Accounts.Registry + resource Orcasite.Accounts.User + resource Orcasite.Accounts.Token end admin do diff --git a/server/lib/orcasite/accounts/registry.ex b/server/lib/orcasite/accounts/registry.ex deleted file mode 100644 index 7c7dd4f9..00000000 --- a/server/lib/orcasite/accounts/registry.ex +++ /dev/null @@ -1,8 +0,0 @@ -defmodule Orcasite.Accounts.Registry do - use Ash.Registry, extensions: [Ash.Registry.ResourceValidations] - - entries do - entry Orcasite.Accounts.User - entry Orcasite.Accounts.Token - end -end diff --git a/server/lib/orcasite/accounts/token.ex b/server/lib/orcasite/accounts/token.ex index afcd02f1..d7322ec2 100644 --- a/server/lib/orcasite/accounts/token.ex +++ b/server/lib/orcasite/accounts/token.ex @@ -1,11 +1,12 @@ defmodule Orcasite.Accounts.Token do use Ash.Resource, + domain: Orcasite.Accounts, data_layer: AshPostgres.DataLayer, extensions: [AshAuthentication.TokenResource], authorizers: [Ash.Policy.Authorizer] token do - api Orcasite.Accounts + domain Orcasite.Accounts end postgres do diff --git a/server/lib/orcasite/accounts/user.ex b/server/lib/orcasite/accounts/user.ex index ac7f956f..3bb9b9db 100644 --- a/server/lib/orcasite/accounts/user.ex +++ b/server/lib/orcasite/accounts/user.ex @@ -1,5 +1,6 @@ defmodule Orcasite.Accounts.User do use Ash.Resource, + domain: Orcasite.Accounts, data_layer: AshPostgres.DataLayer, extensions: [AshAuthentication, AshAdmin.Resource, AshGraphql.Resource], authorizers: [Ash.Policy.Authorizer] @@ -16,14 +17,15 @@ defmodule Orcasite.Accounts.User do attributes do uuid_primary_key :id - attribute :email, :ci_string, allow_nil?: false + attribute :email, :ci_string, allow_nil?: false, public?: true attribute :hashed_password, :string, allow_nil?: false, sensitive?: true - attribute :first_name, :string - attribute :last_name, :string - attribute :admin, :boolean, default: false, allow_nil?: false - attribute :moderator, :boolean, default: false, allow_nil?: false + attribute :first_name, :string, public?: true + attribute :last_name, :string, public?: true + attribute :admin, :boolean, default: false, allow_nil?: false, public?: true + attribute :moderator, :boolean, default: false, allow_nil?: false, public?: true attribute :username, :string do + public? true allow_nil? true constraints allow_empty?: false, trim?: true end @@ -33,7 +35,7 @@ defmodule Orcasite.Accounts.User do end authentication do - api Orcasite.Accounts + domain Orcasite.Accounts strategies do password :password do @@ -93,7 +95,7 @@ defmodule Orcasite.Accounts.User do end actions do - defaults [:read, :create, :update, :destroy] + defaults [:read, :destroy, create: :*, update: :*] read :by_email do get_by :email @@ -106,7 +108,7 @@ defmodule Orcasite.Accounts.User do end code_interface do - define_for Orcasite.Accounts + domain Orcasite.Accounts define :register_with_password define :sign_in_with_password @@ -133,7 +135,6 @@ defmodule Orcasite.Accounts.User do graphql do type :user - hide_fields [:hashed_password] queries do read_one :current_user, :current_user diff --git a/server/lib/orcasite/global_setup.ex b/server/lib/orcasite/global_setup.ex index 54af7044..cce60f7c 100644 --- a/server/lib/orcasite/global_setup.ex +++ b/server/lib/orcasite/global_setup.ex @@ -2,12 +2,12 @@ defmodule Orcasite.GlobalSetup do def populate_feed_streams do Orcasite.Radio.Feed |> Ash.Query.for_read(:read) - |> Orcasite.Radio.read!() + |> Ash.read!() |> Stream.map(fn feed -> Orcasite.Radio.AwsClient.list_timestamps(feed, fn timestamps -> timestamps |> Enum.map(&%{feed: feed, playlist_timestamp: &1}) - |> Orcasite.Radio.bulk_create(Orcasite.Radio.FeedStream, :create) + |> Ash.bulk_create(Orcasite.Radio.FeedStream, :create) end) :ok end) diff --git a/server/lib/orcasite/notifications.ex b/server/lib/orcasite/notifications.ex index ab55ba98..3e929c55 100644 --- a/server/lib/orcasite/notifications.ex +++ b/server/lib/orcasite/notifications.ex @@ -1,8 +1,13 @@ defmodule Orcasite.Notifications do - use Ash.Api, extensions: [AshAdmin.Api, AshJsonApi.Api, AshGraphql.Api] + use Ash.Domain, extensions: [AshAdmin.Domain, AshJsonApi.Domain, AshGraphql.Domain] resources do - registry Orcasite.Notifications.Registry + resource Orcasite.Notifications.Notification + resource Orcasite.Notifications.Subscriber + resource Orcasite.Notifications.Subscription + resource Orcasite.Notifications.NotificationInstance + resource Orcasite.Notifications.Token + resource Orcasite.Notifications.Job end admin do diff --git a/server/lib/orcasite/notifications/changes/extract_notification_instance_meta.ex b/server/lib/orcasite/notifications/changes/extract_notification_instance_meta.ex index e25f3c98..a5f4c2c9 100644 --- a/server/lib/orcasite/notifications/changes/extract_notification_instance_meta.ex +++ b/server/lib/orcasite/notifications/changes/extract_notification_instance_meta.ex @@ -1,7 +1,6 @@ defmodule Orcasite.Notifications.Changes.ExtractNotificationInstanceMeta do use Ash.Resource.Change - alias Orcasite.Notifications alias Orcasite.Notifications.{Notification, Subscription} def change( @@ -19,9 +18,9 @@ defmodule Orcasite.Notifications.Changes.ExtractNotificationInstanceMeta do # body based on event type with {:get_sub, {:ok, subscription}} <- - {:get_sub, Notifications.get(Subscription, changeset.arguments.subscription)}, + {:get_sub, Ash.get(Subscription, changeset.arguments.subscription)}, {:get_notif, {:ok, notification}} <- - {:get_notif, Notifications.get(Notification, changeset.arguments.notification)} do + {:get_notif, Ash.get(Notification, changeset.arguments.notification, authorize?: false)} do changeset |> Ash.Changeset.change_attribute(:meta, %{ email: Map.get(subscription.meta, "email"), diff --git a/server/lib/orcasite/notifications/event.ex b/server/lib/orcasite/notifications/event.ex index 14bfae05..8cdae3a9 100644 --- a/server/lib/orcasite/notifications/event.ex +++ b/server/lib/orcasite/notifications/event.ex @@ -1,8 +1,4 @@ defmodule Orcasite.Notifications.Event do - def types do - [:confirmed_candidate, :new_detection] - end - def humanize(event_type, plural \\ false) def humanize(:confirmed_candidate, false), do: "confirmed candidate" def humanize(:confirmed_candidate, true), do: "confirmed candidates" diff --git a/server/lib/orcasite/notifications/manual_read_notifications_since.ex b/server/lib/orcasite/notifications/manual_read_notifications_since.ex index 12a676a5..9b8f5d78 100644 --- a/server/lib/orcasite/notifications/manual_read_notifications_since.ex +++ b/server/lib/orcasite/notifications/manual_read_notifications_since.ex @@ -1,7 +1,6 @@ defmodule Orcasite.Notifications.ManualReadNotificationsSince do use Ash.Resource.ManualRead - alias Orcasite.Notifications alias Orcasite.Notifications.Notification require Ash.Query @@ -14,13 +13,13 @@ defmodule Orcasite.Notifications.ManualReadNotificationsSince do notification = Notification - |> Notifications.get!(notification_id) + |> Ash.get!(notification_id, authorize?: false) Notification |> Ash.Query.filter( event_type == ^notification.event_type and inserted_at > ^notification.inserted_at ) - |> Notifications.read() + |> Ash.read(authorize?: false) end end diff --git a/server/lib/orcasite/notifications/registry.ex b/server/lib/orcasite/notifications/registry.ex deleted file mode 100644 index cfe58d26..00000000 --- a/server/lib/orcasite/notifications/registry.ex +++ /dev/null @@ -1,15 +0,0 @@ -defmodule Orcasite.Notifications.Registry do - use Ash.Registry, - extensions: [ - Ash.Registry.ResourceValidations - ] - - entries do - entry Orcasite.Notifications.Notification - entry Orcasite.Notifications.Subscriber - entry Orcasite.Notifications.Subscription - entry Orcasite.Notifications.NotificationInstance - entry Orcasite.Notifications.Token - entry Orcasite.Notifications.Job - end -end diff --git a/server/lib/orcasite/notifications/resources/job.ex b/server/lib/orcasite/notifications/resources/job.ex index 55b6b2d2..170d2b02 100644 --- a/server/lib/orcasite/notifications/resources/job.ex +++ b/server/lib/orcasite/notifications/resources/job.ex @@ -1,6 +1,7 @@ defmodule Orcasite.Notifications.Job do use Ash.Resource, - extensions: [AshAdmin.Resource, AshGraphql.Resource], + domain: Orcasite.Notifications, + extensions: [AshAdmin.Resource], data_layer: AshPostgres.DataLayer @states [:available, :scheduled, :executing, :retryable, :completed, :discarded, :cancelled] @@ -59,9 +60,7 @@ defmodule Orcasite.Notifications.Job do argument :email, :string argument :node, :string - argument :event_type, :atom do - constraints one_of: Orcasite.Notifications.Event.types() - end + argument :event_type, Orcasite.Types.NotificationEventType filter expr( if(is_nil(^arg(:notification_id)), diff --git a/server/lib/orcasite/notifications/resources/notification.ex b/server/lib/orcasite/notifications/resources/notification.ex index d3f7a6dd..9dabcded 100644 --- a/server/lib/orcasite/notifications/resources/notification.ex +++ b/server/lib/orcasite/notifications/resources/notification.ex @@ -1,10 +1,11 @@ defmodule Orcasite.Notifications.Notification do use Ash.Resource, + domain: Orcasite.Notifications, extensions: [AshAdmin.Resource, AshGraphql.Resource], data_layer: AshPostgres.DataLayer, authorizers: [Ash.Policy.Authorizer] - alias Orcasite.Notifications.{Event, NotificationInstance, Subscription} + alias Orcasite.Notifications.{NotificationInstance, Subscription} postgres do table "notifications" @@ -19,17 +20,15 @@ defmodule Orcasite.Notifications.Notification do uuid_primary_key :id attribute :meta, :map, default: %{} - attribute :active, :boolean, default: true + attribute :active, :boolean, default: true, public?: true - attribute :event_type, :atom do - constraints one_of: Event.types() - end + attribute :event_type, Orcasite.Types.NotificationEventType, public?: true - attribute :target_count, :integer - attribute :notified_count, :integer, default: 0 - attribute :notified_count_updated_at, :utc_datetime + attribute :target_count, :integer, public?: true + attribute :notified_count, :integer, default: 0, public?: true + attribute :notified_count_updated_at, :utc_datetime, public?: true - create_timestamp :inserted_at, private?: false, writable?: false + create_timestamp :inserted_at, writable?: false, public?: true update_timestamp :updated_at end @@ -41,9 +40,10 @@ defmodule Orcasite.Notifications.Notification do do: 1, else: notified_count / target_count ) - ) + ), + public?: true - calculate :finished, :boolean, expr(notified_count == target_count) + calculate :finished, :boolean, expr(notified_count == target_count), public?: true end relationships do @@ -69,7 +69,7 @@ defmodule Orcasite.Notifications.Notification do end actions do - defaults [:create, :read, :update, :destroy] + defaults [:create, :read, :destroy] read :since_notification do description "Get all notifications after a given notification ID." @@ -82,9 +82,7 @@ defmodule Orcasite.Notifications.Notification do prepare build(sort: [inserted_at: :desc]) argument :candidate_id, :string, allow_nil?: false - argument :event_type, :atom do - constraints one_of: Event.types() - end + argument :event_type, Orcasite.Types.NotificationEventType argument :active, :boolean @@ -101,7 +99,13 @@ defmodule Orcasite.Notifications.Notification do ) end + update :update do + primary? true + accept [:target_count] + end + update :cancel_notification do + require_atomic? false accept [] change set_attribute(:active, false) @@ -127,7 +131,6 @@ defmodule Orcasite.Notifications.Notification do create :notify_confirmed_candidate do description "Create a notification for confirmed candidate (i.e. detection group)" - accept [:candidate_id, :message] argument :candidate_id, :string, allow_nil?: false argument :message, :string do @@ -141,27 +144,26 @@ defmodule Orcasite.Notifications.Notification do change set_attribute(:event_type, :confirmed_candidate) - change fn changeset, _context -> - candidate_id = - Ash.Changeset.get_argument(changeset, :candidate_id) - - candidate = - Orcasite.Radio.Candidate - |> Orcasite.Radio.get(candidate_id) - |> Orcasite.Radio.load!(:feed) - - changeset - |> Ash.Changeset.change_attribute(:meta, %{ - candidate_id: candidate_id, - node: candidate.feed.slug, - message: Ash.Changeset.get_argument(changeset, :message) - }) - end + change before_action(fn changeset, _context -> + candidate_id = + Ash.Changeset.get_argument(changeset, :candidate_id) + + candidate = + Orcasite.Radio.Candidate + |> Ash.get(candidate_id) + |> Ash.load!(:feed) + + changeset + |> Ash.Changeset.change_attribute(:meta, %{ + candidate_id: candidate_id, + node: candidate.feed.slug, + message: Ash.Changeset.get_argument(changeset, :message) + }) + end) end create :notify_new_detection do description "Create a notification for a new detection (e.g. button push from user)." - accept [:detection_id] argument :detection_id, :string argument :node, :string, allow_nil?: false argument :description, :string, allow_nil?: true @@ -185,8 +187,6 @@ defmodule Orcasite.Notifications.Notification do end code_interface do - define_for Orcasite.Notifications - define :notify_new_detection, action: :notify_new_detection, args: [:detection_id, :node, :description, :listener_count, :candidate_id] @@ -219,28 +219,28 @@ defmodule Orcasite.Notifications.Notification do changes do change fn changeset, _context -> changeset - |> Ash.Changeset.after_action(fn _, notification -> + |> Ash.Changeset.after_action(fn _, %{id: notification_id} = notification -> Task.Supervisor.async_nolink(Orcasite.TaskSupervisor, fn -> target_count = Orcasite.Notifications.Subscription |> Ash.Query.for_read(:available_for_notification, %{ - notification_id: notification.id, + notification_id: notification_id, event_type: notification.event_type }) - |> Orcasite.Notifications.stream!() + |> Ash.stream!() |> Stream.map(fn subscription -> Orcasite.Notifications.NotificationInstance |> Ash.Changeset.for_create(:create_with_relationships, %{ - notification: notification.id, - subscription: subscription.id + notification: notification, + subscription: subscription }) - |> Orcasite.Notifications.create!() + |> Ash.create!() end) |> Enum.reduce(0, fn _, sum -> sum + 1 end) notification |> Ash.Changeset.for_update(:update, %{target_count: target_count}) - |> Orcasite.Notifications.update() + |> Ash.update(authorize?: false) end) {:ok, notification} diff --git a/server/lib/orcasite/notifications/resources/notification_instance.ex b/server/lib/orcasite/notifications/resources/notification_instance.ex index 5ad76cb9..1e27ae2b 100644 --- a/server/lib/orcasite/notifications/resources/notification_instance.ex +++ b/server/lib/orcasite/notifications/resources/notification_instance.ex @@ -1,5 +1,6 @@ defmodule Orcasite.Notifications.NotificationInstance do use Ash.Resource, + domain: Orcasite.Notifications, extensions: [AshAdmin.Resource], data_layer: Ash.DataLayer.Ets @@ -32,7 +33,6 @@ defmodule Orcasite.Notifications.NotificationInstance do end code_interface do - define_for Orcasite.Notifications define :update, action: :update, args: [:status, :meta, :processed_at] end @@ -46,10 +46,8 @@ defmodule Orcasite.Notifications.NotificationInstance do defaults [:create, :read, :update, :destroy] create :create_with_relationships do - accept [:subscription, :notification] - - argument :subscription, :uuid - argument :notification, :uuid + argument :subscription, :map + argument :notification, :map change manage_relationship(:subscription, type: :append) change manage_relationship(:notification, type: :append) diff --git a/server/lib/orcasite/notifications/resources/subscriber.ex b/server/lib/orcasite/notifications/resources/subscriber.ex index 83471414..3158cd77 100644 --- a/server/lib/orcasite/notifications/resources/subscriber.ex +++ b/server/lib/orcasite/notifications/resources/subscriber.ex @@ -1,5 +1,6 @@ defmodule Orcasite.Notifications.Subscriber do use Ash.Resource, + domain: Orcasite.Notifications, extensions: [AshAdmin.Resource, AshAuthentication, AshJsonApi.Resource], data_layer: AshPostgres.DataLayer @@ -39,20 +40,19 @@ defmodule Orcasite.Notifications.Subscriber do end code_interface do - define_for Orcasite.Notifications define :by_email, args: [:email] end authentication do - api Orcasite.Notifications + domain Orcasite.Notifications strategies do magic_link :manage_subscriptions do identity_field :id single_use_token? false - # 14 days (in minutes) - token_lifetime 1_209_600 + # 4 weeks (in minutes) + token_lifetime 40_320 sender fn _subscriber, _token, _opts -> # IO.inspect({subscriber, token}, @@ -93,7 +93,6 @@ defmodule Orcasite.Notifications.Subscriber do create :individual_subscriber do description "Create a subscriber for an individual" - accept [:name, :email, :user_id] argument :name, :string argument :email, :string argument :user_id, :string @@ -152,7 +151,7 @@ defmodule Orcasite.Notifications.Subscriber do end validations do - validate fn changeset -> + validate fn changeset, _context -> # Check if email subscriber already exists with email when is_binary(email) <- changeset |> Ash.Changeset.get_argument(:email), %{action_type: :create} <- changeset, diff --git a/server/lib/orcasite/notifications/resources/subscription.ex b/server/lib/orcasite/notifications/resources/subscription.ex index 26666f0f..332c40c3 100644 --- a/server/lib/orcasite/notifications/resources/subscription.ex +++ b/server/lib/orcasite/notifications/resources/subscription.ex @@ -1,9 +1,10 @@ defmodule Orcasite.Notifications.Subscription do use Ash.Resource, + domain: Orcasite.Notifications, extensions: [AshAuthentication, AshAdmin.Resource], data_layer: AshPostgres.DataLayer - alias Orcasite.Notifications.{Event, Notification, NotificationInstance, Subscriber} + alias Orcasite.Notifications.{Notification, NotificationInstance, Subscriber} postgres do table "subscriptions" @@ -27,9 +28,7 @@ defmodule Orcasite.Notifications.Subscription do attribute :active, :boolean, default: true - attribute :event_type, :atom do - constraints one_of: Event.types() - end + attribute :event_type, Orcasite.Types.NotificationEventType attribute :last_notified_at, :utc_datetime_usec @@ -51,7 +50,7 @@ defmodule Orcasite.Notifications.Subscription do end authentication do - api Orcasite.Notifications + domain Orcasite.Notifications strategies do magic_link :unsubscribe do @@ -59,7 +58,7 @@ defmodule Orcasite.Notifications.Subscription do single_use_token? false # 14 days (in minutes) - token_lifetime 1_209_600 + token_lifetime 40_320 sender fn _subscription, _token, _opts -> # IO.inspect({subscription, token}, @@ -84,8 +83,6 @@ defmodule Orcasite.Notifications.Subscription do end code_interface do - define_for Orcasite.Notifications - define :update_last_notification, action: :update_last_notification, args: [:last_notification] @@ -103,28 +100,51 @@ defmodule Orcasite.Notifications.Subscription do end actions do - defaults [:create, :read, :destroy] + defaults [:destroy, :read, create: :*] - update :update do - argument :subscriber_id, :uuid + read :available_for_notification do + description """ + Subscriptions that can be sent a notification. Finds subscriptions that haven't been sent a + notification recently and that match the notification's event type. + """ - change manage_relationship(:subscriber_id, :subscriber, type: :append) + pagination keyset?: true, required?: false + + argument :notification_id, :uuid + + argument :event_type, Orcasite.Types.NotificationEventType do + default nil + end + + argument :minutes_ago, :integer, default: 5 + + filter expr( + active and + event_type == ^arg(:event_type) and + (is_nil(last_notification_id) or last_notification_id != ^arg(:notification_id)) and + (is_nil(last_notified_at) or + fragment( + "? < timezone('UTC', now()) - ?::numeric * interval '1 minute'", + last_notified_at, + ^arg(:minutes_ago) + )) + ) end create :email_subscribe do description "Create a subscription for an individual email" - accept [:event_type, :subscriber, :email, :name] - argument :email, :string + argument :name, :string + argument :email, :string - argument :event_type, :atom do - constraints one_of: Event.types() + argument :event_type, Orcasite.Types.NotificationEventType do default nil end argument :subscriber, :uuid change set_attribute(:event_type, arg(:event_type)) + change set_attribute(:name, arg(:name)) change fn changeset, _context -> changeset @@ -138,43 +158,23 @@ defmodule Orcasite.Notifications.Subscription do change manage_relationship(:subscriber, type: :append) end - update :update_last_notification do - accept [:last_notification] + update :update do + primary? true + require_atomic? false - argument :last_notification, :uuid + accept [:active] + argument :subscriber_id, :uuid - change manage_relationship(:last_notification, type: :append) - change set_attribute(:last_notified_at, &DateTime.utc_now/0) + change manage_relationship(:subscriber_id, :subscriber, type: :append) end - read :available_for_notification do - description """ - Subscriptions that can be sent a notification. Finds subscriptions that haven't been sent a - notification recently and that match the notification's event type. - """ - - pagination keyset?: true, required?: false - - argument :notification_id, :uuid - - argument :event_type, :atom do - constraints one_of: Event.types() - default nil - end + update :update_last_notification do + require_atomic? false - argument :minutes_ago, :integer, default: 5 + argument :last_notification, :uuid - filter expr( - active and - event_type == ^arg(:event_type) and - (is_nil(last_notification_id) or last_notification_id != ^arg(:notification_id)) and - (is_nil(last_notified_at) or - fragment( - "? < timezone('UTC', now()) - ?::numeric * interval '1 minute'", - last_notified_at, - ^arg(:minutes_ago) - )) - ) + change manage_relationship(:last_notification, type: :append) + change set_attribute(:last_notified_at, &DateTime.utc_now/0) end end diff --git a/server/lib/orcasite/notifications/resources/token.ex b/server/lib/orcasite/notifications/resources/token.ex index af95a088..eb792360 100644 --- a/server/lib/orcasite/notifications/resources/token.ex +++ b/server/lib/orcasite/notifications/resources/token.ex @@ -1,10 +1,12 @@ defmodule Orcasite.Notifications.Token do use Ash.Resource, + domain: Orcasite.Notifications, data_layer: AshPostgres.DataLayer, - extensions: [AshAuthentication.TokenResource, Ash.Policy.Authorizer] + extensions: [AshAuthentication.TokenResource], + authorizers: Ash.Policy.Authorizer token do - api Orcasite.Notifications + domain Orcasite.Notifications end postgres do @@ -12,13 +14,13 @@ defmodule Orcasite.Notifications.Token do repo Orcasite.Repo end - # policies do - # bypass AshAuthentication.Checks.AshAuthenticationInteraction do - # authorize_if always() - # end + policies do + bypass AshAuthentication.Checks.AshAuthenticationInteraction do + authorize_if always() + end - # bypass actor_attribute_equals(:admin, true) do - # authorize_if always() - # end - # end + bypass actor_attribute_equals(:admin, true) do + authorize_if always() + end + end end diff --git a/server/lib/orcasite/notifications/workers/send_notification_email.ex b/server/lib/orcasite/notifications/workers/send_notification_email.ex index 622a762b..54105e02 100644 --- a/server/lib/orcasite/notifications/workers/send_notification_email.ex +++ b/server/lib/orcasite/notifications/workers/send_notification_email.ex @@ -1,7 +1,6 @@ defmodule Orcasite.Notifications.Workers.SendNotificationEmail do use Oban.Worker, queue: :email, unique: [keys: [:notification_id, :subscription_id]] - alias Orcasite.Notifications alias Orcasite.Notifications.{Subscription, NotificationInstance, Notification} @impl Oban.Worker @@ -14,10 +13,10 @@ defmodule Orcasite.Notifications.Workers.SendNotificationEmail do "notification_instance_id" => notification_instance_id } = _args }) do - notification = Notification |> Notifications.get!(notification_id) + notification = Notification |> Ash.get!(notification_id, authorize?: false) subscription = - Subscription |> Notifications.get!(subscription_id) |> Notifications.load!(:subscriber) + Subscription |> Ash.get!(subscription_id) |> Ash.load!(:subscriber) params = [meta, subscription.meta, notification.meta] @@ -36,7 +35,7 @@ defmodule Orcasite.Notifications.Workers.SendNotificationEmail do notif_id -> Notification |> Ash.Query.for_read(:since_notification, %{notification_id: notif_id}) - |> Orcasite.Notifications.read!() + |> Ash.read!(authorize?: false) end |> Enum.filter(&(&1.id != notification_id)) @@ -51,7 +50,7 @@ defmodule Orcasite.Notifications.Workers.SendNotificationEmail do notifications_since: notifications_since |> Enum.map(& &1.meta), notifications_since_count: Enum.count(notifications_since) }) - |> email_for_notif(stringify(params["event_type"])) + |> email_for_notif(stringify(notification.event_type)) |> Orcasite.Mailer.deliver() subscription @@ -59,7 +58,7 @@ defmodule Orcasite.Notifications.Workers.SendNotificationEmail do Task.Supervisor.async_nolink(Orcasite.TaskSupervisor, fn -> NotificationInstance - |> Notifications.get(notification_instance_id) + |> Ash.get(notification_instance_id) |> case do {:error, _} -> nil @@ -67,11 +66,11 @@ defmodule Orcasite.Notifications.Workers.SendNotificationEmail do {:ok, notif_instance} -> notif_instance |> Ash.Changeset.for_destroy(:destroy) - |> Notifications.destroy!() + |> Ash.destroy!() end end) - Orcasite.Notifications.Notification.increment_notified_count(notification) + Orcasite.Notifications.Notification.increment_notified_count(notification, authorize?: false) :ok end diff --git a/server/lib/orcasite/radio/aws_client.ex b/server/lib/orcasite/radio/aws_client.ex index f1c81a14..79aea338 100644 --- a/server/lib/orcasite/radio/aws_client.ex +++ b/server/lib/orcasite/radio/aws_client.ex @@ -3,7 +3,7 @@ defmodule Orcasite.Radio.AwsClient do @default_results %{count: 0, timestamps: []} - def get_feed_stream(%FeedStream{ + def get_stream_manifest_body(%FeedStream{ bucket_region: bucket_region, bucket: bucket, playlist_m3u8_path: path diff --git a/server/lib/orcasite/radio/bout.ex b/server/lib/orcasite/radio/bout.ex index 6fa27f72..7321e7ba 100644 --- a/server/lib/orcasite/radio/bout.ex +++ b/server/lib/orcasite/radio/bout.ex @@ -1,5 +1,6 @@ defmodule Orcasite.Radio.Bout do use Ash.Resource, + domain: Orcasite.Radio, extensions: [AshAdmin.Resource, AshUUID, AshGraphql.Resource, AshJsonApi.Resource], data_layer: AshPostgres.DataLayer, authorizers: [Ash.Policy.Authorizer] @@ -17,15 +18,15 @@ defmodule Orcasite.Radio.Bout do end attributes do - uuid_attribute :id, prefix: "bout" + uuid_attribute :id, prefix: "bout", public?: true - attribute :start_time, :utc_datetime - attribute :end_time, :utc_datetime - attribute :duration, :decimal - attribute :ongoing, :boolean + attribute :start_time, :utc_datetime, public?: true + attribute :end_time, :utc_datetime, public?: true + attribute :duration, :decimal, public?: true + attribute :ongoing, :boolean, public?: true - attribute :category, :atom do - constraints one_of: [:biophony, :anthrophony, :geophony] + attribute :category, Orcasite.Types.AudioCategory do + public? true end create_timestamp :inserted_at @@ -33,9 +34,7 @@ defmodule Orcasite.Radio.Bout do end relationships do - belongs_to :created_by_user, Orcasite.Accounts.User do - api Orcasite.Accounts - end + belongs_to :created_by_user, Orcasite.Accounts.User belongs_to :feed, Orcasite.Radio.Feed has_many :bout_feed_streams, Orcasite.Radio.BoutFeedStream diff --git a/server/lib/orcasite/radio/bout_feed_stream.ex b/server/lib/orcasite/radio/bout_feed_stream.ex index 9fc4fddd..7452df61 100644 --- a/server/lib/orcasite/radio/bout_feed_stream.ex +++ b/server/lib/orcasite/radio/bout_feed_stream.ex @@ -1,5 +1,6 @@ defmodule Orcasite.Radio.BoutFeedStream do use Ash.Resource, + domain: Orcasite.Radio, extensions: [AshAdmin.Resource, AshGraphql.Resource], data_layer: AshPostgres.DataLayer, authorizers: [Ash.Policy.Authorizer] diff --git a/server/lib/orcasite/radio/calculations/decode_uuid.ex b/server/lib/orcasite/radio/calculations/decode_uuid.ex index e19d3a66..f1215fa1 100644 --- a/server/lib/orcasite/radio/calculations/decode_uuid.ex +++ b/server/lib/orcasite/radio/calculations/decode_uuid.ex @@ -1,5 +1,5 @@ defmodule Orcasite.Radio.Calculations.DecodeUUID do - use Ash.Calculation + use Ash.Resource.Calculation @impl true def load(_query, _opts, _context) do diff --git a/server/lib/orcasite/radio/calculations/feed_image_url.ex b/server/lib/orcasite/radio/calculations/feed_image_url.ex index 9deb3e83..fd463968 100644 --- a/server/lib/orcasite/radio/calculations/feed_image_url.ex +++ b/server/lib/orcasite/radio/calculations/feed_image_url.ex @@ -1,5 +1,5 @@ defmodule Orcasite.Radio.Calculations.FeedImageUrl do - use Ash.Calculation + use Ash.Resource.Calculation @impl true def load(_query, _opts, _context) do diff --git a/server/lib/orcasite/radio/calculations/lat_lng.ex b/server/lib/orcasite/radio/calculations/lat_lng.ex index 475ab4d2..0f2fcb0e 100644 --- a/server/lib/orcasite/radio/calculations/lat_lng.ex +++ b/server/lib/orcasite/radio/calculations/lat_lng.ex @@ -1,5 +1,5 @@ defmodule Orcasite.Radio.Calculations.LatLng do - use Ash.Calculation + use Ash.Resource.Calculation @impl true def load(_query, _opts, _context) do diff --git a/server/lib/orcasite/radio/candidate.ex b/server/lib/orcasite/radio/candidate.ex index ab954aa4..6bbe36c4 100644 --- a/server/lib/orcasite/radio/candidate.ex +++ b/server/lib/orcasite/radio/candidate.ex @@ -1,12 +1,11 @@ defmodule Orcasite.Radio.Candidate do use Ash.Resource, + domain: Orcasite.Radio, extensions: [AshAdmin.Resource, AshUUID, AshGraphql.Resource], data_layer: AshPostgres.DataLayer, authorizers: [Ash.Policy.Authorizer] - alias Orcasite.Radio.Category alias Orcasite.Radio.{Detection, Feed} - alias Orcasite.Notifications.Event postgres do table "candidates" @@ -23,16 +22,14 @@ defmodule Orcasite.Radio.Candidate do end attributes do - uuid_attribute(:id, prefix: "cand") + uuid_attribute :id, prefix: "cand", public?: true - attribute :detection_count, :integer - attribute :min_time, :utc_datetime_usec, allow_nil?: false - attribute :max_time, :utc_datetime_usec, allow_nil?: false - attribute :visible, :boolean, default: true + attribute :detection_count, :integer, public?: true + attribute :min_time, :utc_datetime_usec, allow_nil?: false, public?: true + attribute :max_time, :utc_datetime_usec, allow_nil?: false, public?: true + attribute :visible, :boolean, default: true, public?: true - attribute :category, :atom do - constraints one_of: Category.list() - end + attribute :category, Orcasite.Types.DetectionCategory, public?: true create_timestamp :inserted_at update_timestamp :updated_at @@ -43,9 +40,14 @@ defmodule Orcasite.Radio.Candidate do end relationships do - has_many :detections, Detection + has_many :detections, Detection do + public? true + end - belongs_to :feed, Feed, allow_nil?: false + belongs_to :feed, Feed do + allow_nil? false + public? true + end end policies do @@ -71,7 +73,7 @@ defmodule Orcasite.Radio.Candidate do end actions do - defaults [:update, :destroy] + defaults [:destroy] read :read do primary? true @@ -103,9 +105,8 @@ defmodule Orcasite.Radio.Candidate do read :find_nearby_candidate do get? true - argument :category, :atom do + argument :category, Orcasite.Types.DetectionCategory do allow_nil? false - constraints one_of: Category.list() end argument :timestamp, :utc_datetime @@ -142,11 +143,16 @@ defmodule Orcasite.Radio.Candidate do end end + update :update do + primary? true + accept [:min_time, :max_time, :detection_count, :visible, :category] + end + update :cancel_notifications do accept [] + require_atomic? false - argument :event_type, :atom do - constraints one_of: Event.types() + argument :event_type, Orcasite.Types.NotificationEventType do default :confirmed_candidate end @@ -159,11 +165,11 @@ defmodule Orcasite.Radio.Candidate do event_type: Ash.Changeset.get_argument(changeset, :event_type), active: true }) - |> Orcasite.Notifications.read!() + |> Ash.read!(authorize?: false) |> Enum.map(fn notification -> notification |> Ash.Changeset.for_update(:cancel_notification, %{}) - |> Orcasite.Notifications.update!() + |> Ash.update!(authorize?: false) end) {:ok, record} diff --git a/server/lib/orcasite/radio/category.ex b/server/lib/orcasite/radio/category.ex deleted file mode 100644 index c5beb94e..00000000 --- a/server/lib/orcasite/radio/category.ex +++ /dev/null @@ -1,3 +0,0 @@ -defmodule Orcasite.Radio.Category do - def list(), do: [:whale, :vessel, :other] -end diff --git a/server/lib/orcasite/radio/detection.ex b/server/lib/orcasite/radio/detection.ex index 6b0df672..18206bef 100644 --- a/server/lib/orcasite/radio/detection.ex +++ b/server/lib/orcasite/radio/detection.ex @@ -1,10 +1,10 @@ defmodule Orcasite.Radio.Detection do use Ash.Resource, + domain: Orcasite.Radio, extensions: [AshAdmin.Resource, AshUUID, AshGraphql.Resource, AshJsonApi.Resource], data_layer: AshPostgres.DataLayer, authorizers: [Ash.Policy.Authorizer] - alias Orcasite.Radio.Category alias Orcasite.Radio.{Feed, Candidate} postgres do @@ -24,21 +24,21 @@ defmodule Orcasite.Radio.Detection do end attributes do - uuid_attribute(:id, prefix: "det") + uuid_attribute :id, prefix: "det", public?: true - attribute :source_ip, :string - attribute :playlist_timestamp, :integer, allow_nil?: false - attribute :player_offset, :decimal, allow_nil?: false - attribute :listener_count, :integer - attribute :timestamp, :utc_datetime_usec, allow_nil?: false - attribute :description, :string - attribute :visible, :boolean, default: true + attribute :source_ip, :string, public?: true + attribute :playlist_timestamp, :integer, allow_nil?: false, public?: true + attribute :player_offset, :decimal, allow_nil?: false, public?: true + attribute :listener_count, :integer, public?: true + attribute :timestamp, :utc_datetime_usec, allow_nil?: false, public?: true + attribute :description, :string, public?: true + attribute :visible, :boolean, default: true, public?: true - attribute :category, :atom do + attribute :category, Orcasite.Types.DetectionCategory do # TODO: Figure out what to do with old detections # without a category # allow_nil? false - constraints one_of: Category.list() + public? true end create_timestamp :inserted_at @@ -50,12 +50,10 @@ defmodule Orcasite.Radio.Detection do end relationships do - belongs_to :candidate, Candidate - belongs_to :feed, Feed + belongs_to :candidate, Candidate, public?: true + belongs_to :feed, Feed, public?: true - belongs_to :user, Orcasite.Accounts.User do - api Orcasite.Accounts - end + belongs_to :user, Orcasite.Accounts.User end policies do @@ -71,6 +69,10 @@ defmodule Orcasite.Radio.Detection do authorize_if always() end + bypass action(:update_candidate) do + authorize_if always() + end + policy changing_attributes([:visible]) do authorize_if actor_attribute_equals(:moderator, true) end @@ -116,9 +118,8 @@ defmodule Orcasite.Radio.Detection do default_limit 100 end - argument :category, :atom do + argument :category, Orcasite.Types.DetectionCategory do allow_nil? false - constraints one_of: Category.list() end prepare build(load: [:uuid], sort: [inserted_at: :desc]) @@ -129,12 +130,22 @@ defmodule Orcasite.Radio.Detection do update :update do primary? true argument :candidate, :map + require_atomic? false + accept [:description, :visible, :category] + + change manage_relationship(:candidate, type: :append) + end + + update :update_candidate do + argument :candidate, :map + require_atomic? false change manage_relationship(:candidate, type: :append) end update :set_visible do accept [:visible] + require_atomic? false argument :visible, :boolean, default: true change set_attribute(:visible, arg(:visible)) @@ -144,7 +155,7 @@ defmodule Orcasite.Radio.Detection do |> Ash.Changeset.after_action(fn changeset, detection -> candidate = detection - |> Orcasite.Radio.load!(candidate: [:detections]) + |> Ash.load!(candidate: [:detections]) |> Map.get(:candidate) # If all detections are hidden, make the candidate hidden @@ -152,7 +163,7 @@ defmodule Orcasite.Radio.Detection do |> Ash.Changeset.for_update(:update, %{ visible: !Enum.all?(candidate.detections, &(!&1.visible)) }) - |> Orcasite.Radio.update!() + |> Ash.update!(authorize?: false) {:ok, detection} end) @@ -174,8 +185,7 @@ defmodule Orcasite.Radio.Detection do :player_offset, :listener_count, :description, - :category, - :send_notifications + :category ] argument :feed_id, :string, allow_nil?: false @@ -185,9 +195,9 @@ defmodule Orcasite.Radio.Detection do argument :listener_count, :integer, allow_nil?: true argument :description, :string - argument :category, :atom, - allow_nil?: false, - constraints: [one_of: Category.list()] + argument :category, Orcasite.Types.DetectionCategory do + allow_nil? false + end argument :send_notifications, :boolean, default: true @@ -232,7 +242,7 @@ defmodule Orcasite.Radio.Detection do feed_id: detection.feed_id, category: category }) - |> Orcasite.Radio.read!() + |> Ash.read!() |> case do [] -> Candidate @@ -243,7 +253,7 @@ defmodule Orcasite.Radio.Detection do feed: %{id: detection.feed_id}, category: category }) - |> Orcasite.Radio.create!() + |> Ash.create!() [candidate] -> candidate @@ -252,18 +262,18 @@ defmodule Orcasite.Radio.Detection do min_time: datetime_min(candidate.min_time, detection.timestamp), max_time: datetime_max(candidate.max_time, detection.timestamp) }) - |> Orcasite.Radio.update!() + |> Ash.update!(authorize?: false) end detection - |> Ash.Changeset.for_update(:update, %{candidate: candidate}) - |> Orcasite.Radio.update() + |> Ash.Changeset.for_update(:update_candidate, %{candidate: candidate}) + |> Ash.update(authorize?: false) end) |> Ash.Changeset.after_action(fn changeset, detection -> # Happens second detection = detection - |> Orcasite.Radio.load!([:feed, :candidate]) + |> Ash.load!([:feed, :candidate]) if Ash.Changeset.get_argument(changeset, :send_notifications) do Task.Supervisor.async_nolink(Orcasite.TaskSupervisor, fn -> @@ -272,7 +282,8 @@ defmodule Orcasite.Radio.Detection do detection.feed.slug, detection.description, detection.listener_count, - detection.candidate.id + detection.candidate.id, + authorize?: false ) end) end @@ -297,8 +308,6 @@ defmodule Orcasite.Radio.Detection do end code_interface do - define_for Orcasite.Radio - define :submit_detection end @@ -327,9 +336,6 @@ defmodule Orcasite.Radio.Detection do graphql do type :detection - # Remove user until we want to make use of this behind - # an authenticated/authorized call - hide_fields [:user] queries do get :detection, :read diff --git a/server/lib/orcasite/radio/feed.ex b/server/lib/orcasite/radio/feed.ex index 8b01aa4b..45709a30 100644 --- a/server/lib/orcasite/radio/feed.ex +++ b/server/lib/orcasite/radio/feed.ex @@ -1,5 +1,6 @@ defmodule Orcasite.Radio.Feed do use Ash.Resource, + domain: Orcasite.Radio, extensions: [AshAdmin.Resource, AshUUID, AshGraphql.Resource, AshJsonApi.Resource], data_layer: AshPostgres.DataLayer, authorizers: [Ash.Policy.Authorizer] @@ -13,6 +14,7 @@ defmodule Orcasite.Radio.Feed do index [:node_name] index [:visible] index [:slug] + index [:dataplicity_id] end migration_defaults id: "fragment(\"uuid_generate_v7()\")" @@ -23,18 +25,19 @@ defmodule Orcasite.Radio.Feed do end attributes do - uuid_attribute(:id, prefix: "feed") - - attribute :name, :string, allow_nil?: false - attribute :node_name, :string, allow_nil?: false - attribute :slug, :string, allow_nil?: false - attribute :location_point, :geometry, allow_nil?: false - attribute :intro_html, :string, default: "" - attribute :image_url, :string, default: "" - attribute :visible, :boolean, default: true - attribute :bucket, :string - attribute :bucket_region, :string - attribute :cloudfront_url, :string + uuid_attribute :id, prefix: "feed", public?: true + + attribute :name, :string, allow_nil?: false, public?: true + attribute :node_name, :string, allow_nil?: false, public?: true + attribute :slug, :string, allow_nil?: false, public?: true + attribute :location_point, :geometry, allow_nil?: false, public?: true + attribute :intro_html, :string, default: "", public?: true + attribute :image_url, :string, default: "", public?: true + attribute :visible, :boolean, default: true, public?: true + attribute :bucket, :string, public?: true + attribute :bucket_region, :string, public?: true + attribute :cloudfront_url, :string, public?: true + attribute :dataplicity_id, :string, public?: true create_timestamp :inserted_at update_timestamp :updated_at @@ -45,7 +48,8 @@ defmodule Orcasite.Radio.Feed do Orcasite.Types.LatLng, {Orcasite.Radio.Calculations.LatLng, keys: [:location_point], select: [:location_point]}, - allow_nil?: false + allow_nil?: false, + public?: true calculate :lat_lng_string, :string, @@ -54,15 +58,30 @@ defmodule Orcasite.Radio.Feed do calculate :thumb_url, :string, - {Orcasite.Radio.Calculations.FeedImageUrl, object: "thumbnail.png"} + {Orcasite.Radio.Calculations.FeedImageUrl, object: "thumbnail.png"}, + public?: true calculate :map_url, :string, - {Orcasite.Radio.Calculations.FeedImageUrl, object: "map.png"} + {Orcasite.Radio.Calculations.FeedImageUrl, object: "map.png"}, + public?: true end + aggregates do + exists :online, :feed_segments do + public? true + filter expr(inserted_at > ago(30, :second)) + end + end + + relationships do - has_many :feed_streams, Orcasite.Radio.FeedStream + has_many :feed_streams, Orcasite.Radio.FeedStream do + public? true + end + has_many :feed_segments, Orcasite.Radio.FeedSegment do + public? true + end end policies do @@ -80,12 +99,12 @@ defmodule Orcasite.Radio.Feed do read :read do primary? true - prepare build(load: [:lat_lng, :lat_lng_string]) + prepare build(load: [:lat_lng, :lat_lng_string, :online]) end read :index do filter expr(visible) - prepare build(load: [:lat_lng, :lat_lng_string]) + prepare build(load: [:lat_lng, :lat_lng_string, :online]) end read :get_by_slug do @@ -98,7 +117,19 @@ defmodule Orcasite.Radio.Feed do create :create do primary? true - reject [:location_point] + + accept [ + :name, + :node_name, + :slug, + :intro_html, + :image_url, + :visible, + :bucket, + :bucket_region, + :cloudfront_url, + :dataplicity_id + ] argument :lat_lng_string, :string do description "A comma-separated string of longitude and latitude" @@ -109,7 +140,20 @@ defmodule Orcasite.Radio.Feed do update :update do primary? true - reject [:location_point] + require_atomic? false + + accept [ + :name, + :node_name, + :slug, + :intro_html, + :image_url, + :visible, + :bucket, + :bucket_region, + :cloudfront_url, + :dataplicity_id + ] argument :lat_lng_string, :string do description "A comma-separated string of longitude and latitude" @@ -120,7 +164,7 @@ defmodule Orcasite.Radio.Feed do end admin do - table_columns [:id, :name, :slug, :node_name, :location_point, :visible] + table_columns [:id, :name, :slug, :node_name, :location_point, :visible, :online] format_fields location_point: {Jason, :encode!, []}, lat_lng: {Jason, :encode!, []} @@ -130,8 +174,6 @@ defmodule Orcasite.Radio.Feed do end code_interface do - define_for Orcasite.Radio - define :get_feed_by_slug, action: :get_by_slug, args: [:slug], get?: true define :get_feed_by_node_name, action: :get_by_node_name, args: [:node_name], get?: true end diff --git a/server/lib/orcasite/radio/feed_segment.ex b/server/lib/orcasite/radio/feed_segment.ex index 2f9b1f2e..7539828b 100644 --- a/server/lib/orcasite/radio/feed_segment.ex +++ b/server/lib/orcasite/radio/feed_segment.ex @@ -1,5 +1,6 @@ defmodule Orcasite.Radio.FeedSegment do use Ash.Resource, + domain: Orcasite.Radio, extensions: [AshAdmin.Resource, AshUUID, AshGraphql.Resource, AshJsonApi.Resource], data_layer: AshPostgres.DataLayer @@ -15,6 +16,7 @@ defmodule Orcasite.Radio.FeedSegment do index [:feed_id] index [:feed_stream_id] index [:bucket] + index [:inserted_at] end end @@ -24,33 +26,38 @@ defmodule Orcasite.Radio.FeedSegment do end attributes do - uuid_attribute :id, prefix: "fdseg" + uuid_attribute :id, prefix: "fdseg", public?: true - attribute :start_time, :utc_datetime - attribute :end_time, :utc_datetime - attribute :duration, :decimal + attribute :start_time, :utc_datetime, public?: true + attribute :end_time, :utc_datetime, public?: true + attribute :duration, :decimal, public?: true - attribute :bucket, :string - attribute :bucket_region, :string - attribute :cloudfront_url, :string + attribute :bucket, :string, public?: true + attribute :bucket_region, :string, public?: true + attribute :cloudfront_url, :string, public?: true attribute :playlist_timestamp, :string do + public? true description "UTC Unix epoch for playlist (m3u8 dir) start (e.g. 1541027406)" end attribute :playlist_path, :string do + public? true description "S3 object path for playlist dir (e.g. /rpi_orcasound_lab/hls/1541027406/)" end attribute :playlist_m3u8_path, :string do + public? true description "S3 object path for playlist file (e.g. /rpi_orcasound_lab/hls/1541027406/live.m3u8)" end attribute :segment_path, :string do + public? true description "S3 object path for ts file (e.g. /rpi_orcasound_lab/hls/1541027406/live005.ts)" end attribute :file_name, :string do + public? true description "ts file name (e.g. live005.ts)" allow_nil? false end @@ -60,8 +67,8 @@ defmodule Orcasite.Radio.FeedSegment do end relationships do - belongs_to :feed, Orcasite.Radio.Feed - belongs_to :feed_stream, Orcasite.Radio.FeedStream + belongs_to :feed, Orcasite.Radio.Feed, public?: true + belongs_to :feed_stream, Orcasite.Radio.FeedStream, public?: true end actions do @@ -115,7 +122,7 @@ defmodule Orcasite.Radio.FeedSegment do :playlist_timestamp, :playlist_m3u8_path, :playlist_path, - :file_name + :file_name, ] argument :feed, :map, allow_nil?: false diff --git a/server/lib/orcasite/radio/feed_stream.ex b/server/lib/orcasite/radio/feed_stream.ex index 1f1bfdc8..4e81b6db 100644 --- a/server/lib/orcasite/radio/feed_stream.ex +++ b/server/lib/orcasite/radio/feed_stream.ex @@ -1,5 +1,6 @@ defmodule Orcasite.Radio.FeedStream do use Ash.Resource, + domain: Orcasite.Radio, extensions: [AshAdmin.Resource, AshUUID, AshGraphql.Resource, AshJsonApi.Resource], data_layer: AshPostgres.DataLayer @@ -25,25 +26,28 @@ defmodule Orcasite.Radio.FeedStream do end attributes do - uuid_attribute :id, prefix: "fdstrm" + uuid_attribute :id, prefix: "fdstrm", public?: true - attribute :start_time, :utc_datetime - attribute :end_time, :utc_datetime - attribute :duration, :decimal + attribute :start_time, :utc_datetime, public?: true + attribute :end_time, :utc_datetime, public?: true + attribute :duration, :decimal, public?: true - attribute :bucket, :string - attribute :bucket_region, :string - attribute :cloudfront_url, :string + attribute :bucket, :string, public?: true + attribute :bucket_region, :string, public?: true + attribute :cloudfront_url, :string, public?: true attribute :playlist_timestamp, :string do + public? true description "UTC Unix epoch for playlist start (e.g. 1541027406)" end attribute :playlist_path, :string do + public? true description "S3 object path for playlist dir (e.g. /rpi_orcasound_lab/hls/1541027406/)" end attribute :playlist_m3u8_path, :string do + public? true description "S3 object path for playlist file (e.g. /rpi_orcasound_lab/hls/1541027406/live.m3u8)" end @@ -98,6 +102,7 @@ defmodule Orcasite.Radio.FeedStream do argument :feed, :map argument :playlist_path, :string argument :update_segments?, :boolean, default: false + argument :link_streams?, :boolean, default: false change fn changeset, _context -> path = @@ -168,6 +173,18 @@ defmodule Orcasite.Radio.FeedStream do |> Oban.insert() end + if Ash.Changeset.get_argument(changeset, :link_streams?) do + %{ + feed_stream_id: feed_stream_id, + enqueue_next_stream: true, + enqueue_prev_stream: true, + prev_depth: 3, + next_depth: 3 + } + |> Orcasite.Radio.Workers.LinkFeedStream.new() + |> Oban.insert() + end + {:ok, feed_stream} end) end @@ -254,92 +271,27 @@ defmodule Orcasite.Radio.FeedStream do update :update_segments do description "Pulls contents of m3u8 file and creates a FeedSegment per new entry" + require_atomic? false validate present([:playlist_m3u8_path, :playlist_path]) - change after_action(fn change, feed_stream -> + change after_action(fn change, feed_stream, _context -> file_name_query = Orcasite.Radio.FeedSegment |> Ash.Query.new() |> Ash.Query.select([:file_name]) %{feed: feed, feed_segments: existing_feed_segments} = - feed_stream |> Orcasite.Radio.load!([:feed, feed_segments: file_name_query]) - - playlist_start_time = Ash.Changeset.get_attribute(change, :start_time) - playlist_path = Ash.Changeset.get_attribute(change, :playlist_path) - - {:ok, body} = Orcasite.Radio.AwsClient.get_feed_stream(feed_stream) - - feed_segments = - body - |> String.split("#") - # Looks like "EXTINF:10.005378,\nlive000.ts\n" - |> Enum.filter(&String.contains?(&1, "EXTINF")) - |> Enum.reduce( - [], - fn extinf_string, acc -> - with %{"duration" => duration_string, "file_name" => file_name} <- - Regex.named_captures( - ~r|EXTINF:(?[^,]+),\n(?[^\n]+)|, - extinf_string - ) do - duration = Decimal.new(duration_string) - - start_offset = - Enum.map(acc, & &1.duration) - |> Enum.reduce(Decimal.new("0"), &Decimal.add/2) - |> Decimal.mult(1000) - - end_offset = - duration - |> Decimal.mult(1000) - |> Decimal.add(start_offset) - |> Decimal.round() - - start_time = - DateTime.add( - playlist_start_time, - Decimal.to_integer(Decimal.round(start_offset)), - :millisecond - ) - - end_time = - DateTime.add( - playlist_start_time, - Decimal.to_integer(end_offset), - :millisecond - ) - - [ - %{ - file_name: file_name, - playlist_path: playlist_path, - duration: duration, - start_time: start_time, - end_time: end_time, - bucket: feed_stream.bucket, - bucket_region: feed_stream.bucket_region, - cloudfront_url: feed_stream.cloudfront_url, - playlist_timestamp: feed_stream.playlist_timestamp, - playlist_m3u8_path: feed_stream.playlist_m3u8_path, - segment_path: playlist_path <> file_name, - feed: feed, - feed_stream: feed_stream - } - | acc - ] - else - _ -> acc - end - end - ) + feed_stream |> Ash.load!([:feed, feed_segments: file_name_query]) + + feed_segments = request_and_parse_manifest(feed_stream, feed) existing_file_names = existing_feed_segments |> Enum.map(& &1.file_name) + # Only insert segments not already present insert_segments = feed_segments |> Enum.filter(&(&1.file_name not in existing_file_names)) insert_segments - |> Orcasite.Radio.bulk_create(Orcasite.Radio.FeedSegment, :create, + |> Ash.bulk_create(Orcasite.Radio.FeedSegment, :create, upsert?: true, upsert_identity: :feed_segment_path, stop_on_error?: true @@ -350,11 +302,97 @@ defmodule Orcasite.Radio.FeedStream do end end) end + + update :link_next_stream do + require_atomic? false + validate present([:start_time]) + validate absent([:next_feed_stream_id]) + + change fn %{data: %{start_time: start_time, feed_id: feed_id}} = change, _context -> + require Ash.Query + + Orcasite.Radio.FeedStream + |> Ash.Query.filter(start_time > ^start_time and feed_id == ^feed_id) + |> Ash.Query.sort(start_time: :asc) + |> Ash.Query.limit(1) + |> Ash.read() + |> case do + {:ok, [next_stream]} -> + change + |> Ash.Changeset.manage_relationship(:next_feed_stream, next_stream, + on_lookup: :relate + ) + + _ -> + change + end + end + end + + update :link_prev_stream do + require_atomic? false + validate present([:start_time]) + validate absent([:prev_feed_stream_id]) + + change fn %{data: %{start_time: start_time, feed_id: feed_id}} = change, _context -> + require Ash.Query + + Orcasite.Radio.FeedStream + |> Ash.Query.filter(start_time < ^start_time and feed_id == ^feed_id) + |> Ash.Query.sort(start_time: :desc) + |> Ash.Query.limit(1) + |> Ash.read() + |> case do + {:ok, [prev_stream]} -> + change + |> Ash.Changeset.manage_relationship(:prev_feed_stream, prev_stream, + on_lookup: :relate + ) + + _ -> + change + end + end + end + + update :update_end_time_and_duration do + description "Pulls and parses the manifest body, updates duration and end time. Only runs if there's a next_feed_stream" + require_atomic? false + validate present([:next_feed_stream_id, :start_time]) + validate absent([:duration, :end_time]) + + change before_action(fn %{data: feed_stream} = change, _context -> + feed_stream + |> request_and_parse_manifest() + |> case do + feed_segments when is_list(feed_segments) and length(feed_segments) > 0 -> + duration = + feed_segments + |> Enum.map(&Map.get(&1, :duration)) + |> Enum.reduce(&Decimal.add/2) + + end_time = + DateTime.add( + feed_stream.start_time, + duration + |> Decimal.mult(1000) + |> Decimal.round() + |> Decimal.to_integer(), + :millisecond + ) + + change + |> Ash.Changeset.change_attribute(:end_time, end_time) + |> Ash.Changeset.change_attribute(:duration, duration) + + _ -> + change + end + end) + end end code_interface do - define_for Orcasite.Radio - define :create_from_m3u8_path, action: :from_m3u8_path, args: [:m3u8_path] end @@ -375,4 +413,74 @@ defmodule Orcasite.Radio.FeedStream do list :feed_streams, :index end end + + def request_and_parse_manifest(feed_stream, feed \\ nil) do + playlist_start_time = feed_stream.start_time + playlist_path = feed_stream.playlist_path + + {:ok, body} = Orcasite.Radio.AwsClient.get_stream_manifest_body(feed_stream) + + body + |> String.split("#") + # Looks like "EXTINF:10.005378,\nlive000.ts\n" + |> Enum.filter(&String.contains?(&1, "EXTINF")) + |> Enum.reduce( + [], + fn extinf_string, acc -> + with %{"duration" => duration_string, "file_name" => file_name} <- + Regex.named_captures( + ~r|EXTINF:(?[^,]+),\n(?[^\n]+)|, + extinf_string + ) do + duration = Decimal.new(duration_string) + + start_offset = + Enum.map(acc, & &1.duration) + |> Enum.reduce(Decimal.new("0"), &Decimal.add/2) + |> Decimal.mult(1000) + + end_offset = + duration + |> Decimal.mult(1000) + |> Decimal.add(start_offset) + |> Decimal.round() + + start_time = + DateTime.add( + playlist_start_time, + Decimal.to_integer(Decimal.round(start_offset)), + :millisecond + ) + + end_time = + DateTime.add( + playlist_start_time, + Decimal.to_integer(end_offset), + :millisecond + ) + + [ + %{ + start_time: start_time, + end_time: end_time, + duration: duration, + bucket: feed_stream.bucket, + bucket_region: feed_stream.bucket_region, + cloudfront_url: feed_stream.cloudfront_url, + playlist_timestamp: feed_stream.playlist_timestamp, + playlist_m3u8_path: feed_stream.playlist_m3u8_path, + playlist_path: playlist_path, + file_name: file_name, + segment_path: playlist_path <> file_name, + feed: feed, + feed_stream: feed_stream + } + | acc + ] + else + _ -> acc + end + end + ) + end end diff --git a/server/lib/orcasite/radio/feed_stream_queue.ex b/server/lib/orcasite/radio/feed_stream_queue.ex index 85b48043..4719ddb7 100644 --- a/server/lib/orcasite/radio/feed_stream_queue.ex +++ b/server/lib/orcasite/radio/feed_stream_queue.ex @@ -58,8 +58,8 @@ defmodule Orcasite.Radio.FeedStreamQueue do Task.Supervisor.start_child(Orcasite.TaskSupervisor, fn -> paths - |> Enum.map(&Map.put(&1, :update_segments?, true)) - |> Orcasite.Radio.bulk_create( + |> Enum.map(&Map.merge(&1, %{update_segments?: true, link_streams?: true})) + |> Ash.bulk_create( Orcasite.Radio.FeedStream, :from_m3u8_path, return_errors?: true, diff --git a/server/lib/orcasite/radio/radio.ex b/server/lib/orcasite/radio/radio.ex index 1a692ac0..46c3455c 100644 --- a/server/lib/orcasite/radio/radio.ex +++ b/server/lib/orcasite/radio/radio.ex @@ -1,8 +1,14 @@ defmodule Orcasite.Radio do - use Ash.Api, extensions: [AshAdmin.Api, AshGraphql.Api, AshJsonApi.Api] + use Ash.Domain, extensions: [AshAdmin.Domain, AshGraphql.Domain, AshJsonApi.Domain] resources do - registry Orcasite.Radio.Registry + resource Orcasite.Radio.Feed + resource Orcasite.Radio.Candidate + resource Orcasite.Radio.Detection + resource Orcasite.Radio.Bout + resource Orcasite.Radio.FeedStream + resource Orcasite.Radio.BoutFeedStream + resource Orcasite.Radio.FeedSegment end admin do diff --git a/server/lib/orcasite/radio/registry.ex b/server/lib/orcasite/radio/registry.ex deleted file mode 100644 index 2ac05a32..00000000 --- a/server/lib/orcasite/radio/registry.ex +++ /dev/null @@ -1,14 +0,0 @@ -defmodule Orcasite.Radio.Registry do - use Ash.Registry, extensions: [Ash.Registry.ResourceValidations] - - entries do - entry Orcasite.Radio.Feed - entry Orcasite.Radio.Candidate - entry Orcasite.Radio.Detection - entry Orcasite.Radio.Bout - entry Orcasite.Radio.FeedStream - entry Orcasite.Radio.BoutFeedStream - entry Orcasite.Radio.FeedSegment - end - -end diff --git a/server/lib/orcasite/radio/workers/link_feed_stream.ex b/server/lib/orcasite/radio/workers/link_feed_stream.ex new file mode 100644 index 00000000..4895944d --- /dev/null +++ b/server/lib/orcasite/radio/workers/link_feed_stream.ex @@ -0,0 +1,72 @@ +defmodule Orcasite.Radio.Workers.LinkFeedStream do + use Oban.Worker, + queue: :feeds, + unique: [ + keys: [:feed_stream_id], + period: :infinity, + states: [:available, :scheduled, :executing] + ] + + @impl Oban.Worker + def perform(%Oban.Job{args: %{"feed_stream_id" => nil}}) do + :ok + end + + def perform(%Oban.Job{args: %{"feed_stream_id" => feed_stream_id} = args}) do + enqueue_prev_stream = Map.get(args, "enqueue_prev_stream", false) + enqueue_next_stream = Map.get(args, "enqueue_next_stream", false) + next_depth = Map.get(args, "next_depth", 3) + prev_depth = Map.get(args, "prev_depth", 3) + + feed_stream = + Orcasite.Radio.FeedStream + |> Ash.get!(feed_stream_id) + + # If new link to next stream, update times and maybe queue next feed_stream to link + feed_stream + |> Ash.Changeset.for_update(:link_next_stream) + |> Ash.update() + |> case do + {:ok, %{next_feed_stream_id: next_feed_stream_id} = fs} + when not is_nil(next_feed_stream_id) -> + fs + |> Ash.Changeset.for_update(:update_end_time_and_duration) + |> Ash.update() + + if enqueue_next_stream and next_depth > 0 do + %{ + feed_stream_id: next_feed_stream_id, + enqueue_next_stream: enqueue_next_stream, + next_depth: next_depth - 1 + } + |> new() + |> Oban.insert() + end + + _ -> + nil + end + + feed_stream + |> Ash.Changeset.for_update(:link_prev_stream) + |> Ash.update() + |> case do + {:ok, %{prev_feed_stream_id: prev_feed_stream_id}} when not is_nil(prev_feed_stream_id) -> + # If new link to previous stream, queue another link job + if enqueue_prev_stream and prev_depth > 0 do + %{ + feed_stream_id: prev_feed_stream_id, + enqueue_prev_stream: enqueue_prev_stream, + prev_depth: prev_depth - 1 + } + |> new() + |> Oban.insert() + end + + _ -> + nil + end + + :ok + end +end diff --git a/server/lib/orcasite/radio/workers/update_feed_segments.ex b/server/lib/orcasite/radio/workers/update_feed_segments.ex index b8b597c3..ee75e6d8 100644 --- a/server/lib/orcasite/radio/workers/update_feed_segments.ex +++ b/server/lib/orcasite/radio/workers/update_feed_segments.ex @@ -1,11 +1,13 @@ defmodule Orcasite.Radio.Workers.UpdateFeedSegments do - use Oban.Worker, queue: :feed_segments, unique: [keys: [:feed_stream_id], period: 10] + use Oban.Worker, + queue: :feeds, + unique: [keys: [:feed_stream_id], period: :infinity, states: [:available, :scheduled, :executing]] @impl Oban.Worker def perform(%Oban.Job{args: %{"feed_stream_id" => feed_stream_id}}) do Orcasite.Radio.FeedStream - |> Orcasite.Radio.get!(feed_stream_id) + |> Ash.get!(feed_stream_id) |> Ash.Changeset.for_update(:update_segments) - |> Orcasite.Radio.update() + |> Ash.update() end end diff --git a/server/lib/orcasite/repo.ex b/server/lib/orcasite/repo.ex index c7719bdc..2af21c51 100644 --- a/server/lib/orcasite/repo.ex +++ b/server/lib/orcasite/repo.ex @@ -9,6 +9,14 @@ defmodule Orcasite.Repo do {:ok, Keyword.put(opts, :url, System.get_env("DATABASE_URL"))} end - def installed_extensions, - do: ["citext", "uuid-ossp", "postgis", AshUUID.PostgresExtension, "pg_stat_statements"] + def installed_extensions do + [ + "citext", + "uuid-ossp", + "postgis", + AshUUID.PostgresExtension, + "pg_stat_statements", + "ash-functions" + ] + end end diff --git a/server/lib/orcasite/types/audio_category.ex b/server/lib/orcasite/types/audio_category.ex new file mode 100644 index 00000000..710d6fdb --- /dev/null +++ b/server/lib/orcasite/types/audio_category.ex @@ -0,0 +1,7 @@ +defmodule Orcasite.Types.AudioCategory do + use Ash.Type.Enum, values: [:biophony, :anthrophony, :geophony] + + + def graphql_type(_), do: :audio_category + def graphql_input_type(_), do: :audio_category +end diff --git a/server/lib/orcasite/types/detection_category.ex b/server/lib/orcasite/types/detection_category.ex new file mode 100644 index 00000000..ac59adde --- /dev/null +++ b/server/lib/orcasite/types/detection_category.ex @@ -0,0 +1,6 @@ +defmodule Orcasite.Types.DetectionCategory do + use Ash.Type.Enum, values: [:whale, :vessel, :other] + + def graphql_type(_), do: :detection_category + def graphql_input_type(_), do: :detection_category +end diff --git a/server/lib/orcasite/types/geometry.ex b/server/lib/orcasite/types/geometry.ex index 78cd405a..01b8f44b 100644 --- a/server/lib/orcasite/types/geometry.ex +++ b/server/lib/orcasite/types/geometry.ex @@ -27,7 +27,7 @@ defmodule Orcasite.Types.Geometry do Geo.PostGIS.Geometry.dump(value) end - def graphql_type, do: :json + def graphql_type(_), do: :json end if Code.ensure_loaded?(Ecto.DevLogger) do diff --git a/server/lib/orcasite/types/notification_event_type.ex b/server/lib/orcasite/types/notification_event_type.ex new file mode 100644 index 00000000..4960e637 --- /dev/null +++ b/server/lib/orcasite/types/notification_event_type.ex @@ -0,0 +1,6 @@ +defmodule Orcasite.Types.NotificationEventType do + use Ash.Type.Enum, values: [:confirmed_candidate, :new_detection] + + def graphql_type(_), do: :notification_event_type + def graphql_input_type(_), do: :notification_event_type +end diff --git a/server/lib/orcasite/utils.ex b/server/lib/orcasite/utils.ex index aaffbced..c9a66b1c 100644 --- a/server/lib/orcasite/utils.ex +++ b/server/lib/orcasite/utils.ex @@ -28,4 +28,12 @@ defmodule Orcasite.Utils do def atomize_keys(not_a_map) do not_a_map end + + def oban_error(error_tuple) do + error_tuple + |> elem(0) + |> Jason.decode!() + |> Map.get("error") + |> IO.puts + end end diff --git a/server/lib/orcasite_web/controllers/subscription_auth_controller.ex b/server/lib/orcasite_web/controllers/subscription_auth_controller.ex index 79741f64..2f8d2ebf 100644 --- a/server/lib/orcasite_web/controllers/subscription_auth_controller.ex +++ b/server/lib/orcasite_web/controllers/subscription_auth_controller.ex @@ -13,7 +13,7 @@ defmodule OrcasiteWeb.SubscriptionAuthController do def success(conn, _activity, subscription, _token) when not is_nil(subscription) do subscription |> Ash.Changeset.for_update(:update, %{active: false}) - |> Orcasite.Notifications.update!() + |> Ash.update!() conn |> delete_session(:return_to) diff --git a/server/lib/orcasite_web/graphql/schema.ex b/server/lib/orcasite_web/graphql/schema.ex index 9e8527e6..c47ba426 100644 --- a/server/lib/orcasite_web/graphql/schema.ex +++ b/server/lib/orcasite_web/graphql/schema.ex @@ -1,9 +1,9 @@ defmodule OrcasiteWeb.Schema do use Absinthe.Schema - @apis [Orcasite.Radio, Orcasite.Accounts, Orcasite.Notifications] + @domains [Orcasite.Radio, Orcasite.Accounts, Orcasite.Notifications] - use AshGraphql, apis: @apis + use AshGraphql, domains: @domains import_types Absinthe.Type.Custom import_types OrcasiteWeb.Graphql.Types.Accounts diff --git a/server/lib/orcasite_web/json_api_router.ex b/server/lib/orcasite_web/json_api_router.ex index 8d48b106..1192aa3b 100644 --- a/server/lib/orcasite_web/json_api_router.ex +++ b/server/lib/orcasite_web/json_api_router.ex @@ -1,6 +1,6 @@ defmodule OrcasiteWeb.JsonApiRouter do - use AshJsonApi.Api.Router, - apis: [Orcasite.Notifications, Orcasite.Radio], + use AshJsonApi.Router, + domains: [Orcasite.Notifications, Orcasite.Radio], json_schema: "/json_schema", open_api: "/open_api", modify_open_api: {__MODULE__, :modify_open_api, []} diff --git a/server/mix.exs b/server/mix.exs index 5a29e064..d3ce54a6 100644 --- a/server/mix.exs +++ b/server/mix.exs @@ -76,20 +76,22 @@ defmodule Orcasite.Mixfile do {:corsica, "~> 2.1"}, {:telemetry_metrics, "~> 1.0"}, {:telemetry_poller, "~> 1.0"}, - {:ash, "~> 2.21.2"}, - {:ash_admin, "~> 0.10.8"}, - {:ash_postgres, "~> 1.3"}, + {:ash, "~> 3.0.16"}, + {:ash_admin, "~> 0.11.3"}, + {:ash_postgres, "~> 2.0"}, + {:picosat_elixir, "~> 0.2.3"}, {:heroicons, "~> 0.5"}, {:oban, "~> 2.14"}, {:gen_smtp, "~> 1.0"}, - {:ash_authentication, "~> 3.12.0"}, - {:ash_authentication_phoenix, "~> 1.9.3"}, + {:ash_authentication, "~> 4.0.1"}, + {:ash_authentication_phoenix, "~> 2.0"}, {:syn, "~> 3.3"}, {:mjml, "~> 3.0.1"}, {:zappa, github: "skanderm/zappa", branch: "master"}, - {:ash_uuid, "~> 0.4"}, - {:ash_graphql, "~> 0.27.0"}, - {:ash_json_api, "~> 0.34.0"}, + {:ash_uuid, github: "skanderm/ash_uuid", tag: "319e6d6"}, + # {:ash_uuid, "~> 1.1.1"}, + {:ash_graphql, github: "ash-project/ash_graphql", branch: "main"}, + {:ash_json_api, "~> 1.2"}, {:open_api_spex, "~> 3.16"}, {:redoc_ui_plug, "~> 0.2.1"}, {:phoenix_pubsub_redis, "~> 3.0.1"}, diff --git a/server/mix.lock b/server/mix.lock index 73813a78..fdfc2969 100644 --- a/server/mix.lock +++ b/server/mix.lock @@ -1,20 +1,21 @@ %{ "absinthe": {:hex, :absinthe, "1.7.6", "0b897365f98d068cfcb4533c0200a8e58825a4aeeae6ec33633ebed6de11773b", [:mix], [{:dataloader, "~> 1.0.0 or ~> 2.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:opentelemetry_process_propagator, "~> 0.2.1", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7626951ca5eec627da960615b51009f3a774765406ff02722b1d818f17e5778"}, "absinthe_plug": {:hex, :absinthe_plug, "1.5.8", "38d230641ba9dca8f72f1fed2dfc8abd53b3907d1996363da32434ab6ee5d6ab", [:mix], [{:absinthe, "~> 1.5", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bbb04176647b735828861e7b2705465e53e2cf54ccf5a73ddd1ebd855f996e5a"}, - "ash": {:hex, :ash, "2.21.14", "9db129af417a1a07adfaa24d3a5ac360bf1e4510f7a4b6a6b2d5003c6cec0704", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.6", [hex: :reactor, repo: "hexpm", optional: false]}, {:spark, ">= 1.1.55 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a77833b0409899dcb15d153b6b0a9c7980330bd329ffdcc1a454a5dee5f876de"}, - "ash_admin": {:hex, :ash_admin, "0.10.9", "d8a1dc8e8ae481f87db371a5f75d2d86b58cd59ea44075d6065cf701dbb895ad", [:mix], [{:ash, "~> 2.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_phoenix, "~> 1.1", [hex: :ash_phoenix, repo: "hexpm", optional: false]}, {:ash_postgres, "~> 1.0", [hex: :ash_postgres, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:tails, "~> 0.1", [hex: :tails, repo: "hexpm", optional: false]}], "hexpm", "28e6dfd43343dabe3759d4db87acf0f5ead59530b1b17281b1651bbe0714b1ea"}, - "ash_authentication": {:hex, :ash_authentication, "3.12.4", "7f1535c55d8f5896b33960daaf1f046d365b830be5292d94a72eef1a8be918bf", [:mix], [{:ash, ">= 2.5.11 and < 3.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_postgres, "~> 1.5.1", [hex: :ash_postgres, repo: "hexpm", optional: true]}, {:assent, ">= 0.2.8 and < 1.0.0-0", [hex: :assent, repo: "hexpm", optional: false]}, {:bcrypt_elixir, "~> 3.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: false]}, {:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:finch, "~> 0.18.0", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:joken, "~> 2.5", [hex: :joken, repo: "hexpm", optional: false]}, {:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}, {:spark, ">= 1.1.39 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}], "hexpm", "771827fabcba017a0c36858a50533263fb30a77ffbecbbec92a2733412e6a1c1"}, - "ash_authentication_phoenix": {:hex, :ash_authentication_phoenix, "1.9.4", "bd7b4c8597e1479f385d93f83bb5b9a486e1ad3e77b0ed3f682c482e549bfa77", [:mix], [{:ash, "~> 2.2", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_authentication, ">= 3.11.9 and < 4.0.0-0", [hex: :ash_authentication, repo: "hexpm", optional: false]}, {:ash_phoenix, "~> 1.1", [hex: :ash_phoenix, repo: "hexpm", optional: false]}, {:bcrypt_elixir, "~> 3.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_html_helpers, "~> 1.0", [hex: :phoenix_html_helpers, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:slugify, "~> 1.3", [hex: :slugify, repo: "hexpm", optional: false]}], "hexpm", "a37966ceb75423cbebbcc01224b4bc4e4904bcf21d7468498994b40d43b4b254"}, - "ash_graphql": {:hex, :ash_graphql, "0.27.1", "514ea4d3f2dafab45d6d4a0c7eb8037719a693a905295575f1faa069b6791b09", [:mix], [{:absinthe, "~> 1.7", [hex: :absinthe, repo: "hexpm", optional: false]}, {:absinthe_plug, "~> 1.4", [hex: :absinthe_plug, repo: "hexpm", optional: false]}, {:ash, "~> 2.17", [hex: :ash, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "81585f7db55730938f263f78d32e35cea81b6d1139b14ce59cdf59e1ab3a688b"}, - "ash_json_api": {:hex, :ash_json_api, "0.34.2", "21a1f935d1208d7f419f08cb44ae379ffa9919dc4860e6bbc6e7499762986e7e", [:mix], [{:ash, ">= 2.9.24 and < 3.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:json_xema, "~> 0.4.0", [hex: :json_xema, repo: "hexpm", optional: false]}, {:open_api_spex, "~> 3.16", [hex: :open_api_spex, repo: "hexpm", optional: true]}, {:plug, "~> 1.11", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "620658e495ac745807d8eab0e752836f44e1368c98c7beaad5d4c2bd8c286cf4"}, - "ash_phoenix": {:hex, :ash_phoenix, "1.3.6", "786b3a1a87cc58432746480b8f8596a7f018c6a66bc16f48929ea081784a692c", [:mix], [{:ash, "~> 2.16", [hex: :ash, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.5.6 or ~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.20.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "0255c0a6e905d0dfea8cb66ceacc8dd33947089851e9c739b44496a9c493489a"}, - "ash_postgres": {:hex, :ash_postgres, "1.5.28", "d0c524b143c03782a7fd2c53122f6ad3b436c0d66aaaa64ef080ec4d59b54c43", [:mix], [{:ash, ">= 2.20.3 and < 3.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "eeb044b73896d7e77d9ec38dd01cbdaa23f765356b66774f51e3a2cd6a48e4c1"}, - "ash_uuid": {:hex, :ash_uuid, "0.7.0", "d045ce940364eb0e002c218b60514a8912012e081f111be8faa70fa0710d026c", [:mix], [{:ash, "~> 2.13", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_postgres, ">= 1.3.41", [hex: :ash_postgres, repo: "hexpm", optional: false]}, {:uniq, "~> 0.6", [hex: :uniq, repo: "hexpm", optional: false]}], "hexpm", "009e6579e6fee8f99aaf7c8afeda4dd28e2474af23a281c553ca7d466ef5948d"}, + "ash": {:hex, :ash, "3.0.16", "8eaebd5a9f3ee404937ac811a240799613b0619026e097436132d60eaf18ed16", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "36c0d7653f7fb1d13cc03e1cc7ea7f6b9aadd278b9c9375ff5f0636ed0d7a785"}, + "ash_admin": {:hex, :ash_admin, "0.11.3", "e240bbef2b1cfb338f08178558e3932393ce5c1838b1bbd9bbe51da3f46afb4a", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_phoenix, ">= 2.0.4 and < 3.0.0-0", [hex: :ash_phoenix, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.20", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:tails, "~> 0.1", [hex: :tails, repo: "hexpm", optional: false]}], "hexpm", "ae700d8f51669966f9a6557f05d19857ee5c06f09249dece06df3392a2e4c2b7"}, + "ash_authentication": {:hex, :ash_authentication, "4.0.1", "27e5fcda1022897a02903441a049ba9e5f655e51a757039d946f5bce1de0447c", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_postgres, "~> 2.0", [hex: :ash_postgres, repo: "hexpm", optional: true]}, {:assent, ">= 0.2.8 and < 1.0.0-0", [hex: :assent, repo: "hexpm", optional: false]}, {:bcrypt_elixir, "~> 3.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: false]}, {:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:finch, "~> 0.18.0", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:joken, "~> 2.5", [hex: :joken, repo: "hexpm", optional: false]}, {:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}], "hexpm", "e204585c8eed2d46a12e7031da48a169c513d5074ba43da90be0a92f7e1e0413"}, + "ash_authentication_phoenix": {:hex, :ash_authentication_phoenix, "2.0.0", "1d2dd0abc9b9e008ea4423e902eb24825dbf4b9d1329bd079d7064ecfc45d319", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_authentication, "~> 4.0", [hex: :ash_authentication, repo: "hexpm", optional: false]}, {:ash_phoenix, "~> 2.0", [hex: :ash_phoenix, repo: "hexpm", optional: false]}, {:bcrypt_elixir, "~> 3.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_html_helpers, "~> 1.0", [hex: :phoenix_html_helpers, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:slugify, "~> 1.3", [hex: :slugify, repo: "hexpm", optional: false]}], "hexpm", "6a7c24d57ef6f7a4456d5ba139c8221df6a7ed81f15707a23fc33ad369e43a36"}, + "ash_graphql": {:git, "https://github.com/ash-project/ash_graphql.git", "d47ab787476f8d5311d7079e1089feafd9213ef0", [branch: "main"]}, + "ash_json_api": {:hex, :ash_json_api, "1.3.2", "aeb1f043f09cb7ae1da4a4630804dd9b3b2f5bc853798968c4bd519e03157cc2", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:json_xema, "~> 0.4", [hex: :json_xema, repo: "hexpm", optional: false]}, {:open_api_spex, "~> 3.16", [hex: :open_api_spex, repo: "hexpm", optional: true]}, {:plug, "~> 1.11", [hex: :plug, repo: "hexpm", optional: false]}, {:spark, ">= 2.2.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}], "hexpm", "f10cab0a442e446fd7f05b27004194c3bab80a628409b8dcc865e512ffc5ffc1"}, + "ash_phoenix": {:hex, :ash_phoenix, "2.0.4", "0d18a3371879b875865180aaabc1697a35a6dcb9ebd2f346456208214bd02c9e", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.5.6 or ~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.20.3 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "f3ea5309b42cdcaafc0ca713757cd4bb4819e02aeacc5195a040a955e861767d"}, + "ash_postgres": {:hex, :ash_postgres, "2.0.12", "0cd30b5eab6ef6fc77d1f29c23bd8b9ad62e676f8aa14bf9194d3cf87e10adf2", [:mix], [{:ash, ">= 3.0.15 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_sql, ">= 0.2.6 and < 1.0.0-0", [hex: :ash_sql, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "a6536a641bcb3dc0ff5b351c35b9334e5586170037f88c0035f532dcba872700"}, + "ash_sql": {:hex, :ash_sql, "0.2.7", "56bfddcb4cf3edbbf702e2b665497309e43672fbf449ef049f4805211b9cd1b7", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "14622713cc08ede8fd0d2618b1718d759a6ee28839b8f738e6ee084703bd9437"}, + "ash_uuid": {:git, "https://github.com/skanderm/ash_uuid.git", "319e6d6ed5878947b175b65965e2528a228945c9", [tag: "319e6d6"]}, "assent": {:hex, :assent, "0.2.10", "27e544c3428996c8ad744d473b3ceae86e4eb7db6bc7432676420e67e9148dd7", [:mix], [{:certifi, ">= 0.0.0", [hex: :certifi, repo: "hexpm", optional: true]}, {:finch, "~> 0.15", [hex: :finch, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: true]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: true]}, {:ssl_verify_fun, ">= 0.0.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: true]}], "hexpm", "8483bf9621e994795a70a4ad8fda725abfb6a9675d63a9bfd4217c76d4a2d82a"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.1.0", "0b110a9a6c619b19a7f73fa3004aa11d6e719a67e672d1633dc36b6b2290a0f7", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2ad2acb5a8bc049e8d5aa267802631912bb80d5f4110a178ae7999e69dca1bf7"}, - "broadway": {:hex, :broadway, "1.0.7", "7808f9e3eb6f53ca6d060f0f9d61012dd8feb0d7a82e62d087dd517b9b66fa53", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.7 or ~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e76cfb0a7d64176c387b8b1ddbfb023e2ee8a63e92f43664d78e6d5d0b1177c6"}, - "broadway_sqs": {:hex, :broadway_sqs, "0.7.3", "b7b99cf4d21e9d87a64853c4c502690ece01897a3a08bfc6df01ad8999e19da3", [:mix], [{:broadway, "~> 1.0", [hex: :broadway, repo: "hexpm", optional: false]}, {:ex_aws_sqs, "~> 3.2.1 or ~> 3.3", [hex: :ex_aws_sqs, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.7 or ~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:saxy, "~> 1.1", [hex: :saxy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0a9f02d4a32ba65feebb0cd247c466342d4eb1803ee7db993f2886810dfc1d3a"}, - "castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"}, + "broadway": {:hex, :broadway, "1.1.0", "8ed3aea01fd6f5640b3e1515b90eca51c4fc1fac15fb954cdcf75dc054ae719c", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.7 or ~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "25e315ef1afe823129485d981dcc6d9b221cea30e625fd5439e9b05f44fb60e4"}, + "broadway_sqs": {:hex, :broadway_sqs, "0.7.4", "ab89b298f9253adb8534f92095b56d4879e35fe2f5a0730256f7e824572c637f", [:mix], [{:broadway, "~> 1.0", [hex: :broadway, repo: "hexpm", optional: false]}, {:ex_aws_sqs, "~> 3.2.1 or ~> 3.3", [hex: :ex_aws_sqs, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.7 or ~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:saxy, "~> 1.1", [hex: :saxy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7140085c4f7c4b27886b3a8f3d0942976f39f195fdbc2f652c5d7b157f93ae28"}, + "castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"}, @@ -26,7 +27,7 @@ "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.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, "dataloader": {:hex, :dataloader, "2.0.0", "49b42d60b9bb06d761a71d7b034c4b34787957e713d4fae15387a25fcd639112", [:mix], [{:ecto, ">= 3.4.3 and < 4.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:opentelemetry_process_propagator, "~> 0.2.1", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "09d61781b76ce216e395cdbc883ff00d00f46a503e215c22722dba82507dfef0"}, - "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.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"}, "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [: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", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, @@ -38,7 +39,7 @@ "ex_aws": {:hex, :ex_aws, "2.5.4", "86c5bb870a49e0ab6f5aa5dd58cf505f09d2624ebe17530db3c1b61c88a673af", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e82bd0091bb9a5bb190139599f922ff3fc7aebcca4374d65c99c4e23aa6d1625"}, "ex_aws_s3": {:hex, :ex_aws_s3, "2.5.3", "422468e5c3e1a4da5298e66c3468b465cfd354b842e512cb1f6fbbe4e2f5bdaf", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "4f09dd372cc386550e484808c5ac5027766c8d0cd8271ccc578b82ee6ef4f3b8"}, "ex_aws_sqs": {:hex, :ex_aws_sqs, "3.4.0", "f7c4d0177c1c954776363d3dc05e5dfd37ddf0e2c65ec3f047e5c9c7dd1b71ac", [:mix], [{:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:saxy, "~> 1.1", [hex: :saxy, repo: "hexpm", optional: true]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "b504482206ccaf767b714888e9d41a1cfcdcb241577985517114191c812f155a"}, - "ex_unit_notifier": {:hex, :ex_unit_notifier, "1.3.0", "1d82aa6d2fb44e6f0f219142661a46e13dcba833e150e1395190d2e0fb721990", [:mix], [], "hexpm", "55fffd6062e8d962fc44e8b06fa30a87dc7251ee2a69f520781a3bb29858c365"}, + "ex_unit_notifier": {:hex, :ex_unit_notifier, "1.3.1", "5f93a9e5c3340c21eeb1b97cd15f9a1734a18a8b5566ec392d7035a1a0b1c1b0", [:mix], [], "hexpm", "87eb1cea911ed1753e1cc046cbf1c7f86af9058e30672a355f0699b41e5e119d"}, "expo": {:hex, :expo, "0.5.2", "beba786aab8e3c5431813d7a44b828e7b922bfa431d6bfbada0904535342efe2", [:mix], [], "hexpm", "8c9bfa06ca017c9cb4020fabe980bc7fdb1aaec059fd004c2ab3bff03b1c599c"}, "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [: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", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, @@ -48,69 +49,75 @@ "geo": {:hex, :geo, "3.6.0", "00c9c6338579f67e91cd5950af4ae2eb25cdce0c3398718c232539f61625d0bd", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "1dbdebf617183b54bc3c8ad7a36531a9a76ada8ca93f75f573b0ae94006168da"}, "geo_postgis": {:hex, :geo_postgis, "3.7.0", "aa12fd608649e296f97bc81fc2e1499b88eb6c6ac24c652ca852b91c3ca3b9a9", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:geo, "~> 3.6", [hex: :geo, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "e4e692c515d7d92381e7d0256f1bb7f8fe8760a254b7cd0c37a19c8f4a397904"}, "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, + "glob_ex": {:hex, :glob_ex, "0.1.7", "eae6b6377147fb712ac45b360e6dbba00346689a87f996672fe07e97d70597b1", [:mix], [], "hexpm", "decc1c21c0c73df3c9c994412716345c1692477b9470e337f628a7e08da0da6a"}, "guardian": {:hex, :guardian, "2.3.1", "2b2d78dc399a7df182d739ddc0e566d88723299bfac20be36255e2d052fd215d", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bbe241f9ca1b09fad916ad42d6049d2600bbc688aba5b3c4a6c82592a54274c3"}, "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"}, "hammer": {:hex, :hammer, "6.2.1", "5ae9c33e3dceaeb42de0db46bf505bd9c35f259c8defb03390cd7556fea67ee2", [:mix], [{:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "b9476d0c13883d2dc0cc72e786bac6ac28911fba7cc2e04b70ce6a6d9c4b2bdc"}, "hammer_backend_redis": {:hex, :hammer_backend_redis, "6.1.2", "eb296bb4924928e24135308b2afc189201fd09411c870c6bbadea444a49b2f2c", [:mix], [{:hammer, "~> 6.0", [hex: :hammer, repo: "hexpm", optional: false]}, {:redix, "~> 1.1", [hex: :redix, repo: "hexpm", optional: false]}], "hexpm", "217ea066278910543a5e9b577d5bf2425419446b94fe76bdd9f255f39feec9fa"}, "heroicons": {:hex, :heroicons, "0.5.5", "c2bcb05a90f010df246a5a2a2b54cac15483b5de137b2ef0bead77fcdf06e21a", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:phoenix_live_view, ">= 0.18.2", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "2f4bf929440fecd5191ba9f40e5009b0f75dc993d765c0e4d068fcb7026d6da1"}, - "hpax": {:hex, :hpax, "0.2.0", "5a58219adcb75977b2edce5eb22051de9362f08236220c9e859a47111c194ff5", [:mix], [], "hexpm", "bea06558cdae85bed075e6c036993d43cd54d447f76d8190a8db0dc5893fa2f1"}, + "hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"}, "httpoison": {:hex, :httpoison, "2.2.1", "87b7ed6d95db0389f7df02779644171d7319d319178f6680438167d7b69b1f3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "51364e6d2f429d80e14fe4b5f8e39719cacd03eb3f9a9286e61e216feac2d2df"}, "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"}, + "igniter": {:hex, :igniter, "0.2.6", "472a4b97c779dd9f30d3947e23217a7e20bff840fde83a16ca70ce96ca76a803", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "a234c958b90f152fd4145ebaa6463731e24a9bb83749586ac890e37b9868c1c6"}, + "jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"}, "joken": {:hex, :joken, "2.6.1", "2ca3d8d7f83bf7196296a3d9b2ecda421a404634bfc618159981a960020480a1", [:mix], [{:jose, "~> 1.11.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "ab26122c400b3d254ce7d86ed066d6afad27e70416df947cdcb01e13a7382e68"}, "jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"}, - "json_xema": {:hex, :json_xema, "0.4.2", "85de190f597a98ce9da436b8a59c97ef561a6ab6017255df8b494babefd6fb10", [:mix], [{:conv_case, "~> 0.2", [hex: :conv_case, repo: "hexpm", optional: false]}, {:xema, "~> 0.11", [hex: :xema, repo: "hexpm", optional: false]}], "hexpm", "5516213758667d21669e0d63ea287238d277519527bac6c02140a5e34c1fda80"}, + "json_xema": {:hex, :json_xema, "0.6.2", "2af74c57f3f8dff8b74502d69d53f1ae7b6b236b477f30871c415ea8377a4c72", [:mix], [{:conv_case, "~> 0.2", [hex: :conv_case, repo: "hexpm", optional: false]}, {:xema, "~> 0.16", [hex: :xema, repo: "hexpm", optional: false]}], "hexpm", "50c84c537c95fcc76677f1f030af4aed188f538820fc488aeaa3f7dfe04d0edf"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "logfmt": {:hex, :logfmt, "3.3.3", "6521ee4a5c532088e15d487fab9f736c07bdd161d643560c73cd4b10685deb65", [:mix], [], "hexpm", "dbd51cd3fe37c3429b9bd687bad1f531a533505f4a641592129e7a47e24104d1"}, "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.6.1", "065e8a5bc9bbd46a41099dfea3e0656436c5cbcb6e741c80bd2bad5cd872446f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4fc518dcc191d02f433393a72a7ba3f6f94b101d094cb6bf532ea54c89423780"}, + "mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"}, "mix_test_watch": {:hex, :mix_test_watch, "1.2.0", "1f9acd9e1104f62f280e30fc2243ae5e6d8ddc2f7f4dc9bceb454b9a41c82b42", [:mix], [{:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "278dc955c20b3fb9a3168b5c2493c2e5cffad133548d307e0a50c7f2cfbf34f6"}, "mjml": {:hex, :mjml, "3.0.3", "03e743ca79e770c5fb656c5613ada68f817ca28fd012b75c225b36eda80f2127", [:mix], [{:rustler, ">= 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.7.0", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "c488027d217db4caa2a760a642a6eccef07d6b808d6657e0016b80f360b9c595"}, "mjml_eex": {:hex, :mjml_eex, "0.9.1", "102b6b6e57bfd6db01e0feef801b573fcddb1ee34effb884695da8407544a5be", [:mix], [{:erlexec, "~> 2.0", [hex: :erlexec, repo: "hexpm", optional: true]}, {:mjml, "~> 1.5.0", [hex: :mjml, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "310f9364d4f1126170835db6fb8dad87e393b28860b0e710d870812fb0bd7892"}, - "nebulex": {:hex, :nebulex, "2.6.1", "58c1924fa9f4e844c3470c20e6351b311a556652de29ed3b05fd2e5d817c6fef", [:mix], [{:decorator, "~> 1.4", [hex: :decorator, repo: "hexpm", optional: true]}, {:shards, "~> 1.1", [hex: :shards, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "177949fef2dc34a0055d7140b6bc94f6904225e2b5bbed6266ea9679522d23c6"}, + "nebulex": {:hex, :nebulex, "2.6.2", "0874989db4e382362884662d2ee9f31b4c4862595f4ec300bd279068729dd2d0", [:mix], [{:decorator, "~> 1.4", [hex: :decorator, repo: "hexpm", optional: true]}, {:shards, "~> 1.1", [hex: :shards, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "002a1774d5a187eb631ae4006db13df4bb6b325fe2a3c14cb14a1f3e989042b4"}, "nebulex_redis_adapter": {:hex, :nebulex_redis_adapter, "2.4.0", "bebd7aac9fc92378115131b9d90f094c62e5d72869d97191550fb0591d6c1d8a", [:mix], [{:crc, "~> 0.10", [hex: :crc, repo: "hexpm", optional: true]}, {:jchash, "~> 0.1", [hex: :jchash, repo: "hexpm", optional: true]}, {:nebulex, "~> 2.6", [hex: :nebulex, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:redix, "~> 1.3", [hex: :redix, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "0934142f92b71519fb81324721fab7bc164036c5b91eef7e879a63b98e659814"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, - "oban": {:hex, :oban, "2.17.10", "c3e5bd739b5c3fdc38eba1d43ab270a8c6ca4463bb779b7705c69400b0d87678", [:mix], [{:ecto_sql, "~> 3.10", [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", "4afd027b8e2bc3c399b54318b4f46ee8c40251fb55a285cb4e38b5363f0ee7c4"}, + "oban": {:hex, :oban, "2.17.12", "33fb0cbfb92b910d48dd91a908590fe3698bb85eacec8cd0d9bc6aa13dddd6d6", [:mix], [{:ecto_sql, "~> 3.10", [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", "7a647d6cd6bb300073db17faabce22d80ae135da3baf3180a064fa7c4fa046e3"}, "open_api_spex": {:hex, :open_api_spex, "3.19.1", "65ccb5d06e3d664d1eec7c5ea2af2289bd2f37897094a74d7219fb03fc2b5994", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "392895827ce2984a3459c91a484e70708132d8c2c6c5363972b4b91d6bbac3dd"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, - "phoenix": {:hex, :phoenix, "1.7.12", "1cc589e0eab99f593a8aa38ec45f15d25297dd6187ee801c8de8947090b5a9d3", [: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", "d646192fbade9f485b01bc9920c139bfdd19d0f8df3d73fd8eaf2dfbe0d2837c"}, - "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.1", "96798325fab2fed5a824ca204e877b81f9afd2e480f581e81f7b4b64a5a477f2", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.17", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "0ae544ff99f3c482b0807c5cec2c8289e810ecacabc04959d82c3337f4703391"}, + "phoenix": {:hex, :phoenix, "1.7.14", "a7d0b3f1bc95987044ddada111e77bd7f75646a08518942c72a8440278ae7825", [: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", "c7859bc56cc5dfef19ecfc240775dae358cbaa530231118a9e014df392ace61a"}, + "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.2", "3b83b24ab5a2eb071a20372f740d7118767c272db386831b2e77638c4dcc606d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "3f94d025f59de86be00f5f8c5dd7b5965a3298458d21ab1c328488be3b5fcd59"}, "phoenix_html": {:hex, :phoenix_html, "4.1.1", "4c064fd3873d12ebb1388425a8f2a19348cef56e7289e1998e2d2fa758aa982e", [:mix], [], "hexpm", "f2f2df5a72bc9a2f510b21497fd7d2b86d932ec0598f0210fed4114adc546c6f"}, "phoenix_html_helpers": {:hex, :phoenix_html_helpers, "1.0.1", "7eed85c52eff80a179391036931791ee5d2f713d76a81d0d2c6ebafe1e11e5ec", [:mix], [{:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cffd2385d1fa4f78b04432df69ab8da63dc5cf63e07b713a4dcf36a3740e3090"}, - "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"}, + "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.4", "4508e481f791ce62ec6a096e13b061387158cbeefacca68c6c1928e1305e23ed", [: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", "2984aae96994fbc5c61795a73b8fb58153b41ff934019cfb522343d2d3817d59"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.5.3", "f2161c207fda0e4fb55165f650f7f8db23f02b29e3bff00ff7ef161d6ac1f09d", [:mix], [{:file_system, "~> 0.3 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b4ec9cd73cb01ff1bd1cac92e045d13e7030330b74164297d1aee3907b54803c"}, - "phoenix_live_view": {:hex, :phoenix_live_view, "0.20.14", "70fa101aa0539e81bed4238777498f6215e9dda3461bdaa067cad6908110c364", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {: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", "82f6d006c5264f979ed5eb75593d808bbe39020f20df2e78426f4f2d570e2402"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "0.20.17", "f396bbdaf4ba227b82251eb75ac0afa6b3da5e509bc0d030206374237dfc9450", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {: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", "a61d741ffb78c85fdbca0de084da6a48f8ceb5261a79165b5a0b59e5f65ce98b"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, "phoenix_pubsub_redis": {:hex, :phoenix_pubsub_redis, "3.0.1", "d4d856b1e57a21358e448543e1d091e07e83403dde4383b8be04ed9d2c201cbc", [:mix], [{:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5.1 or ~> 1.6", [hex: :poolboy, repo: "hexpm", optional: false]}, {:redix, "~> 0.10.0 or ~> 1.0", [hex: :redix, repo: "hexpm", optional: false]}], "hexpm", "0b36a17ff6e9a56159f8df8933d62b5c1f0695eae995a02e0c86c035ace6a309"}, "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"}, "phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"}, "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, - "plug": {:hex, :plug, "1.16.0", "1d07d50cb9bb05097fdf187b31cf087c7297aafc3fed8299aac79c128a707e47", [: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", "cbf53aa1f5c4d758a7559c0bd6d59e286c2be0c6a1fac8cc3eee2f638243b93e"}, + "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [: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", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, "plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [: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", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"}, "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, "poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, "postgrex": {:hex, :postgrex, "0.18.0", "f34664101eaca11ff24481ed4c378492fed2ff416cd9b06c399e90f321867d7e", [: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", "a042989ba1bc1cca7383ebb9e461398e3f89f868c92ce6671feb7ef132a252d1"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, - "reactor": {:hex, :reactor, "0.7.0", "fb76d23d95829b28ac9b9d654620c43c890c6a32ea26ac13086c48540b34e8c5", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 1.0", [hex: :spark, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4310da820d753aafd7dc4ee8cc687b84565dd6d9536e38806ee211da792178fd"}, + "reactor": {:hex, :reactor, "0.8.4", "344d02ba4a0010763851f4e4aa0ff190ebe7e392e3c27c6cd143dde077b986e7", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "49c1fd3c786603cec8140ce941c41c7ea72cc4411860ccdee9876c4ca2204f81"}, "recon": {:hex, :recon, "2.5.5", "c108a4c406fa301a529151a3bb53158cadc4064ec0c5f99b03ddb8c0e4281bdf", [:mix, :rebar3], [], "hexpm", "632a6f447df7ccc1a4a10bdcfce71514412b16660fe59deca0fcf0aa3c054404"}, "redix": {:hex, :redix, "1.5.1", "a2386971e69bf23630fb3a215a831b5478d2ee7dc9ea7ac811ed89186ab5d7b7", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:nimble_options, "~> 0.5.0 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "85224eb2b683c516b80d472eb89b76067d5866913bf0be59d646f550de71f5c4"}, "redoc_ui_plug": {:hex, :redoc_ui_plug, "0.2.1", "5e9760c17ed450fc9df671d5fbc70a6f06179c41d9d04ae3c33f16baca3a5b19", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "7be01db31f210887e9fc18f8fbccc7788de32c482b204623556e415ed1fe714b"}, "remote_ip": {:hex, :remote_ip, "1.2.0", "fb078e12a44414f4cef5a75963c33008fe169b806572ccd17257c208a7bc760f", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2ff91de19c48149ce19ed230a81d377186e4412552a597d6a5137373e5877cb7"}, + "req": {:hex, :req, "0.5.1", "90584216d064389a4ff2d4279fe2c11ff6c812ab00fa01a9fb9d15457f65ba70", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "7ea96a1a95388eb0fefa92d89466cdfedba24032794e5c1147d78ec90db7edca"}, "reverse_proxy_plug": {:hex, :reverse_proxy_plug, "2.3.1", "218c62bf3697c2db83cb58b7182fb09aa3d46ddc5e5837e77062104f37373a9b", [:mix], [{:cowboy, "~> 2.4", [hex: :cowboy, repo: "hexpm", optional: false]}, {:httpoison, "~> 1.2 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: false]}, {:tesla, "~> 1.4", [hex: :tesla, repo: "hexpm", optional: true]}], "hexpm", "9d32c410ac7df0f5835143bfb9d87300d0cdb216940b84b02797f3bdce1337e8"}, - "rustler_precompiled": {:hex, :rustler_precompiled, "0.7.1", "ecadf02cc59a0eccbaed6c1937303a5827fbcf60010c541595e6d3747d3d0f9f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "b9e4657b99a1483ea31502e1d58c464bedebe9028808eda45c3a429af4550c66"}, + "rewrite": {:hex, :rewrite, "0.10.5", "6afadeae0b9d843b27ac6225e88e165884875e0aed333ef4ad3bf36f9c101bed", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "51cc347a4269ad3a1e7a2c4122dbac9198302b082f5615964358b4635ebf3d4f"}, + "rustler_precompiled": {:hex, :rustler_precompiled, "0.7.2", "097f657e401f02e7bc1cab808cfc6abdc1f7b9dc5e5adee46bf2fd8fdcce9ecf", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "7663faaeadc9e93e605164dcf9e69168e35f2f8b7f2b9eb4e400d1a8e0fe2999"}, "saxy": {:hex, :saxy, "1.5.0", "0141127f2d042856f135fb2d94e0beecda7a2306f47546dbc6411fc5b07e28bf", [:mix], [], "hexpm", "ea7bb6328fbd1f2aceffa3ec6090bfb18c85aadf0f8e5030905e84235861cf89"}, "scrivener": {:hex, :scrivener, "2.7.2", "1d913c965ec352650a7f864ad7fd8d80462f76a32f33d57d1e48bc5e9d40aba2", [:mix], [], "hexpm", "7866a0ec4d40274efbee1db8bead13a995ea4926ecd8203345af8f90d2b620d9"}, "scrivener_ecto": {:hex, :scrivener_ecto, "2.7.0", "cf64b8cb8a96cd131cdbcecf64e7fd395e21aaa1cb0236c42a7c2e34b0dca580", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:scrivener, "~> 2.4", [hex: :scrivener, repo: "hexpm", optional: false]}], "hexpm", "e809f171687806b0031129034352f5ae44849720c48dd839200adeaf0ac3e260"}, "slugify": {:hex, :slugify, "1.3.1", "0d3b8b7e5c1eeaa960e44dce94382bee34a39b3ea239293e457a9c5b47cc6fd3", [:mix], [], "hexpm", "cb090bbeb056b312da3125e681d98933a360a70d327820e4b7f91645c4d8be76"}, - "sourceror": {:hex, :sourceror, "1.2.1", "b415255ad8bd05f0e859bb3d7ea617f6c2a4a405f2a534a231f229bd99b89f8b", [:mix], [], "hexpm", "e4d97087e67584a7585b5fe3d5a71bf8e7332f795dd1a44983d750003d5e750c"}, - "spark": {:hex, :spark, "1.1.55", "d20c3f899b23d841add29edc912ffab4463d3bb801bc73448738631389291d2e", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "bbc15a4223d8e610c81ceca825d5d0bae3738d1c4ac4dbb1061749966776c3f1"}, + "sourceror": {:hex, :sourceror, "1.4.0", "be87319b1579191e25464005d465713079b3fd7124a3938a1e6cf4def39735a9", [:mix], [], "hexpm", "16751ca55e3895f2228938b703ad399b0b27acfe288eff6c0e629ed3e6ec0358"}, + "spark": {:hex, :spark, "2.2.7", "96113e09a52a2a95fd696e06f310950132aabfacf5c7b34e0666d26ce4a7b7a7", [:mix], [{:igniter, "~> 0.2.6", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "e192add56a260382d4d270e1490401786f96545b86d67b466544cecb48c3f9a4"}, + "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, + "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, - "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, + "stream_data": {:hex, :stream_data, "1.1.1", "fd515ca95619cca83ba08b20f5e814aaf1e5ebff114659dc9731f966c9226246", [:mix], [], "hexpm", "45d0cd46bd06738463fd53f22b70042dbb58c384bb99ef4e7576e7bb7d3b8c8c"}, "sweet_xml": {:hex, :sweet_xml, "0.7.4", "a8b7e1ce7ecd775c7e8a65d501bc2cd933bff3a9c41ab763f5105688ef485d08", [:mix], [], "hexpm", "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"}, "swoosh": {:hex, :swoosh, "1.16.9", "20c6a32ea49136a4c19f538e27739bb5070558c0fa76b8a95f4d5d5ca7d319a1", [: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]}, {:mua, "~> 0.2.0", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "878b1a7a6c10ebbf725a3349363f48f79c5e3d792eb621643b0d276a38acc0a6"}, "syn": {:hex, :syn, "3.3.0", "4684a909efdfea35ce75a9662fc523e4a8a4e8169a3df275e4de4fa63f99c486", [:rebar3], [], "hexpm", "e58ee447bc1094bdd21bf0acc102b1fbf99541a508cd48060bf783c245eaf7d6"}, diff --git a/server/priv/repo/migrations/20240701205703_install_ash-functions_extension_3.exs b/server/priv/repo/migrations/20240701205703_install_ash-functions_extension_3.exs new file mode 100644 index 00000000..e035e7c6 --- /dev/null +++ b/server/priv/repo/migrations/20240701205703_install_ash-functions_extension_3.exs @@ -0,0 +1,102 @@ +defmodule Orcasite.Repo.Migrations.InstallAshFunctionsExtension320240701205702 do + @moduledoc """ + Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback + + This file was autogenerated with `mix ash_postgres.generate_migrations` + """ + + use Ecto.Migration + + def up do + execute(""" + CREATE OR REPLACE FUNCTION ash_elixir_or(left BOOLEAN, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) + AS $$ SELECT COALESCE(NULLIF($1, FALSE), $2) $$ + LANGUAGE SQL + IMMUTABLE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_elixir_or(left ANYCOMPATIBLE, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) + AS $$ SELECT COALESCE($1, $2) $$ + LANGUAGE SQL + IMMUTABLE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_elixir_and(left BOOLEAN, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) AS $$ + SELECT CASE + WHEN $1 IS TRUE THEN $2 + ELSE $1 + END $$ + LANGUAGE SQL + IMMUTABLE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_elixir_and(left ANYCOMPATIBLE, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) AS $$ + SELECT CASE + WHEN $1 IS NOT NULL THEN $2 + ELSE $1 + END $$ + LANGUAGE SQL + IMMUTABLE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_trim_whitespace(arr text[]) + RETURNS text[] AS $$ + DECLARE + start_index INT = 1; + end_index INT = array_length(arr, 1); + BEGIN + WHILE start_index <= end_index AND arr[start_index] = '' LOOP + start_index := start_index + 1; + END LOOP; + + WHILE end_index >= start_index AND arr[end_index] = '' LOOP + end_index := end_index - 1; + END LOOP; + + IF start_index > end_index THEN + RETURN ARRAY[]::text[]; + ELSE + RETURN arr[start_index : end_index]; + END IF; + END; $$ + LANGUAGE plpgsql + IMMUTABLE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_raise_error(json_data jsonb) + RETURNS BOOLEAN AS $$ + BEGIN + -- Raise an error with the provided JSON data. + -- The JSON object is converted to text for inclusion in the error message. + RAISE EXCEPTION 'ash_error: %', json_data::text; + RETURN NULL; + END; + $$ LANGUAGE plpgsql; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_raise_error(json_data jsonb, type_signal ANYCOMPATIBLE) + RETURNS ANYCOMPATIBLE AS $$ + BEGIN + -- Raise an error with the provided JSON data. + -- The JSON object is converted to text for inclusion in the error message. + RAISE EXCEPTION 'ash_error: %', json_data::text; + RETURN NULL; + END; + $$ LANGUAGE plpgsql; + """) + end + + def down do + # Uncomment this if you actually want to uninstall the extensions + # when this migration is rolled back: + execute( + "DROP FUNCTION IF EXISTS ash_raise_error(jsonb), ash_raise_error(jsonb, ANYCOMPATIBLE), ash_elixir_and(BOOLEAN, ANYCOMPATIBLE), ash_elixir_and(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(BOOLEAN, ANYCOMPATIBLE), ash_trim_whitespace(text[])" + ) + end +end diff --git a/server/priv/repo/migrations/20240701205705_update_defaults.exs b/server/priv/repo/migrations/20240701205705_update_defaults.exs new file mode 100644 index 00000000..f74ab2d1 --- /dev/null +++ b/server/priv/repo/migrations/20240701205705_update_defaults.exs @@ -0,0 +1,153 @@ +defmodule Orcasite.Repo.Migrations.UpdateDefaults do + @moduledoc """ + Updates resources based on their most recent snapshots. + + This file was autogenerated with `mix ash_postgres.generate_migrations` + """ + + use Ecto.Migration + + def up do + alter table(:users) do + modify :updated_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + modify :inserted_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + modify :id, :uuid, default: fragment("gen_random_uuid()") + end + + alter table(:tokens) do + modify :created_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + modify :updated_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + end + + alter table(:subscriptions) do + modify :updated_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + modify :inserted_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + modify :id, :uuid, default: fragment("gen_random_uuid()") + end + + alter table(:subscribers) do + modify :updated_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + modify :inserted_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + modify :id, :uuid, default: fragment("gen_random_uuid()") + end + + alter table(:subscriber_tokens) do + modify :created_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + modify :updated_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + end + + alter table(:notifications) do + modify :updated_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + modify :inserted_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + modify :id, :uuid, default: fragment("gen_random_uuid()") + end + + alter table(:feeds) do + modify :updated_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + modify :inserted_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + end + + alter table(:feed_streams) do + modify :updated_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + modify :inserted_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + end + + alter table(:feed_segments) do + modify :updated_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + modify :inserted_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + end + + alter table(:detections) do + modify :updated_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + modify :inserted_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + end + + alter table(:candidates) do + modify :updated_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + modify :inserted_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + end + + alter table(:bouts) do + modify :updated_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + modify :inserted_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + end + + alter table(:bout_feed_streams) do + modify :updated_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + modify :inserted_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')") + modify :id, :uuid, default: fragment("gen_random_uuid()") + end + end + + def down do + alter table(:bout_feed_streams) do + modify :id, :uuid, default: nil + modify :inserted_at, :utc_datetime_usec, default: fragment("now()") + modify :updated_at, :utc_datetime_usec, default: fragment("now()") + end + + alter table(:bouts) do + modify :inserted_at, :utc_datetime_usec, default: fragment("now()") + modify :updated_at, :utc_datetime_usec, default: fragment("now()") + end + + alter table(:candidates) do + modify :inserted_at, :utc_datetime_usec, default: fragment("now()") + modify :updated_at, :utc_datetime_usec, default: fragment("now()") + end + + alter table(:detections) do + modify :inserted_at, :utc_datetime_usec, default: fragment("now()") + modify :updated_at, :utc_datetime_usec, default: fragment("now()") + end + + alter table(:feed_segments) do + modify :inserted_at, :utc_datetime_usec, default: fragment("now()") + modify :updated_at, :utc_datetime_usec, default: fragment("now()") + end + + alter table(:feed_streams) do + modify :inserted_at, :utc_datetime_usec, default: fragment("now()") + modify :updated_at, :utc_datetime_usec, default: fragment("now()") + end + + alter table(:feeds) do + modify :inserted_at, :utc_datetime_usec, default: fragment("now()") + modify :updated_at, :utc_datetime_usec, default: fragment("now()") + end + + alter table(:notifications) do + modify :id, :uuid, default: nil + modify :inserted_at, :utc_datetime_usec, default: fragment("now()") + modify :updated_at, :utc_datetime_usec, default: fragment("now()") + end + + alter table(:subscriber_tokens) do + modify :updated_at, :utc_datetime_usec, default: fragment("now()") + modify :created_at, :utc_datetime_usec, default: fragment("now()") + end + + alter table(:subscribers) do + modify :id, :uuid, default: nil + modify :inserted_at, :utc_datetime_usec, default: fragment("now()") + modify :updated_at, :utc_datetime_usec, default: fragment("now()") + end + + alter table(:subscriptions) do + modify :id, :uuid, default: nil + modify :inserted_at, :utc_datetime_usec, default: fragment("now()") + modify :updated_at, :utc_datetime_usec, default: fragment("now()") + end + + alter table(:tokens) do + modify :updated_at, :utc_datetime_usec, default: fragment("now()") + modify :created_at, :utc_datetime_usec, default: fragment("now()") + end + + alter table(:users) do + modify :id, :uuid, default: nil + modify :inserted_at, :utc_datetime_usec, default: fragment("now()") + modify :updated_at, :utc_datetime_usec, default: fragment("now()") + end + end +end diff --git a/server/priv/repo/migrations/20240725191221_add_inserted_at_index_to_feed_segments.exs b/server/priv/repo/migrations/20240725191221_add_inserted_at_index_to_feed_segments.exs new file mode 100644 index 00000000..b7821229 --- /dev/null +++ b/server/priv/repo/migrations/20240725191221_add_inserted_at_index_to_feed_segments.exs @@ -0,0 +1,17 @@ +defmodule Orcasite.Repo.Migrations.AddInsertedAtIndexToFeedSegments do + @moduledoc """ + Updates resources based on their most recent snapshots. + + This file was autogenerated with `mix ash_postgres.generate_migrations` + """ + + use Ecto.Migration + + def up do + create index(:feed_segments, [:inserted_at]) + end + + def down do + drop_if_exists index(:feed_segments, [:inserted_at]) + end +end diff --git a/server/priv/repo/migrations/20240725192709_add_dataplicity_id_to_feeds.exs b/server/priv/repo/migrations/20240725192709_add_dataplicity_id_to_feeds.exs new file mode 100644 index 00000000..67a12859 --- /dev/null +++ b/server/priv/repo/migrations/20240725192709_add_dataplicity_id_to_feeds.exs @@ -0,0 +1,25 @@ +defmodule Orcasite.Repo.Migrations.AddDataplicityIdToFeeds do + @moduledoc """ + Updates resources based on their most recent snapshots. + + This file was autogenerated with `mix ash_postgres.generate_migrations` + """ + + use Ecto.Migration + + def up do + alter table(:feeds) do + add :dataplicity_id, :text + end + + create index(:feeds, [:dataplicity_id]) + end + + def down do + drop_if_exists index(:feeds, [:dataplicity_id]) + + alter table(:feeds) do + remove :dataplicity_id + end + end +end diff --git a/server/priv/repo/scripts/20230824_populate_feeds_candidates_detections.exs b/server/priv/repo/scripts/20230824_populate_feeds_candidates_detections.exs index 9af1c558..20824169 100644 --- a/server/priv/repo/scripts/20230824_populate_feeds_candidates_detections.exs +++ b/server/priv/repo/scripts/20230824_populate_feeds_candidates_detections.exs @@ -10,7 +10,7 @@ feed_map = Orcasite.Radio.Feed |> Ash.Query.for_read(:read) |> Ash.Query.filter(slug == ^feed.slug) - |> Orcasite.Radio.read() + |> Ash.read() |> case do {:ok, [%Orcasite.Radio.Feed{} = new_feed | _]} -> {feed.id, new_feed.id} @@ -51,7 +51,7 @@ from(cand in Orcasite.RadioLegacy.Candidate, min_time == ^candidate.min_time and max_time == ^candidate.max_time and feed_id == ^feed_id ) - |> Orcasite.Radio.read() + |> Ash.read() |> case do {:ok, [%Orcasite.Radio.Candidate{} | _]} -> nil @@ -86,7 +86,7 @@ from(det in Orcasite.RadioLegacy.Detection, order_by: [asc: det.inserted_at]) playlist_timestamp == ^detection.playlist_timestamp and player_offset == ^detection.player_offset ) - |> Orcasite.Radio.read() + |> Ash.read() |> case do {:ok, [%Orcasite.Radio.Detection{} | _]} -> nil @@ -99,7 +99,7 @@ from(det in Orcasite.RadioLegacy.Detection, order_by: [asc: det.inserted_at]) feed_id == ^feed_id and min_time <= ^detection.timestamp and max_time >= ^detection.timestamp ) - |> Orcasite.Radio.read!() do + |> Ash.read!() do Orcasite.Radio.Detection |> Ash.Changeset.for_create( :create, diff --git a/server/priv/repo/scripts/20230825_populate_intro_html.exs b/server/priv/repo/scripts/20230825_populate_intro_html.exs index 487089b5..f26102b5 100644 --- a/server/priv/repo/scripts/20230825_populate_intro_html.exs +++ b/server/priv/repo/scripts/20230825_populate_intro_html.exs @@ -1,12 +1,11 @@ require Ash.Query -alias Orcasite.Radio alias Orcasite.Radio.Feed Feed |> Ash.Query.for_read(:read) |> Ash.Query.filter(intro_html == "") -|> Radio.read!() +|> Ash.read!() |> Enum.map(fn feed -> node_name = feed.node_name s3_url = Application.get_env(:orcasite, :orcasite_s3_url) @@ -28,5 +27,5 @@ Feed |> Ash.Changeset.for_update(:update, %{ intro_html: intro_html }) - |> Radio.update() + |> Ash.update() end) diff --git a/server/priv/repo/scripts/20230901_populate_detection_category.exs b/server/priv/repo/scripts/20230901_populate_detection_category.exs index 5980cf14..374e054e 100644 --- a/server/priv/repo/scripts/20230901_populate_detection_category.exs +++ b/server/priv/repo/scripts/20230901_populate_detection_category.exs @@ -1,6 +1,6 @@ Orcasite.Radio.Detection |> Ash.Query.for_read(:read) -|> Orcasite.Radio.read!() +|> Ash.read!() |> Enum.map(fn detection -> category = case Regex.run(~r/^\[(orca|vessel|other)\]/, detection.description || "") do [_full, category | _rest ] -> category @@ -11,5 +11,5 @@ Orcasite.Radio.Detection |> Ash.Changeset.for_update(:update, %{ category: category }) - |> Orcasite.Radio.update!() + |> Ash.update!() end) diff --git a/server/priv/repo/scripts/20230906_populate_feeds_image_url.exs b/server/priv/repo/scripts/20230906_populate_feeds_image_url.exs index 45813ced..ee67f301 100644 --- a/server/priv/repo/scripts/20230906_populate_feeds_image_url.exs +++ b/server/priv/repo/scripts/20230906_populate_feeds_image_url.exs @@ -1,11 +1,11 @@ Orcasite.Radio.Feed |> Ash.Query.for_read(:read) |> Ash.Query.load(:thumb_url) -|> Orcasite.Radio.read!() +|> Ash.read!() |> Enum.map(fn feed -> feed |> Ash.Changeset.for_update(:update, %{ image_url: feed.thumb_url }) - |> Orcasite.Radio.update!() + |> Ash.update!() end) diff --git a/server/priv/repo/seeds.exs b/server/priv/repo/seeds.exs index c2488338..60154d84 100644 --- a/server/priv/repo/seeds.exs +++ b/server/priv/repo/seeds.exs @@ -27,7 +27,7 @@ feeds = [ node_name: "rpi_mast_center", slug: "mast-center", bucket: "dev-streaming-orcasound-net", - bucket_region: "us-west-2", + bucket_region: "us-west-2" }, # %{ # lat_lng_string: "48.0336664, -122.6040035", @@ -51,7 +51,7 @@ feeds = [ node_name: "rpi_sunset_bay", slug: "sunset-bay", bucket: "dev-streaming-orcasound-net", - bucket_region: "us-west-2", + bucket_region: "us-west-2" }, %{ lat_lng_string: "48.591294, -123.058779", @@ -59,7 +59,7 @@ feeds = [ node_name: "rpi_north_sjc", slug: "north-sjc", bucket: "dev-streaming-orcasound-net", - bucket_region: "us-west-2", + bucket_region: "us-west-2" } ] @@ -68,12 +68,12 @@ feeds = Orcasite.Radio.Feed |> Ash.Query.for_read(:read) |> Ash.Query.filter(slug == ^attrs.slug) - |> Orcasite.Radio.read() + |> Ash.read() |> case do {:ok, []} -> Orcasite.Radio.Feed |> Ash.Changeset.for_create(:create, attrs) - |> Orcasite.Radio.create!(verbose?: true, authorize?: false) + |> Ash.create!(authorize?: false) {:ok, [feed | _]} -> feed @@ -91,7 +91,7 @@ Orcasite.Accounts.User }) |> Ash.Changeset.force_change_attribute(:admin, true) |> Ash.Changeset.force_change_attribute(:moderator, true) -|> Orcasite.Accounts.create(authorize?: false) +|> Ash.create(authorize?: false) [ %{ @@ -297,12 +297,19 @@ Orcasite.Accounts.User } ] |> Enum.map(fn attrs -> - feed_id = feeds |> Enum.find(fn feed -> feed.slug == attrs[:slug] end) |> Map.get(:id) + feeds + |> Enum.find(fn feed -> feed.slug == attrs[:slug] end) + |> case do + %{id: feed_id} -> + detection_attrs = Map.drop(attrs, [:slug]) + Orcasite.Radio.Detection + |> Ash.Changeset.for_create( + :submit_detection, + Map.merge(detection_attrs, %{feed_id: feed_id, send_notifications: false}) + ) + |> Ash.create!(authorize?: false) - Orcasite.Radio.Detection - |> Ash.Changeset.for_create( - :submit_detection, - Map.merge(attrs, %{feed_id: feed_id, send_notifications: false}) - ) - |> Orcasite.Radio.create!(verbose?: true, authorize?: false) + _ -> + :ok + end end) diff --git a/server/priv/resource_snapshots/repo/bout_feed_streams/20240701205705.json b/server/priv/resource_snapshots/repo/bout_feed_streams/20240701205705.json new file mode 100644 index 00000000..61a06fe9 --- /dev/null +++ b/server/priv/resource_snapshots/repo/bout_feed_streams/20240701205705.json @@ -0,0 +1,152 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "bout_id", + "references": { + "name": "bout_feed_streams_bout_id_fkey", + "table": "bouts", + "schema": null, + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "index?": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "feed_stream_id", + "references": { + "name": "bout_feed_streams_feed_stream_id_fkey", + "table": "feed_streams", + "schema": null, + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "index?": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + } + ], + "table": "bout_feed_streams", + "hash": "3A536C90ED99314C258C383335B84CC85654974A1ED6FFCBCBEC2F399A8FB420", + "repo": "Elixir.Orcasite.Repo", + "identities": [], + "schema": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_indexes": [ + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "bout_id" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "bout_id" + ], + "nulls_distinct": true, + "using": null + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "feed_stream_id" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "feed_stream_id" + ], + "nulls_distinct": true, + "using": null + } + ], + "base_filter": null, + "check_constraints": [], + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/server/priv/resource_snapshots/repo/bouts/20240701205705.json b/server/priv/resource_snapshots/repo/bouts/20240701205705.json new file mode 100644 index 00000000..c0a3d111 --- /dev/null +++ b/server/priv/resource_snapshots/repo/bouts/20240701205705.json @@ -0,0 +1,202 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v7()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "utc_datetime", + "source": "start_time", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "utc_datetime", + "source": "end_time", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "decimal", + "source": "duration", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "boolean", + "source": "ongoing", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "category", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "feed_id", + "references": { + "name": "bouts_feed_id_fkey", + "table": "feeds", + "schema": null, + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "index?": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "created_by_user_id", + "references": { + "name": "bouts_created_by_user_id_fkey", + "table": "users", + "schema": null, + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "index?": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + } + ], + "table": "bouts", + "hash": "4F62AEB2F4F1C8442B53FACF08A552BF842E16448E90CC9658FB2732ED708F57", + "repo": "Elixir.Orcasite.Repo", + "identities": [], + "schema": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_indexes": [ + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "feed_id" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "feed_id" + ], + "nulls_distinct": true, + "using": null + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "created_by_user_id" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "created_by_user_id" + ], + "nulls_distinct": true, + "using": null + } + ], + "base_filter": null, + "check_constraints": [], + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/server/priv/resource_snapshots/repo/candidates/20240701205705.json b/server/priv/resource_snapshots/repo/candidates/20240701205705.json new file mode 100644 index 00000000..bf82dd73 --- /dev/null +++ b/server/priv/resource_snapshots/repo/candidates/20240701205705.json @@ -0,0 +1,217 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v7()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "detection_count", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "utc_datetime_usec", + "source": "min_time", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "utc_datetime_usec", + "source": "max_time", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "true", + "size": null, + "type": "boolean", + "source": "visible", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "category", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "feed_id", + "references": { + "name": "candidates_feed_id_fkey", + "table": "feeds", + "schema": null, + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "index?": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + } + ], + "table": "candidates", + "hash": "CE8DDB12B8692ABE63E042B81737CF959E2D4D48F0DBCC72EDC2F73026BDDD66", + "repo": "Elixir.Orcasite.Repo", + "identities": [], + "schema": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_indexes": [ + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "min_time" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "min_time" + ], + "nulls_distinct": true, + "using": null + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "max_time" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "max_time" + ], + "nulls_distinct": true, + "using": null + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "inserted_at" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "inserted_at" + ], + "nulls_distinct": true, + "using": null + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "category" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "category" + ], + "nulls_distinct": true, + "using": null + } + ], + "base_filter": null, + "check_constraints": [], + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/server/priv/resource_snapshots/repo/detections/20240701205705.json b/server/priv/resource_snapshots/repo/detections/20240701205705.json new file mode 100644 index 00000000..004710fd --- /dev/null +++ b/server/priv/resource_snapshots/repo/detections/20240701205705.json @@ -0,0 +1,349 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v7()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "source_ip", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "playlist_timestamp", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "decimal", + "source": "player_offset", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "listener_count", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "utc_datetime_usec", + "source": "timestamp", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "description", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "true", + "size": null, + "type": "boolean", + "source": "visible", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "category", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "candidate_id", + "references": { + "name": "detections_candidate_id_fkey", + "table": "candidates", + "schema": null, + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "index?": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "feed_id", + "references": { + "name": "detections_feed_id_fkey", + "table": "feeds", + "schema": null, + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "index?": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "user_id", + "references": { + "name": "detections_user_id_fkey", + "table": "users", + "schema": null, + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "index?": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + } + ], + "table": "detections", + "hash": "81C883EBE303353FEBD2D6EA14C07FB475E3293DE2A70BBEFF177B9C49EBFC75", + "repo": "Elixir.Orcasite.Repo", + "identities": [], + "schema": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_indexes": [ + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "playlist_timestamp" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "playlist_timestamp" + ], + "nulls_distinct": true, + "using": null + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "player_offset" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "player_offset" + ], + "nulls_distinct": true, + "using": null + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "timestamp" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "timestamp" + ], + "nulls_distinct": true, + "using": null + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "description" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "description" + ], + "nulls_distinct": true, + "using": null + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "inserted_at" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "inserted_at" + ], + "nulls_distinct": true, + "using": null + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "category" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "category" + ], + "nulls_distinct": true, + "using": null + } + ], + "base_filter": null, + "check_constraints": [], + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/server/priv/resource_snapshots/repo/extensions.json b/server/priv/resource_snapshots/repo/extensions.json index 934c96b4..16fcff6f 100644 --- a/server/priv/resource_snapshots/repo/extensions.json +++ b/server/priv/resource_snapshots/repo/extensions.json @@ -4,6 +4,8 @@ "uuid-ossp", "postgis", "ash-uuid_v1", - "pg_stat_statements" - ] + "pg_stat_statements", + "ash-functions" + ], + "ash_functions_version": 3 } \ No newline at end of file diff --git a/server/priv/resource_snapshots/repo/feed_segments/20240701205705.json b/server/priv/resource_snapshots/repo/feed_segments/20240701205705.json new file mode 100644 index 00000000..01c7f1a1 --- /dev/null +++ b/server/priv/resource_snapshots/repo/feed_segments/20240701205705.json @@ -0,0 +1,361 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v7()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "utc_datetime", + "source": "start_time", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "utc_datetime", + "source": "end_time", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "decimal", + "source": "duration", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "bucket", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "bucket_region", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "cloudfront_url", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "playlist_timestamp", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "playlist_path", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "playlist_m3u8_path", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "segment_path", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "file_name", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "feed_id", + "references": { + "name": "feed_segments_feed_id_fkey", + "table": "feeds", + "schema": null, + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "index?": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "feed_stream_id", + "references": { + "name": "feed_segments_feed_stream_id_fkey", + "table": "feed_streams", + "schema": null, + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "index?": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + } + ], + "table": "feed_segments", + "hash": "180459EAB236FD15DE5C439755D8BA32F12C79F94A1D1C03BF9864FB1EC579D5", + "repo": "Elixir.Orcasite.Repo", + "identities": [ + { + "name": "feed_segment_path", + "keys": [ + { + "type": "atom", + "value": "segment_path" + } + ], + "where": null, + "nils_distinct?": true, + "all_tenants?": false, + "index_name": "feed_segments_feed_segment_path_index", + "base_filter": null + }, + { + "name": "feed_segment_timestamp", + "keys": [ + { + "type": "atom", + "value": "feed_id" + }, + { + "type": "atom", + "value": "start_time" + } + ], + "where": null, + "nils_distinct?": true, + "all_tenants?": false, + "index_name": "feed_segments_feed_segment_timestamp_index", + "base_filter": null + } + ], + "schema": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_indexes": [ + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "start_time" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "start_time" + ], + "nulls_distinct": true, + "using": null + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "end_time" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "end_time" + ], + "nulls_distinct": true, + "using": null + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "feed_id" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "feed_id" + ], + "nulls_distinct": true, + "using": null + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "feed_stream_id" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "feed_stream_id" + ], + "nulls_distinct": true, + "using": null + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "bucket" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "bucket" + ], + "nulls_distinct": true, + "using": null + } + ], + "base_filter": null, + "check_constraints": [], + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/server/priv/resource_snapshots/repo/feed_segments/20240725191221.json b/server/priv/resource_snapshots/repo/feed_segments/20240725191221.json new file mode 100644 index 00000000..7bea71a7 --- /dev/null +++ b/server/priv/resource_snapshots/repo/feed_segments/20240725191221.json @@ -0,0 +1,383 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v7()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "primary_key?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "utc_datetime", + "source": "start_time", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "utc_datetime", + "source": "end_time", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "decimal", + "source": "duration", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "bucket", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "bucket_region", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "cloudfront_url", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "playlist_timestamp", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "playlist_path", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "playlist_m3u8_path", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "segment_path", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "file_name", + "references": null, + "allow_nil?": false, + "primary_key?": false, + "generated?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "allow_nil?": false, + "primary_key?": false, + "generated?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "feed_id", + "references": { + "name": "feed_segments_feed_id_fkey", + "table": "feeds", + "primary_key?": true, + "schema": null, + "destination_attribute": "id", + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "on_delete": null, + "on_update": null, + "deferrable": false, + "match_with": null, + "match_type": null, + "index?": false, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "feed_stream_id", + "references": { + "name": "feed_segments_feed_stream_id_fkey", + "table": "feed_streams", + "primary_key?": true, + "schema": null, + "destination_attribute": "id", + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "on_delete": null, + "on_update": null, + "deferrable": false, + "match_with": null, + "match_type": null, + "index?": false, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + } + ], + "table": "feed_segments", + "hash": "982739E3EA32BAC99918F1BF32FF8221939BA9A923B5E5AA5238E991B2EB9E4C", + "repo": "Elixir.Orcasite.Repo", + "identities": [ + { + "name": "feed_segment_path", + "keys": [ + { + "type": "atom", + "value": "segment_path" + } + ], + "where": null, + "base_filter": null, + "nils_distinct?": true, + "all_tenants?": false, + "index_name": "feed_segments_feed_segment_path_index" + }, + { + "name": "feed_segment_timestamp", + "keys": [ + { + "type": "atom", + "value": "feed_id" + }, + { + "type": "atom", + "value": "start_time" + } + ], + "where": null, + "base_filter": null, + "nils_distinct?": true, + "all_tenants?": false, + "index_name": "feed_segments_feed_segment_timestamp_index" + } + ], + "custom_indexes": [ + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "start_time" + } + ], + "unique": false, + "all_tenants?": false, + "error_fields": [ + "start_time" + ], + "concurrently": false, + "using": null, + "nulls_distinct": true + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "end_time" + } + ], + "unique": false, + "all_tenants?": false, + "error_fields": [ + "end_time" + ], + "concurrently": false, + "using": null, + "nulls_distinct": true + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "feed_id" + } + ], + "unique": false, + "all_tenants?": false, + "error_fields": [ + "feed_id" + ], + "concurrently": false, + "using": null, + "nulls_distinct": true + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "feed_stream_id" + } + ], + "unique": false, + "all_tenants?": false, + "error_fields": [ + "feed_stream_id" + ], + "concurrently": false, + "using": null, + "nulls_distinct": true + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "bucket" + } + ], + "unique": false, + "all_tenants?": false, + "error_fields": [ + "bucket" + ], + "concurrently": false, + "using": null, + "nulls_distinct": true + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "inserted_at" + } + ], + "unique": false, + "all_tenants?": false, + "error_fields": [ + "inserted_at" + ], + "concurrently": false, + "using": null, + "nulls_distinct": true + } + ], + "schema": null, + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "base_filter": null, + "check_constraints": [], + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/server/priv/resource_snapshots/repo/feed_streams/20240701205705.json b/server/priv/resource_snapshots/repo/feed_streams/20240701205705.json new file mode 100644 index 00000000..3225c7e5 --- /dev/null +++ b/server/priv/resource_snapshots/repo/feed_streams/20240701205705.json @@ -0,0 +1,392 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v7()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "utc_datetime", + "source": "start_time", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "utc_datetime", + "source": "end_time", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "decimal", + "source": "duration", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "bucket", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "bucket_region", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "cloudfront_url", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "playlist_timestamp", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "playlist_path", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "playlist_m3u8_path", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "feed_id", + "references": { + "name": "feed_streams_feed_id_fkey", + "table": "feeds", + "schema": null, + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "index?": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "prev_feed_stream_id", + "references": { + "name": "feed_streams_prev_feed_stream_id_fkey", + "table": "feed_streams", + "schema": null, + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "index?": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "next_feed_stream_id", + "references": { + "name": "feed_streams_next_feed_stream_id_fkey", + "table": "feed_streams", + "schema": null, + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "index?": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + } + ], + "table": "feed_streams", + "hash": "0F1428A9265D9662B50F991718EC05C2218F284B02BF1ACE4753FB821CBEE9CF", + "repo": "Elixir.Orcasite.Repo", + "identities": [ + { + "name": "feed_stream_timestamp", + "keys": [ + { + "type": "atom", + "value": "feed_id" + }, + { + "type": "atom", + "value": "start_time" + } + ], + "where": null, + "nils_distinct?": true, + "all_tenants?": false, + "index_name": "feed_streams_feed_stream_timestamp_index", + "base_filter": null + }, + { + "name": "playlist_m3u8_path", + "keys": [ + { + "type": "atom", + "value": "playlist_m3u8_path" + } + ], + "where": null, + "nils_distinct?": true, + "all_tenants?": false, + "index_name": "feed_streams_playlist_m3u8_path_index", + "base_filter": null + } + ], + "schema": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_indexes": [ + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "start_time" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "start_time" + ], + "nulls_distinct": true, + "using": null + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "end_time" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "end_time" + ], + "nulls_distinct": true, + "using": null + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "feed_id" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "feed_id" + ], + "nulls_distinct": true, + "using": null + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "prev_feed_stream_id" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "prev_feed_stream_id" + ], + "nulls_distinct": true, + "using": null + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "next_feed_stream_id" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "next_feed_stream_id" + ], + "nulls_distinct": true, + "using": null + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "bucket" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "bucket" + ], + "nulls_distinct": true, + "using": null + } + ], + "base_filter": null, + "check_constraints": [], + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/server/priv/resource_snapshots/repo/feeds/20240701205705.json b/server/priv/resource_snapshots/repo/feeds/20240701205705.json new file mode 100644 index 00000000..4b768d2e --- /dev/null +++ b/server/priv/resource_snapshots/repo/feeds/20240701205705.json @@ -0,0 +1,253 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v7()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "name", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "node_name", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "slug", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "geometry", + "source": "location_point", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "\"\"", + "size": null, + "type": "text", + "source": "intro_html", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "\"\"", + "size": null, + "type": "text", + "source": "image_url", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "true", + "size": null, + "type": "boolean", + "source": "visible", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "bucket", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "bucket_region", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "cloudfront_url", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + } + ], + "table": "feeds", + "hash": "3CC42BF2F8DE806926059D1DA78F2777F93BD6A853698EC1039A9662EF2499B9", + "repo": "Elixir.Orcasite.Repo", + "identities": [ + { + "name": "unique_slug", + "keys": [ + { + "type": "atom", + "value": "slug" + } + ], + "where": null, + "nils_distinct?": true, + "all_tenants?": false, + "index_name": "feeds_unique_slug_index", + "base_filter": null + } + ], + "schema": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_indexes": [ + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "name" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "name" + ], + "nulls_distinct": true, + "using": null + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "node_name" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "node_name" + ], + "nulls_distinct": true, + "using": null + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "visible" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "visible" + ], + "nulls_distinct": true, + "using": null + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "slug" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "slug" + ], + "nulls_distinct": true, + "using": null + } + ], + "base_filter": null, + "check_constraints": [], + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/server/priv/resource_snapshots/repo/feeds/20240725192709.json b/server/priv/resource_snapshots/repo/feeds/20240725192709.json new file mode 100644 index 00000000..4ae7104d --- /dev/null +++ b/server/priv/resource_snapshots/repo/feeds/20240725192709.json @@ -0,0 +1,285 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v7()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "primary_key?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "name", + "references": null, + "allow_nil?": false, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "node_name", + "references": null, + "allow_nil?": false, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "slug", + "references": null, + "allow_nil?": false, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "geometry", + "source": "location_point", + "references": null, + "allow_nil?": false, + "primary_key?": false, + "generated?": false + }, + { + "default": "\"\"", + "size": null, + "type": "text", + "source": "intro_html", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "\"\"", + "size": null, + "type": "text", + "source": "image_url", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "true", + "size": null, + "type": "boolean", + "source": "visible", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "bucket", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "bucket_region", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "cloudfront_url", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "dataplicity_id", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "allow_nil?": false, + "primary_key?": false, + "generated?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "primary_key?": false, + "generated?": false + } + ], + "table": "feeds", + "hash": "99973C5AE25C93EA36B8CDCC884A4A925D9E05BDF3D1659589D36BFBDD9CFF8C", + "repo": "Elixir.Orcasite.Repo", + "identities": [ + { + "name": "unique_slug", + "keys": [ + { + "type": "atom", + "value": "slug" + } + ], + "where": null, + "base_filter": null, + "nils_distinct?": true, + "all_tenants?": false, + "index_name": "feeds_unique_slug_index" + } + ], + "custom_indexes": [ + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "fields": [ + { + "type": "atom", + "value": "name" + } + ], + "where": null, + "unique": false, + "all_tenants?": false, + "error_fields": [ + "name" + ], + "concurrently": false, + "using": null, + "nulls_distinct": true + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "fields": [ + { + "type": "atom", + "value": "node_name" + } + ], + "where": null, + "unique": false, + "all_tenants?": false, + "error_fields": [ + "node_name" + ], + "concurrently": false, + "using": null, + "nulls_distinct": true + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "fields": [ + { + "type": "atom", + "value": "visible" + } + ], + "where": null, + "unique": false, + "all_tenants?": false, + "error_fields": [ + "visible" + ], + "concurrently": false, + "using": null, + "nulls_distinct": true + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "fields": [ + { + "type": "atom", + "value": "slug" + } + ], + "where": null, + "unique": false, + "all_tenants?": false, + "error_fields": [ + "slug" + ], + "concurrently": false, + "using": null, + "nulls_distinct": true + }, + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "fields": [ + { + "type": "atom", + "value": "dataplicity_id" + } + ], + "where": null, + "unique": false, + "all_tenants?": false, + "error_fields": [ + "dataplicity_id" + ], + "concurrently": false, + "using": null, + "nulls_distinct": true + } + ], + "schema": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "base_filter": null, + "check_constraints": [], + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/server/priv/resource_snapshots/repo/notifications/20240701205705.json b/server/priv/resource_snapshots/repo/notifications/20240701205705.json new file mode 100644 index 00000000..138cc19a --- /dev/null +++ b/server/priv/resource_snapshots/repo/notifications/20240701205705.json @@ -0,0 +1,132 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "%{}", + "size": null, + "type": "map", + "source": "meta", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "true", + "size": null, + "type": "boolean", + "source": "active", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "event_type", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "target_count", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "0", + "size": null, + "type": "bigint", + "source": "notified_count", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "utc_datetime", + "source": "notified_count_updated_at", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + } + ], + "table": "notifications", + "hash": "6C167642C3F4790238092E229C8A9E0F42B37CB425C054FD2734455DB4934B6F", + "repo": "Elixir.Orcasite.Repo", + "identities": [], + "schema": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_indexes": [ + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "meta" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "meta" + ], + "nulls_distinct": true, + "using": "gin" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/server/priv/resource_snapshots/repo/subscriber_tokens/20240701205705.json b/server/priv/resource_snapshots/repo/subscriber_tokens/20240701205705.json new file mode 100644 index 00000000..32b0a3dc --- /dev/null +++ b/server/priv/resource_snapshots/repo/subscriber_tokens/20240701205705.json @@ -0,0 +1,89 @@ +{ + "attributes": [ + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "created_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "map", + "source": "extra_data", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "purpose", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "utc_datetime", + "source": "expires_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "subject", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "jti", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + } + ], + "table": "subscriber_tokens", + "hash": "3E4B64F7C9A78161A50DECAA55C629C80DD5421E48B1467157DD91497F6C989B", + "repo": "Elixir.Orcasite.Repo", + "identities": [], + "schema": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_indexes": [], + "base_filter": null, + "check_constraints": [], + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/server/priv/resource_snapshots/repo/subscribers/20240701205705.json b/server/priv/resource_snapshots/repo/subscribers/20240701205705.json new file mode 100644 index 00000000..357eff38 --- /dev/null +++ b/server/priv/resource_snapshots/repo/subscribers/20240701205705.json @@ -0,0 +1,117 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "name", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "subscriber_type", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "%{}", + "size": null, + "type": "map", + "source": "meta", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + } + ], + "table": "subscribers", + "hash": "592591A1F3DD4ADA2AB18C3E10F3B8503F77517409241E76F3D9BF6C37D0D4B4", + "repo": "Elixir.Orcasite.Repo", + "identities": [ + { + "name": "id", + "keys": [ + { + "type": "atom", + "value": "id" + } + ], + "where": null, + "nils_distinct?": true, + "all_tenants?": false, + "index_name": "subscribers_id_index", + "base_filter": null + } + ], + "schema": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_indexes": [ + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "meta" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "meta" + ], + "nulls_distinct": true, + "using": "gin" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/server/priv/resource_snapshots/repo/subscriptions/20240701205705.json b/server/priv/resource_snapshots/repo/subscriptions/20240701205705.json new file mode 100644 index 00000000..627985a4 --- /dev/null +++ b/server/priv/resource_snapshots/repo/subscriptions/20240701205705.json @@ -0,0 +1,195 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "name", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "%{}", + "size": null, + "type": "map", + "source": "meta", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "true", + "size": null, + "type": "boolean", + "source": "active", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "event_type", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "utc_datetime_usec", + "source": "last_notified_at", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "subscriber_id", + "references": { + "name": "subscriptions_subscriber_id_fkey", + "table": "subscribers", + "schema": null, + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "index?": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "last_notification_id", + "references": { + "name": "subscriptions_last_notification_id_fkey", + "table": "notifications", + "schema": null, + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "index?": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + } + ], + "table": "subscriptions", + "hash": "DAD97B010FA089D41967236B85CF768CCAC13C02BF9A7586B536DCADF6F23602", + "repo": "Elixir.Orcasite.Repo", + "identities": [ + { + "name": "id", + "keys": [ + { + "type": "atom", + "value": "id" + } + ], + "where": null, + "nils_distinct?": true, + "all_tenants?": false, + "index_name": "subscriptions_id_index", + "base_filter": null + } + ], + "schema": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_indexes": [ + { + "message": null, + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "meta" + } + ], + "unique": false, + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "meta" + ], + "nulls_distinct": true, + "using": "gin" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/server/priv/resource_snapshots/repo/tokens/20240701205705.json b/server/priv/resource_snapshots/repo/tokens/20240701205705.json new file mode 100644 index 00000000..a9fa21a2 --- /dev/null +++ b/server/priv/resource_snapshots/repo/tokens/20240701205705.json @@ -0,0 +1,89 @@ +{ + "attributes": [ + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "created_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "map", + "source": "extra_data", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "purpose", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "utc_datetime", + "source": "expires_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "subject", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "jti", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + } + ], + "table": "tokens", + "hash": "6CF0EBB4329879545FFBF88DD0FB68B943AEECFBAA94F36B99559EF43D90BB87", + "repo": "Elixir.Orcasite.Repo", + "identities": [], + "schema": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_indexes": [], + "base_filter": null, + "check_constraints": [], + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/server/priv/resource_snapshots/repo/users/20240701205705.json b/server/priv/resource_snapshots/repo/users/20240701205705.json new file mode 100644 index 00000000..90c298ed --- /dev/null +++ b/server/priv/resource_snapshots/repo/users/20240701205705.json @@ -0,0 +1,148 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "citext", + "source": "email", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "hashed_password", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "first_name", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "last_name", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "false", + "size": null, + "type": "boolean", + "source": "admin", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "false", + "size": null, + "type": "boolean", + "source": "moderator", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "username", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + } + ], + "table": "users", + "hash": "C815DA2549697B0F87CC6A57FAB0C45045536689F99E43FF1CD0E70F61BC708E", + "repo": "Elixir.Orcasite.Repo", + "identities": [ + { + "name": "unique_email", + "keys": [ + { + "type": "atom", + "value": "email" + } + ], + "where": null, + "nils_distinct?": true, + "all_tenants?": false, + "index_name": "users_unique_email_index", + "base_filter": null + }, + { + "name": "unique_username", + "keys": [ + { + "type": "atom", + "value": "username" + } + ], + "where": null, + "nils_distinct?": true, + "all_tenants?": false, + "index_name": "users_unique_username_index", + "base_filter": null + } + ], + "schema": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_indexes": [], + "base_filter": null, + "check_constraints": [], + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/server/test/orcasite_web/graphql/moderator_test.exs b/server/test/orcasite_web/graphql/moderator_test.exs index b0015859..642d3a86 100644 --- a/server/test/orcasite_web/graphql/moderator_test.exs +++ b/server/test/orcasite_web/graphql/moderator_test.exs @@ -32,7 +32,7 @@ defmodule OrcasiteWeb.ModeratorTest do slug: "orcasound-lab" } ) - |> Orcasite.Radio.create!() + |> Ash.create!(authorize?: false) detection = Orcasite.Radio.Detection.submit_detection(%{ @@ -44,7 +44,7 @@ defmodule OrcasiteWeb.ModeratorTest do listener_count: 1, send_notifications: false }) - |> Orcasite.Radio.load!(:candidate) + |> Ash.load!(:candidate) [moderator: moderator, user: user, detection: detection, candidate: detection.candidate] end diff --git a/server/test/orcasite_web/graphql/radio_test.exs b/server/test/orcasite_web/graphql/radio_test.exs index 4c9cdde6..d940405b 100644 --- a/server/test/orcasite_web/graphql/radio_test.exs +++ b/server/test/orcasite_web/graphql/radio_test.exs @@ -13,7 +13,7 @@ defmodule OrcasiteWeb.RadioTest do slug: "orcasound-lab" } ) - |> Orcasite.Radio.create!() + |> Ash.create!(authorize?: false) [feed: feed] end diff --git a/server/test/support/authentication_helper.ex b/server/test/support/authentication_helper.ex index 0fbae469..d632dbac 100644 --- a/server/test/support/authentication_helper.ex +++ b/server/test/support/authentication_helper.ex @@ -29,7 +29,7 @@ defmodule OrcasiteWeb.TestSupport.AuthenticationHelper do user_changeset end - user_changeset |> Orcasite.Accounts.create!() + user_changeset |> Ash.create!() end def register_user(conn, %{ diff --git a/ui/src/graphql/generated/index.ts b/ui/src/graphql/generated/index.ts index 87bf98d7..48c19287 100644 --- a/ui/src/graphql/generated/index.ts +++ b/ui/src/graphql/generated/index.ts @@ -37,15 +37,111 @@ export type Scalars = { Json: { input: { [key: string]: any }; output: { [key: string]: any } }; }; +export type AudioCategory = "ANTHROPHONY" | "BIOPHONY" | "GEOPHONY"; + +export type Bout = { + __typename?: "Bout"; + category?: Maybe; + duration?: Maybe; + endTime?: Maybe; + id: Scalars["ID"]["output"]; + ongoing?: Maybe; + startTime?: Maybe; +}; + +export type BoutFilterCategory = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + notEq?: InputMaybe; +}; + +export type BoutFilterDuration = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + notEq?: InputMaybe; +}; + +export type BoutFilterEndTime = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + notEq?: InputMaybe; +}; + +export type BoutFilterId = { + isNil?: InputMaybe; +}; + +export type BoutFilterInput = { + and?: InputMaybe>; + category?: InputMaybe; + duration?: InputMaybe; + endTime?: InputMaybe; + id?: InputMaybe; + not?: InputMaybe>; + ongoing?: InputMaybe; + or?: InputMaybe>; + startTime?: InputMaybe; +}; + +export type BoutFilterOngoing = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + notEq?: InputMaybe; +}; + +export type BoutFilterStartTime = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + notEq?: InputMaybe; +}; + +export type BoutSortField = + | "CATEGORY" + | "DURATION" + | "END_TIME" + | "ID" + | "ONGOING" + | "START_TIME"; + +export type BoutSortInput = { + field: BoutSortField; + order?: InputMaybe; +}; + export type CancelCandidateNotificationsInput = { - eventType?: InputMaybe; + eventType?: InputMaybe; }; /** The result of the :cancel_candidate_notifications mutation */ export type CancelCandidateNotificationsResult = { __typename?: "CancelCandidateNotificationsResult"; /** Any errors generated, if the mutation failed */ - errors?: Maybe>; + errors: Array; /** The successful result of the mutation */ result?: Maybe; }; @@ -54,21 +150,21 @@ export type CancelCandidateNotificationsResult = { export type CancelNotificationResult = { __typename?: "CancelNotificationResult"; /** Any errors generated, if the mutation failed */ - errors?: Maybe>; + errors: Array; /** The successful result of the mutation */ result?: Maybe; }; export type Candidate = { __typename?: "Candidate"; - category?: Maybe; + category?: Maybe; detectionCount?: Maybe; detections: Array; feed: Feed; + feedId: Scalars["ID"]["output"]; id: Scalars["ID"]["output"]; maxTime: Scalars["DateTime"]["output"]; minTime: Scalars["DateTime"]["output"]; - uuid?: Maybe; visible?: Maybe; }; @@ -79,19 +175,15 @@ export type CandidateDetectionsArgs = { sort?: InputMaybe>>; }; -export type CandidateCategory = "OTHER" | "VESSEL" | "WHALE"; - -export type CandidateEventType = "CONFIRMED_CANDIDATE" | "NEW_DETECTION"; - export type CandidateFilterCategory = { - eq?: InputMaybe; - greaterThan?: InputMaybe; - greaterThanOrEqual?: InputMaybe; - in?: InputMaybe>>; + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + in?: InputMaybe>>; isNil?: InputMaybe; - lessThan?: InputMaybe; - lessThanOrEqual?: InputMaybe; - notEq?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + notEq?: InputMaybe; }; export type CandidateFilterDetectionCount = { @@ -105,6 +197,10 @@ export type CandidateFilterDetectionCount = { notEq?: InputMaybe; }; +export type CandidateFilterFeedId = { + isNil?: InputMaybe; +}; + export type CandidateFilterId = { isNil?: InputMaybe; }; @@ -115,6 +211,7 @@ export type CandidateFilterInput = { detectionCount?: InputMaybe; detections?: InputMaybe; feed?: InputMaybe; + feedId?: InputMaybe; id?: InputMaybe; maxTime?: InputMaybe; minTime?: InputMaybe; @@ -159,6 +256,7 @@ export type CandidateFilterVisible = { export type CandidateSortField = | "CATEGORY" | "DETECTION_COUNT" + | "FEED_ID" | "ID" | "MAX_TIME" | "MIN_TIME" @@ -169,24 +267,38 @@ export type CandidateSortInput = { order?: InputMaybe; }; +/** The result of the :create_bout mutation */ +export type CreateBoutResult = { + __typename?: "CreateBoutResult"; + /** Any errors generated, if the mutation failed */ + errors: Array; + /** The successful result of the mutation */ + result?: Maybe; +}; + export type Detection = { __typename?: "Detection"; candidate?: Maybe; + candidateId?: Maybe; category?: Maybe; description?: Maybe; feed?: Maybe; + feedId?: Maybe; id: Scalars["ID"]["output"]; listenerCount?: Maybe; playerOffset: Scalars["Decimal"]["output"]; playlistTimestamp: Scalars["Int"]["output"]; sourceIp?: Maybe; timestamp: Scalars["DateTime"]["output"]; - uuid?: Maybe; visible?: Maybe; }; export type DetectionCategory = "OTHER" | "VESSEL" | "WHALE"; +export type DetectionFilterCandidateId = { + isNil?: InputMaybe; +}; + export type DetectionFilterCategory = { eq?: InputMaybe; greaterThan?: InputMaybe; @@ -211,6 +323,10 @@ export type DetectionFilterDescription = { notEq?: InputMaybe; }; +export type DetectionFilterFeedId = { + isNil?: InputMaybe; +}; + export type DetectionFilterId = { isNil?: InputMaybe; }; @@ -218,9 +334,11 @@ export type DetectionFilterId = { export type DetectionFilterInput = { and?: InputMaybe>; candidate?: InputMaybe; + candidateId?: InputMaybe; category?: InputMaybe; description?: InputMaybe; feed?: InputMaybe; + feedId?: InputMaybe; id?: InputMaybe; listenerCount?: InputMaybe; not?: InputMaybe>; @@ -301,8 +419,10 @@ export type DetectionFilterVisible = { }; export type DetectionSortField = + | "CANDIDATE_ID" | "CATEGORY" | "DESCRIPTION" + | "FEED_ID" | "ID" | "LISTENER_COUNT" | "PLAYER_OFFSET" @@ -318,20 +438,78 @@ export type DetectionSortInput = { export type Feed = { __typename?: "Feed"; + bucket?: Maybe; + bucketRegion?: Maybe; + cloudfrontUrl?: Maybe; + feedSegments: Array; + feedStreams: Array; id: Scalars["ID"]["output"]; imageUrl?: Maybe; introHtml?: Maybe; latLng: LatLng; - latLngString?: Maybe; locationPoint: Scalars["Json"]["output"]; mapUrl?: Maybe; name: Scalars["String"]["output"]; nodeName: Scalars["String"]["output"]; + online?: Maybe; slug: Scalars["String"]["output"]; thumbUrl?: Maybe; visible?: Maybe; }; +export type FeedFeedSegmentsArgs = { + filter?: InputMaybe; + limit?: InputMaybe; + offset?: InputMaybe; + sort?: InputMaybe>>; +}; + +export type FeedFeedStreamsArgs = { + filter?: InputMaybe; + limit?: InputMaybe; + offset?: InputMaybe; + sort?: InputMaybe>>; +}; + +export type FeedFilterBucket = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + ilike?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + like?: InputMaybe; + notEq?: InputMaybe; +}; + +export type FeedFilterBucketRegion = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + ilike?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + like?: InputMaybe; + notEq?: InputMaybe; +}; + +export type FeedFilterCloudfrontUrl = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + ilike?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + like?: InputMaybe; + notEq?: InputMaybe; +}; + export type FeedFilterId = { isNil?: InputMaybe; }; @@ -351,6 +529,11 @@ export type FeedFilterImageUrl = { export type FeedFilterInput = { and?: InputMaybe>; + bucket?: InputMaybe; + bucketRegion?: InputMaybe; + cloudfrontUrl?: InputMaybe; + feedSegments?: InputMaybe; + feedStreams?: InputMaybe; id?: InputMaybe; imageUrl?: InputMaybe; introHtml?: InputMaybe; @@ -358,6 +541,7 @@ export type FeedFilterInput = { name?: InputMaybe; nodeName?: InputMaybe; not?: InputMaybe>; + online?: InputMaybe; or?: InputMaybe>; slug?: InputMaybe; visible?: InputMaybe; @@ -406,6 +590,17 @@ export type FeedFilterNodeName = { notEq?: InputMaybe; }; +export type FeedFilterOnline = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + notEq?: InputMaybe; +}; + export type FeedFilterSlug = { eq?: InputMaybe; greaterThan?: InputMaybe; @@ -430,13 +625,239 @@ export type FeedFilterVisible = { notEq?: InputMaybe; }; +export type FeedSegment = { + __typename?: "FeedSegment"; + bucket?: Maybe; + bucketRegion?: Maybe; + cloudfrontUrl?: Maybe; + duration?: Maybe; + endTime?: Maybe; + feed?: Maybe; + feedId?: Maybe; + feedStream?: Maybe; + feedStreamId?: Maybe; + /** ts file name (e.g. live005.ts) */ + fileName: Scalars["String"]["output"]; + id: Scalars["ID"]["output"]; + /** S3 object path for playlist file (e.g. /rpi_orcasound_lab/hls/1541027406/live.m3u8) */ + playlistM3u8Path?: Maybe; + /** S3 object path for playlist dir (e.g. /rpi_orcasound_lab/hls/1541027406/) */ + playlistPath?: Maybe; + /** UTC Unix epoch for playlist (m3u8 dir) start (e.g. 1541027406) */ + playlistTimestamp?: Maybe; + /** S3 object path for ts file (e.g. /rpi_orcasound_lab/hls/1541027406/live005.ts) */ + segmentPath?: Maybe; + startTime?: Maybe; +}; + +export type FeedSegmentFilterBucket = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + ilike?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + like?: InputMaybe; + notEq?: InputMaybe; +}; + +export type FeedSegmentFilterBucketRegion = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + ilike?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + like?: InputMaybe; + notEq?: InputMaybe; +}; + +export type FeedSegmentFilterCloudfrontUrl = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + ilike?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + like?: InputMaybe; + notEq?: InputMaybe; +}; + +export type FeedSegmentFilterDuration = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + notEq?: InputMaybe; +}; + +export type FeedSegmentFilterEndTime = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + notEq?: InputMaybe; +}; + +export type FeedSegmentFilterFeedId = { + isNil?: InputMaybe; +}; + +export type FeedSegmentFilterFeedStreamId = { + isNil?: InputMaybe; +}; + +export type FeedSegmentFilterFileName = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + ilike?: InputMaybe; + in?: InputMaybe>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + like?: InputMaybe; + notEq?: InputMaybe; +}; + +export type FeedSegmentFilterId = { + isNil?: InputMaybe; +}; + +export type FeedSegmentFilterInput = { + and?: InputMaybe>; + bucket?: InputMaybe; + bucketRegion?: InputMaybe; + cloudfrontUrl?: InputMaybe; + duration?: InputMaybe; + endTime?: InputMaybe; + feed?: InputMaybe; + feedId?: InputMaybe; + feedStream?: InputMaybe; + feedStreamId?: InputMaybe; + /** ts file name (e.g. live005.ts) */ + fileName?: InputMaybe; + id?: InputMaybe; + not?: InputMaybe>; + or?: InputMaybe>; + /** S3 object path for playlist file (e.g. /rpi_orcasound_lab/hls/1541027406/live.m3u8) */ + playlistM3u8Path?: InputMaybe; + /** S3 object path for playlist dir (e.g. /rpi_orcasound_lab/hls/1541027406/) */ + playlistPath?: InputMaybe; + /** UTC Unix epoch for playlist (m3u8 dir) start (e.g. 1541027406) */ + playlistTimestamp?: InputMaybe; + /** S3 object path for ts file (e.g. /rpi_orcasound_lab/hls/1541027406/live005.ts) */ + segmentPath?: InputMaybe; + startTime?: InputMaybe; +}; + +export type FeedSegmentFilterPlaylistM3u8Path = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + ilike?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + like?: InputMaybe; + notEq?: InputMaybe; +}; + +export type FeedSegmentFilterPlaylistPath = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + ilike?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + like?: InputMaybe; + notEq?: InputMaybe; +}; + +export type FeedSegmentFilterPlaylistTimestamp = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + ilike?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + like?: InputMaybe; + notEq?: InputMaybe; +}; + +export type FeedSegmentFilterSegmentPath = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + ilike?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + like?: InputMaybe; + notEq?: InputMaybe; +}; + +export type FeedSegmentFilterStartTime = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + notEq?: InputMaybe; +}; + +export type FeedSegmentSortField = + | "BUCKET" + | "BUCKET_REGION" + | "CLOUDFRONT_URL" + | "DURATION" + | "END_TIME" + | "FEED_ID" + | "FEED_STREAM_ID" + | "FILE_NAME" + | "ID" + | "PLAYLIST_M3U8_PATH" + | "PLAYLIST_PATH" + | "PLAYLIST_TIMESTAMP" + | "SEGMENT_PATH" + | "START_TIME"; + +export type FeedSegmentSortInput = { + field: FeedSegmentSortField; + order?: InputMaybe; +}; + export type FeedSortField = + | "BUCKET" + | "BUCKET_REGION" + | "CLOUDFRONT_URL" | "ID" | "IMAGE_URL" | "INTRO_HTML" | "LOCATION_POINT" | "NAME" | "NODE_NAME" + | "ONLINE" | "SLUG" | "VISIBLE"; @@ -445,6 +866,174 @@ export type FeedSortInput = { order?: InputMaybe; }; +export type FeedStream = { + __typename?: "FeedStream"; + bucket?: Maybe; + bucketRegion?: Maybe; + cloudfrontUrl?: Maybe; + duration?: Maybe; + endTime?: Maybe; + id: Scalars["ID"]["output"]; + /** S3 object path for playlist file (e.g. /rpi_orcasound_lab/hls/1541027406/live.m3u8) */ + playlistM3u8Path?: Maybe; + /** S3 object path for playlist dir (e.g. /rpi_orcasound_lab/hls/1541027406/) */ + playlistPath?: Maybe; + /** UTC Unix epoch for playlist start (e.g. 1541027406) */ + playlistTimestamp?: Maybe; + startTime?: Maybe; +}; + +export type FeedStreamFilterBucket = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + ilike?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + like?: InputMaybe; + notEq?: InputMaybe; +}; + +export type FeedStreamFilterBucketRegion = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + ilike?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + like?: InputMaybe; + notEq?: InputMaybe; +}; + +export type FeedStreamFilterCloudfrontUrl = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + ilike?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + like?: InputMaybe; + notEq?: InputMaybe; +}; + +export type FeedStreamFilterDuration = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + notEq?: InputMaybe; +}; + +export type FeedStreamFilterEndTime = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + notEq?: InputMaybe; +}; + +export type FeedStreamFilterId = { + isNil?: InputMaybe; +}; + +export type FeedStreamFilterInput = { + and?: InputMaybe>; + bucket?: InputMaybe; + bucketRegion?: InputMaybe; + cloudfrontUrl?: InputMaybe; + duration?: InputMaybe; + endTime?: InputMaybe; + id?: InputMaybe; + not?: InputMaybe>; + or?: InputMaybe>; + /** S3 object path for playlist file (e.g. /rpi_orcasound_lab/hls/1541027406/live.m3u8) */ + playlistM3u8Path?: InputMaybe; + /** S3 object path for playlist dir (e.g. /rpi_orcasound_lab/hls/1541027406/) */ + playlistPath?: InputMaybe; + /** UTC Unix epoch for playlist start (e.g. 1541027406) */ + playlistTimestamp?: InputMaybe; + startTime?: InputMaybe; +}; + +export type FeedStreamFilterPlaylistM3u8Path = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + ilike?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + like?: InputMaybe; + notEq?: InputMaybe; +}; + +export type FeedStreamFilterPlaylistPath = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + ilike?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + like?: InputMaybe; + notEq?: InputMaybe; +}; + +export type FeedStreamFilterPlaylistTimestamp = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + ilike?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + like?: InputMaybe; + notEq?: InputMaybe; +}; + +export type FeedStreamFilterStartTime = { + eq?: InputMaybe; + greaterThan?: InputMaybe; + greaterThanOrEqual?: InputMaybe; + in?: InputMaybe>>; + isNil?: InputMaybe; + lessThan?: InputMaybe; + lessThanOrEqual?: InputMaybe; + notEq?: InputMaybe; +}; + +export type FeedStreamSortField = + | "BUCKET" + | "BUCKET_REGION" + | "CLOUDFRONT_URL" + | "DURATION" + | "END_TIME" + | "ID" + | "PLAYLIST_M3U8_PATH" + | "PLAYLIST_PATH" + | "PLAYLIST_TIMESTAMP" + | "START_TIME"; + +export type FeedStreamSortInput = { + field: FeedStreamSortField; + order?: InputMaybe; +}; + export type LatLng = { __typename?: "LatLng"; lat: Scalars["Float"]["output"]; @@ -478,7 +1067,6 @@ export type Notification = { finished?: Maybe; id: Scalars["ID"]["output"]; insertedAt: Scalars["DateTime"]["output"]; - meta?: Maybe; notifiedCount?: Maybe; notifiedCountUpdatedAt?: Maybe; progress?: Maybe; @@ -538,7 +1126,6 @@ export type NotificationFilterInput = { finished?: InputMaybe; id?: InputMaybe; insertedAt?: InputMaybe; - meta?: InputMaybe; not?: InputMaybe>; notifiedCount?: InputMaybe; notifiedCountUpdatedAt?: InputMaybe; @@ -558,17 +1145,6 @@ export type NotificationFilterInsertedAt = { notEq?: InputMaybe; }; -export type NotificationFilterMeta = { - eq?: InputMaybe; - greaterThan?: InputMaybe; - greaterThanOrEqual?: InputMaybe; - in?: InputMaybe>>; - isNil?: InputMaybe; - lessThan?: InputMaybe; - lessThanOrEqual?: InputMaybe; - notEq?: InputMaybe; -}; - export type NotificationFilterNotifiedCount = { eq?: InputMaybe; greaterThan?: InputMaybe; @@ -619,7 +1195,6 @@ export type NotificationSortField = | "FINISHED" | "ID" | "INSERTED_AT" - | "META" | "NOTIFIED_COUNT" | "NOTIFIED_COUNT_UPDATED_AT" | "PROGRESS" @@ -643,11 +1218,22 @@ export type NotifyConfirmedCandidateInput = { export type NotifyConfirmedCandidateResult = { __typename?: "NotifyConfirmedCandidateResult"; /** Any errors generated, if the mutation failed */ - errors?: Maybe>; + errors: Array; /** The successful result of the mutation */ result?: Maybe; }; +/** A page of :bout */ +export type PageOfBout = { + __typename?: "PageOfBout"; + /** Total count on all pages */ + count?: Maybe; + /** Whether or not there is a next page */ + hasNextPage: Scalars["Boolean"]["output"]; + /** The records contained in the page */ + results?: Maybe>; +}; + /** A page of :candidate */ export type PageOfCandidate = { __typename?: "PageOfCandidate"; @@ -670,6 +1256,28 @@ export type PageOfDetection = { results?: Maybe>; }; +/** A page of :feed_segment */ +export type PageOfFeedSegment = { + __typename?: "PageOfFeedSegment"; + /** Total count on all pages */ + count?: Maybe; + /** Whether or not there is a next page */ + hasNextPage: Scalars["Boolean"]["output"]; + /** The records contained in the page */ + results?: Maybe>; +}; + +/** A page of :feed_stream */ +export type PageOfFeedStream = { + __typename?: "PageOfFeedStream"; + /** Total count on all pages */ + count?: Maybe; + /** Whether or not there is a next page */ + hasNextPage: Scalars["Boolean"]["output"]; + /** The records contained in the page */ + results?: Maybe>; +}; + export type PasswordResetInput = { password: Scalars["String"]["input"]; passwordConfirmation: Scalars["String"]["input"]; @@ -703,7 +1311,7 @@ export type RegisterWithPasswordMetadata = { export type RegisterWithPasswordResult = { __typename?: "RegisterWithPasswordResult"; /** Any errors generated, if the mutation failed */ - errors?: Maybe>; + errors: Array; /** Metadata produced by the mutation */ metadata?: Maybe; /** The successful result of the mutation */ @@ -718,6 +1326,7 @@ export type RootMutationType = { __typename?: "RootMutationType"; cancelCandidateNotifications: CancelCandidateNotificationsResult; cancelNotification: CancelNotificationResult; + createBout: CreateBoutResult; /** Create a notification for confirmed candidate (i.e. detection group) */ notifyConfirmedCandidate: NotifyConfirmedCandidateResult; /** Register a new user with a username and password. */ @@ -731,20 +1340,20 @@ export type RootMutationType = { }; export type RootMutationTypeCancelCandidateNotificationsArgs = { - id?: InputMaybe; + id: Scalars["ID"]["input"]; input?: InputMaybe; }; export type RootMutationTypeCancelNotificationArgs = { - id?: InputMaybe; + id: Scalars["ID"]["input"]; }; export type RootMutationTypeNotifyConfirmedCandidateArgs = { - input?: InputMaybe; + input: NotifyConfirmedCandidateInput; }; export type RootMutationTypeRegisterWithPasswordArgs = { - input?: InputMaybe; + input: RegisterWithPasswordInput; }; export type RootMutationTypeRequestPasswordResetArgs = { @@ -756,7 +1365,7 @@ export type RootMutationTypeResetPasswordArgs = { }; export type RootMutationTypeSetDetectionVisibleArgs = { - id?: InputMaybe; + id: Scalars["ID"]["input"]; input?: InputMaybe; }; @@ -765,21 +1374,32 @@ export type RootMutationTypeSignInWithPasswordArgs = { }; export type RootMutationTypeSubmitDetectionArgs = { - input?: InputMaybe; + input: SubmitDetectionInput; }; export type RootQueryType = { __typename?: "RootQueryType"; + bouts?: Maybe; candidate?: Maybe; candidates?: Maybe; currentUser?: Maybe; detection?: Maybe; detections?: Maybe; feed: Feed; + feedSegments?: Maybe; + feedStreams?: Maybe; feeds: Array; notificationsForCandidate: Array; }; +export type RootQueryTypeBoutsArgs = { + feedId?: InputMaybe; + filter?: InputMaybe; + limit?: InputMaybe; + offset?: InputMaybe; + sort?: InputMaybe>>; +}; + export type RootQueryTypeCandidateArgs = { id: Scalars["ID"]["input"]; }; @@ -811,6 +1431,23 @@ export type RootQueryTypeFeedArgs = { slug?: InputMaybe; }; +export type RootQueryTypeFeedSegmentsArgs = { + feedId?: InputMaybe; + feedStreamId?: InputMaybe; + filter?: InputMaybe; + limit?: InputMaybe; + offset?: InputMaybe; + sort?: InputMaybe>>; +}; + +export type RootQueryTypeFeedStreamsArgs = { + feedId?: InputMaybe; + filter?: InputMaybe; + limit?: InputMaybe; + offset?: InputMaybe; + sort?: InputMaybe>>; +}; + export type RootQueryTypeFeedsArgs = { filter?: InputMaybe; sort?: InputMaybe>>; @@ -832,7 +1469,7 @@ export type SetDetectionVisibleInput = { export type SetDetectionVisibleResult = { __typename?: "SetDetectionVisibleResult"; /** Any errors generated, if the mutation failed */ - errors?: Maybe>; + errors: Array; /** The successful result of the mutation */ result?: Maybe; }; @@ -870,7 +1507,7 @@ export type SubmitDetectionInput = { export type SubmitDetectionResult = { __typename?: "SubmitDetectionResult"; /** Any errors generated, if the mutation failed */ - errors?: Maybe>; + errors: Array; /** The successful result of the mutation */ result?: Maybe; }; @@ -991,14 +1628,14 @@ export type CancelCandidateNotificationsMutation = { cancelCandidateNotifications: { __typename?: "CancelCandidateNotificationsResult"; result?: { __typename?: "Candidate"; id: string } | null; - errors?: Array<{ + errors: Array<{ __typename?: "MutationError"; code?: string | null; fields?: Array | null; message?: string | null; shortMessage?: string | null; vars?: { [key: string]: any } | null; - }> | null; + }>; }; }; @@ -1013,7 +1650,6 @@ export type CancelNotificationMutation = { result?: { __typename?: "Notification"; id: string; - meta?: { [key: string]: any } | null; active?: boolean | null; insertedAt: Date; targetCount?: number | null; @@ -1022,14 +1658,14 @@ export type CancelNotificationMutation = { progress?: number | null; finished?: boolean | null; } | null; - errors?: Array<{ + errors: Array<{ __typename?: "MutationError"; code?: string | null; fields?: Array | null; message?: string | null; shortMessage?: string | null; vars?: { [key: string]: any } | null; - }> | null; + }>; }; }; @@ -1046,7 +1682,6 @@ export type NotifyConfirmedCandidateMutation = { __typename?: "Notification"; id: string; eventType?: NotificationEventType | null; - meta?: { [key: string]: any } | null; active?: boolean | null; targetCount?: number | null; notifiedCount?: number | null; @@ -1054,14 +1689,14 @@ export type NotifyConfirmedCandidateMutation = { finished?: boolean | null; notifiedCountUpdatedAt?: Date | null; } | null; - errors?: Array<{ + errors: Array<{ __typename?: "MutationError"; code?: string | null; fields?: Array | null; message?: string | null; shortMessage?: string | null; vars?: { [key: string]: any } | null; - }> | null; + }>; }; }; @@ -1087,14 +1722,14 @@ export type RegisterWithPasswordMutation = { firstName?: string | null; lastName?: string | null; } | null; - errors?: Array<{ + errors: Array<{ __typename?: "MutationError"; message?: string | null; code?: string | null; fields?: Array | null; shortMessage?: string | null; vars?: { [key: string]: any } | null; - }> | null; + }>; }; }; @@ -1150,14 +1785,14 @@ export type SetDetectionVisibleMutation = { id: string; visible?: boolean | null; } | null; - errors?: Array<{ + errors: Array<{ __typename?: "MutationError"; code?: string | null; fields?: Array | null; message?: string | null; shortMessage?: string | null; vars?: { [key: string]: any } | null; - }> | null; + }>; }; }; @@ -1210,14 +1845,14 @@ export type SubmitDetectionMutation = { submitDetection: { __typename?: "SubmitDetectionResult"; result?: { __typename?: "Detection"; id: string } | null; - errors?: Array<{ + errors: Array<{ __typename?: "MutationError"; message?: string | null; code?: string | null; fields?: Array | null; shortMessage?: string | null; vars?: { [key: string]: any } | null; - }> | null; + }>; }; }; @@ -1234,7 +1869,7 @@ export type CandidateQuery = { maxTime: Date; detectionCount?: number | null; visible?: boolean | null; - category?: CandidateCategory | null; + category?: DetectionCategory | null; feed: { __typename?: "Feed"; id: string; @@ -1303,7 +1938,6 @@ export type NotificationsForCandidateQuery = { __typename?: "Notification"; id: string; eventType?: NotificationEventType | null; - meta?: { [key: string]: any } | null; active?: boolean | null; insertedAt: Date; targetCount?: number | null; @@ -1334,7 +1968,7 @@ export type CandidatesQuery = { id: string; minTime: Date; maxTime: Date; - category?: CandidateCategory | null; + category?: DetectionCategory | null; detectionCount?: number | null; visible?: boolean | null; feed: { @@ -1439,7 +2073,6 @@ export const CancelNotificationDocument = ` cancelNotification(id: $id) { result { id - meta active insertedAt targetCount @@ -1504,7 +2137,6 @@ export const NotifyConfirmedCandidateDocument = ` result { id eventType - meta active targetCount notifiedCount @@ -2107,7 +2739,6 @@ export const NotificationsForCandidateDocument = ` notificationsForCandidate(candidateId: $candidateId, eventType: $eventType) { id eventType - meta active insertedAt targetCount diff --git a/ui/src/graphql/mutations/cancelNotification.graphql b/ui/src/graphql/mutations/cancelNotification.graphql index d647c607..f9619c87 100644 --- a/ui/src/graphql/mutations/cancelNotification.graphql +++ b/ui/src/graphql/mutations/cancelNotification.graphql @@ -2,7 +2,6 @@ mutation cancelNotification($id: ID!) { cancelNotification(id: $id) { result { id - meta active insertedAt targetCount diff --git a/ui/src/graphql/mutations/notifyConfirmedCandidate.graphql b/ui/src/graphql/mutations/notifyConfirmedCandidate.graphql index 72cb35f8..c5338d5f 100644 --- a/ui/src/graphql/mutations/notifyConfirmedCandidate.graphql +++ b/ui/src/graphql/mutations/notifyConfirmedCandidate.graphql @@ -5,7 +5,6 @@ mutation notifyConfirmedCandidate($candidateId: String!, $message: String!) { result { id eventType - meta active targetCount notifiedCount diff --git a/ui/src/graphql/queries/getNotificationsForCandidate.graphql b/ui/src/graphql/queries/getNotificationsForCandidate.graphql index d65abfec..520b8903 100644 --- a/ui/src/graphql/queries/getNotificationsForCandidate.graphql +++ b/ui/src/graphql/queries/getNotificationsForCandidate.graphql @@ -5,7 +5,6 @@ query notificationsForCandidate( notificationsForCandidate(candidateId: $candidateId, eventType: $eventType) { id eventType - meta active insertedAt targetCount