From d1414cf2154a962879613e62773331578c9bf692 Mon Sep 17 00:00:00 2001 From: Mike Buhot Date: Mon, 21 Nov 2022 17:47:48 +1000 Subject: [PATCH] Proof of concept Json-API with OpenAPI support Using AshJsonApi.OpenApi helpers to generate OpenAPI --- config/config.exs | 5 ++++ lib/ash_hq/docs/docs.ex | 8 ++++++ lib/ash_hq/docs/open_api.ex | 35 ++++++++++++++++++++++++ lib/ash_hq/docs/resources/guide/guide.ex | 17 ++++++++++++ lib/ash_hq_web/docs_json_api_router.ex | 15 ++++++++++ lib/ash_hq_web/router.ex | 2 +- mix.exs | 5 +++- mix.lock | 4 ++- 8 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 lib/ash_hq/docs/open_api.ex create mode 100644 lib/ash_hq_web/docs_json_api_router.ex diff --git a/config/config.exs b/config/config.exs index 1d93e4d8..6d94841b 100644 --- a/config/config.exs +++ b/config/config.exs @@ -75,6 +75,11 @@ config :plug_content_security_policy, worker_src: ~w('self') } +# Mime type for JSON:API routes +config :mime, :types, %{ + "application/vnd.api+json" => ["json"] +} + # 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" diff --git a/lib/ash_hq/docs/docs.ex b/lib/ash_hq/docs/docs.ex index 040fa7c7..bc5e4931 100644 --- a/lib/ash_hq/docs/docs.ex +++ b/lib/ash_hq/docs/docs.ex @@ -5,6 +5,7 @@ defmodule AshHq.Docs do use Ash.Api, extensions: [ AshGraphql.Api, + AshJsonApi.Api, AshAdmin.Api ] @@ -20,4 +21,11 @@ defmodule AshHq.Docs do resources do registry AshHq.Docs.Registry end + + json_api do + prefix "/json_api" + serve_schema? true + open_api {AshHq.Docs.OpenApi, :spec, []} + log_errors? true + end end diff --git a/lib/ash_hq/docs/open_api.ex b/lib/ash_hq/docs/open_api.ex new file mode 100644 index 00000000..102a021c --- /dev/null +++ b/lib/ash_hq/docs/open_api.ex @@ -0,0 +1,35 @@ +defmodule AshHq.Docs.OpenApi do + alias OpenApiSpex.{Info, OpenApi, Server, SecurityScheme} + + def spec do + %OpenApi{ + info: %Info{ + title: "AshHQ Docs API", + version: "1.1" + }, + servers: [ + Server.from_endpoint(AshHqWeb.Endpoint) + ], + paths: AshJsonApi.OpenApi.paths(AshHq.Docs), + tags: AshJsonApi.OpenApi.tags(AshHq.Docs), + components: %{ + responses: AshJsonApi.OpenApi.responses(), + schemas: AshJsonApi.OpenApi.schemas(AshHq.Docs), + securitySchemes: %{ + "api_key" => %SecurityScheme{ + type: "apiKey", + description: "API Key provided in the Authorization header", + name: "api_key", + in: "header" + } + } + }, + security: [ + %{ + # API Key security applies to all operations + "api_key" => [] + } + ] + } + end +end diff --git a/lib/ash_hq/docs/resources/guide/guide.ex b/lib/ash_hq/docs/resources/guide/guide.ex index 21f45f9d..951c0c0b 100644 --- a/lib/ash_hq/docs/resources/guide/guide.ex +++ b/lib/ash_hq/docs/resources/guide/guide.ex @@ -6,6 +6,7 @@ defmodule AshHq.Docs.Guide do AshHq.Docs.Extensions.Search, AshHq.Docs.Extensions.RenderMarkdown, AshGraphql.Resource, + AshJsonApi.Resource, AshAdmin.Resource ] @@ -17,6 +18,19 @@ defmodule AshHq.Docs.Guide do end end + json_api do + type "guide" + includes [library_version: [:library]] + + routes do + base "/guides" + + get :read + index :read + index :read_for_version, route: "/for-version" + end + end + admin do form do field :text do @@ -52,9 +66,12 @@ defmodule AshHq.Docs.Guide do end read :read_for_version do + description "Query for Guides given a list of library versions to allow" + argument :library_versions, {:array, :uuid} do allow_nil? false constraints max_length: 20, min_length: 1 + description "UUIDs of the library versions to allow in the query" end pagination offset?: true, countable: true, default_limit: 25, required?: false diff --git a/lib/ash_hq_web/docs_json_api_router.ex b/lib/ash_hq_web/docs_json_api_router.ex new file mode 100644 index 00000000..1aff2a57 --- /dev/null +++ b/lib/ash_hq_web/docs_json_api_router.ex @@ -0,0 +1,15 @@ +defmodule AshHqWeb.DocsJsonApiRouter do + use AshJsonApi.Api.Router, api: AshHq.Docs, registry: AshHq.Docs.Registry + + get "/swaggerui", + to: OpenApiSpex.Plug.SwaggerUI, + init_opts: [ + path: "/json_api/openapi", + title: "AshHQ JSON-API - Swagger UI", + default_model_expand_depth: 4 + ] + + get "/redoc", + to: Redoc.Plug.RedocUI, + init_opts: [spec_url: "/json_api/openapi"] +end diff --git a/lib/ash_hq_web/router.ex b/lib/ash_hq_web/router.ex index 28e96e2d..1595b26d 100644 --- a/lib/ash_hq_web/router.ex +++ b/lib/ash_hq_web/router.ex @@ -71,8 +71,8 @@ defmodule AshHqWeb.Router do ## Api routes scope "/" do + forward "/json_api", AshHqWeb.DocsJsonApiRouter forward "/gql", Absinthe.Plug, schema: AshHqWeb.Schema - forward "/playground", Absinthe.Plug.GraphiQL, schema: AshHqWeb.Schema, diff --git a/mix.exs b/mix.exs index 2f00fab0..21cff715 100644 --- a/mix.exs +++ b/mix.exs @@ -40,7 +40,7 @@ defmodule AshHq.MixProject do {:ash_admin, github: "ash-project/ash_admin"}, {:ash_phoenix, github: "ash-project/ash_phoenix", override: true}, {:ash_graphql, github: "ash-project/ash_graphql"}, - {:ash_json_api, github: "ash-project/ash_json_api"}, + {:ash_json_api, github: "team-alembic/ash_json_api", branch: "feat/open-api-spex-helpers"}, {:absinthe_plug, "~> 1.5"}, {:ash_blog, github: "ash-project/ash_blog"}, {:ash_csv, github: "ash-project/ash_csv"}, @@ -70,6 +70,9 @@ defmodule AshHq.MixProject do {:phoenix_live_dashboard, "~> 0.6"}, {:ecto_psql_extras, "~> 0.6"}, {:phoenix_ecto, "~> 4.4"}, + # OpenApiSpex / SwaggerUI / Redoc + {:open_api_spex, "~> 3.16"}, + {:redoc_ui_plug, github: "team-alembic/redoc_ui_plug"}, # Phoenix/Core dependencies {:phoenix, "~> 1.6.14"}, {:ecto_sql, "~> 3.6"}, diff --git a/mix.lock b/mix.lock index b80a40c7..9a7b200d 100644 --- a/mix.lock +++ b/mix.lock @@ -6,7 +6,7 @@ "ash_blog": {:git, "https://github.com/ash-project/ash_blog.git", "79d1b9077b2712d4d4651eaa36c8e63119efd565", []}, "ash_csv": {:git, "https://github.com/ash-project/ash_csv.git", "bad0f6961bf5d135450dacda687d9df0549e80ae", []}, "ash_graphql": {:git, "https://github.com/ash-project/ash_graphql.git", "fb7b60f9e1793c912e238043aa115cd6585c19b7", []}, - "ash_json_api": {:git, "https://github.com/ash-project/ash_json_api.git", "2525172e72d0e8b0e369155f32e5edfbf25b9691", []}, + "ash_json_api": {:git, "https://github.com/team-alembic/ash_json_api.git", "22a3fc6ef59ac6aec93e6af15125eeabc037300c", [branch: "feat/open-api-spex-helpers"]}, "ash_phoenix": {:git, "https://github.com/ash-project/ash_phoenix.git", "5ffcd1916ece639c684273e632cc0aecebe82f26", []}, "ash_postgres": {:hex, :ash_postgres, "1.1.1", "2bbc2b39d9e387f89b964b29b042f88dd352b71e486d9aea7f9390ab1db3ced4", [:mix], [{:ash, "~> 2.1", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "fe47a6e629b6b23ce17c1d70b1bd4b3fd732df513b67126514fb88be86a6439e"}, "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"}, @@ -70,6 +70,7 @@ "nimble_options": {:hex, :nimble_options, "0.4.0", "c89babbab52221a24b8d1ff9e7d838be70f0d871be823165c94dd3418eea728f", [:mix], [], "hexpm", "e6701c1af326a11eea9634a3b1c62b475339ace9456c1a23ec3bc9a847bca02d"}, "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, "nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"}, + "open_api_spex": {:hex, :open_api_spex, "3.16.0", "9843af4e87550cd8ac5821b10e4c74f1d51f0d4e3310f824d780614743423b25", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "bb0be24a648b73e8fc8cbda17f514b8486262275e8b33e8b5ae66283df972129"}, "parallel_stream": {:hex, :parallel_stream, "1.1.0", "f52f73eb344bc22de335992377413138405796e0d0ad99d995d9977ac29f1ca9", [:mix], [], "hexpm", "684fd19191aedfaf387bbabbeb8ff3c752f0220c8112eb907d797f4592d6e871"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "phoenix": {:hex, :phoenix, "1.6.15", "0a1d96bbc10747fd83525370d691953cdb6f3ccbac61aa01b4acb012474b047d", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d70ab9fbf6b394755ea88b644d34d79d8b146e490973151f248cacd122d20672"}, @@ -90,6 +91,7 @@ "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"}, + "redoc_ui_plug": {:git, "https://github.com/team-alembic/redoc_ui_plug.git", "19d2b85f6ae122e08fdf37b7b2c1cd170ac846c8", []}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, "sourceror": {:hex, :sourceror, "0.11.2", "549ce48be666421ac60cfb7f59c8752e0d393baa0b14d06271d3f6a8c1b027ab", [:mix], [], "hexpm", "9ab659118896a36be6eec68ff7b0674cba372fc8e210b1e9dc8cf2b55bb70dfb"}, "spark": {:hex, :spark, "0.2.8", "adce1887be100fae124c65f9dcfb6675fefdcd0ffd6355bb02f638bf88e26630", [:mix], [{:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "1619dc8fd899980e33a7a63939e0a02783ed32c3344d192ff6669bf44ff637a6"},