From 025b56d1a45e7c2fe3dbc5e72d2313ebf19f7e29 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 6 Aug 2022 19:22:58 -0400 Subject: [PATCH] improvement: get a build set up improvement: fix lint/security issues improvement: add CSP improvement: remove currently unnecessary/old code --- .check.exs | 19 ++ .credo.exs | 209 ++++++++++++++ .formatter.exs | 3 +- config/config.exs | 6 + config/dev.exs | 3 + config/prod.exs | 6 + config/test.exs | 3 + lib/ash_hq/accounts/accounts.ex | 3 + lib/ash_hq/accounts/email_notifier.ex | 4 + lib/ash_hq/accounts/emails.ex | 4 + .../preparations/determine_days_for_token.ex | 9 +- .../accounts/preparations/set_hashed_token.ex | 4 + lib/ash_hq/accounts/registry.ex | 2 + .../user/changes/remove_all_tokens.ex | 5 + lib/ash_hq/accounts/resources/user/helpers.ex | 1 + .../user/preparations/decode_token.ex | 28 -- .../user/preparations/validate_password.ex | 6 + lib/ash_hq/accounts/resources/user/user.ex | 65 +++-- .../validations/validate_current_password.ex | 6 + .../resources/user/validations/validations.ex | 7 - .../user_token/changes/build_hashed_token.ex | 4 - .../user_token/changes/build_session_token.ex | 4 - .../resources/user_token/user_token.ex | 47 +-- .../docs/changes/add_arg_to_relationship.ex | 4 + lib/ash_hq/docs/docs.ex | 3 + .../changes/render_markdown.ex | 4 + .../render_markdown/render_markdown.ex | 4 + .../add_render_markdown_structure.ex | 11 +- .../search/changes/sanitize_name.ex | 4 + .../search/preparations/load_search_data.ex | 5 +- lib/ash_hq/docs/extensions/search/search.ex | 22 ++ .../transformers/add_search_structure.ex | 81 ++++-- lib/ash_hq/docs/extensions/search/types.ex | 6 +- lib/ash_hq/docs/flows/search/search.ex | 48 ++-- .../docs/flows/search/steps/build_results.ex | 3 + .../search/steps/experimental_run_search.ex | 127 -------- .../flows/search/steps/search_resource.ex | 3 + lib/ash_hq/docs/importer/importer.ex | 1 + lib/ash_hq/docs/registry.ex | 1 + lib/ash_hq/docs/resources/dsl/dsl.ex | 6 + .../docs/resources/extension/extension.ex | 6 + .../docs/resources/function/function.ex | 16 +- .../docs/resources/guide/changes/set_route.ex | 3 + lib/ash_hq/docs/resources/guide/guide.ex | 14 +- lib/ash_hq/docs/resources/library/library.ex | 5 + .../library_version/library_version.ex | 6 + .../sort_by_sortable_version_instead.ex | 7 +- lib/ash_hq/docs/resources/module/module.ex | 6 + lib/ash_hq/docs/resources/option/option.ex | 6 + lib/ash_hq/guardian.ex | 17 -- lib/ash_hq/repo.ex | 2 +- lib/ash_hq/resource.ex | 25 +- lib/ash_hq_web/components/callout_text.ex | 1 + lib/ash_hq_web/components/code_example.ex | 14 +- lib/ash_hq_web/components/doc_sidebar.ex | 19 +- .../components/progressive_heading.ex | 49 ---- lib/ash_hq_web/components/right_nav.ex | 5 +- lib/ash_hq_web/components/search.ex | 7 +- lib/ash_hq_web/components/search_bar.ex | 1 + lib/ash_hq_web/components/tag.ex | 5 +- lib/ash_hq_web/{routes.ex => doc_routes.ex} | 3 +- lib/ash_hq_web/endpoint.ex | 3 + lib/ash_hq_web/helpers.ex | 2 + lib/ash_hq_web/live_user_auth.ex | 17 ++ lib/ash_hq_web/pages/docs.ex | 73 ++--- lib/ash_hq_web/pages/home.ex | 270 ++++++++++-------- lib/ash_hq_web/plugs/auth_access_pipeline.ex | 7 - lib/ash_hq_web/plugs/auth_error_handler.ex | 11 - lib/ash_hq_web/router.ex | 31 +- lib/ash_hq_web/telemetry.ex | 1 + .../layout/{root.html.heex => root.html.eex} | 12 +- .../templates/layout/session.html.eex | 6 +- lib/ash_hq_web/{controllers => }/user_auth.ex | 24 +- lib/ash_hq_web/views/app_view_live.ex | 25 ++ mix.exs | 17 +- mix.lock | 19 +- .../20220806220239_migrate_resources19.exs | 21 ++ .../repo/guides/20220806220239.json | 132 +++++++++ 78 files changed, 1037 insertions(+), 632 deletions(-) create mode 100644 .check.exs create mode 100644 .credo.exs delete mode 100644 lib/ash_hq/accounts/resources/user/preparations/decode_token.ex delete mode 100644 lib/ash_hq/accounts/resources/user/validations/validations.ex delete mode 100644 lib/ash_hq/docs/flows/search/steps/experimental_run_search.ex delete mode 100644 lib/ash_hq/guardian.ex delete mode 100644 lib/ash_hq_web/components/progressive_heading.ex rename lib/ash_hq_web/{routes.ex => doc_routes.ex} (97%) create mode 100644 lib/ash_hq_web/live_user_auth.ex delete mode 100644 lib/ash_hq_web/plugs/auth_access_pipeline.ex delete mode 100644 lib/ash_hq_web/plugs/auth_error_handler.ex rename lib/ash_hq_web/templates/layout/{root.html.heex => root.html.eex} (62%) rename lib/ash_hq_web/{controllers => }/user_auth.ex (89%) create mode 100644 priv/repo/migrations/20220806220239_migrate_resources19.exs create mode 100644 priv/resource_snapshots/repo/guides/20220806220239.json diff --git a/.check.exs b/.check.exs new file mode 100644 index 0000000..5c712d6 --- /dev/null +++ b/.check.exs @@ -0,0 +1,19 @@ +[ + ## all available options with default values (see `mix check` docs for description) + # parallel: true, + # skipped: true, + + ## list of tools (see `mix check` docs for defaults) + tools: [ + ## curated tools may be disabled (e.g. the check for compilation warnings) + # {:compiler, false}, + + ## ...or adjusted (e.g. use one-line formatter for more compact credo output) + # {:credo, "mix credo --format oneline"}, + ## custom new tools may be added (mix tasks or arbitrary commands) + # {:my_mix_task, command: "mix release", env: %{"MIX_ENV" => "prod"}}, + # {:my_arbitrary_tool, command: "npm test", cd: "assets"}, + # {:my_arbitrary_script, command: ["my_script", "argument with spaces"], cd: "scripts"} + {:npm_test, false} + ] +] diff --git a/.credo.exs b/.credo.exs new file mode 100644 index 0000000..a890f0a --- /dev/null +++ b/.credo.exs @@ -0,0 +1,209 @@ +# This file contains the configuration for Credo and you are probably reading +# this after creating it with `mix credo.gen.config`. +# +# If you find anything wrong or unclear in this file, please report an +# issue on GitHub: https://github.com/rrrene/credo/issues +# +%{ + # + # You can have as many configs as you like in the `configs:` field. + configs: [ + %{ + # + # Run any config using `mix credo -C `. If no config name is given + # "default" is used. + # + name: "default", + # + # These are the files included in the analysis: + files: %{ + # + # You can give explicit globs or simply directories. + # In the latter case `**/*.{ex,exs}` will be used. + # + included: [ + "lib/", + "src/", + "test/", + "web/", + "apps/*/lib/", + "apps/*/src/", + "apps/*/test/", + "apps/*/web/" + ], + excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"] + }, + # + # Load and configure plugins here: + # + plugins: [], + # + # If you create your own checks, you must specify the source files for + # them here, so they can be loaded by Credo before running the analysis. + # + requires: [], + # + # If you want to enforce a style guide and need a more traditional linting + # experience, you can change `strict` to `true` below: + # + strict: false, + # + # To modify the timeout for parsing files, change this value: + # + parse_timeout: 5000, + # + # If you want to use uncolored output by default, you can change `color` + # to `false` below: + # + color: true, + # + # You can customize the parameters of any check by adding a second element + # to the tuple. + # + # To disable a check put `false` as second element: + # + # {Credo.Check.Design.DuplicatedCode, false} + # + checks: %{ + enabled: [ + # + ## Consistency Checks + # + {Credo.Check.Consistency.ExceptionNames, []}, + {Credo.Check.Consistency.LineEndings, []}, + {Credo.Check.Consistency.ParameterPatternMatching, []}, + {Credo.Check.Consistency.SpaceAroundOperators, []}, + {Credo.Check.Consistency.SpaceInParentheses, []}, + {Credo.Check.Consistency.TabsOrSpaces, []}, + + # + ## Design Checks + # + # You can customize the priority of any check + # Priority values are: `low, normal, high, higher` + # + {Credo.Check.Design.AliasUsage, false}, + # You can also customize the exit_status of each check. + # If you don't want TODO comments to cause `mix credo` to fail, just + # set this value to 0 (zero). + # + {Credo.Check.Design.TagTODO, [exit_status: 2]}, + {Credo.Check.Design.TagFIXME, []}, + + # + ## Readability Checks + # + {Credo.Check.Readability.AliasOrder, []}, + {Credo.Check.Readability.FunctionNames, []}, + {Credo.Check.Readability.LargeNumbers, []}, + {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, + {Credo.Check.Readability.ModuleAttributeNames, []}, + {Credo.Check.Readability.ModuleDoc, []}, + {Credo.Check.Readability.ModuleNames, []}, + {Credo.Check.Readability.ParenthesesInCondition, []}, + {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, + {Credo.Check.Readability.PipeIntoAnonymousFunctions, []}, + {Credo.Check.Readability.PredicateFunctionNames, []}, + {Credo.Check.Readability.PreferImplicitTry, []}, + {Credo.Check.Readability.RedundantBlankLines, []}, + {Credo.Check.Readability.Semicolons, []}, + {Credo.Check.Readability.SpaceAfterCommas, []}, + {Credo.Check.Readability.StringSigils, []}, + {Credo.Check.Readability.TrailingBlankLine, []}, + {Credo.Check.Readability.TrailingWhiteSpace, []}, + {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, + {Credo.Check.Readability.VariableNames, []}, + {Credo.Check.Readability.WithSingleClause, []}, + + # + ## Refactoring Opportunities + # + {Credo.Check.Refactor.Apply, []}, + {Credo.Check.Refactor.CondStatements, []}, + {Credo.Check.Refactor.CyclomaticComplexity, false}, + {Credo.Check.Refactor.FunctionArity, []}, + {Credo.Check.Refactor.LongQuoteBlocks, []}, + {Credo.Check.Refactor.MatchInCondition, []}, + {Credo.Check.Refactor.MapJoin, []}, + {Credo.Check.Refactor.NegatedConditionsInUnless, []}, + {Credo.Check.Refactor.NegatedConditionsWithElse, []}, + {Credo.Check.Refactor.Nesting, [max_nesting: 4]}, + {Credo.Check.Refactor.UnlessWithElse, []}, + {Credo.Check.Refactor.WithClauses, []}, + {Credo.Check.Refactor.FilterFilter, []}, + {Credo.Check.Refactor.RejectReject, []}, + {Credo.Check.Refactor.RedundantWithClauseResult, []}, + + # + ## Warnings + # + {Credo.Check.Warning.ApplicationConfigInModuleAttribute, []}, + {Credo.Check.Warning.BoolOperationOnSameValues, []}, + {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, + {Credo.Check.Warning.IExPry, []}, + {Credo.Check.Warning.IoInspect, []}, + {Credo.Check.Warning.OperationOnSameValues, []}, + {Credo.Check.Warning.OperationWithConstantResult, []}, + {Credo.Check.Warning.RaiseInsideRescue, []}, + {Credo.Check.Warning.SpecWithStruct, []}, + {Credo.Check.Warning.WrongTestFileExtension, []}, + {Credo.Check.Warning.UnusedEnumOperation, []}, + {Credo.Check.Warning.UnusedFileOperation, []}, + {Credo.Check.Warning.UnusedKeywordOperation, []}, + {Credo.Check.Warning.UnusedListOperation, []}, + {Credo.Check.Warning.UnusedPathOperation, []}, + {Credo.Check.Warning.UnusedRegexOperation, []}, + {Credo.Check.Warning.UnusedStringOperation, []}, + {Credo.Check.Warning.UnusedTupleOperation, []}, + {Credo.Check.Warning.UnsafeExec, []} + ], + disabled: [ + # + # Checks scheduled for next check update (opt-in for now, just replace `false` with `[]`) + + # + # Controversial and experimental checks (opt-in, just move the check to `:enabled` + # and be sure to use `mix credo --strict` to see low priority checks) + # + {Credo.Check.Consistency.MultiAliasImportRequireUse, []}, + {Credo.Check.Consistency.UnusedVariableNames, []}, + {Credo.Check.Design.DuplicatedCode, []}, + {Credo.Check.Design.SkipTestWithoutComment, []}, + {Credo.Check.Readability.AliasAs, []}, + {Credo.Check.Readability.BlockPipe, []}, + {Credo.Check.Readability.ImplTrue, []}, + {Credo.Check.Readability.MultiAlias, []}, + {Credo.Check.Readability.NestedFunctionCalls, []}, + {Credo.Check.Readability.SeparateAliasRequire, []}, + {Credo.Check.Readability.SingleFunctionToBlockPipe, []}, + {Credo.Check.Readability.SinglePipe, []}, + {Credo.Check.Readability.Specs, []}, + {Credo.Check.Readability.StrictModuleLayout, []}, + {Credo.Check.Readability.WithCustomTaggedTuple, []}, + {Credo.Check.Refactor.ABCSize, []}, + {Credo.Check.Refactor.AppendSingleItem, []}, + {Credo.Check.Refactor.DoubleBooleanNegation, []}, + {Credo.Check.Refactor.FilterReject, []}, + {Credo.Check.Refactor.IoPuts, []}, + {Credo.Check.Refactor.MapMap, []}, + {Credo.Check.Refactor.ModuleDependencies, []}, + {Credo.Check.Refactor.NegatedIsNil, []}, + {Credo.Check.Refactor.PipeChainStart, []}, + {Credo.Check.Refactor.RejectFilter, []}, + {Credo.Check.Refactor.VariableRebinding, []}, + {Credo.Check.Warning.LazyLogging, []}, + {Credo.Check.Warning.LeakyEnvironment, []}, + {Credo.Check.Warning.MapGetUnsafePass, []}, + {Credo.Check.Warning.MixEnv, []}, + {Credo.Check.Warning.UnsafeToAtom, []} + + # {Credo.Check.Refactor.MapInto, []}, + + # + # Custom checks can be created using `mix credo.gen.check`. + # + ] + } + } + ] +} diff --git a/.formatter.exs b/.formatter.exs index 8bbdca5..6ef8bf3 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -11,6 +11,7 @@ render_attributes: 1, use_path_for_name?: 1, sanitized_name_attribute: 1, - show_docs_on: 1 + show_docs_on: 1, + header_ids?: 1 ] ] diff --git a/config/config.exs b/config/config.exs index b69b614..5cb03e5 100644 --- a/config/config.exs +++ b/config/config.exs @@ -68,3 +68,9 @@ config :phoenix, :json_library, Jason # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{config_env()}.exs" + +config :plug_content_security_policy, + nonces_for: [:script_src], + directives: %{ + img_src: ~w('self' data data:) + } diff --git a/config/dev.exs b/config/dev.exs index fd399a4..666b837 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -83,3 +83,6 @@ config :phoenix, :stacktrace_depth, 20 config :phoenix, :plug_init_mode, :runtime config :ash_hq, AshHq.Mailer, adapter: Swoosh.Adapters.Local + +config :plug_content_security_policy, + report_only: true diff --git a/config/prod.exs b/config/prod.exs index 6bc0d1a..d3cf905 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -52,3 +52,9 @@ config :logger, level: :info config :ash_hq, AshHq.Mailer, adapter: Swoosh.Adapters.Postmark config :swoosh, :api_client, Swoosh.ApiClient.Finch + +config :plug_content_security_policy, + nonces_for: [:script_src], + directives: %{ + img_src: ~w('self' data data:) + } diff --git a/config/test.exs b/config/test.exs index d84dc2d..b6f68fd 100644 --- a/config/test.exs +++ b/config/test.exs @@ -30,3 +30,6 @@ config :logger, level: :warn config :phoenix, :plug_init_mode, :runtime config :ash_hq, AshHq.Mailer, adapter: Swoosh.Adapters.Local + +config :plug_content_security_policy, + report_only: true diff --git a/lib/ash_hq/accounts/accounts.ex b/lib/ash_hq/accounts/accounts.ex index 9b62bbf..c9af965 100644 --- a/lib/ash_hq/accounts/accounts.ex +++ b/lib/ash_hq/accounts/accounts.ex @@ -1,3 +1,6 @@ defmodule AshHq.Accounts do + @moduledoc """ + Handles user and user token related operations/state + """ use Ash.Api, otp_app: :ash_hq end diff --git a/lib/ash_hq/accounts/email_notifier.ex b/lib/ash_hq/accounts/email_notifier.ex index b4adca6..0f77382 100644 --- a/lib/ash_hq/accounts/email_notifier.ex +++ b/lib/ash_hq/accounts/email_notifier.ex @@ -1,4 +1,8 @@ defmodule AshHq.Accounts.EmailNotifier do + @moduledoc """ + Hooks into resource notifications on the user token resource to send emails + """ + def notify(%Ash.Notifier.Notification{ resource: AshHq.Accounts.UserToken, action: %{name: :build_email_token}, diff --git a/lib/ash_hq/accounts/emails.ex b/lib/ash_hq/accounts/emails.ex index 00de067..2dac264 100644 --- a/lib/ash_hq/accounts/emails.ex +++ b/lib/ash_hq/accounts/emails.ex @@ -1,4 +1,8 @@ defmodule AshHq.Accounts.Emails do + @moduledoc """ + Delivers emails. + """ + import Swoosh.Email def deliver_confirmation_instructions(user, url) do diff --git a/lib/ash_hq/accounts/preparations/determine_days_for_token.ex b/lib/ash_hq/accounts/preparations/determine_days_for_token.ex index 1b276f5..a618321 100644 --- a/lib/ash_hq/accounts/preparations/determine_days_for_token.ex +++ b/lib/ash_hq/accounts/preparations/determine_days_for_token.ex @@ -1,9 +1,10 @@ defmodule AshHq.Accounts.Preparations.DetermineDaysForToken do - use Ash.Resource.Preparation + @moduledoc """ + Sets a `days_for_token` context on the query. - def determine_days_for_token() do - {__MODULE__, []} - end + This corresponds to how many days the token should be considered valid. See `AshHq.Accounts.User.Helpers` for more. + """ + use Ash.Resource.Preparation def prepare(query, _opts, _) do Ash.Query.put_context( diff --git a/lib/ash_hq/accounts/preparations/set_hashed_token.ex b/lib/ash_hq/accounts/preparations/set_hashed_token.ex index da73c11..db359a5 100644 --- a/lib/ash_hq/accounts/preparations/set_hashed_token.ex +++ b/lib/ash_hq/accounts/preparations/set_hashed_token.ex @@ -1,4 +1,8 @@ defmodule AshHq.Accounts.Preparations.SetHashedToken do + @moduledoc """ + Takes a provided token and hashes it, setting it as the context `hashed_token` + """ + use Ash.Resource.Preparation @hash_algorithm :sha256 diff --git a/lib/ash_hq/accounts/registry.ex b/lib/ash_hq/accounts/registry.ex index 31704a1..9a51e99 100644 --- a/lib/ash_hq/accounts/registry.ex +++ b/lib/ash_hq/accounts/registry.ex @@ -1,4 +1,6 @@ defmodule AshHq.Accounts.Registry do + @moduledoc false + use Ash.Registry, extensions: [Ash.Registry.ResourceValidations] diff --git a/lib/ash_hq/accounts/resources/user/changes/remove_all_tokens.ex b/lib/ash_hq/accounts/resources/user/changes/remove_all_tokens.ex index 6005cf9..06c186c 100644 --- a/lib/ash_hq/accounts/resources/user/changes/remove_all_tokens.ex +++ b/lib/ash_hq/accounts/resources/user/changes/remove_all_tokens.ex @@ -1,4 +1,9 @@ defmodule AshHq.Accounts.User.Changes.RemoveAllTokens do + @moduledoc """ + Removes all tokens for a given user. + + Since Ash does not yet support bulk actions, this goes straight to the data layer. + """ use Ash.Resource.Change require Ash.Query diff --git a/lib/ash_hq/accounts/resources/user/helpers.ex b/lib/ash_hq/accounts/resources/user/helpers.ex index c3c32b4..f28d1f8 100644 --- a/lib/ash_hq/accounts/resources/user/helpers.ex +++ b/lib/ash_hq/accounts/resources/user/helpers.ex @@ -1,4 +1,5 @@ defmodule AshHq.Accounts.User.Helpers do + @moduledoc "Contains values used in various places for authentication" @reset_password_validity_in_days 1 @confirm_validity_in_days 7 @change_email_validity_in_days 7 diff --git a/lib/ash_hq/accounts/resources/user/preparations/decode_token.ex b/lib/ash_hq/accounts/resources/user/preparations/decode_token.ex deleted file mode 100644 index 4c86e80..0000000 --- a/lib/ash_hq/accounts/resources/user/preparations/decode_token.ex +++ /dev/null @@ -1,28 +0,0 @@ -defmodule AshHq.Accounts.User.Preparations.DecodeToken do - use Ash.Resource.Preparation - - alias Ash.Error.Query.InvalidArgument - - def prepare(query, _opts, _) do - case Ash.Query.get_argument(query, :token) do - nil -> - query - - token -> - case Base.url_decode64(token, padding: false) do - {:ok, decoded} -> - Ash.Query.set_argument( - query, - :token, - decoded - ) - - :error -> - Ash.Query.add_error( - query, - InvalidArgument.exception(field: :token, message: "could not be decoded") - ) - end - end - end -end diff --git a/lib/ash_hq/accounts/resources/user/preparations/validate_password.ex b/lib/ash_hq/accounts/resources/user/preparations/validate_password.ex index c3593d0..2919e2f 100644 --- a/lib/ash_hq/accounts/resources/user/preparations/validate_password.ex +++ b/lib/ash_hq/accounts/resources/user/preparations/validate_password.ex @@ -1,4 +1,10 @@ defmodule AshHq.Accounts.User.Preparations.ValidatePassword do + @moduledoc """ + Given the result of a query for users, and a password argument, ensures that the `password` is valid. + + If there is more or less than one result, or if the password is invalid, then this removes the results of the query. + In this way, you can't tell from the outside wether or not the password was invalid or there was no matching account. + """ use Ash.Resource.Preparation def prepare(query, _opts, _) do diff --git a/lib/ash_hq/accounts/resources/user/user.ex b/lib/ash_hq/accounts/resources/user/user.ex index f23d1cb..7eb5619 100644 --- a/lib/ash_hq/accounts/resources/user/user.ex +++ b/lib/ash_hq/accounts/resources/user/user.ex @@ -1,21 +1,9 @@ defmodule AshHq.Accounts.User do - use Ash.Resource, + @moduledoc false + + use AshHq.Resource, data_layer: AshPostgres.DataLayer - alias AshHq.Accounts.Preparations, warn: false - alias AshHq.Accounts.User.Preparations, as: UserPreparations, warn: false - alias AshHq.Accounts.User.Changes, warn: false - alias AshHq.Accounts.User.Validations, warn: false - - identities do - identity :unique_email, [:email] - end - - postgres do - table "users" - repo AshHq.Repo - end - actions do defaults [:read] @@ -23,7 +11,7 @@ defmodule AshHq.Accounts.User do argument :email, :string, allow_nil?: false, sensitive?: true argument :password, :string, allow_nil?: false, sensitive?: true - prepare UserPreparations.ValidatePassword + prepare AshHq.Accounts.User.Preparations.ValidatePassword filter expr(email == ^arg(:email)) end @@ -31,7 +19,7 @@ defmodule AshHq.Accounts.User do read :by_token do argument :token, :url_encoded_binary, allow_nil?: false argument :context, :string, allow_nil?: false - prepare Preparations.DetermineDaysForToken + prepare AshHq.Accounts.Preparations.DetermineDaysForToken filter expr( token.token == ^arg(:token) and token.context == ^arg(:context) and @@ -43,8 +31,8 @@ defmodule AshHq.Accounts.User do argument :token, :url_encoded_binary, allow_nil?: false argument :context, :string, allow_nil?: false - prepare Preparations.SetHashedToken - prepare Preparations.DetermineDaysForToken + prepare AshHq.Accounts.Preparations.SetHashedToken + prepare AshHq.Accounts.Preparations.DetermineDaysForToken filter expr( token.created_at > ago(^context(:days_for_token), :day) and @@ -63,7 +51,7 @@ defmodule AshHq.Accounts.User do min_length: 12 ] - change Changes.HashPassword + change AshHq.Accounts.User.Changes.HashPassword end update :deliver_user_confirmation_instructions do @@ -74,7 +62,7 @@ defmodule AshHq.Accounts.User do end validate attribute_equals(:confirmed_at, nil), message: "already confirmed" - change Changes.CreateEmailConfirmationToken + change AshHq.Accounts.User.Changes.CreateEmailConfirmationToken end update :deliver_update_email_instructions do @@ -86,11 +74,11 @@ defmodule AshHq.Accounts.User do constraints arity: 1 end - validate Validations.ValidateCurrentPassword + validate AshHq.Accounts.User.Validations.ValidateCurrentPassword validate changing(:email) change prevent_change(:email) - change Changes.CreateEmailUpdateToken + change AshHq.Accounts.User.Changes.CreateEmailUpdateToken end update :deliver_user_reset_password_instructions do @@ -100,21 +88,21 @@ defmodule AshHq.Accounts.User do constraints arity: 1 end - change Changes.CreateResetPasswordToken + change AshHq.Accounts.User.Changes.CreateResetPasswordToken end update :logout do accept [] - change Changes.RemoveAllTokens + change AshHq.Accounts.User.Changes.RemoveAllTokens end update :change_email do accept [] argument :token, :url_encoded_binary - change Changes.GetEmailFromToken - change Changes.DeleteEmailChangeTokens + change AshHq.Accounts.User.Changes.GetEmailFromToken + change AshHq.Accounts.User.Changes.DeleteEmailChangeTokens end update :change_password do @@ -131,10 +119,10 @@ defmodule AshHq.Accounts.User do argument :current_password, :string validate confirm(:password, :password_confirmation) - validate Validations.ValidateCurrentPassword + validate AshHq.Accounts.User.Validations.ValidateCurrentPassword - change Changes.HashPassword - change Changes.RemoveAllTokens + change AshHq.Accounts.User.Changes.HashPassword + change AshHq.Accounts.User.Changes.RemoveAllTokens end update :confirm do @@ -142,7 +130,7 @@ defmodule AshHq.Accounts.User do argument :delete_confirm_tokens, :boolean, default: false change set_attribute(:confirmed_at, &DateTime.utc_now/0) - change Changes.DeleteConfirmTokens + change AshHq.Accounts.User.Changes.DeleteConfirmTokens end end @@ -162,12 +150,27 @@ defmodule AshHq.Accounts.User do update_timestamp :updated_at end + identities do + identity :unique_email, [:email] + end + + postgres do + table "users" + repo AshHq.Repo + end + relationships do has_one :token, AshHq.Accounts.UserToken, destination_field: :user_id, private?: true end + resource do + description """ + Represents the user of a system. + """ + end + validations do validate match(:email, ~r/^[^\s]+@[^\s]+$/, "must have the @ sign and no spaces") end diff --git a/lib/ash_hq/accounts/resources/user/validations/validate_current_password.ex b/lib/ash_hq/accounts/resources/user/validations/validate_current_password.ex index 8ecc352..1ac9461 100644 --- a/lib/ash_hq/accounts/resources/user/validations/validate_current_password.ex +++ b/lib/ash_hq/accounts/resources/user/validations/validate_current_password.ex @@ -1,4 +1,10 @@ defmodule AshHq.Accounts.User.Validations.ValidateCurrentPassword do + @moduledoc """ + Confirms that the provided password is valid. + + This is useful for actions that should only be able to be taken on a given user if you know + their password (like changing the email, for example). + """ use Ash.Resource.Validation @impl true diff --git a/lib/ash_hq/accounts/resources/user/validations/validations.ex b/lib/ash_hq/accounts/resources/user/validations/validations.ex deleted file mode 100644 index 3716e0a..0000000 --- a/lib/ash_hq/accounts/resources/user/validations/validations.ex +++ /dev/null @@ -1,7 +0,0 @@ -defmodule AshHq.Accounts.User.Validations do - alias AshHq.Accounts.User.Validations - - def validate_current_password() do - {Validations.ValidateCurrentPassword, []} - end -end diff --git a/lib/ash_hq/accounts/resources/user_token/changes/build_hashed_token.ex b/lib/ash_hq/accounts/resources/user_token/changes/build_hashed_token.ex index de66f37..77a7aab 100644 --- a/lib/ash_hq/accounts/resources/user_token/changes/build_hashed_token.ex +++ b/lib/ash_hq/accounts/resources/user_token/changes/build_hashed_token.ex @@ -6,10 +6,6 @@ defmodule AshHq.Accounts.UserToken.Changes.BuildHashedToken do @rand_size 32 @hash_algorithm :sha256 - def build_hashed_token() do - {__MODULE__, []} - end - def change(changeset, _opts, _context) do token = :crypto.strong_rand_bytes(@rand_size) diff --git a/lib/ash_hq/accounts/resources/user_token/changes/build_session_token.ex b/lib/ash_hq/accounts/resources/user_token/changes/build_session_token.ex index 17b1f90..a6d9cf8 100644 --- a/lib/ash_hq/accounts/resources/user_token/changes/build_session_token.ex +++ b/lib/ash_hq/accounts/resources/user_token/changes/build_session_token.ex @@ -4,10 +4,6 @@ defmodule AshHq.Accounts.UserToken.Changes.BuildSessionToken do use Ash.Resource.Change @rand_size 32 - def build_session_token() do - {__MODULE__, []} - end - def change(changeset, _opts, _context) do token = :crypto.strong_rand_bytes(@rand_size) diff --git a/lib/ash_hq/accounts/resources/user_token/user_token.ex b/lib/ash_hq/accounts/resources/user_token/user_token.ex index dd1018d..f0fa0e4 100644 --- a/lib/ash_hq/accounts/resources/user_token/user_token.ex +++ b/lib/ash_hq/accounts/resources/user_token/user_token.ex @@ -1,32 +1,18 @@ defmodule AshHq.Accounts.UserToken do - use Ash.Resource, + @moduledoc false + + use AshHq.Resource, data_layer: AshPostgres.DataLayer, notifiers: [AshHq.Accounts.EmailNotifier] - alias AshHq.Accounts.UserToken.Changes, warn: false - alias AshHq.Accounts.Preparations, warn: false - - postgres do - table "user_tokens" - repo AshHq.Repo - - references do - reference :user, on_delete: :delete, on_update: :update - end - end - - identities do - identity :token_context, [:context, :token] - end - actions do defaults [:read] read :verify_email_token do argument :token, :url_encoded_binary, allow_nil?: false argument :context, :string, allow_nil?: false - prepare Preparations.SetHashedToken - prepare Preparations.DetermineDaysForToken + prepare AshHq.Accounts.Preparations.SetHashedToken + prepare AshHq.Accounts.Preparations.DetermineDaysForToken filter expr( token == ^context(:hashed_token) and context == ^arg(:context) and @@ -41,7 +27,7 @@ defmodule AshHq.Accounts.UserToken do change manage_relationship(:user, type: :replace) change set_attribute(:context, "session") - change Changes.BuildSessionToken + change AshHq.Accounts.UserToken.Changes.BuildSessionToken end create :build_email_token do @@ -50,7 +36,7 @@ defmodule AshHq.Accounts.UserToken do argument :user, :map change manage_relationship(:user, type: :replace) - change Changes.BuildHashedToken + change AshHq.Accounts.UserToken.Changes.BuildHashedToken end end @@ -64,7 +50,26 @@ defmodule AshHq.Accounts.UserToken do create_timestamp :created_at end + identities do + identity :token_context, [:context, :token] + end + + postgres do + table "user_tokens" + repo AshHq.Repo + + references do + reference :user, on_delete: :delete, on_update: :update + end + end + relationships do belongs_to :user, AshHq.Accounts.User end + + resource do + description """ + Represents a token allowing a user to log in, reset their password, or confirm their email. + """ + end end diff --git a/lib/ash_hq/docs/changes/add_arg_to_relationship.ex b/lib/ash_hq/docs/changes/add_arg_to_relationship.ex index 87fe92a..f6cc638 100644 --- a/lib/ash_hq/docs/changes/add_arg_to_relationship.ex +++ b/lib/ash_hq/docs/changes/add_arg_to_relationship.ex @@ -1,4 +1,8 @@ defmodule AshHq.Docs.Changes.AddArgToRelationship do + @moduledoc """ + A general utility to pass an argument of the current action down to a relationship change + that is being made. + """ use Ash.Resource.Change def change(changeset, opts, _) do diff --git a/lib/ash_hq/docs/docs.ex b/lib/ash_hq/docs/docs.ex index 673b64e..dc6ef73 100644 --- a/lib/ash_hq/docs/docs.ex +++ b/lib/ash_hq/docs/docs.ex @@ -1,4 +1,7 @@ defmodule AshHq.Docs do + @moduledoc """ + Handles documentation data. + """ use Ash.Api, otp_app: :ash_hq execution do diff --git a/lib/ash_hq/docs/extensions/render_markdown/changes/render_markdown.ex b/lib/ash_hq/docs/extensions/render_markdown/changes/render_markdown.ex index 1eab4ba..d11ab24 100644 --- a/lib/ash_hq/docs/extensions/render_markdown/changes/render_markdown.ex +++ b/lib/ash_hq/docs/extensions/render_markdown/changes/render_markdown.ex @@ -1,4 +1,8 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown.Changes.RenderMarkdown do + @moduledoc """ + Writes a markdown text attribute to its corresponding html attribute. + """ + use Ash.Resource.Change def change(changeset, opts, _) do diff --git a/lib/ash_hq/docs/extensions/render_markdown/render_markdown.ex b/lib/ash_hq/docs/extensions/render_markdown/render_markdown.ex index c6cbe40..8d383ea 100644 --- a/lib/ash_hq/docs/extensions/render_markdown/render_markdown.ex +++ b/lib/ash_hq/docs/extensions/render_markdown/render_markdown.ex @@ -1,4 +1,8 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown do + @moduledoc """ + Sets up markdown text attributes to be transformed to html (in another column). + """ + @render_markdown %Ash.Dsl.Section{ name: :render_markdown, schema: [ diff --git a/lib/ash_hq/docs/extensions/render_markdown/transformers/add_render_markdown_structure.ex b/lib/ash_hq/docs/extensions/render_markdown/transformers/add_render_markdown_structure.ex index 41040f7..26a53e1 100644 --- a/lib/ash_hq/docs/extensions/render_markdown/transformers/add_render_markdown_structure.ex +++ b/lib/ash_hq/docs/extensions/render_markdown/transformers/add_render_markdown_structure.ex @@ -1,4 +1,11 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown.Transformers.AddRenderMarkdownStructure do + @moduledoc """ + Adds the resource structure required for the render markdown extension + + Currently, this simply adds the relevant change and adds the destination + attributes to the `allow_nil_input` of each action, since it will be adding them automatically. + """ + use Ash.Dsl.Transformer alias Ash.Dsl.Transformer @@ -34,7 +41,5 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown.Transformers.AddRenderMarkdownStr end) end - def after?(Ash.Resource.Transformers.DefaultAccept), do: true - def after?(Ash.Resource.Transformers.SetPrimaryActions), do: true - def after?(_), do: false + def after?(_), do: true end diff --git a/lib/ash_hq/docs/extensions/search/changes/sanitize_name.ex b/lib/ash_hq/docs/extensions/search/changes/sanitize_name.ex index 2e3674f..7b84c3d 100644 --- a/lib/ash_hq/docs/extensions/search/changes/sanitize_name.ex +++ b/lib/ash_hq/docs/extensions/search/changes/sanitize_name.ex @@ -1,4 +1,8 @@ defmodule AshHq.Docs.Extensions.Search.Changes.SanitizeName do + @moduledoc """ + Writes the sanitized (url-safe) name of a record + """ + use Ash.Resource.Change def change(changeset, opts, _) do diff --git a/lib/ash_hq/docs/extensions/search/preparations/load_search_data.ex b/lib/ash_hq/docs/extensions/search/preparations/load_search_data.ex index 65c48e7..b2883bc 100644 --- a/lib/ash_hq/docs/extensions/search/preparations/load_search_data.ex +++ b/lib/ash_hq/docs/extensions/search/preparations/load_search_data.ex @@ -1,4 +1,7 @@ defmodule AshHq.Extensions.Search.Preparations.LoadSearchData do + @moduledoc """ + Ensures that any data needed for search results is loaded. + """ use Ash.Resource.Preparation def prepare(query, _, _) do @@ -10,7 +13,7 @@ defmodule AshHq.Extensions.Search.Preparations.LoadSearchData do |> Ash.Query.load( search_headline: [query: query_string], match_rank: [query: query_string], - name_matches: %{query: query_string, similarity: 0.7} + name_matches: [query: query_string, similarity: 0.7] ) |> Ash.Query.load(to_load) |> Ash.Query.sort(match_rank: {:asc, %{query: query_string}}) diff --git a/lib/ash_hq/docs/extensions/search/search.ex b/lib/ash_hq/docs/extensions/search/search.ex index 3136d5a..fba57be 100644 --- a/lib/ash_hq/docs/extensions/search/search.ex +++ b/lib/ash_hq/docs/extensions/search/search.ex @@ -1,4 +1,10 @@ defmodule AshHq.Docs.Extensions.Search do + @moduledoc """ + Sets a resource up to be searchable. See the configuration for explanation of the options. + + This generally involves ensuring that there is a url safe name attribute to be used in routing, + and configuring how the item will be searched for. + """ alias Ash.Dsl.Extension @search %Ash.Dsl.Section{ @@ -24,6 +30,12 @@ defmodule AshHq.Docs.Extensions.Search do doc: "The name of the attribute to store the sanitized name in. If not set, will default to the `sanitized_`" ], + auto_sanitize_name_attribute?: [ + type: :boolean, + default: true, + doc: + "Wether or not the name attribute will be sanitized by default. If not, you should have a change on the resource that sets it." + ], show_docs_on: [ type: :atom, doc: @@ -72,6 +84,7 @@ defmodule AshHq.Docs.Extensions.Search do Extension.get_opt(resource, [:search], :doc_attribute, nil) end + # sobelow_skip ["DOS.BinToAtom"] def sanitized_name_attribute(resource) do Extension.get_opt( resource, @@ -81,6 +94,15 @@ defmodule AshHq.Docs.Extensions.Search do ) end + def auto_sanitize_name_attribute?(resource) do + Extension.get_opt( + resource, + [:search], + :auto_sanitize_name_attribute?, + true + ) + end + def use_path_for_name?(resource) do Extension.get_opt( resource, diff --git a/lib/ash_hq/docs/extensions/search/transformers/add_search_structure.ex b/lib/ash_hq/docs/extensions/search/transformers/add_search_structure.ex index 2cd9dba..350e30a 100644 --- a/lib/ash_hq/docs/extensions/search/transformers/add_search_structure.ex +++ b/lib/ash_hq/docs/extensions/search/transformers/add_search_structure.ex @@ -1,4 +1,18 @@ defmodule AshHq.Docs.Extensions.Search.Transformers.AddSearchStructure do + @moduledoc """ + Adds the resource structure required by the search extension. + + * Adds a sanitized name attribute if it doesn't already exist + * Adds a change to set the sanitized name, if it should. + * Adds a `search_headline` calculation + * Adds a `name_matches` calculation + * Adds a `matches` calculation + * Adds relevant indexes using custom sql statements + * Adds an `html_for` calculation, that shows the html if a certain field matches, so docs are only shown on the right pages + * Adds a `match_rank` calculation. + * Adds a search action + * Adds a code interface for the search action + """ use Ash.Dsl.Transformer import Ash.Filter.TemplateHelpers require Ash.Query @@ -19,9 +33,9 @@ defmodule AshHq.Docs.Extensions.Search.Transformers.AddSearchStructure do {:ok, dsl_state - |> add_code_interface() |> add_sanitized_name(config) |> add_search_action(config) + |> add_code_interface() |> add_search_headline_calculation(config) |> add_name_matches_calculation(config) |> add_matches_calculation(config) @@ -55,28 +69,39 @@ defmodule AshHq.Docs.Extensions.Search.Transformers.AddSearchStructure do end defp add_sanitized_name(dsl_state, config) do - dsl_state - |> Transformer.add_entity( - [:attributes], - Transformer.build_entity!( - Ash.Resource.Dsl, - [:attributes], - :attribute, - name: config.sanitized_name_attribute, - type: :string, - allow_nil?: false + dsl_state = + if Ash.Resource.Info.attribute(config.resource, config.sanitized_name_attribute) do + dsl_state + else + Transformer.add_entity( + dsl_state, + [:attributes], + Transformer.build_entity!( + Ash.Resource.Dsl, + [:attributes], + :attribute, + name: config.sanitized_name_attribute, + type: :string, + allow_nil?: false + ) + ) + end + + if AshHq.Docs.Extensions.Search.auto_sanitize_name_attribute?(config.resource) do + Transformer.add_entity( + dsl_state, + [:changes], + Transformer.build_entity!(Ash.Resource.Dsl, [:changes], :change, + change: + {AshHq.Docs.Extensions.Search.Changes.SanitizeName, + source: config.name_attribute, + destination: config.sanitized_name_attribute, + use_path_for_name?: AshHq.Docs.Extensions.Search.use_path_for_name?(config.resource)} + ) ) - ) - |> Transformer.add_entity( - [:changes], - Transformer.build_entity!(Ash.Resource.Dsl, [:changes], :change, - change: - {AshHq.Docs.Extensions.Search.Changes.SanitizeName, - source: config.name_attribute, - destination: config.sanitized_name_attribute, - use_path_for_name?: AshHq.Docs.Extensions.Search.use_path_for_name?(config.resource)} - ) - ) + else + dsl_state + end end defp add_indexes(dsl_state, config) do @@ -275,7 +300,7 @@ defmodule AshHq.Docs.Extensions.Search.Transformers.AddSearchStructure do calculation: Ash.Query.expr( fragment( - "ts_headline('english', ?, plainto_tsquery('english', ?), 'MaxFragments=2,StartSel=\"\", StopSel=')", + ~S[ts_headline('english', ?, plainto_tsquery('english', ?), 'MaxFragments=2,StartSel=\"\", StopSel=')], ^ref(config.doc_attribute), ^arg(:query) ) @@ -296,7 +321,7 @@ defmodule AshHq.Docs.Extensions.Search.Transformers.AddSearchStructure do end end - defp html_for_argument() do + defp html_for_argument do Transformer.build_entity!( Ash.Resource.Dsl, [:calculations, :calculate], @@ -307,7 +332,7 @@ defmodule AshHq.Docs.Extensions.Search.Transformers.AddSearchStructure do ) end - defp query_argument() do + defp query_argument do Transformer.build_entity!( Ash.Resource.Dsl, [:calculations, :calculate], @@ -318,7 +343,7 @@ defmodule AshHq.Docs.Extensions.Search.Transformers.AddSearchStructure do ) end - defp similarity_argument() do + defp similarity_argument do Transformer.build_entity!( Ash.Resource.Dsl, [:calculations, :calculate], @@ -357,7 +382,7 @@ defmodule AshHq.Docs.Extensions.Search.Transformers.AddSearchStructure do ) end - defp search_arguments() do + defp search_arguments do [ Transformer.build_entity!( Ash.Resource.Dsl, @@ -376,7 +401,7 @@ defmodule AshHq.Docs.Extensions.Search.Transformers.AddSearchStructure do ] end - defp search_preparations() do + defp search_preparations do [ Transformer.build_entity!(Ash.Resource.Dsl, [:actions, :read], :prepare, preparation: AshHq.Extensions.Search.Preparations.LoadSearchData diff --git a/lib/ash_hq/docs/extensions/search/types.ex b/lib/ash_hq/docs/extensions/search/types.ex index 24468aa..4977e16 100644 --- a/lib/ash_hq/docs/extensions/search/types.ex +++ b/lib/ash_hq/docs/extensions/search/types.ex @@ -1,4 +1,8 @@ defmodule AshHq.Docs.Extensions.Search.Types do + @moduledoc """ + A static list of all search types that currently exist + """ + @search_types AshHq.Docs.Registry |> Ash.Registry.entries() |> Enum.filter( @@ -7,7 +11,7 @@ defmodule AshHq.Docs.Extensions.Search.Types do |> Enum.map(&AshHq.Docs.Extensions.Search.type/1) |> Enum.uniq() - def types() do + def types do @search_types end end diff --git a/lib/ash_hq/docs/flows/search/search.ex b/lib/ash_hq/docs/flows/search/search.ex index 4966a36..c596977 100644 --- a/lib/ash_hq/docs/flows/search/search.ex +++ b/lib/ash_hq/docs/flows/search/search.ex @@ -1,89 +1,95 @@ defmodule AshHq.Docs.Search do + @moduledoc false + use Ash.Flow flow do - api(AshHq.Docs) + api AshHq.Docs + + description """ + Runs a search over all searchable items. + """ argument :query, :string do - allow_nil?(false) + allow_nil? false constraints trim?: false, allow_empty?: true end argument :library_versions, {:array, :uuid} do - allow_nil?(false) + allow_nil? false end - argument(:types, {:array, :string}) + argument :types, {:array, :string} - returns(:build_results) + returns :build_results end steps do custom :options, AshHq.Docs.Search.Steps.SearchResource do - input(%{ + input %{ query: arg(:query), library_versions: arg(:library_versions), types: arg(:types), resource: AshHq.Docs.Option - }) + } end custom :dsls, AshHq.Docs.Search.Steps.SearchResource do - input(%{ + input %{ query: arg(:query), library_versions: arg(:library_versions), types: arg(:types), resource: AshHq.Docs.Dsl - }) + } end custom :guides, AshHq.Docs.Search.Steps.SearchResource do - input(%{ + input %{ query: arg(:query), library_versions: arg(:library_versions), types: arg(:types), resource: AshHq.Docs.Guide - }) + } end custom :library_versions, AshHq.Docs.Search.Steps.SearchResource do - input(%{ + input %{ query: arg(:query), library_versions: arg(:library_versions), types: arg(:types), resource: AshHq.Docs.LibraryVersion - }) + } end custom :extensions, AshHq.Docs.Search.Steps.SearchResource do - input(%{ + input %{ query: arg(:query), library_versions: arg(:library_versions), types: arg(:types), resource: AshHq.Docs.Extension - }) + } end custom :functions, AshHq.Docs.Search.Steps.SearchResource do - input(%{ + input %{ query: arg(:query), library_versions: arg(:library_versions), types: arg(:types), resource: AshHq.Docs.Function - }) + } end custom :modules, AshHq.Docs.Search.Steps.SearchResource do - input(%{ + input %{ query: arg(:query), library_versions: arg(:library_versions), types: arg(:types), resource: AshHq.Docs.Module - }) + } end custom :build_results, AshHq.Docs.Search.Steps.BuildResults do - input(%{ + input %{ dsls: result(:dsls), options: result(:options), guides: result(:guides), @@ -91,7 +97,7 @@ defmodule AshHq.Docs.Search do extensions: result(:extensions), functions: result(:functions), modules: result(:modules) - }) + } end end end diff --git a/lib/ash_hq/docs/flows/search/steps/build_results.ex b/lib/ash_hq/docs/flows/search/steps/build_results.ex index 2cd4a9f..80239fc 100644 --- a/lib/ash_hq/docs/flows/search/steps/build_results.ex +++ b/lib/ash_hq/docs/flows/search/steps/build_results.ex @@ -1,4 +1,7 @@ defmodule AshHq.Docs.Search.Steps.BuildResults do + @moduledoc """ + Sorts the results of search. + """ use Ash.Flow.Step def run(input, _opts, _context) do diff --git a/lib/ash_hq/docs/flows/search/steps/experimental_run_search.ex b/lib/ash_hq/docs/flows/search/steps/experimental_run_search.ex deleted file mode 100644 index 1d25013..0000000 --- a/lib/ash_hq/docs/flows/search/steps/experimental_run_search.ex +++ /dev/null @@ -1,127 +0,0 @@ -defmodule AshHq.Docs.Search.Steps.RunSearch do - # use Ash.Flow.Step - # import Ecto.Query, only: [from: 2] - # require Ecto.Query - # require Ash.Query - - # @resources AshHq.Docs.Registry - # |> Ash.Registry.entries() - # |> Enum.filter(&(AshHq.Docs.Extensions.Search in Ash.Resource.Info.extensions(&1))) - - # def run(input, _opts, _context) do - # @resources - # |> Enum.reduce(nil, fn resource, query -> - # {:ok, next_query} = - # resource - # |> Ash.Query.for_read(:search, %{ - # library_versions: input[:library_versions], - # query: input[:query] - # }) - # |> Ash.Query.select([:id]) - # |> Ash.Query.data_layer_query() - - # next_query = - # from row in next_query, - # select_merge: %{__metadata__: %{resource: ^to_string(resource)}} - - # if query do - # Ecto.Query.union_all(query, ^next_query) - # else - # next_query - # end - # end) - # |> then(fn query -> - # query = - # from row in query, - # order_by: [ - # fragment( - # "ts_rank(setweight(to_tsvector(?), 'A') || setweight(to_tsvector(?), 'D'), plainto_tsquery(?))", - # field(row, ^name_attribute), - # field(row, ^doc_attribute), - # ^input[:query] - # ) - # ], - # limit: 20 - - # ids = AshHq.Repo.all(query) - - # data = - # ids - # |> Enum.group_by(& &1.__metadata__.resource, & &1) - # |> Enum.reduce(%{}, fn {resource, id_data}, results -> - # resource = Module.concat([resource]) - # primary_read = Ash.Resource.Info.primary_action!(resource, :read).name - # to_load = AshHq.Docs.Extensions.Search.load_for_search(resource) - # ids = Enum.map(id_data, & &1.id) - - # resource - # |> Ash.Query.filter(id in ^ids) - # |> Ash.Query.load( - # search_headline: %{query: input[:query]}, - # name_matches: %{query: input[:query], similarity: 0.7}, - # match_rank: %{query: input[:query]} - # ) - # |> Ash.Query.load(to_load) - # |> Ash.Query.for_read(primary_read) - # |> AshHq.Docs.read!() - # |> Enum.reduce(results, fn item, results -> - # Map.put(results, item.id, item) - # end) - # end) - - # {:ok, - # Enum.map(ids, fn %{id: id} -> - # Map.fetch!(data, id) - # end)} - # end) - # end - - # # read :options, AshHq.Docs.Option, :search do - # # input %{ - # # library_versions: arg(:library_versions), - # # query: arg(:query) - # # } - # # end - - # # read :dsls, AshHq.Docs.Dsl, :search do - # # input %{ - # # library_versions: arg(:library_versions), - # # query: arg(:query) - # # } - # # end - - # # read :guides, AshHq.Docs.Guide, :search do - # # input %{ - # # library_versions: arg(:library_versions), - # # query: arg(:query) - # # } - # # end - - # # read :library_versions, AshHq.Docs.LibraryVersion, :search do - # # input %{ - # # library_versions: arg(:library_versions), - # # query: arg(:query) - # # } - # # end - - # # read :extensions, AshHq.Docs.Extension, :search do - # # input %{ - # # library_versions: arg(:library_versions), - # # query: arg(:query) - # # } - # # end - - # # read :functions, AshHq.Docs.Function, :search do - # # input %{ - # # library_versions: arg(:library_versions), - # # query: arg(:query) - # # } - # # end - - # # read :modules, AshHq.Docs.Module, :search do - # # input %{ - # # library_versions: arg(:library_versions), - # # query: arg(:query) - # # } - # # end -end diff --git a/lib/ash_hq/docs/flows/search/steps/search_resource.ex b/lib/ash_hq/docs/flows/search/steps/search_resource.ex index 4e0f323..4d41519 100644 --- a/lib/ash_hq/docs/flows/search/steps/search_resource.ex +++ b/lib/ash_hq/docs/flows/search/steps/search_resource.ex @@ -1,4 +1,7 @@ defmodule AshHq.Docs.Search.Steps.SearchResource do + @moduledoc """ + Runs the search action of a given resource, or skips it if it should not be included in the results. + """ use Ash.Flow.Step def run(input, _opts, _context) do diff --git a/lib/ash_hq/docs/importer/importer.ex b/lib/ash_hq/docs/importer/importer.ex index 2205546..110b911 100644 --- a/lib/ash_hq/docs/importer/importer.ex +++ b/lib/ash_hq/docs/importer/importer.ex @@ -7,6 +7,7 @@ defmodule AshHq.Docs.Importer do require Logger require Ash.Query + # sobelow_skip ["Misc.BinToTerm", "Traversal.FileModule"] def import(opts \\ []) do only = opts[:only] || nil only_branches? = opts[:only_branches?] || false diff --git a/lib/ash_hq/docs/registry.ex b/lib/ash_hq/docs/registry.ex index 21abf2d..2b6e98a 100644 --- a/lib/ash_hq/docs/registry.ex +++ b/lib/ash_hq/docs/registry.ex @@ -1,4 +1,5 @@ defmodule AshHq.Docs.Registry do + @moduledoc false use Ash.Registry, extensions: [Ash.Registry.ResourceValidations] diff --git a/lib/ash_hq/docs/resources/dsl/dsl.ex b/lib/ash_hq/docs/resources/dsl/dsl.ex index acff9df..add3db3 100644 --- a/lib/ash_hq/docs/resources/dsl/dsl.ex +++ b/lib/ash_hq/docs/resources/dsl/dsl.ex @@ -1,8 +1,14 @@ defmodule AshHq.Docs.Dsl do + @moduledoc false + use AshHq.Resource, data_layer: AshPostgres.DataLayer, extensions: [AshHq.Docs.Extensions.Search, AshHq.Docs.Extensions.RenderMarkdown] + resource do + description "An entity or section in an Ash DSL" + end + render_markdown do render_attributes doc: :doc_html end diff --git a/lib/ash_hq/docs/resources/extension/extension.ex b/lib/ash_hq/docs/resources/extension/extension.ex index 8eea9b5..818c680 100644 --- a/lib/ash_hq/docs/resources/extension/extension.ex +++ b/lib/ash_hq/docs/resources/extension/extension.ex @@ -1,8 +1,14 @@ defmodule AshHq.Docs.Extension do + @moduledoc false + use AshHq.Resource, data_layer: AshPostgres.DataLayer, extensions: [AshHq.Docs.Extensions.Search, AshHq.Docs.Extensions.RenderMarkdown] + resource do + description "An Ash DSL extension." + end + render_markdown do render_attributes doc: :doc_html end diff --git a/lib/ash_hq/docs/resources/function/function.ex b/lib/ash_hq/docs/resources/function/function.ex index 3e90749..0a27b82 100644 --- a/lib/ash_hq/docs/resources/function/function.ex +++ b/lib/ash_hq/docs/resources/function/function.ex @@ -1,22 +1,28 @@ defmodule AshHq.Docs.Function do + @moduledoc false + use AshHq.Resource, data_layer: AshPostgres.DataLayer, extensions: [AshHq.Docs.Extensions.Search, AshHq.Docs.Extensions.RenderMarkdown] + resource do + description "A function in a module exposed by an Ash library" + end + render_markdown do - render_attributes(doc: :doc_html) - header_ids?(false) + render_attributes doc: :doc_html + header_ids? false end search do doc_attribute :doc - load_for_search([ + load_for_search [ :version_name, :library_name, :module_name, :library_id - ]) + ] type "Code" @@ -28,7 +34,7 @@ defmodule AshHq.Docs.Function do repo AshHq.Repo references do - reference(:library_version, on_delete: :delete) + reference :library_version, on_delete: :delete end end diff --git a/lib/ash_hq/docs/resources/guide/changes/set_route.ex b/lib/ash_hq/docs/resources/guide/changes/set_route.ex index b30e882..50c369c 100644 --- a/lib/ash_hq/docs/resources/guide/changes/set_route.ex +++ b/lib/ash_hq/docs/resources/guide/changes/set_route.ex @@ -1,4 +1,7 @@ defmodule AshHq.Docs.Guide.Changes.SetRoute do + @moduledoc """ + Sets the route of a guide. + """ use Ash.Resource.Change def change(changeset, _, _) do diff --git a/lib/ash_hq/docs/resources/guide/guide.ex b/lib/ash_hq/docs/resources/guide/guide.ex index 7e41712..cdf9a03 100644 --- a/lib/ash_hq/docs/resources/guide/guide.ex +++ b/lib/ash_hq/docs/resources/guide/guide.ex @@ -1,8 +1,13 @@ defmodule AshHq.Docs.Guide do + @moduledoc false use AshHq.Resource, data_layer: AshPostgres.DataLayer, extensions: [AshHq.Docs.Extensions.Search, AshHq.Docs.Extensions.RenderMarkdown] + resource do + description "Represents a markdown guide exposed by a library" + end + render_markdown do render_attributes text: :text_html end @@ -12,6 +17,8 @@ defmodule AshHq.Docs.Guide do type "Guides" load_for_search library_version: [:library_name, :library_display_name] show_docs_on :route + sanitized_name_attribute :route + auto_sanitize_name_attribute?(false) end code_interface do @@ -19,12 +26,7 @@ defmodule AshHq.Docs.Guide do end actions do - defaults [:read, :update, :destroy] - - create :create do - primary? true - allow_nil_input [:route] - end + defaults [:create, :read, :update, :destroy] end changes do diff --git a/lib/ash_hq/docs/resources/library/library.ex b/lib/ash_hq/docs/resources/library/library.ex index 2ca6506..1632ab2 100644 --- a/lib/ash_hq/docs/resources/library/library.ex +++ b/lib/ash_hq/docs/resources/library/library.ex @@ -1,7 +1,12 @@ defmodule AshHq.Docs.Library do + @moduledoc false use AshHq.Resource, data_layer: AshPostgres.DataLayer + resource do + description "Represents a library that will be imported into AshHq" + end + postgres do table "libraries" repo AshHq.Repo diff --git a/lib/ash_hq/docs/resources/library_version/library_version.ex b/lib/ash_hq/docs/resources/library_version/library_version.ex index e4c9000..a07e9bf 100644 --- a/lib/ash_hq/docs/resources/library_version/library_version.ex +++ b/lib/ash_hq/docs/resources/library_version/library_version.ex @@ -1,8 +1,14 @@ defmodule AshHq.Docs.LibraryVersion do + @moduledoc false + use AshHq.Resource, data_layer: AshPostgres.DataLayer, extensions: [AshHq.Docs.Extensions.Search, AshHq.Docs.Extensions.RenderMarkdown] + resource do + description "Represents a version of a library that has been imported." + end + search do name_attribute :version library_version_attribute :id diff --git a/lib/ash_hq/docs/resources/library_version/preparations/sort_by_sortable_version_instead.ex b/lib/ash_hq/docs/resources/library_version/preparations/sort_by_sortable_version_instead.ex index 8194542..d22199b 100644 --- a/lib/ash_hq/docs/resources/library_version/preparations/sort_by_sortable_version_instead.ex +++ b/lib/ash_hq/docs/resources/library_version/preparations/sort_by_sortable_version_instead.ex @@ -1,4 +1,7 @@ defmodule AshHq.Docs.LibraryVersion.Preparations.SortBySortableVersionInstead do + @moduledoc """ + Replaces any sort on `version` by a sort on `sortable_version` instead. + """ use Ash.Resource.Preparation def prepare(query, _, _) do @@ -6,8 +9,8 @@ defmodule AshHq.Docs.LibraryVersion.Preparations.SortBySortableVersionInstead do end defp replace_sort(nil), do: nil - defp replace_sort(:version), do: :version - defp replace_sort({:version, order}), do: {:version, order} + defp replace_sort(:version), do: :sortable_version + defp replace_sort({:version, order}), do: {:sortable_version, order} defp replace_sort(list) when is_list(list), do: Enum.map(list, &replace_sort/1) defp replace_sort(other), do: other end diff --git a/lib/ash_hq/docs/resources/module/module.ex b/lib/ash_hq/docs/resources/module/module.ex index ccfcbc0..8c1b63b 100644 --- a/lib/ash_hq/docs/resources/module/module.ex +++ b/lib/ash_hq/docs/resources/module/module.ex @@ -1,8 +1,14 @@ defmodule AshHq.Docs.Module do + @moduledoc false + use AshHq.Resource, data_layer: AshPostgres.DataLayer, extensions: [AshHq.Docs.Extensions.Search, AshHq.Docs.Extensions.RenderMarkdown] + resource do + description "Represents a module that has been exposed by a library" + end + render_markdown do render_attributes doc: :doc_html end diff --git a/lib/ash_hq/docs/resources/option/option.ex b/lib/ash_hq/docs/resources/option/option.ex index b0c38d5..1de7542 100644 --- a/lib/ash_hq/docs/resources/option/option.ex +++ b/lib/ash_hq/docs/resources/option/option.ex @@ -1,8 +1,14 @@ defmodule AshHq.Docs.Option do + @moduledoc false + use AshHq.Resource, data_layer: AshPostgres.DataLayer, extensions: [AshHq.Docs.Extensions.Search, AshHq.Docs.Extensions.RenderMarkdown] + resource do + description "Represents an option on a DSL section or entity" + end + render_markdown do render_attributes doc: :doc_html end diff --git a/lib/ash_hq/guardian.ex b/lib/ash_hq/guardian.ex deleted file mode 100644 index f7c32b7..0000000 --- a/lib/ash_hq/guardian.ex +++ /dev/null @@ -1,17 +0,0 @@ -defmodule AshHq.Guardian do - use Guardian, otp_app: :ash_hq - - alias AshHq.Accounts - - def subject_for_token(resource, _claims) do - sub = to_string(resource.id) - {:ok, sub} - end - - def resource_from_claims(claims) do - id = claims["sub"] - resource = Accounts.get!(Accounts.User, id) - - {:ok, resource} - end -end diff --git a/lib/ash_hq/repo.ex b/lib/ash_hq/repo.ex index 9617ca1..da32f52 100644 --- a/lib/ash_hq/repo.ex +++ b/lib/ash_hq/repo.ex @@ -2,7 +2,7 @@ defmodule AshHq.Repo do use AshPostgres.Repo, otp_app: :ash_hq - def installed_extensions() do + def installed_extensions do ["pg_trgm", "uuid-ossp", "citext"] end end diff --git a/lib/ash_hq/resource.ex b/lib/ash_hq/resource.ex index 3ac95f0..91ad6a4 100644 --- a/lib/ash_hq/resource.ex +++ b/lib/ash_hq/resource.ex @@ -1,31 +1,8 @@ defmodule AshHq.Resource do + @moduledoc "AshHq's base resource." defmacro __using__(opts) do - opts = - if opts[:notifiers] && Ash.Notifier.PubSub in opts[:notifiers] do - opts - else - opts - |> Keyword.put_new(:notifiers, []) - |> Keyword.update!(:notifiers, &[Ash.Notifier.PubSub | &1]) - end - quote do use Ash.Resource, unquote(opts) - - pub_sub do - module AshHqWeb.Endpoint - - prefix Module.split(__MODULE__) - |> Enum.reverse() - |> Enum.take(2) - |> Enum.reverse() - |> Enum.map(&Macro.underscore/1) - |> Enum.join(".") - - publish_all :create, ["created"] - publish_all :update, ["updated"] - publish_all :destroy, ["destroyed"] - end end end end diff --git a/lib/ash_hq_web/components/callout_text.ex b/lib/ash_hq_web/components/callout_text.ex index cb06356..6f4ddb7 100644 --- a/lib/ash_hq_web/components/callout_text.ex +++ b/lib/ash_hq_web/components/callout_text.ex @@ -1,4 +1,5 @@ defmodule AshHqWeb.Components.CalloutText do + @moduledoc "Highlights some text on the page" use Surface.Component slot default, required: true diff --git a/lib/ash_hq_web/components/code_example.ex b/lib/ash_hq_web/components/code_example.ex index babc8fb..bf72b38 100644 --- a/lib/ash_hq_web/components/code_example.ex +++ b/lib/ash_hq_web/components/code_example.ex @@ -1,14 +1,14 @@ defmodule AshHqWeb.Components.CodeExample do + @moduledoc "Renders a code example, as seen on the home page" use Surface.LiveComponent - prop text, :string, required: true + prop code, :string, required: true prop class, :css_class prop title, :string prop start_collapsed, :boolean, default: false prop collapsible, :boolean, default: false data collapsed, :string, default: false - data code, :string, default: "" def render(assigns) do ~F""" @@ -73,19 +73,17 @@ defmodule AshHqWeb.Components.CodeExample do {:ok, socket |> assign(assigns) - |> assign(:collapsed, true) - |> assign(:code, to_code(assigns[:text]))} + |> assign(:collapsed, true)} else {:ok, socket |> assign(assigns) - |> assign(:collapsed, false) - |> assign(:code, to_code(assigns[:text]))} + |> assign(:collapsed, false)} end end - defp to_code(text) do - # TODO: do this at compile time + @doc false + def to_code(text) do lines = text # this is pretty naive, won't handle things like block comments diff --git a/lib/ash_hq_web/components/doc_sidebar.ex b/lib/ash_hq_web/components/doc_sidebar.ex index ea51ba7..5a0cb85 100644 --- a/lib/ash_hq_web/components/doc_sidebar.ex +++ b/lib/ash_hq_web/components/doc_sidebar.ex @@ -1,7 +1,8 @@ defmodule AshHqWeb.Components.DocSidebar do + @moduledoc "The left sidebar of the docs pages" use Surface.Component - alias AshHqWeb.Routes + alias AshHqWeb.DocRoutes alias Surface.Components.LiveRedirect prop class, :css_class, default: "" @@ -29,21 +30,21 @@ defmodule AshHqWeb.Components.DocSidebar do {#for {category, guides} <- guides_by_category(@libraries)}
- {#if @sidebar_state["guides-#{Routes.sanitize_name(category)}"] == "open" || (@guide && Enum.any?(guides, &(&1.id == @guide.id))) || (@sidebar_state["guides-#{Routes.sanitize_name(category)}"] != "closed" && category == "Tutorials")} - {#else} - {/if}
- {#if @sidebar_state["guides-#{Routes.sanitize_name(category)}"] == "open" || (@guide && Enum.any?(guides, &(&1.id == @guide.id))) || (@sidebar_state["guides-#{Routes.sanitize_name(category)}"] != "closed" && category == "Tutorials")} + {#if @sidebar_state["guides-#{DocRoutes.sanitize_name(category)}"] == "open" || (@guide && Enum.any?(guides, &(&1.id == @guide.id))) || (@sidebar_state["guides-#{DocRoutes.sanitize_name(category)}"] != "closed" && category == "Tutorials")} {#for guide <- guides}
  • - <#slot /> - - {#match 2} -

    - <#slot /> -

    - {#match 3} -

    - <#slot /> -

    - {#match 4} -

    - <#slot /> -

    - {#match 5} -
    - <#slot /> -
    - {#match 6} -
    - <#slot /> -
    - {#match 7} - - <#slot /> - - {#match 8} - - <#slot /> - - {#match 9} - - <#slot /> - - {/case} - """ - end -end diff --git a/lib/ash_hq_web/components/right_nav.ex b/lib/ash_hq_web/components/right_nav.ex index 49f23f8..a9dbb44 100644 --- a/lib/ash_hq_web/components/right_nav.ex +++ b/lib/ash_hq_web/components/right_nav.ex @@ -1,8 +1,9 @@ defmodule AshHqWeb.Components.RightNav do + @moduledoc "The right nav shown for functions in a module." use Surface.Component - prop(functions, :list, default: []) - prop(module, :string, required: true) + prop functions, :list, default: [] + prop module, :string, required: true def render(assigns) do ~F""" diff --git a/lib/ash_hq_web/components/search.ex b/lib/ash_hq_web/components/search.ex index d8a7a9f..74de65e 100644 --- a/lib/ash_hq_web/components/search.ex +++ b/lib/ash_hq_web/components/search.ex @@ -1,10 +1,11 @@ defmodule AshHqWeb.Components.Search do + @moduledoc "The search overlay modal" use Surface.LiveComponent require Ash.Query - alias AshHqWeb.Routes alias AshHqWeb.Components.CalloutText + alias AshHqWeb.DocRoutes alias Surface.Components.{Form, LiveRedirect} alias Surface.Components.Form.{Checkbox, Label, Select} @@ -108,7 +109,7 @@ defmodule AshHqWeb.Components.Search do defp render_items(assigns, items) do ~F""" {#for item <- items} - +
    - {:noreply, push_redirect(socket, to: Routes.doc_link(item))} + {:noreply, push_redirect(socket, to: DocRoutes.doc_link(item))} end end diff --git a/lib/ash_hq_web/components/search_bar.ex b/lib/ash_hq_web/components/search_bar.ex index 48b64a9..f8ce5ef 100644 --- a/lib/ash_hq_web/components/search_bar.ex +++ b/lib/ash_hq_web/components/search_bar.ex @@ -1,4 +1,5 @@ defmodule AshHqWeb.Components.SearchBar do + @moduledoc "A clickable search bar that brings up the search overlay" use Surface.Component prop class, :css_class, default: "" diff --git a/lib/ash_hq_web/components/tag.ex b/lib/ash_hq_web/components/tag.ex index bb29d1d..913de65 100644 --- a/lib/ash_hq_web/components/tag.ex +++ b/lib/ash_hq_web/components/tag.ex @@ -1,8 +1,9 @@ defmodule AshHqWeb.Components.Tag do + @moduledoc "Renders a simple pill style tag" use Surface.Component - prop(color, :atom, values: [:red]) - slot(default) + prop color, :atom, values: [:red] + slot default def render(assigns) do ~F""" diff --git a/lib/ash_hq_web/routes.ex b/lib/ash_hq_web/doc_routes.ex similarity index 97% rename from lib/ash_hq_web/routes.ex rename to lib/ash_hq_web/doc_routes.ex index 3b6b148..175bdac 100644 --- a/lib/ash_hq_web/routes.ex +++ b/lib/ash_hq_web/doc_routes.ex @@ -1,4 +1,5 @@ -defmodule AshHqWeb.Routes do +defmodule AshHqWeb.DocRoutes do + @moduledoc "Helpers for routing to results of searches" def library_link(library, name) do "/docs/dsl/#{library.name}/#{name}" end diff --git a/lib/ash_hq_web/endpoint.ex b/lib/ash_hq_web/endpoint.ex index 7220e8c..1b9922d 100644 --- a/lib/ash_hq_web/endpoint.ex +++ b/lib/ash_hq_web/endpoint.ex @@ -22,6 +22,9 @@ defmodule AshHqWeb.Endpoint do gzip: false, only: ~w(assets fonts images favicon.ico robots.txt) + # Pass configuration explicitly + plug PlugContentSecurityPolicy + # Code reloading can be explicitly enabled under the # :code_reloader configuration of your endpoint. if code_reloading? do diff --git a/lib/ash_hq_web/helpers.ex b/lib/ash_hq_web/helpers.ex index e05df47..6edc740 100644 --- a/lib/ash_hq_web/helpers.ex +++ b/lib/ash_hq_web/helpers.ex @@ -1,4 +1,6 @@ defmodule AshHqWeb.Helpers do + @moduledoc "Simple helpers for doc liveviews" + def latest_version(library) do Enum.find(library.versions, fn version -> !String.contains?(version.version, ".") diff --git a/lib/ash_hq_web/live_user_auth.ex b/lib/ash_hq_web/live_user_auth.ex new file mode 100644 index 0000000..11cf428 --- /dev/null +++ b/lib/ash_hq_web/live_user_auth.ex @@ -0,0 +1,17 @@ +defmodule AshHqWeb.LiveUserAuth do + @moduledoc """ + Helpers for authenticating users in liveviews + """ + + @doc """ + Sets the current user on each mount of a liveview + """ + def on_mount(:live_user, _params, session, socket) do + {:cont, + Phoenix.LiveView.assign( + socket, + :current_user, + AshHqWeb.UserAuth.user_for_session_token(session["user_token"]) + )} + end +end diff --git a/lib/ash_hq_web/pages/docs.ex b/lib/ash_hq_web/pages/docs.ex index cb1ff37..4fe4381 100644 --- a/lib/ash_hq_web/pages/docs.ex +++ b/lib/ash_hq_web/pages/docs.ex @@ -1,30 +1,31 @@ defmodule AshHqWeb.Pages.Docs do + @moduledoc "The page for showing documentation" use Surface.Component - alias Phoenix.LiveView.JS alias AshHq.Docs.Extensions.RenderMarkdown alias AshHqWeb.Components.{CalloutText, DocSidebar, RightNav, Tag} - alias AshHqWeb.Routes + alias AshHqWeb.DocRoutes + alias Phoenix.LiveView.JS require Logger - prop(change_versions, :event, required: true) - prop(selected_versions, :map, required: true) - prop(libraries, :list, default: []) - prop(uri, :string) - prop(sidebar_state, :map, required: true) - prop(collapse_sidebar, :event, required: true) - prop(expand_sidebar, :event, required: true) + prop change_versions, :event, required: true + prop selected_versions, :map, required: true + prop libraries, :list, default: [] + prop uri, :string + prop sidebar_state, :map, required: true + prop collapse_sidebar, :event, required: true + prop expand_sidebar, :event, required: true - prop(library, :any) - prop(extension, :any) - prop(docs, :any) - prop(library_version, :any) - prop(guide, :any) - prop(doc_path, :list, default: []) - prop(dsls, :list, default: []) - prop(dsl, :any) - prop(options, :list, default: []) - prop(module, :any) + prop library, :any + prop extension, :any + prop docs, :any + prop library_version, :any + prop guide, :any + prop doc_path, :list, default: [] + prop dsls, :list, default: [] + prop dsl, :any + prop options, :list, default: [] + prop module, :any @spec render(any) :: Phoenix.LiveView.Rendered.t() def render(assigns) do @@ -137,7 +138,7 @@ defmodule AshHqWeb.Pages.Docs do {#for mod <- imports} {/for} @@ -151,7 +152,7 @@ defmodule AshHqWeb.Pages.Docs do @@ -281,13 +282,7 @@ defmodule AshHqWeb.Pages.Docs do dsl.imports || [] end) |> Enum.flat_map(fn mod_name -> - case Enum.find_value(libraries, fn library -> - Enum.find_value(library.versions, fn version -> - if version.id == selected_versions[library.id] do - Enum.find(version.modules, &(&1.name == mod_name)) - end - end) - end) do + case find_module(libraries, selected_versions, mod_name) do nil -> Logger.warn("No such module found called #{inspect(mod_name)}") [] @@ -298,6 +293,16 @@ defmodule AshHqWeb.Pages.Docs do end) end + defp find_module(libraries, selected_versions, mod_name) do + Enum.find_value(libraries, fn library -> + Enum.find_value(library.versions, fn version -> + if version.id == selected_versions[library.id] do + Enum.find(version.modules, &(&1.name == mod_name)) + end + end) + end) + end + defp child_dsls(_, nil), do: [] defp child_dsls(nil, _), do: [] @@ -363,7 +368,7 @@ defmodule AshHqWeb.Pages.Docs do end def path_to_name(path, name) do - Enum.map_join(path ++ [name], "-", &Routes.sanitize_name/1) + Enum.map_join(path ++ [name], "-", &DocRoutes.sanitize_name/1) end defp render_tags(assigns, option) do @@ -376,8 +381,8 @@ defmodule AshHqWeb.Pages.Docs do """ end - def show_sidebar() do - %JS{} + def show_sidebar(js \\ %JS{}) do + js |> JS.toggle( to: "#mobile-sidebar-container", in: { @@ -493,7 +498,7 @@ defmodule AshHqWeb.Pages.Docs do raise "No such guide in link: #{source}" """ - #{item} + #{item} """ "dsl" -> @@ -508,12 +513,12 @@ defmodule AshHqWeb.Pages.Docs do "module" -> """ - #{item} + #{item} """ "extension" -> """ - #{item} + #{item} """ type -> diff --git a/lib/ash_hq_web/pages/home.ex b/lib/ash_hq_web/pages/home.ex index 9157088..aea4883 100644 --- a/lib/ash_hq_web/pages/home.ex +++ b/lib/ash_hq_web/pages/home.ex @@ -1,9 +1,10 @@ defmodule AshHqWeb.Pages.Home do + @moduledoc "The home page" + use Surface.LiveComponent alias AshHqWeb.Components.{CalloutText, CodeExample, SearchBar} - - prop libraries, :list, default: [] + import AshHqWeb.Components.CodeExample, only: [to_code: 1] def render(assigns) do ~F""" @@ -26,7 +27,7 @@ defmodule AshHqWeb.Pages.Home do
    @@ -34,14 +35,14 @@ defmodule AshHqWeb.Pages.Home do class="w-auto" collapsible id="use-it-programmatically" - text={changeset_example()} + code={changeset_example()} title="Use it programmatically" />
    @@ -84,153 +85,170 @@ defmodule AshHqWeb.Pages.Home do """ end - defp changeset_example() do - """ - post = Example.Post.create!(%{ - text: "Declarative programming is fun!" - }) + @changeset_example """ + post = Example.Post.create!(%{ + text: "Declarative programming is fun!" + }) - Example.Post.react!(post, %{type: :like}) + Example.Post.react!(post, %{type: :like}) - Example.Post - |> Ash.Query.filter(likes > 10) - |> Ash.Query.sort(likes: :desc) - |> Example.read!() - """ + Example.Post + |> Ash.Query.filter(likes > 10) + |> Ash.Query.sort(likes: :desc) + |> Example.read!() + """ + |> to_code() + + defp changeset_example do + @changeset_example end - defp live_view_example() do - """ - def mount(_params, _session, socket) do - form = AshPhoenix.Form.for_create(Example.Post, :create) + @live_view_example """ + def mount(_params, _session, socket) do + form = AshPhoenix.Form.for_create(Example.Post, :create) - {:ok, assign(socket, :form, form}} - end + {:ok, assign(socket, :form, form}} + end - def handle_event("validate", %{"form" => input}, socket) do - form = AshPhoenix.Form.validate(socket.assigns.form, input) + def handle_event("validate", %{"form" => input}, socket) do + form = AshPhoenix.Form.validate(socket.assigns.form, input) - {:ok, assign(socket, :form, form)} - end + {:ok, assign(socket, :form, form)} + end - def handle_event("submit", _, socket) do - case AshPhoenix.Form.submit(socket.assigns.form) do - {:ok, post} -> - {:ok, redirect_to_post(socket, post)} + def handle_event("submit", _, socket) do + case AshPhoenix.Form.submit(socket.assigns.form) do + {:ok, post} -> + {:ok, redirect_to_post(socket, post)} - {:error, form_with_errors} -> - {:noreply, assign(socket, :form, form_with_errors)} - end - end - """ + {:error, form_with_errors} -> + {:noreply, assign(socket, :form, form_with_errors)} + end + end + """ + |> to_code() + defp live_view_example do + @live_view_example end - defp graphql_example() do - """ - graphql do - type :post + @graphql_example """ + graphql do + type :post - queries do - get :get_post, :read - list :feed, :read - end + queries do + get :get_post, :read + list :feed, :read + end - mutations do - create :create_post, :create - update :react_to_post, :react - end - end - """ + mutations do + create :create_post, :create + update :react_to_post, :react + end + end + """ + |> to_code() + defp graphql_example do + @graphql_example end - defp policies_example() do - """ - policies do - policy action_type(:read) do - authorize_if expr(visibility == :everyone) - authorize_if relates_to_actor_via([:author, :friends]) - end - end - """ + @policies_example """ + policies do + policy action_type(:read) do + authorize_if expr(visibility == :everyone) + authorize_if relates_to_actor_via([:author, :friends]) + end + end + """ + |> to_code() + defp policies_example do + @policies_example end - defp notifier_example() do - """ - pub_sub do - module ExampleEndpoint - prefix "post" + @notifier_example """ + pub_sub do + module ExampleEndpoint + prefix "post" - publish_all :create, ["created"] - publish :react, ["reaction", :id] event: "reaction" - end - """ + publish_all :create, ["created"] + publish :react, ["reaction", :id] event: "reaction" + end + """ + |> to_code() + defp notifier_example do + @notifier_example end - defp aggregate_example() do - """ - aggregates do - count :likes, :reactions do - filter expr(type == :like) - end + @aggregate_example """ + aggregates do + count :likes, :reactions do + filter expr(type == :like) + end - count :dislikes, :reactions do - filter expr(type == :dislike) - end - end + count :dislikes, :reactions do + filter expr(type == :dislike) + end + end - calculations do - calculate :like_ratio, :float do - expr(likes / (likes + dislikes)) - end - end - """ + calculations do + calculate :like_ratio, :float do + expr(likes / (likes + dislikes)) + end + end + """ + |> to_code() + + defp aggregate_example do + @aggregate_example end - defp post_example() do - """ - defmodule Example.Post do - use AshHq.Resource, - data_layer: AshPostgres.DataLayer + @post_example """ + defmodule Example.Post do + use AshHq.Resource, + data_layer: AshPostgres.DataLayer - postgres do - table "posts" - repo Example.Repo - end + postgres do + table "posts" + repo Example.Repo + end - attributes do - attribute :text, :string do - allow_nil? false - end + attributes do + attribute :text, :string do + allow_nil? false + end - attribute :visibility, :atom do - constraints [ - one_of: [:friends, :everyone] - ] - end - end + attribute :visibility, :atom do + constraints [ + one_of: [:friends, :everyone] + ] + end + end - actions do - update :react do - argument :type, Example.Types.ReactionType do - allow_nil? false - end + actions do + update :react do + argument :type, Example.Types.ReactionType do + allow_nil? false + end - change manage_relationship( - :type, - :reactions, - type: :append - ) - end - end + change manage_relationship( + :type, + :reactions, + type: :append + ) + end + end - relationships do - belongs_to :author, Example.User do - required? true - end + relationships do + belongs_to :author, Example.User do + required? true + end - has_many :reactions, Example.Reaction - end - end - """ + has_many :reactions, Example.Reaction + end + end + """ + |> to_code() + + defp post_example do + @post_example end end diff --git a/lib/ash_hq_web/plugs/auth_access_pipeline.ex b/lib/ash_hq_web/plugs/auth_access_pipeline.ex deleted file mode 100644 index bd0f011..0000000 --- a/lib/ash_hq_web/plugs/auth_access_pipeline.ex +++ /dev/null @@ -1,7 +0,0 @@ -defmodule AshHqWeb.AuthAccessPipeline do - use Guardian.Plug.Pipeline, otp_app: :ash_hq - - plug Guardian.Plug.VerifyHeader, claims: %{"typ" => "access"} - plug Guardian.Plug.EnsureAuthenticated - plug Guardian.Plug.LoadResource, allow_blank: true -end diff --git a/lib/ash_hq_web/plugs/auth_error_handler.ex b/lib/ash_hq_web/plugs/auth_error_handler.ex deleted file mode 100644 index 3c13539..0000000 --- a/lib/ash_hq_web/plugs/auth_error_handler.ex +++ /dev/null @@ -1,11 +0,0 @@ -defmodule AshHqWeb.AuthErrorHandler do - import Plug.Conn - - @behaviour Guardian.Plug.ErrorHandler - - @impl Guardian.Plug.ErrorHandler - def auth_error(conn, {type, _reason}, _opts) do - body = Jason.encode!(%{message: to_string(type)}) - send_resp(conn, 401, body) - end -end diff --git a/lib/ash_hq_web/router.ex b/lib/ash_hq_web/router.ex index cec3e69..6419833 100644 --- a/lib/ash_hq_web/router.ex +++ b/lib/ash_hq_web/router.ex @@ -9,19 +9,17 @@ defmodule AshHqWeb.Router do plug :fetch_live_flash plug :put_root_layout, {AshHqWeb.LayoutView, :root} plug :protect_from_forgery - plug :put_secure_browser_headers - plug :fetch_current_user plug AshHqWeb.SessionPlug end + pipeline :dead_view_authentication do + plug :fetch_current_user + end + pipeline :api do plug :accepts, ["json"] end - pipeline :api_authenticated do - plug AshHqWeb.AuthAccessPipeline - end - scope "/", AshHqWeb do pipe_through :api post "/import/:library", ImportController, :import @@ -30,7 +28,9 @@ defmodule AshHqWeb.Router do scope "/", AshHqWeb do pipe_through :browser - live_session :main, root_layout: {AshHqWeb.LayoutView, "root.html"} do + live_session :main, + on_mount: {AshHqWeb.LiveUserAuth, :live_user}, + root_layout: {AshHqWeb.LayoutView, "root.html"} do live "/", AppViewLive, :home live "/docs/", AppViewLive, :docs_dsl live "/docs/guides/:library/:version/*guide", AppViewLive, :docs_dsl @@ -45,7 +45,12 @@ defmodule AshHqWeb.Router do ## Authentication routes scope "/", AshHqWeb do - pipe_through [:browser, :redirect_if_user_is_authenticated, :put_session_layout] + pipe_through [ + :browser, + :dead_view_authentication, + :redirect_if_user_is_authenticated, + :put_session_layout + ] get "/users/register", UserRegistrationController, :new post "/users/register", UserRegistrationController, :create @@ -58,7 +63,7 @@ defmodule AshHqWeb.Router do end scope "/", AshHqWeb do - pipe_through [:browser, :require_authenticated_user] + pipe_through [:browser, :dead_view_authentication, :require_authenticated_user] get "/users/settings", UserSettingsController, :edit put "/users/settings", UserSettingsController, :update @@ -66,9 +71,9 @@ defmodule AshHqWeb.Router do end scope "/", AshHqWeb do - pipe_through [:browser] + pipe_through [:browser, :dead_view_authentication] - get "/users/log_out", UserSessionController, :delete + # get "/users/log_out", UserSessionController, :delete delete "/users/log_out", UserSessionController, :delete get "/users/confirm", UserConfirmationController, :new post "/users/confirm", UserConfirmationController, :create @@ -91,7 +96,7 @@ defmodule AshHqWeb.Router do import Phoenix.LiveDashboard.Router scope "/" do - pipe_through :browser + pipe_through [:browser, :dead_view_authentication] live_dashboard "/dashboard", metrics: AshHqWeb.Telemetry end @@ -103,7 +108,7 @@ defmodule AshHqWeb.Router do # node running the Phoenix server. if Mix.env() == :dev do scope "/dev" do - pipe_through :browser + pipe_through [:browser, :dead_view_authentication] forward "/mailbox", Plug.Swoosh.MailboxPreview end diff --git a/lib/ash_hq_web/telemetry.ex b/lib/ash_hq_web/telemetry.ex index dd606a9..e63ca70 100644 --- a/lib/ash_hq_web/telemetry.ex +++ b/lib/ash_hq_web/telemetry.ex @@ -1,4 +1,5 @@ defmodule AshHqWeb.Telemetry do + @moduledoc "Telemetry metrics registry/handler" use Supervisor import Telemetry.Metrics diff --git a/lib/ash_hq_web/templates/layout/root.html.heex b/lib/ash_hq_web/templates/layout/root.html.eex similarity index 62% rename from lib/ash_hq_web/templates/layout/root.html.heex rename to lib/ash_hq_web/templates/layout/root.html.eex index 144f126..3002152 100644 --- a/lib/ash_hq_web/templates/layout/root.html.heex +++ b/lib/ash_hq_web/templates/layout/root.html.eex @@ -1,13 +1,13 @@ - +"> <%= csrf_meta_tag() %> <%= live_title_tag assigns[:page_title] || "Ash Framework" %> - - - - + + + diff --git a/lib/ash_hq_web/templates/layout/session.html.eex b/lib/ash_hq_web/templates/layout/session.html.eex index 6ba6efa..0fa8876 100644 --- a/lib/ash_hq_web/templates/layout/session.html.eex +++ b/lib/ash_hq_web/templates/layout/session.html.eex @@ -5,9 +5,9 @@ <%= csrf_meta_tag() %> - <%= live_title_tag assigns[:page_title] || "AshHq", suffix: " ยท Phoenix Framework" %> - "/> - + <%= live_title_tag assigns[:page_title] || "AshHq" %> + "/> +
    diff --git a/lib/ash_hq_web/controllers/user_auth.ex b/lib/ash_hq_web/user_auth.ex similarity index 89% rename from lib/ash_hq_web/controllers/user_auth.ex rename to lib/ash_hq_web/user_auth.ex index c1300b6..e949a1b 100644 --- a/lib/ash_hq_web/controllers/user_auth.ex +++ b/lib/ash_hq_web/user_auth.ex @@ -1,4 +1,8 @@ defmodule AshHqWeb.UserAuth do + @moduledoc """ + Helpers for authenticating, logging in and logging out users. + """ + import Plug.Conn import Phoenix.Controller @@ -106,14 +110,20 @@ defmodule AshHqWeb.UserAuth do def fetch_current_user(conn, _opts) do {user_token, conn} = ensure_user_token(conn) - user = - if user_token do - AshHq.Accounts.User - |> Ash.Query.for_read(:by_token, token: user_token, context: "session") - |> AshHq.Accounts.read_one!() - end + assign(conn, :current_user, user_for_session_token(user_token)) + end - assign(conn, :current_user, user) + @doc """ + Gets the user corresponding to a given session token. + + If the session token is nil or does not exist, then `nil` is returned. + """ + def user_for_session_token(nil), do: nil + + def user_for_session_token(user_token) do + AshHq.Accounts.User + |> Ash.Query.for_read(:by_token, token: user_token, context: "session") + |> AshHq.Accounts.read_one!() end defp ensure_user_token(conn) do diff --git a/lib/ash_hq_web/views/app_view_live.ex b/lib/ash_hq_web/views/app_view_live.ex index c90074c..bb4dea3 100644 --- a/lib/ash_hq_web/views/app_view_live.ex +++ b/lib/ash_hq_web/views/app_view_live.ex @@ -5,7 +5,9 @@ defmodule AshHqWeb.AppViewLive do alias AshHq.Docs.Extensions.RenderMarkdown alias AshHqWeb.Components.{Search, SearchBar} alias AshHqWeb.Pages.{Docs, Home} + alias AshHqWeb.Router.Helpers, as: Routes alias Phoenix.LiveView.JS + alias Surface.Components.LiveRedirect require Ash.Query data configured_theme, :string, default: :system @@ -14,6 +16,7 @@ defmodule AshHqWeb.AppViewLive do data libraries, :list, default: [] data selected_types, :map, default: %{} data sidebar_state, :map, default: %{} + data current_user, :map data library, :any, default: nil data extension, :any, default: nil @@ -130,6 +133,11 @@ defmodule AshHqWeb.AppViewLive do {/case} + {#if @current_user} + + {#else} + Sign In + {/if}
    {#case @live_action} @@ -161,6 +169,23 @@ defmodule AshHqWeb.AppViewLive do """ end + defp toggle_account_dropdown(js \\ %JS{}) do + js + |> JS.toggle( + to: "#account-dropdown", + in: { + "transition ease-in duration-100", + "opacity-0", + "opacity-100" + }, + out: { + "transition ease-out duration-75", + "opacity-100", + "opacity-0" + } + ) + end + def handle_params(params, uri, socket) do {:noreply, socket diff --git a/mix.exs b/mix.exs index 1fbf560..cbc0108 100644 --- a/mix.exs +++ b/mix.exs @@ -56,8 +56,9 @@ defmodule AshHq.MixProject do {:swoosh, "~> 1.3"}, {:premailex, "~> 0.3.0"}, # Authentication - {:guardian, "~> 2.0"}, {:bcrypt_elixir, "~> 3.0"}, + # CSP + {:plug_content_security_policy, "~> 0.2.1"}, # Phoenix/Core dependencies {:phoenix, "~> 1.6.6"}, {:phoenix_ecto, "~> 4.4"}, @@ -77,9 +78,15 @@ defmodule AshHq.MixProject do {:jason, "~> 1.2"}, {:plug_cowboy, "~> 2.5"}, # Dependencies - {:sobelow, "~> 0.8", only: :dev}, - {:credo, "~> 1.4", only: [:dev, :test], runtime: false}, - {:elixir_sense, github: "elixir-lsp/elixir_sense"} + {:elixir_sense, github: "elixir-lsp/elixir_sense"}, + # Build/Check dependencies + {:git_ops, "~> 2.4.4", only: :dev}, + {:ex_doc, "~> 0.23", only: :dev, runtime: false}, + {:ex_check, "~> 0.14", only: :dev}, + {:credo, ">= 0.0.0", only: :dev, runtime: false}, + {:dialyxir, ">= 0.0.0", only: :dev, runtime: false}, + {:sobelow, ">= 0.0.0", only: :dev, runtime: false}, + {:excoveralls, "~> 0.14", only: [:dev, :test]} ] end @@ -94,8 +101,10 @@ defmodule AshHq.MixProject do seed: ["run priv/repo/seeds.exs"], setup: ["ash_postgres.create", "ash_postgres.migrate", "seed"], reset: ["drop", "setup"], + credo: "credo --strict", drop: ["ash_postgres.drop"], test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"], + sobelow: ["sobelow --skip"], "assets.deploy": [ "cmd --cd assets npm run deploy", "esbuild default --minify", diff --git a/mix.lock b/mix.lock index 6e6bd32..666c603 100644 --- a/mix.lock +++ b/mix.lock @@ -2,9 +2,6 @@ "ash": {:git, "https://github.com/ash-project/ash.git", "fe12f40056661e84e702b3fb50badef1d9f3c99f", []}, "ash_phoenix": {:git, "https://github.com/ash-project/ash_phoenix.git", "538784765f5c38cde1b9b527aa348b62d625c01f", []}, "ash_postgres": {:git, "https://github.com/ash-project/ash_postgres.git", "e20e68e73af334dec540786b9275fcdf0cb86731", []}, - "bamboo": {:hex, :bamboo, "2.2.0", "f10a406d2b7f5123eb1f02edfa043c259db04b47ab956041f279eaac776ef5ce", [:mix], [{:hackney, ">= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.4", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "8c3b14ba7d2f40cb4be04128ed1e2aff06d91d9413d38bafb4afccffa3ade4fc"}, - "bamboo_phoenix": {:hex, :bamboo_phoenix, "1.0.0", "f3cc591ffb163ed0bf935d256f1f4645cd870cf436545601215745fb9cc9953f", [:mix], [{:bamboo, ">= 2.0.0", [hex: :bamboo, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.3.0", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "6db88fbb26019c84a47994bb2bd879c0887c29ce6c559bc6385fd54eb8b37dee"}, - "bamboo_postmark": {:hex, :bamboo_postmark, "1.0.0", "37e3dea3d06b79a17b6b98ef9261f8f4488619c6283f19306f93d3b636d6f9fb", [:mix], [{:bamboo, ">= 2.0.0", [hex: :bamboo, repo: "hexpm", optional: false]}, {:hackney, ">= 1.6.5", [hex: :hackney, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "443b3fb9e00a5d092ccfc91cfe3dbecab2a931114d4dc5e1e70f28f6c640c63d"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.0.1", "9be815469e6bfefec40fa74658ecbbe6897acfb57614df1416eeccd4903f602c", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "486bb95efb645d1efc6794c1ddd776a186a9a713abf06f45708a6ce324fb96cf"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "castore": {:hex, :castore, "0.1.17", "ba672681de4e51ed8ec1f74ed624d104c0db72742ea1a5e74edbc770c815182f", [:mix], [], "hexpm", "d9844227ed52d26e7519224525cb6868650c272d4a3d327ce3ca5570c12163f9"}, @@ -19,29 +16,32 @@ "credo": {:hex, :credo, "1.6.6", "f51f8d45db1af3b2e2f7bee3e6d3c871737bda4a91bff00c5eec276517d1a19c", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "625520ce0984ee0f9f1f198165cd46fa73c1e59a17ebc520038b8fce056a5bdc"}, "db_connection": {:hex, :db_connection, "2.4.2", "f92e79aff2375299a16bcb069a14ee8615c3414863a6fef93156aee8e86c2ff3", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4fe53ca91b99f55ea249693a0229356a08f4d1a7931d8ffa79289b145fe83668"}, "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, + "dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"}, "docsh": {:hex, :docsh, "0.7.2", "f893d5317a0e14269dd7fe79cf95fb6b9ba23513da0480ec6e77c73221cae4f2", [:rebar3], [{:providers, "1.8.1", [hex: :providers, repo: "hexpm", optional: false]}], "hexpm", "4e7db461bb07540d2bc3d366b8513f0197712d0495bb85744f367d3815076134"}, "earmark": {:hex, :earmark, "1.5.0-pre1", "e04aca73692bc3cda3429d6df99c8dae2bf76411e5e76d006a4bc04ac81ef1c1", [:mix], [{:earmark_parser, "~> 1.4.21", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "26ec0473ad2ef995b9672f89309a7a4952887f69b78cfc7af14e320bc6546bfa"}, "earmark_parser": {:hex, :earmark_parser, "1.4.26", "f4291134583f373c7d8755566122908eb9662df4c4b63caa66a0eabe06569b0a", [:mix], [], "hexpm", "48d460899f8a0c52c5470676611c01f64f3337bad0b26ddab43648428d94aabc"}, "ecto": {:hex, :ecto, "3.8.4", "e06b8b87e62b27fea17fd2ff6041572ddd10339fd16cdf58446e402c6c90a74b", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f9244288b8d42db40515463a008cf3f4e0e564bb9c249fe87bf28a6d79fe82d4"}, "ecto_sql": {:hex, :ecto_sql, "3.8.3", "a7d22c624202546a39d615ed7a6b784580391e65723f2d24f65941b4dd73d471", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.8.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0 or ~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "348cb17fb9e6daf6f251a87049eafcb57805e2892e5e6a0f5dea0985d367329b"}, - "elasticlunr": {:hex, :elasticlunr, "0.6.6", "937a41a7293040e060f880817abac8e025ac9e146554e24042aaf8fbe94a0d1f", [:mix], [{:jason, "~> 1.3", [hex: :jason, repo: "hexpm", optional: false]}, {:stemmer, "~> 1.0", [hex: :stemmer, repo: "hexpm", optional: false]}, {:uuid, "~> 1.1", [hex: :uuid, repo: "hexpm", optional: false]}], "hexpm", "d02244cb10c46b82bbc1e68477be296aa78f2d6ecf70355722ce916ff24f6958"}, "elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"}, "elixir_sense": {:git, "https://github.com/elixir-lsp/elixir_sense.git", "6e3334406c1dca8d1809cd9d64a2b1a7888c56d3", []}, + "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "esbuild": {:hex, :esbuild, "0.4.0", "9f17db148aead4cf1e6e6a584214357287a93407b5fb51a031f122b61385d4c2", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "b61e4e6b92ffe45e4ee4755a22de6211a67c67987dc02afb35a425a0add1d447"}, "ets": {:hex, :ets, "0.8.1", "8ff9bcda5682b98493f8878fc9dbd990e48d566cba8cce59f7c2a78130da29ea", [:mix], [], "hexpm", "6be41b50adb5bc5c43626f25ea2d0af1f4a242fb3fad8d53f0c67c20b78915cc"}, + "ex_check": {:hex, :ex_check, "0.14.0", "d6fbe0bcc51cf38fea276f5bc2af0c9ae0a2bb059f602f8de88709421dae4f0e", [:mix], [], "hexpm", "8a602e98c66e6a4be3a639321f1f545292042f290f91fa942a285888c6868af0"}, + "ex_doc": {:hex, :ex_doc, "0.28.4", "001a0ea6beac2f810f1abc3dbf4b123e9593eaa5f00dd13ded024eae7c523298", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bf85d003dd34911d89c8ddb8bda1a958af3471a274a4c2150a9c01c78ac3f8ed"}, + "excoveralls": {:hex, :excoveralls, "0.14.6", "610e921e25b180a8538229ef547957f7e04bd3d3e9a55c7c5b7d24354abbba70", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "0eceddaa9785cfcefbf3cd37812705f9d8ad34a758e513bb975b081dce4eb11e"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "finch": {:hex, :finch, "0.10.2", "9ad27d68270d879f73f26604bb2e573d40f29bf0e907064a9a337f90a16a0312", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dd8b11b282072cec2ef30852283949c248bd5d2820c88d8acc89402b81db7550"}, "floki": {:hex, :floki, "0.32.1", "dfe3b8db3b793939c264e6f785bca01753d17318d144bd44b407fb3493acaa87", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "d4b91c713e4a784a3f7b1e3cc016eefc619f6b1c3898464222867cafd3c681a3"}, "getopt": {:hex, :getopt, "1.0.1", "c73a9fa687b217f2ff79f68a3b637711bb1936e712b521d8ce466b29cbf7808a", [:rebar3], [], "hexpm", "53e1ab83b9ceb65c9672d3e7a35b8092e9bdc9b3ee80721471a161c10c59959c"}, "gettext": {:hex, :gettext, "0.20.0", "75ad71de05f2ef56991dbae224d35c68b098dd0e26918def5bb45591d5c8d429", [:mix], [], "hexpm", "1c03b177435e93a47441d7f681a7040bd2a816ece9e2666d1c9001035121eb3d"}, - "guardian": {:hex, :guardian, "2.2.4", "3dafdc19665411c96b2796d184064d691bc08813a132da5119e39302a252b755", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "6f83d4309c16ec2469da8606bb2a9815512cc2fac1595ad34b79940a224eb110"}, + "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, + "git_ops": {:hex, :git_ops, "2.4.5", "185a724dfde3745edd22f7571d59c47a835cf54ded67e9ccbc951920b7eec4c2", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e323a5b01ad53bc8c19c3a444be3e61ed7803ecd2e95530446ae9327d0143ecc"}, "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.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.3.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", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, "hpax": {:hex, :hpax, "0.1.1", "2396c313683ada39e98c20a75a82911592b47e5c24391363343bde74f82396ca", [:mix], [], "hexpm", "0ae7d5a0b04a8a60caf7a39fcf3ec476f35cc2cc16c05abea730d3ce6ac6c826"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"}, - "jose": {:hex, :jose, "1.11.2", "f4c018ccf4fdce22c71e44d471f15f723cb3efab5d909ab2ba202b5bf35557b3", [:mix, :rebar3], [], "hexpm", "98143fbc48d55f3a18daba82d34fe48959d44538e9697c08f34200fa5f0947d2"}, - "kino": {:hex, :kino, "0.6.2", "3e8463ea19551f368c3dcbbf39d36b2627a33916598bfe87f51adc9aaab453fb", [:mix], [{:table, "~> 0.1.2", [hex: :table, repo: "hexpm", optional: false]}], "hexpm", "488cd83fa6efcdb4d5289c25daf842c44b33508fea048eb98f58132afc4ed513"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_eex": {:hex, :makeup_eex, "0.1.1", "89352d5da318d97ae27bbcc87201f274504d2b71ede58ca366af6a5fbed9508d", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.16", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_html, "~> 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d111a0994eaaab09ef1a4b3b313ef806513bb4652152c26c0d7ca2be8402a964"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, @@ -68,22 +68,20 @@ "phoenix_view": {:hex, :phoenix_view, "1.1.2", "1b82764a065fb41051637872c7bd07ed2fdb6f5c3bd89684d4dca6e10115c95a", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "7ae90ad27b09091266f6adbb61e1d2516a7c3d7062c6789d46a7554ec40f3a56"}, "picosat_elixir": {:hex, :picosat_elixir, "0.2.1", "407dcb90755167fd9e3311b60565ff32ed0d234010363406c07cdb4175b95bc5", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "68f4bdb2ac3b594209e54625d3d58c9e2e98b90f2ec8e03235f66e88c9eda5fe"}, "plug": {:hex, :plug, "1.13.6", "187beb6b67c6cec50503e940f0434ea4692b19384d47e5fdfd701e93cadb4cc2", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02b9c6b9955bce92c829f31d6284bf53c591ca63c4fb9ff81dfd0418667a34ff"}, + "plug_content_security_policy": {:hex, :plug_content_security_policy, "0.2.1", "0a19c76307ad000b3757739c14b34b83ecccf7d0a3472e64e14797a20b62939b", [:mix], [{:plug, "~> 1.3", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ceea10050671c0387c64526e2cb337ee08e12705c737eaed80439266df5b2e29"}, "plug_cowboy": {:hex, :plug_cowboy, "2.5.2", "62894ccd601cf9597e2c23911ff12798a8a18d237e9739f58a6b04e4988899fe", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ea6e87f774c8608d60c8d34022a7d073bd7680a0a013f049fc62bf35efea1044"}, "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"}, "postgrex": {:hex, :postgrex, "0.16.3", "fac79a81a9a234b11c44235a4494d8565303fa4b9147acf57e48978a074971db", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "aeaae1d2d1322da4e5fe90d241b0a564ce03a3add09d7270fb85362166194590"}, "premailex": {:hex, :premailex, "0.3.16", "25c0c9c969f0025bbfdb06834f8f0fbd46e5ec50f5c252e6492165802ffbd2a6", [:mix], [{:certifi, ">= 0.0.0", [hex: :certifi, repo: "hexpm", optional: true]}, {:floki, "~> 0.19", [hex: :floki, repo: "hexpm", optional: false]}, {:meeseeks, "~> 0.11", [hex: :meeseeks, repo: "hexpm", optional: true]}, {:ssl_verify_fun, ">= 0.0.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: true]}], "hexpm", "c6b042f89ca63025dfbe3ef54fdbbe9d5f043b7c33d8e58f43a41d13a9475111"}, "providers": {:hex, :providers, "1.8.1", "70b4197869514344a8a60e2b2a4ef41ca03def43cfb1712ecf076a0f3c62f083", [:rebar3], [{:getopt, "1.0.1", [hex: :getopt, repo: "hexpm", optional: false]}], "hexpm", "e45745ade9c476a9a469ea0840e418ab19360dc44f01a233304e118a44486ba0"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, - "req": {:hex, :req, "0.2.1", "5d4ee7bc6666cd4d77e95f89ce75ca0ca73b6a25eeebbe2e7bc60cdd56d73865", [:mix], [{:finch, "~> 0.9.1", [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]}], "hexpm", "ababd5c8a334848bde2bc3c2f518df22211c8533d863d15bfefa04796abc3633"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, "sourceror": {:hex, :sourceror, "0.11.1", "1b80efe84330beefb6b3da95b75c1e1cdefe9dc785bf4c5064fae251a8af615c", [:mix], [], "hexpm", "22b6828ee5572f6cec75cc6357f3ca6c730a02954cef0302c428b3dba31e5e74"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, - "stemmer": {:hex, :stemmer, "1.1.0", "71221331ced40832b47e6989a12dd9de1b15c982043d1014742be83c34ec9e79", [:mix], [], "hexpm", "0cb5faf73476b84500e371ff39fd9a494f60ab31d991689c1cd53b920556228f"}, "stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"}, "surface": {:hex, :surface, "0.7.4", "ce9cf98a11e6572008d82b6dd1dd25fd90966d69cc72a06d69058ef3e7063df8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.17.4", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.9", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "052c2a9a35e260339ec0f9bbc667224993e7e2805c36409736f673ffe7d486ac"}, "surface_heroicons": {:hex, :surface_heroicons, "0.6.0", "04e171843439d2d52c868f8adf5294c49505f504a74a0200179e49c447d6f354", [:mix], [{:surface, ">= 0.5.0", [hex: :surface, repo: "hexpm", optional: false]}], "hexpm", "1136c88a8de44a63c050cec9b0b64f771127dfd96feabab4cd0bde8b6b727ba2"}, "swoosh": {:hex, :swoosh, "1.6.6", "6018c6f4659ac0b4f30684982993b7812b2bb97436d39f76fcfa8c9e3ae74f85", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e92c7206efd442f08484993676ab072afab2f2bb1e87e604230bb1183c5980de"}, - "table": {:hex, :table, "0.1.2", "87ad1125f5b70c5dea0307aa633194083eb5182ec537efc94e96af08937e14a8", [:mix], [], "hexpm", "7e99bc7efef806315c7e65640724bf165c3061cdc5d854060f74468367065029"}, "telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"}, "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"}, "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, @@ -91,5 +89,4 @@ "typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"}, "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, - "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"}, } diff --git a/priv/repo/migrations/20220806220239_migrate_resources19.exs b/priv/repo/migrations/20220806220239_migrate_resources19.exs new file mode 100644 index 0000000..bc4d019 --- /dev/null +++ b/priv/repo/migrations/20220806220239_migrate_resources19.exs @@ -0,0 +1,21 @@ +defmodule AshHq.Repo.Migrations.MigrateResources19 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(:guides) do + remove :sanitized_name + end + end + + def down do + alter table(:guides) do + add :sanitized_name, :text, null: false + end + end +end diff --git a/priv/resource_snapshots/repo/guides/20220806220239.json b/priv/resource_snapshots/repo/guides/20220806220239.json new file mode 100644 index 0000000..84cbbaa --- /dev/null +++ b/priv/resource_snapshots/repo/guides/20220806220239.json @@ -0,0 +1,132 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "route", + "type": "text" + }, + { + "allow_nil?": false, + "default": "fragment(\"uuid_generate_v4()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "order", + "type": "bigint" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "name", + "type": "text" + }, + { + "allow_nil?": false, + "default": "\"\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "text", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "text_html", + "type": "text" + }, + { + "allow_nil?": false, + "default": "\"Topics\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "category", + "type": "text" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "destination_field": "id", + "destination_field_default": null, + "destination_field_generated": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "guides_library_version_id_fkey", + "on_delete": "delete", + "on_update": null, + "schema": "public", + "table": "library_versions" + }, + "size": null, + "source": "library_version_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [ + { + "code?": false, + "down": "DROP INDEX guides_name_lower_index;", + "name": "name_index", + "up": "CREATE INDEX guides_name_lower_index ON guides(lower(name));\n" + }, + { + "code?": false, + "down": "DROP INDEX guides_name_trigram_index;", + "name": "trigram_index", + "up": "CREATE INDEX guides_name_trigram_index ON guides USING GIST (name gist_trgm_ops);\n" + }, + { + "code?": false, + "down": "DROP INDEX guides_search_index;", + "name": "search_index", + "up": "CREATE INDEX guides_search_index ON guides USING GIN((\n setweight(to_tsvector('english', name), 'A') ||\n setweight(to_tsvector('english', text), 'D')\n));\n" + } + ], + "has_create_action": true, + "hash": "17C8A7CB3BDF7B8B32274969F1E405C358CEDFA18C0D60E1699F0D63E1D54697", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshHq.Repo", + "schema": null, + "table": "guides" +} \ No newline at end of file