├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── docker-compose.yml ├── elm-package.json ├── elvis.config ├── frontend ├── Decoders.elm ├── Main.elm ├── Messages.elm ├── Model.elm ├── Service.elm ├── Update.elm ├── Urls.elm └── View.elm ├── priv ├── assets │ └── .ignoreme └── index.html ├── rebar.config ├── rebar.lock ├── rel ├── sys.config └── vm.args └── src ├── catalog.app.src ├── catalog.erl ├── catalog_handler.erl ├── catalog_sup.erl └── rest_mixin.erl /.gitignore: -------------------------------------------------------------------------------- 1 | .rebar3 2 | _* 3 | .eunit 4 | *.o 5 | *.beam 6 | *.plt 7 | *.swp 8 | *.swo 9 | .erlang.cookie 10 | ebin 11 | log 12 | erl_crash.dump 13 | rebar3.crashdump 14 | .rebar 15 | logs 16 | _build 17 | .idea/ 18 | .elm-stuff/ 19 | .priv/node_modules 20 | .priv/assets/Main.js 21 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM erlang:slim 2 | MAINTAINER Dairon Medina 3 | 4 | RUN mkdir /build 5 | RUN mkdir /app 6 | ADD . /build 7 | 8 | RUN set -xe \ 9 | && buildDeps=' \ 10 | curl \ 11 | nodejs-legacy \ 12 | npm \ 13 | git \ 14 | make \ 15 | curl \ 16 | ' \ 17 | && apt-get update \ 18 | && apt-get install -y --no-install-recommends $buildDeps \ 19 | && npm install -g elm elm-test --silent \ 20 | && cd /build \ 21 | && make install \ 22 | && make \ 23 | && mv _build/default/rel/catalog /app \ 24 | && cd / \ 25 | && rm -rf /build \ 26 | && apt-get purge -y --auto-remove $buildDeps \ 27 | && rm -rf /var/lib/apt/lists/* 28 | 29 | WORKDIR /app 30 | 31 | EXPOSE 4000 32 | CMD ["/app/catalog/bin/catalog", "foreground"] 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Dairon Medina Caro . 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | * The names of its contributors may not be used to endorse or promote 16 | products derived from this software without specific prior written 17 | permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | APPNAME=catalog 2 | FE_DIR=frontend 3 | FE_OUTPUT_DIR=priv/assets 4 | RELPATH=./_build/default/rel/$(APPNAME) 5 | 6 | REBAR=./rebar3 7 | ERL=erl 8 | ELM_MAKE=elm-make 9 | ELM_PACKAGE=elm-package 10 | 11 | CONFIG=rel/sys.config 12 | 13 | .PHONY: all install compile release clean 14 | 15 | all: release 16 | 17 | $(REBAR): 18 | $(ERL) \ 19 | -noshell -s inets start -s ssl start \ 20 | -eval 'httpc:request(get, {"https://s3.amazonaws.com/rebar3/rebar3", []}, [], [{stream, "./rebar3"}])' \ 21 | -s inets stop -s init stop 22 | chmod +x $(REBAR) 23 | 24 | install: 25 | $(ELM_PACKAGE) install -y 26 | 27 | $(FE_OUTPUT_DIR)/Main.js: $(FE_DIR)/Main.elm 28 | $(ELM_MAKE) $(FE_DIR)/Main.elm --output $(FE_OUTPUT_DIR)/Main.js 29 | 30 | compile: $(FE_OUTPUT_DIR)/Main.js 31 | $(REBAR) compile 32 | 33 | clean: $(REBAR) 34 | $(REBAR) clean 35 | rm -rvf $(FE_OUTPUT_DIR)/Main.js 36 | 37 | release: compile 38 | $(REBAR) release 39 | 40 | run: release 41 | $(RELPATH)/bin/$(APPNAME) console 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Elm-Erlang Catalog 2 | ================== 3 | 4 | Simple products catalog built with Erlang using ELM on the Frontend. 5 | 6 | Requirements 7 | ------------ 8 | 9 | Required software/libraries: 10 | 11 | * Erlang >= 19.0 12 | * elm-lang >= 1.8 13 | 14 | 15 | Run Locally 16 | ----------- 17 | 18 | $ make 19 | $ make run 20 | 21 | 22 | Run in Docker 23 | ------------ 24 | 25 | $ docker-compose build 26 | $ docker-compose up 27 | 28 | Go to http://localhost:4000. 29 | 30 | 31 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | catalog: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | ports: 8 | - "4000:4000" 9 | -------------------------------------------------------------------------------- /elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.1", 3 | "summary": "ELM Products Catalog", 4 | "repository": "https://github.com/codeadict/elm-erlang-catalog.git", 5 | "license": "GPL-3", 6 | "source-directories": [ 7 | "frontend/" 8 | ], 9 | "exposed-modules": [], 10 | "dependencies": { 11 | "elm-lang/core": "5.1.1 <= v < 6.0.0", 12 | "elm-lang/html": "2.0.0 <= v < 3.0.0", 13 | "elm-lang/http": "1.0.0 <= v < 2.0.0", 14 | "ggb/numeral-elm": "1.1.3 <= v < 2.0.0" 15 | }, 16 | "elm-version": "0.18.0 <= v < 0.19.0" 17 | } 18 | -------------------------------------------------------------------------------- /elvis.config: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | elvis, 4 | [ 5 | {config, 6 | [#{dirs => ["src/*", "test"], 7 | filter => "*.erl", 8 | ruleset => erl_files 9 | }, 10 | #{dirs => ["."], 11 | filter => "Makefile", 12 | ruleset => makefiles 13 | }, 14 | #{dirs => ["."], 15 | filter => "rebar.config", 16 | ruleset => rebar_config 17 | }, 18 | #{dirs => ["."], 19 | filter => "elvis.config", 20 | ruleset => elvis_config 21 | } 22 | ] 23 | } 24 | ] 25 | } 26 | ]. 27 | -------------------------------------------------------------------------------- /frontend/Decoders.elm: -------------------------------------------------------------------------------- 1 | module Decoders exposing (..) 2 | 3 | import Model exposing (Product) 4 | import Json.Decode exposing (..) 5 | 6 | product : Decoder Product 7 | product = 8 | map3 Product 9 | (field "id" int) 10 | (field "name" string) 11 | (field "price" float) 12 | 13 | 14 | products : Decoder (List Product) 15 | products = 16 | list product 17 | -------------------------------------------------------------------------------- /frontend/Main.elm: -------------------------------------------------------------------------------- 1 | module Main exposing(..) 2 | 3 | import Html exposing (program) 4 | import Model exposing (Model) 5 | import Update exposing (update) 6 | import View exposing (view) 7 | import Messages exposing (Msg(..)) 8 | import Service exposing (getProducts) 9 | 10 | 11 | init : (Model, Cmd Msg) 12 | init = 13 | ( Model.init 14 | , getProducts 15 | ) 16 | 17 | subscriptions : Model -> Sub Msg 18 | subscriptions products = 19 | Sub.none 20 | 21 | main : Program Never Model Msg 22 | main = 23 | program 24 | { init = init 25 | , view = view 26 | , subscriptions = subscriptions 27 | , update = update 28 | } 29 | -------------------------------------------------------------------------------- /frontend/Messages.elm: -------------------------------------------------------------------------------- 1 | module Messages exposing (Msg(..)) 2 | 3 | import Http 4 | import Model exposing (Product) 5 | 6 | 7 | type Msg 8 | = ReceiveProducts (Result Http.Error (List Product)) 9 | -------------------------------------------------------------------------------- /frontend/Model.elm: -------------------------------------------------------------------------------- 1 | module Model exposing (..) 2 | 3 | type alias Model = 4 | { products : List Product} 5 | 6 | 7 | init : Model 8 | init = 9 | Model [] 10 | 11 | 12 | type alias Product = 13 | { id : Int 14 | , name : String 15 | , price : Float 16 | } 17 | -------------------------------------------------------------------------------- /frontend/Service.elm: -------------------------------------------------------------------------------- 1 | module Service exposing (getProducts) 2 | 3 | import Http 4 | import Decoders exposing (products) 5 | import Messages exposing (Msg(ReceiveProducts)) 6 | import Urls 7 | 8 | getProducts : Cmd Msg 9 | getProducts = 10 | Http.get Urls.products products 11 | |> Http.send ReceiveProducts 12 | -------------------------------------------------------------------------------- /frontend/Update.elm: -------------------------------------------------------------------------------- 1 | module Update exposing (update) 2 | 3 | import Model exposing (Model) 4 | import Messages exposing (Msg(..)) 5 | 6 | 7 | update : Msg -> Model -> (Model, Cmd Msg) 8 | update msg model = 9 | case msg of 10 | ReceiveProducts (Ok data) -> 11 | ( { model | products = data }, Cmd.none ) 12 | 13 | ReceiveProducts (Err _) -> 14 | ( model, Cmd.none ) 15 | -------------------------------------------------------------------------------- /frontend/Urls.elm: -------------------------------------------------------------------------------- 1 | module Urls exposing (..) 2 | 3 | products : String 4 | products = 5 | "http://localhost:4000/products" 6 | -------------------------------------------------------------------------------- /frontend/View.elm: -------------------------------------------------------------------------------- 1 | module View exposing (view) 2 | 3 | import Html exposing (Html, main_, h1, table, caption, thead, tr, th, tbody, td, text, span, img) 4 | import Html.Attributes exposing (class, align, width) 5 | import Model exposing (..) 6 | import Numeral exposing (format) 7 | import Messages exposing (Msg(..)) 8 | 9 | productView : Product -> Html Msg 10 | productView product = 11 | tr [] 12 | [ td [] [ text product.name ] 13 | , td [align "right"] [ text (formatPrice product.price) ] 14 | ] 15 | 16 | 17 | formatPrice : Float -> String 18 | 19 | formatPrice price = 20 | format "$0,0.00" price 21 | 22 | 23 | view : Model -> Html Msg 24 | view model = 25 | main_ [ class "elm-products-page" ] 26 | [ h1 [] [ text "Products" ] 27 | , table [] 28 | [thead [] 29 | [ tr [] 30 | [ th [align "left", width 100] [ text "Name" ] 31 | , th [align "right", width 100] [ text "Price" ] 32 | ] 33 | ] 34 | , tbody [] (List.map (productView) model.products) 35 | ] 36 | ] 37 | -------------------------------------------------------------------------------- /priv/assets/.ignoreme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeadict/elm-erlang-catalog/feb798bbe1f4f7eb6297c423a9b5929330b34ce0/priv/assets/.ignoreme -------------------------------------------------------------------------------- /priv/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Product Catalog 5 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | %% == Erlang Compiler == 2 | 3 | {erl_opts, [ 4 | debug_info, 5 | {parse_transform, lager_transform} 6 | ]}. 7 | 8 | 9 | %% == Dependencies == 10 | 11 | {deps, [ 12 | {lager, "3.2.1"}, 13 | {mixer, "0.1.5", {pkg, inaka_mixer}}, 14 | {cowboy_swagger, "1.2.1"}, 15 | {jsx, "~> 2.8"} 16 | ]}. 17 | 18 | %% == Cover == 19 | 20 | {cover_enabled, true}. 21 | 22 | {cover_opts, [verbose]}. 23 | 24 | %% == EDoc == 25 | 26 | {edoc_opts, [ 27 | {report_missing_types, true}, 28 | {source_path, ["src"]}, 29 | {report_missing_types, true}, 30 | {todo, true}, 31 | {packages, false}, 32 | {subpackages, false} 33 | ]}. 34 | 35 | %% == Dialyzer == 36 | 37 | {dialyzer, [ 38 | {plt_apps, top_level_deps}, 39 | {plt_location, local} 40 | ]}. 41 | 42 | %% == Release == 43 | 44 | {relx, [{release, {catalog, "0.0.1"}, 45 | [catalog], 46 | [ {dev_mode, false} 47 | , {include_erts, true} 48 | ]}, 49 | {sys_config, "rel/sys.config"}, 50 | {vm_args, "rel/vm.args"}, 51 | {extended_start_script, true}, 52 | {overlay, 53 | [ 54 | {copy, "./_build/default/lib/cowboy_swagger/priv/swagger", "priv/swagger"} 55 | ] 56 | } 57 | ]}. -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | {"1.1.0", 2 | [{<<"cowboy">>,{pkg,<<"cowboy">>,<<"1.0.4">>},1}, 3 | {<<"cowboy_swagger">>,{pkg,<<"cowboy_swagger">>,<<"1.2.1">>},0}, 4 | {<<"cowlib">>,{pkg,<<"cowlib">>,<<"1.0.2">>},2}, 5 | {<<"goldrush">>,{pkg,<<"goldrush">>,<<"0.1.8">>},1}, 6 | {<<"jsx">>,{pkg,<<"jsx">>,<<"2.8.2">>},0}, 7 | {<<"lager">>,{pkg,<<"lager">>,<<"3.2.1">>},0}, 8 | {<<"mixer">>,{pkg,<<"inaka_mixer">>,<<"0.1.5">>},0}, 9 | {<<"ranch">>,{pkg,<<"ranch">>,<<"1.2.1">>},1}, 10 | {<<"trails">>,{pkg,<<"trails">>,<<"0.2.1">>},0}]}. 11 | [ 12 | {pkg_hash,[ 13 | {<<"cowboy">>, <<"A324A8DF9F2316C833A470D918AAF73AE894278B8AA6226CE7A9BF699388F878">>}, 14 | {<<"cowboy_swagger">>, <<"FB0B5A10783BECF337D4AFB1392B2D2AE08A29522043E9249C7E5C718E771ED8">>}, 15 | {<<"cowlib">>, <<"9D769A1D062C9C3AC753096F868CA121E2730B9A377DE23DEC0F7E08B1DF84EE">>}, 16 | {<<"goldrush">>, <<"2024BA375CEEA47E27EA70E14D2C483B2D8610101B4E852EF7F89163CDB6E649">>}, 17 | {<<"jsx">>, <<"7ACC7D785B5ABE8A6E9ADBDE926A24E481F29956DD8B4DF49E3E4E7BCC92A018">>}, 18 | {<<"lager">>, <<"EEF4E18B39E4195D37606D9088EA05BF1B745986CF8EC84F01D332456FE88D17">>}, 19 | {<<"mixer">>, <<"754630C0E60221B23E4D83ADF6E789A8B283855E23F9391EEC40980E6E800486">>}, 20 | {<<"ranch">>, <<"A6FB992C10F2187B46FFD17CE398DDF8A54F691B81768F9EF5F461EA7E28C762">>}, 21 | {<<"trails">>, <<"6769F7A2B777EC9CEAF07F22C23E5F3BE6A7F07E9A11B44725B102A832334D46">>}]} 22 | ]. 23 | -------------------------------------------------------------------------------- /rel/sys.config: -------------------------------------------------------------------------------- 1 | [ 2 | {catalog, 3 | [ 4 | {http_port, 4000}, 5 | {http_listener_count, 10} 6 | ] 7 | }, 8 | 9 | {cowboy_swagger, 10 | [ 11 | {static_files, "priv/swagger"}, 12 | {global_spec, 13 | #{swagger => "2.0", 14 | info => #{title => "Catalog API"}, 15 | basePath => "" 16 | } 17 | } 18 | ] 19 | }, 20 | 21 | { 22 | lager, 23 | [ 24 | {colored, true}, 25 | {async_threshold, 200}, 26 | {async_threshold_window, 5}, 27 | {error_logger_hwm, 500}, 28 | {handlers, 29 | [ 30 | {lager_console_backend, 31 | [debug, 32 | {lager_default_formatter, 33 | [ 34 | color, time, " [", severity, "]", 35 | " [", {module, ""}, ":", {function, ""}, ":", {line, ""}, "] ", 36 | message, "\e[0m\n" 37 | ] 38 | } 39 | ] 40 | } 41 | ] 42 | } 43 | ] 44 | } 45 | ]. -------------------------------------------------------------------------------- /rel/vm.args: -------------------------------------------------------------------------------- 1 | ## Name of the node 2 | -sname catalog 3 | 4 | ## Cookie for distributed erlang 5 | -setcookie catalog_cookie 6 | 7 | ## Enable kernel poll and a few async threads 8 | +K true 9 | +A 5 10 | 11 | ## Increase number of concurrent ports/sockets 12 | -env ERL_MAX_PORTS 4096 13 | 14 | ## Tweak GC to run more often 15 | -env ERL_FULLSWEEP_AFTER 10 -------------------------------------------------------------------------------- /src/catalog.app.src: -------------------------------------------------------------------------------- 1 | {application, catalog, 2 | [{description, "Simple Products API built with Erlang and ELM on the Frontend."}, 3 | {vsn, "0.0.1"}, 4 | {registered, []}, 5 | {mod, { catalog, []}}, 6 | {applications, 7 | [kernel, 8 | stdlib, 9 | lager, 10 | jsx, 11 | cowboy, 12 | trails, 13 | cowboy_swagger 14 | ]}, 15 | {env,[]}, 16 | {modules, []}, 17 | {start_phases, [{start_trails_http, []}]}, 18 | {build_tools,["rebar3"]}, 19 | {maintainers, ["Dairon Medina "]}, 20 | {licenses, ["Apache 2"]}, 21 | {links, ["https://github.com/codeadict/elm-erlang-catalog"]} 22 | ]}. 23 | -------------------------------------------------------------------------------- /src/catalog.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %% @doc catalog public API 3 | %% @end 4 | %%%------------------------------------------------------------------- 5 | 6 | -module(catalog). 7 | 8 | -behaviour(application). 9 | 10 | %% Application callbacks 11 | -export([start/0]). 12 | -export([start/2]). 13 | -export([stop/0]). 14 | -export([stop/1]). 15 | -export([start_phase/3]). 16 | 17 | 18 | %%==================================================================== 19 | %% API 20 | %%==================================================================== 21 | 22 | %% application 23 | %% @doc Starts the application 24 | -spec start() -> ok. 25 | start() -> 26 | {ok, _} = application:ensure_all_started(catalog), 27 | ok. 28 | 29 | %% @doc Stops the application 30 | stop() -> 31 | ok = application:stop(catalog), 32 | ok. 33 | 34 | %% behaviour 35 | %% @private 36 | start(_StartType, _StartArgs) -> 37 | catalog_sup:start_link(). 38 | 39 | %% @private 40 | stop(_State) -> 41 | ok = cowboy:stop_listener(catalog_http). 42 | 43 | -spec start_phase(atom(), application:start_type(), []) -> ok | {error, term()}. 44 | start_phase(start_trails_http, _StartType, []) -> 45 | {ok, Port} = application:get_env(catalog, http_port), 46 | {ok, ListenerCount} = application:get_env(catalog, http_listener_count), 47 | 48 | Handlers = [ catalog_handler 49 | , cowboy_swagger_handler 50 | ], 51 | 52 | %% Get the trails for each handler 53 | Trails = [ { "/" 54 | , cowboy_static 55 | , {file, filename:join([code:priv_dir(catalog), "index.html"])}} 56 | , { "/assets/[...]" 57 | , cowboy_static 58 | , {dir, filename:join([code:priv_dir(catalog), "assets"])}} 59 | | trails:trails(Handlers) 60 | ], 61 | 62 | %% Store them so Cowboy is able to get them 63 | trails:store(Trails), 64 | %% Set server routes 65 | Dispatch = trails:single_host_compile(Trails), 66 | RanchOptions = [{port, Port}], 67 | CowboyOptions = 68 | [ 69 | {env, 70 | [ 71 | {dispatch, Dispatch} 72 | ]}, 73 | {compress, true}, 74 | {timeout, 12000} 75 | ], 76 | %% Start Cowboy HTTP server 77 | case cowboy:start_http( catalog 78 | , ListenerCount 79 | , RanchOptions 80 | , CowboyOptions) of 81 | {ok, _} -> ok; 82 | {error, {already_started, _}} -> ok 83 | end. 84 | -------------------------------------------------------------------------------- /src/catalog_handler.erl: -------------------------------------------------------------------------------- 1 | -module(catalog_handler). 2 | -author("codeadict"). 3 | 4 | -include_lib("mixer/include/mixer.hrl"). 5 | -mixin([ 6 | {rest_mixin, 7 | [ 8 | init/3, 9 | rest_init/2, 10 | content_types_accepted/2, 11 | content_types_provided/2, 12 | resource_exists/2 13 | ]} 14 | ]). 15 | 16 | %% API 17 | -export([ 18 | allowed_methods/2, 19 | handle_get/2 20 | ]). 21 | 22 | 23 | %% trails 24 | -behaviour(trails_handler). 25 | -export([trails/0]). 26 | 27 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 28 | %% Trails Definition 29 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 30 | 31 | -spec trails() -> trails:trails(). 32 | trails() -> 33 | Metadata = 34 | #{ get => 35 | #{ description => "Returns the list of Products" 36 | , produces => ["application/json"] 37 | } 38 | }, 39 | [trails:trail("/products", catalog_handler, [], Metadata)]. 40 | 41 | 42 | %% cowboy 43 | allowed_methods(Req, State) -> 44 | {[<<"GET">>], Req, State}. 45 | 46 | to_json(Req, State) -> 47 | Reply = [ #{id => 1, name => <<"iPhone 7 Plus">>, price => 675.00} 48 | , #{id => 2, name => <<"NES case for Raspberry Pi 3">>, price => 19.85} 49 | , #{id => 3, name => <<"Echo Dot (2nd Generation) - Black">>, price => 49.99} 50 | , #{id => 4, name => <<"Pebble Time Steel Smartwatch">>, price => 102.95} 51 | , #{id => 5, name => <<"Nest Cam Indoor security camera">>, price => 163.80} 52 | , #{id => 6, name => <<"Bose SoundLink Bluetooth Speaker III">>, price => 258.99} 53 | , #{id => 7, name => <<"Hubsan X4 4 Channel 2.4GHz RC Quad Copter">>, price => 33.23} 54 | , #{id => 8, name => <<"USB Classic Super Nintendo Controller">>, price => 19.95} 55 | ], 56 | JSON = jsx:encode(Reply), 57 | {JSON, Req, State}. 58 | 59 | handle_get(Req, State) -> 60 | lager:info("Get GET request on products resource"), 61 | to_json(Req, State). 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/catalog_sup.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %% @doc catalog top level supervisor. 3 | %% @end 4 | %%%------------------------------------------------------------------- 5 | 6 | -module(catalog_sup). 7 | 8 | -behaviour(supervisor). 9 | 10 | -export([start_link/0]). 11 | -export([init/1]). 12 | 13 | %% admin api 14 | start_link() -> 15 | supervisor:start_link({local, ?MODULE}, ?MODULE, {}). 16 | 17 | %% behaviour callbacks 18 | init({}) -> 19 | {ok, {{one_for_one, 5, 10}, []} }. 20 | -------------------------------------------------------------------------------- /src/rest_mixin.erl: -------------------------------------------------------------------------------- 1 | -module(rest_mixin). 2 | -author("codeadict"). 3 | 4 | -export([ init/3 5 | , rest_init/2 6 | , content_types_accepted/2 7 | , content_types_provided/2 8 | , forbidden/2 9 | , resource_exists/2 10 | ]). 11 | 12 | %% cowboy 13 | init(_Transport, _Req, _Opts) -> 14 | {upgrade, protocol, cowboy_rest}. 15 | 16 | rest_init(Req, _Opts) -> 17 | {ok, Req, #{}}. 18 | 19 | content_types_accepted(Req, State) -> 20 | {[{'*', handle_put}], Req, State}. 21 | 22 | content_types_provided(Req, State) -> 23 | {[{<<"application/json">>, handle_get}], Req, State}. 24 | 25 | forbidden(Req, State) -> 26 | {false, Req, State}. 27 | 28 | resource_exists(Req, State) -> 29 | {true, Req, State}. 30 | --------------------------------------------------------------------------------