Skip to content

Commit

Permalink
make api accessible from outside
Browse files Browse the repository at this point in the history
- also resolve todos/fixmes
  • Loading branch information
fschoenfeldt committed Jul 2, 2024
1 parent b81f5b4 commit a86e602
Show file tree
Hide file tree
Showing 13 changed files with 201 additions and 25 deletions.
4 changes: 3 additions & 1 deletion .check.exs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@
## ...or reconfigured (e.g. disable parallel execution of ex_unit in umbrella)
# {:ex_unit, umbrella: [parallel: false]},
{:gettext, "mix gettext.extract --check-up-to-date",
fix: "mix gettext.extract --merge priv/gettext"}
fix: "mix gettext.extract --merge priv/gettext"},

## custom new tools may be added (Mix tasks or arbitrary commands)
# run phx_schema before any other tool
{:phx_swagger_generate, "mix phx.swagger.generate", order: -1}
# {:my_task, "mix my_task", env: %{"MIX_ENV" => "prod"}},
# {:my_tool, ["my_tool", "arg with spaces"]}
]
Expand Down
1 change: 1 addition & 0 deletions .credo.exs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@
{Credo.Check.Refactor.ModuleDependencies,
[
excluded_namespaces: [
"Fotohaecker.Application",
"Fotohaecker.Content",
"FotohaeckerWeb",
"FotohaeckerWeb.Endpoint",
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ fotohaecker-*.tar
/priv/static/uploads/*

# Ignore swagger files
/priv/static/api.json
/priv/static/schema.json

# Ignore digested assets cache.
/priv/static/cache_manifest.json
Expand Down
2 changes: 1 addition & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ config :ueberauth, Ueberauth.Strategy.Auth0.OAuth,
# Configure Swagger
config :fotohaecker, :phoenix_swagger,
swagger_files: %{
"priv/static/api.json" => [
"priv/static/schema.json" => [
# phoenix routes will be converted to swagger paths
router: FotohaeckerWeb.Router,
# (optional) endpoint config used to set host, port and https schemes.
Expand Down
2 changes: 1 addition & 1 deletion config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ config :fotohaecker, FotohaeckerWeb.Endpoint,
# Binding to loopback ipv4 address prevents access from other machines.
# Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
http: [ip: {0, 0, 0, 0}, port: 1337],
# FIXME: dont put url here
# TODO: test this url doesn't break anything
url: [host: "localhost", port: 1337],
static_url: [path: "/fh"],
check_origin: false,
Expand Down
3 changes: 1 addition & 2 deletions config/prod.exs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import Config
# before starting your production server.
config :fotohaecker, FotohaeckerWeb.Endpoint,
cache_static_manifest: "priv/static/cache_manifest.json",
# FIXME: dont put url here
url: [host: "fschoenf.uber.space", port: 443]
url: [host: System.get_env("PHX_HOST"), port: 443]

# Do not print debug messages in production
config :logger, level: :info
Expand Down
11 changes: 11 additions & 0 deletions lib/fotohaecker/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ defmodule Fotohaecker.Application do
}
]

# Parse the swagger schema on startup as described here:
# - https://hexdocs.pm/phoenix_swagger/PhoenixSwagger.Validator.html#parse_swagger_schema/1
# - https://github.com/xerions/phoenix_swagger/issues/62#issuecomment-381932391
[
:code.priv_dir(:fotohaecker),
"static",
"schema.json"
]
|> Path.join()
|> PhoenixSwagger.Validator.parse_swagger_schema()

# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Fotohaecker.Supervisor]
Expand Down
15 changes: 15 additions & 0 deletions lib/fotohaecker_web/controllers/error_json.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
defmodule FotohaeckerWeb.ErrorJSON do
@behaviour FotohaeckerWeb.SchemaBehaviour

def schema do
use PhoenixSwagger

swagger_schema do
title("Error")
description("Error")

properties do
errors(array(:object))
end
end
end

def not_found(_conn) do
%{
errors: [
Expand Down
54 changes: 39 additions & 15 deletions lib/fotohaecker_web/controllers/photo_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,7 @@ defmodule FotohaeckerWeb.PhotoController do

def swagger_definitions do
%{
Photo:
swagger_schema do
title("Photo")
description("A photo")

properties do
id(:integer, "Photo ID", required: true)
title(:string, "Photo title", required: true)
description(:string, "Photo description")
url(:string, "Photo URL", required: true)
inserted_at(:string, "Creation timestamp", format: "date-time")
updated_at(:string, "Last update timestamp", format: "date-time")
end
end
Photo: FotohaeckerWeb.PhotoJSON.schema()
}
end

Expand All @@ -32,7 +19,9 @@ defmodule FotohaeckerWeb.PhotoController do
response(200, "OK", Schema.ref(:Photo))
end

# TODO: result pagination
# TODO: add pagination
# see https://github.com/fschoenfeldt/fotohaecker/issues/119

def index(conn, _params) do
photos = Content.list_photos()
render(conn, :index, photos: photos)
Expand All @@ -55,6 +44,21 @@ defmodule FotohaeckerWeb.PhotoController do
end
end

swagger_path :search do
tag("Photo")
description("Search for photos")
deprecated(true)

parameters do
search(:path, :string, "Search string", required: true)
end

response(200, "OK", Schema.ref(:Photo))
end

# TODO: add pagination
# see https://github.com/fschoenfeldt/fotohaecker/issues/119

def search(conn, %{"search" => search}) do
# decode uri encoded search string
search = URI.decode(search)
Expand All @@ -63,6 +67,26 @@ defmodule FotohaeckerWeb.PhotoController do
render(conn, :index, photos: photos)
end

swagger_path :search_query do
tag("Photo")
description("Search for photos")
deprecated(true)

parameters do
query(:query, :string, "Search string", required: true)
end

response(200, "OK", Schema.ref(:Photo))
end

def search_query(conn, %{"query" => search}) do
# decode uri encoded search string
search = URI.decode(search)

photos = Fotohaecker.Search.search!(search)
render(conn, :index, photos: photos)
end

# def create(conn, %{"photo" => photo_params}) do
# with {:ok, %Photo{} = photo} <- Contents.create_photo(photo_params) do
# conn
Expand Down
45 changes: 44 additions & 1 deletion lib/fotohaecker_web/controllers/photo_json.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
defmodule FotohaeckerWeb.PhotoJSON do
alias Fotohaecker.Search
@behaviour FotohaeckerWeb.SchemaBehaviour

alias Fotohaecker.Content.Photo
alias Fotohaecker.Search

@doc """
Renders a list of photos.
Expand All @@ -18,6 +20,47 @@ defmodule FotohaeckerWeb.PhotoJSON do
}
end

@doc """
Schema for usage in swagger
# TODO: enforce schema function by using elixirs behaviour
"""
def schema do
use PhoenixSwagger

swagger_schema do
title("Photo")
description("A photo")

properties do
id(:integer, "Photo ID", required: true)
title(:string, "Photo title", required: true)
tags(array(:string), "Photo tags")

links(
Schema.new do
property(:html, :string, "HTML link to photo")
end
)

urls(
Schema.new do
property(:thumb1x, :string, "Small Thumbnail URL")
property(:thumb2x, :string, "Medium Thumb URL")
property(:thumb3x, :string, "Large Thumb URL")
property(:raw, :string, "Original photo URL")
property(:full, :string, "Full size photo URL")
end
)

description(:string, "Photo description")
url(:string, "Photo URL", required: true)
inserted_at(:string, "Creation timestamp", format: "date-time")
updated_at(:string, "Last update timestamp", format: "date-time")
end
end
end

# Instead of deriving the Jason.Encoder protocol for the Photo struct,
# we can also define a data/1 function that returns a map with the
# desired fields.
Expand Down
4 changes: 3 additions & 1 deletion lib/fotohaecker_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ defmodule FotohaeckerWeb.Router do

pipeline :api do
plug CORSPlug
plug PhoenixSwagger.Plug.Validate

plug :accepts, ["json"]
end
Expand All @@ -24,7 +25,7 @@ defmodule FotohaeckerWeb.Router do
scope "/api/swagger", PhoenixSwagger do
forward "/", Plug.SwaggerUI,
otp_app: :fotohaecker,
swagger_file: "api.json"
swagger_file: "schema.json"
end

scope "/api", FotohaeckerWeb do
Expand All @@ -38,6 +39,7 @@ defmodule FotohaeckerWeb.Router do
scope "/search" do
scope "/photos" do
get "/:search", PhotoController, :search
get "/", PhotoController, :search_query
end
end
end
Expand Down
7 changes: 7 additions & 0 deletions lib/fotohaecker_web/schema_behaviour.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
defmodule FotohaeckerWeb.SchemaBehaviour do
@moduledoc """
Behaviour for schema modules that are used to generate Swagger schemas.
"""

@callback schema() :: PhoenixSwagger.Schema
end
76 changes: 74 additions & 2 deletions test/fotohaecker_web/controllers/photo_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ defmodule FotohaeckerWeb.PhotoControllerTest do
end

describe "search" do
test "finds photo", %{conn: conn} do
test "finds photo (deprecated endpoint)", %{conn: conn} do
Mox.expect(Fotohaecker.UserManagement.UserManagementMock, :search!, fn _term ->
[]
end)
Expand Down Expand Up @@ -166,7 +166,51 @@ defmodule FotohaeckerWeb.PhotoControllerTest do
assert actual == expected
end

test "returns empty list when empty string provided as input", %{conn: conn} do
test "finds photo", %{conn: conn} do
Mox.expect(Fotohaecker.UserManagement.UserManagementMock, :search!, fn _term ->
[]
end)

photo_fixture(%{
id: 1,
title: "scottish coast",
tags: ["scotland", "coast"]
})

photo_fixture(%{
id: 2,
title: "spain"
})

actual =
conn
|> get(~p"/fh/api/search/photos?query=scotland")
|> json_response(200)

expected = %{
"data" => [
%{
"id" => 1,
"links" => %{"html" => "http://localhost:4002/fh/en_US/photos/1"},
"tags" => ["scotland", "coast"],
"title" => "scottish coast",
"urls" => %{
"full" => "http://localhost:4002/uploads/some_file_name_preview.jpg",
"raw" => "http://localhost:4002/uploads/some_file_name_og.jpg",
"thumb1x" => "http://localhost:4002/uploads/some_file_name_thumb@1x.jpg",
"thumb2x" => "http://localhost:4002/uploads/some_file_name_thumb@2x.jpg",
"thumb3x" => "http://localhost:4002/uploads/some_file_name_thumb@3x.jpg"
}
}
]
}

assert actual == expected
end

test "returns empty list when empty string provided as input (deprecated endpoint)", %{
conn: conn
} do
actual =
conn
|> get(~p"/fh/api/search/photos/%20")
Expand All @@ -178,6 +222,34 @@ defmodule FotohaeckerWeb.PhotoControllerTest do

assert actual == expected
end

test "returns empty list when empty string provided as input", %{
conn: conn
} do
actual =
conn
|> get(~p"/fh/api/search/photos?query=%20")
|> json_response(200)

expected = %{
"data" => []
}

assert actual == expected
end

test "returns error when unknown parameter is provided", %{conn: conn} do
actual =
conn
|> get(~p"/fh/api/search/photos?unknown=123")
|> json_response(400)

expected = %{
"error" => %{"message" => "Required property query was not present.", "path" => "#"}
}

assert actual == expected
end
end

# describe "create photo" do
Expand Down

0 comments on commit a86e602

Please sign in to comment.