Namdak Tonpa 5 years ago
parent
commit
5a8f0c69de
8 changed files with 151 additions and 21 deletions
  1. 2 1
      mix.exs
  2. 6 0
      src/Organization.erl
  3. 6 0
      src/Payment.erl
  4. 3 3
      src/rest.app.src
  5. 3 0
      src/rest.erl
  6. 0 17
      src/rest_app.erl
  7. 130 0
      src/rest_kvs.erl
  8. 1 0
      src/users.erl

+ 2 - 1
mix.exs

@@ -12,7 +12,7 @@ defmodule REST.Mixfile do
   def application() do
   def application() do
     [
     [
       mod: {:rest, []},
       mod: {:rest, []},
-      applications: [:public_key,:asn1,:kernel,:stdlib,:ranch,:cowboy,:syntax_tools,:compiler,:n2o]
+      applications: [:public_key,:asn1,:kernel,:stdlib,:ranch,:cowboy,:syntax_tools,:compiler,:rocksdb,:kvs,:n2o,:erp]
     ]
     ]
   end
   end
 
 
@@ -20,6 +20,7 @@ defmodule REST.Mixfile do
                   {:rocksdb, "~> 1.2.0"},
                   {:rocksdb, "~> 1.2.0"},
                   {:kvs, "~> 6.7.7"},
                   {:kvs, "~> 6.7.7"},
                   {:syn, "~> 1.6.3"},
                   {:syn, "~> 1.6.3"},
+                  {:erp, "~> 0.7.15"},
                   {:n2o, "~> 6.7.7"},
                   {:n2o, "~> 6.7.7"},
                   {:jsone, "~> 1.5.0"},
                   {:jsone, "~> 1.5.0"},
                   {:cowboy, "~> 2.5.0"} ]
                   {:cowboy, "~> 2.5.0"} ]

+ 6 - 0
src/Organization.erl

@@ -0,0 +1,6 @@
+-module('Organization').
+-include_lib("erp/include/organization.hrl").
+-compile({parse_transform, rest}).
+-compile(export_all).
+-rest_record('Organization').
+new() -> #'Organization'{}.

+ 6 - 0
src/Payment.erl

@@ -0,0 +1,6 @@
+-module('Payment').
+-include_lib("erp/include/payment.hrl").
+-compile({parse_transform, rest}).
+-compile(export_all).
+-rest_record('Payment').
+new() -> #'Payment'{}.

+ 3 - 3
src/rest.app.src

@@ -1,9 +1,9 @@
 {application, rest, [
 {application, rest, [
     {description,  "REST Yoctoframework"},
     {description,  "REST Yoctoframework"},
-    {vsn,          "5.10.2"},
+    {vsn, "5.10.2"},
     {applications, [public_key,asn1,kernel,stdlib,ranch,cowboy,syntax_tools,compiler,n2o]},
     {applications, [public_key,asn1,kernel,stdlib,ranch,cowboy,syntax_tools,compiler,n2o]},
     {modules, []},
     {modules, []},
-    {registered,   []},
-    {mod, { rest_app, []}},
+    {registered, []},
+    {mod, { rest, []}},
     {env, []}
     {env, []}
 ]}.
 ]}.

+ 3 - 0
src/rest.erl

@@ -10,11 +10,14 @@ stop(_State) -> ok.
 start(_StartType, _StartArgs) -> supervisor:start_link({local, ?MODULE}, ?MODULE, []).
 start(_StartType, _StartArgs) -> supervisor:start_link({local, ?MODULE}, ?MODULE, []).
 init([]) ->
 init([]) ->
    users:init(),
    users:init(),
+   kvs:join(),
    cowboy:start_clear(http, [{port,application:get_env(n2o,port,8005)}],
    cowboy:start_clear(http, [{port,application:get_env(n2o,port,8005)}],
                             #{env=>#{dispatch=> points() }}),
                             #{env=>#{dispatch=> points() }}),
    {ok, {{one_for_one, 5, 10}, []}}.
    {ok, {{one_for_one, 5, 10}, []}}.
 
 
 points() -> cowboy_router:compile([{'_', [
 points() -> cowboy_router:compile([{'_', [
+             {"/rest/kvs/0/[...]",     rest_kvs,    []},
+             {"/rest/kvs/1/:id/[...]", rest_kvs,    []},
              {"/rest/:resource",     rest_cowboy, []},
              {"/rest/:resource",     rest_cowboy, []},
              {"/rest/:resource/:id", rest_cowboy, []}
              {"/rest/:resource/:id", rest_cowboy, []}
             ]}]).
             ]}]).

+ 0 - 17
src/rest_app.erl

@@ -1,17 +0,0 @@
--module(rest_app).
--behaviour(application).
--behaviour(supervisor).
--export([init/1,start/2, stop/1]).
-
-init([]) ->
-   users:init(),
-   cowboy:start_clear(http, [{port,8005}], #{env=>#{dispatch=> points() }}),
-   {ok, {{one_for_one, 5, 10}, []}}.
-stop(_State) -> ok.
-start(_StartType, _StartArgs) ->
-   supervisor:start_link({local, ?MODULE}, ?MODULE, []).
-
-points() -> cowboy_router:compile([{'_', [
-             {"/rest/:resource",     rest_cowboy, []},
-             {"/rest/:resource/:id", rest_cowboy, []}
-            ]}]).

+ 130 - 0
src/rest_kvs.erl

@@ -0,0 +1,130 @@
+-module(rest_kvs).
+-include_lib("kvs/include/kvs.hrl").
+-export([exists/1, exists/2, new/1, get/1, delete/2, post/2, post/3]).
+-export([init/2, resource_exists/2, allowed_methods/2, content_types_provided/2,
+         to_html/2, to_json/2, content_types_accepted/2, delete_resource/2,
+         handle_urlencoded_data/2, handle_json_data/2]).
+
+-ifndef(REST_JSON).
+-define(REST_JSON, (application:get_env(rest,json,jsone))).
+-endif.
+
+c(X) -> binary_to_list(X).
+
+% kvs rest api
+
+new(Type)          -> Type:new().
+exists(Mod)        -> {X,_} = kvs:get(writer,c(Mod)), X == ok.
+exists(Mod,Id)     -> {X,_} = kvs:get(c(Mod),c(Id)), X == ok.
+get(Mod)           -> kvs:all(Mod).
+delete(Mod,Id)     -> kvs:delete(Mod,Id).
+post(Mod,Resource) when is_tuple(Resource) -> kvs:append(Mod,Resource).
+post(Type,Mod,Data)     when is_list(Data) -> post(Mod,Type:from_json(new(Type))).
+
+% cowboy rest api
+
+update_req(Req) -> #{ bindings := Bindings, path_info := List } = Req,
+   Req#{bindings => Bindings#{ resource => list_to_binary("/" ++
+      string:join(lists:map(fun (X) -> binary_to_list(X) end, List),"/")) }}.
+
+init(#{bindings := #{id := Id}} = Req, State) -> {cowboy_rest, update_req(Req), State};
+init(Req, State) -> {cowboy_rest, update_req(Req), State}.
+
+resource_exists(#{bindings := #{resource := Module, id := Id}} = Req, State) -> {rest_kvs:exists(Module,Id), Req, State};
+resource_exists(#{bindings := #{resource := Module}} = Req, State) -> {rest_kvs:exists(Module), Req, State};
+resource_exists(#{bindings := #{id := _}} = Req, State) -> {true, Req, State}.
+
+allowed_methods(#{bindings := #{resource := _}} = Req, State) -> {[<<"GET">>, <<"POST">>], Req, State};
+allowed_methods(#{bindings := #{resource := _, id := _}} = Req, State) -> {[<<"GET">>, <<"PUT">>, <<"DELETE">>], Req, State}.
+
+delete_resource(#{bindings := #{resource := Module, id := []}} = Req, State) -> {[], Req, State};
+delete_resource(#{bindings := #{resource := Module, id := Id}} = Req, State) -> {rest_kvs:delete(Module,Id), Req, State}.
+
+content_types_provided(#{bindings := #{resource := Module}} = Req, State) ->
+    {case application:get_env(rest,html,false) of
+         false -> [{<<"application/json">>, to_json}];
+         true  -> [{<<"text/html">>, to_html},
+                   {<<"application/json">>, to_json}] end, Req, State}.
+
+to_html(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
+    Body = case Id of
+               Id when Id==[];Id==undefined -> [ rest_kvs:to_html(Module, Resource) || Resource <- rest_kvs:get(Module,Id) ];
+               _ -> rest_kvs:to_html(rest_kvs:get(Module,Id)) end,
+    Html = case application:get_env(rest,html_layout,false) of
+               true  -> rest_kvs:html_layout(Module, Req, Body);
+               false -> default_html_layout(Body) end,
+    {Html, Req, State}.
+
+default_html_layout(Body) -> [<<"<html><body>">>, Body, <<"</body></html>">>].
+
+to_json(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
+    {ok,Resource} = kvs:get(c(Module),c(Id)),
+    Type = element(1,Resource),
+    {iolist_to_binary(?REST_JSON:encode(Type:to_json(Resource))), Req, State};
+to_json(#{bindings := #{resource := Module}} = Req, State) ->
+    Fold = [ begin M = element(1,Resource), M:to_json(Resource) end || Resource <- kvs:all(c(Module))],
+    {iolist_to_binary(?REST_JSON:encode([{Module,Fold}])), Req, State}.
+
+content_types_accepted(Req, State) ->
+  {[{<<"application/x-www-form-urlencoded">>, handle_urlencoded_data},
+    {<<"application/json">>, handle_json_data}], Req, State}.
+
+handle_urlencoded_data(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
+    {ok, Data, Req2} = cowboy_req:read_urlencoded_body(Req),
+    io:format("FORM: ~p~n",[Data]),
+    {handle_data(Module, Id, Data, Req), Req2, State};
+
+handle_urlencoded_data(#{bindings := #{resource := Module}} = Req0, State) ->
+    {ok, Data1, Req} = cowboy_req:read_urlencoded_body(Req0),
+    io:format("FORM: ~p, Data1: ~p~n",[Module,Data1]),
+    {handle_data(Module, [], Data1, Req), Req, State}.
+
+handle_json_data(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
+    {ok, Binary, Req2} = cowboy_req:read_body(Req),
+    io:format("JSON: ~p~n",[Binary]),
+    Data = case ?REST_JSON:decode(Binary) of {struct, Struct} -> Struct; S -> S end,
+    {handle_data(Module, Id, Data, Req), Req2, State};
+
+handle_json_data(#{bindings := #{resource := Module}} = Req, State) ->
+    {ok, Binary, Req2} = cowboy_req:read_body(Req),
+    io:format("JSON: ~p~n",[Binary]),
+    Data = case ?REST_JSON:decode(Binary) of {struct, Struct} -> Struct; S -> S end,
+    {handle_data(Module, [], Data, Req), Req2, State}.
+
+handle_data(Mod, Id, Data, Req) ->
+    Type = proplists:get_value(<<"rec">>, Data),
+    Valid = case application:get_env(rest,validate,false) of
+                true  -> rest_kvs:validate(Mod,Id, Data);
+                false -> default_validate(Mod, Id, Data, Req) end,
+    case {Valid, Id} of
+        {false, _}         -> false;
+        {true,  <<"undefined">>} -> rest_kvs:post(Type,Mod,Data);
+        {true,  _}         -> case application:get_env(rest,custom_put,false) of
+                                  true  -> Type:put(Mod, Id, Data);
+                                  false -> default_put(Type, Mod, Id, Data, Req) end
+    end.
+
+default_put(Type, Mod, Id, Data, Req) when is_map(Data) -> default_put(Type, Mod, Id, maps:to_list(Data), Req);
+default_put(Type, Mod, Id, Data, Req) ->
+    NewRes = Type:from_json(Data, rest_kvs:get(Mod,Id)),
+    NewId = proplists:get_value(id, Type:to_json(NewRes)),
+    case Id =/= NewId of
+        true when Id =:= [] -> skip;
+        true -> rest_kvs:delete(Mod,Id);
+        false -> true end,
+    rest_kvs:post(Type,Mod,NewRes).
+
+default_validate(Mod, Id, DataX, Req0) when is_map(DataX) -> default_validate(Mod, Id, maps:to_list(DataX), Req0);
+default_validate(Mod, Id, Data, Req0) ->
+    Allowed = case application:get_env(rest, keys_allowed, false) of
+      true  -> rest_kvs:keys_allowed(c(Mod),proplists:get_keys(Data));
+      false -> true end,
+    validate_match(Mod, Id, Allowed, proplists:get_value(<<"id">>, Data)).
+
+validate_match(_Mod,  [], true, [])      -> false;
+validate_match( Mod,  [], true, NewId)   -> not rest_kvs:exists(Mod,NewId);
+validate_match(_Mod, _Id, true, [])      -> true;
+validate_match(_Mod,  Id, true, Id)      -> true;
+validate_match( Mod, _Id, true, NewId)   -> not rest_kvs:exists(Mod,NewId);
+validate_match(   _,         _,    _, _) -> false.
+

+ 1 - 0
src/users.erl

@@ -4,6 +4,7 @@
 -export([init/0, populate/1, exists/1, get/0, get/1, post/1, delete/1]).
 -export([init/0, populate/1, exists/1, get/0, get/1, post/1, delete/1]).
 -rest_record(user).
 -rest_record(user).
 
 
+new() -> #user{}.
 init()               -> ets:new(users, [public, named_table, {keypos, #user.id}]).
 init()               -> ets:new(users, [public, named_table, {keypos, #user.id}]).
 populate(Users)      -> ets:insert(users, Users).
 populate(Users)      -> ets:insert(users, Users).
 exists(Id)           -> X = ets:member(users, binary_to_list(Id)), io:format("Member: ~p~n",[X]), X.
 exists(Id)           -> X = ets:member(users, binary_to_list(Id)), io:format("Member: ~p~n",[X]), X.