~benaiah/fennel-openresty

70d93ed8bc72c0cb2857d2cf73bc9a0ad4101f7a — Benaiah Mischenko 2 years ago 8937171
WIP - this was already staged
M .gitmodules => .gitmodules +3 -0
@@ 4,3 4,6 @@
[submodule "modules/lume"]
	path = modules/lume
	url = git@github.com:rxi/lume.git
[submodule "modules/lua-resty-http"]
	path = modules/lua-resty-http
	url = git@github.com:ledgetech/lua-resty-http.git

M Dockerfile => Dockerfile +5 -8
@@ 18,12 18,6 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get update \
RUN printf '#!/usr/bin/env bash\nexec rlwrap /app/modules/fennel/fennel "$@"\n' | tee /usr/local/bin/fennel \
    && chmod +x /usr/local/bin/fennel

# RUN mkdir /var/log/postgres \
#     && echo "deb http://apt.postgresql.org/pub/repos/apt/ stretch-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list \
#     && wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | DEBIAN_FRONTEND=noninteractive apt-key add - \
#     && DEBIAN_FRONTEND=noninteractive apt-get update \
#     && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends postgresql-11

ENV NVM_DIR /usr/local/nvm
ENV NODE_VERSION 10.15.1
RUN mkdir $NVM_DIR \


@@ 42,10 36,13 @@ RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | DEBIAN_FRONTEND=noninter
    && DEBIAN_FRONTEND=noninteractive apt-get update \
    && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends yarn

RUN opm get leafo/pgmoon
RUN opm get leafo/pgmoon && opm get SkyLothar/lua-resty-jwt

# openresty doesn't have any program named "lua" by default, but the
# fennel script uses that as its shebang. luajit works just fine to
# run fennel.
RUN ln -sf /usr/local/openresty/luajit/bin/luajit /usr/local/bin/lua \
    && make
    
RUN ln -sf /dev/stdout /app/nginx/logs/access.log \
    && ln -sf /dev/stderr /app/nginx/logs/error.log
\ No newline at end of file
    && ln -sf /dev/stderr /app/nginx/logs/error.log

M app/makefile => app/makefile +8 -2
@@ 5,13 5,19 @@ all: dist/app.js dist/app.html
clean:
	rm -f dist/*
	rm -f src/*.lua
	rm -f src/components/*.lua
	rm -rf node_modules

dist/app.html: src/app.html.fnl
	modules/fennel/fennel $< > $@
dist/app.js: src/react-helpers.lua src/app.lua src/index.js src/set-function-name.js
dist/app.js: src/react-helpers.lua src/app.lua src/index.js
	yarn && yarn run build
src/app.lua: src/app.fnl src/react-macros.fnl
COMPONENTS_SRC = $(wildcard src/components/*.fnl)
COMPONENTS_OBJ = $(patsubst src/components/%.fnl, src/components/%.lua, $(COMPONENTS_SRC))
src/components/%.lua: src/components/%.fnl
	modules/fennel/fennel --compile $< > $@
src/app.lua: src/app.fnl src/react-macros.fnl $(COMPONENTS_OBJ)
	modules/fennel/fennel --compile $< > $@
src/react-helpers.lua: src/react-helpers.fnl src/react-macros.fnl
	modules/fennel/fennel --compile $< > $@


M app/package.json => app/package.json +1 -0
@@ 14,6 14,7 @@
  },
  "dependencies": {
    "fengari-web": "^0.1.4",
    "jwt-decode": "^2.2.0",
    "react": "^16.8.1",
    "react-dom": "^16.8.1"
  }

M app/src/app.fnl => app/src/app.fnl +2 -33
@@ 2,7 2,6 @@
(local React (require "react"))
(local ReactDOM (require "react-dom"))
(local <> React.Fragment)

(local {:use-state use-state
        :use-effect use-effect
        :use-layout-effect use-layout-effect


@@ 12,33 11,10 @@
        :use-context use-context
        :get-children-as-array get-children-as-array}
       (require "./react-helpers.lua"))
(local Log (require "./components/Log.lua"))

(local console {:log (fn [...] (: js.global.console :log ...))})

(component! LogLine [{:children message}]
  (c! :pre {:className :log-message} message))

(component! Log [{:children maybe-children}]
  (let [children (get-children-as-array maybe-children)
        container-ref (use-ref nil)]
    ;; scroll to bottom on change
    (use-effect
     (fn []
       (when (and container-ref.current (not (= container-ref.current js.null))
                  container-ref.current.lastChild (not (= container-ref.current.lastChild js.null)))
         (: container-ref.current.lastChild :scrollIntoView (js! {}))))
     (js! [children.length]))
    (c! :div {:style {:color :#EEEEEE
                      :backgroundColor :#222222
                      :overflowY :scroll
                      :position :fixed
                      :left :0px
                      :right :0px
                      :top :100px
                      :height "calc(100vh - 200px)"}
              :ref container-ref}
        (: children :map (fn [_ child i] (c! LogLine {:key i} child))))))

(component! WebSocketDebugForm
  [{:connect connect :disconnect disconnect :on-message on-message}]
  (let [input-element (use-ref nil)


@@ 60,13 36,7 @@
                   :value current-message
                   :ref input-element
                   :onChange
                   (fn []
                     ;; (print (.. "Changing current-message '"
                     ;;            (if current-message current-message "")
                     ;;            "' to '"
                     ;;            input-element.current.value
                     ;;            "'."))
                     (set-current-message input-element.current.value))
                   (fn [] (set-current-message input-element.current.value))
                   }]
          [:button {:type :submit} :Send]]])))



@@ 117,7 87,6 @@
        debug-form (c! [WebSocketDebugForm {:connect connect
                                            :disconnect disconnect
                                            :on-message send-message}])]
    (set js.global.fennelLog (fn [_ message] (log message)))
    (c! [<> {}
         [:div {} (if ws "WebSocket connected" "Websocket not connected")]
         debug-form

A app/src/components/Log.fnl => app/src/components/Log.fnl +29 -0
@@ 0,0 1,29 @@
(require-macros :src.react-macros)
(local React (require "react"))
(local {:use-effect use-effect
        :use-ref use-ref
        :get-children-as-array get-children-as-array}
       (require "../react-helpers.lua"))
(local LogLine (require "./LogLine.lua"))

(component! Log [{:children maybe-children}]
  (let [children (get-children-as-array maybe-children)
        container-ref (use-ref nil)]
    ;; scroll to bottom on change
    (use-effect
     (fn []
       (when (and container-ref.current
                  (not (= container-ref.current js.null))
                  container-ref.current.lastChild
                  (not (= container-ref.current.lastChild js.null)))
         (: container-ref.current.lastChild :scrollIntoView (js! {}))))
     (js! [children.length]))
    (c! :div {:style {:color :#EEEEEE
                      :backgroundColor :#222222
                      :overflowY :scroll
                      :width :100%
                      :height "calc(100vh - 200px)"}
              :ref container-ref}
        (: children :map (fn [_ child i] (c! LogLine {:key i} child))))))

Log

A app/src/components/Log.lua => app/src/components/Log.lua +77 -0
@@ 0,0 1,77 @@
local React = require("react")
local _0_ = require("../react-helpers.lua")
local use_ref = _0_["use-ref"]
local get_children_as_array = _0_["get-children-as-array"]
local use_effect = _0_["use-effect"]
local LogLine = require("./LogLine.lua")
local Log
do
  local _6_0
  local function _9_(_, _, _10_0)
    local _11_ = _10_0
    local maybe_children = _11_["children"]
    do
      local children = get_children_as_array(maybe_children)
      local container_ref = use_ref(nil)
      local function _12_()
        if (container_ref.current and not (container_ref.current == js.null) and container_ref.current.lastChild and not (container_ref.current.lastChild == js.null)) then
          local function _14_()
            local _13_0 = js.new(js.global.Object)
            return _13_0
          end
          return container_ref.current.lastChild:scrollIntoView(_14_())
        end
      end
      local function _14_()
        local _13_0 = js.new(js.global.Array)
        _13_0[0] = children.length
        return _13_0
      end
      use_effect(_12_, _14_())
      do
        local _15_0
        do
          local _17_0 = js.new(js.global.Object)
          _17_0["ref"] = container_ref
          local _19_
          do
            local _18_0 = js.new(js.global.Object)
            _18_0["color"] = "#EEEEEE"
            _18_0["height"] = "calc(100vh - 200px)"
            _18_0["backgroundColor"] = "#222222"
            _18_0["overflowY"] = "scroll"
            _18_0["width"] = "100%"
            _19_ = _18_0
          end
          _17_0["style"] = _19_
          _15_0 = _17_0
        end
        local function _19_(_, child, i)
          local _20_0
          do
            local _22_0 = js.new(js.global.Object)
            _22_0["key"] = i
            _20_0 = _22_0
          end
          return React:createElement(LogLine, _20_0, child)
        end
        return React:createElement("div", _15_0, children:map(_19_))
      end
    end
  end
  _6_0 = _9_
  local _1_0 = {displayName = "Log", length = 1, name = "Log"}
  local _7_0
  local function _11_(_, _4_0)
    return rawget(_1_0, _4_0)
  end
  _7_0 = _11_
  local _8_0
  local function _12_(_, _4_0, _5_0)
    return rawset(_1_0, _4_0, _5_0)
  end
  _8_0 = _12_
  setmetatable(_1_0, {__call = _6_0, __index = _7_0, __newindex = _8_0})
  Log = js.createproxy(_1_0, "arrow_function")
end
return Log

A app/src/components/LogLine.fnl => app/src/components/LogLine.fnl +7 -0
@@ 0,0 1,7 @@
(require-macros :src.react-macros)
(local React (require "react"))

(component! LogLine [{:children message}]
  (c! :pre {:className :log-message} message))

LogLine

A app/src/components/LogLine.lua => app/src/components/LogLine.lua +33 -0
@@ 0,0 1,33 @@
local React = require("react")
local LogLine
do
  local _5_0
  local function _8_(_, _, _9_0)
    local _10_ = _9_0
    local message = _10_["children"]
    do
      local _11_0
      do
        local _13_0 = js.new(js.global.Object)
        _13_0["className"] = "log-message"
        _11_0 = _13_0
      end
      return React:createElement("pre", _11_0, message)
    end
  end
  _5_0 = _8_
  local _0_0 = {displayName = "LogLine", length = 1, name = "LogLine"}
  local _6_0
  local function _10_(_, _3_0)
    return rawget(_0_0, _3_0)
  end
  _6_0 = _10_
  local _7_0
  local function _11_(_, _3_0, _4_0)
    return rawset(_0_0, _3_0, _4_0)
  end
  _7_0 = _11_
  setmetatable(_0_0, {__call = _5_0, __index = _6_0, __newindex = _7_0})
  LogLine = js.createproxy(_0_0, "arrow_function")
end
return LogLine

A app/src/components/TestComponent.fnl => app/src/components/TestComponent.fnl +4 -0
@@ 0,0 1,4 @@
(require-macros :src.react-macros)

(component! TestComponent []
  (c! [:div {} "Hello, world!"]))

A app/src/components/TestComponent.lua => app/src/components/TestComponent.lua +27 -0
@@ 0,0 1,27 @@
local TestComponent
do
  local _5_0
  local function _8_(_, _)
    local _9_0
    do
      local _11_0 = js.new(js.global.Object)
      _9_0 = _11_0
    end
    return React:createElement("div", _9_0, "Hello, world!")
  end
  _5_0 = _8_
  local _0_0 = {displayName = "TestComponent", length = 0, name = "TestComponent"}
  local _6_0
  local function _9_(_, _3_0)
    return rawget(_0_0, _3_0)
  end
  _6_0 = _9_
  local _7_0
  local function _10_(_, _3_0, _4_0)
    return rawset(_0_0, _3_0, _4_0)
  end
  _7_0 = _10_
  setmetatable(_0_0, {__call = _5_0, __index = _6_0, __newindex = _7_0})
  TestComponent = js.createproxy(_0_0, "arrow_function")
end
return nil

M db/.gitignore => db/.gitignore +2 -1
@@ 1,2 1,3 @@
migrations/*.lua
config.lua
\ No newline at end of file
config.lua
data
\ No newline at end of file

M db/config.fnl => db/config.fnl +1 -1
@@ 1,4 1,4 @@
{:host :db
{:host :172.28.0.102
 :port 5432
 :database :fennel-openresty
 :user :fennel-openresty

M db/migrations/create-users-table.fnl => db/migrations/create-users-table.fnl +0 -2
@@ 14,8 14,6 @@ create table if not exists users (
);
")))

# hello world

(print (.. "creating users table: " (fennelview users-table-created)))

users-table-created

M docker-compose.yml => docker-compose.yml +2 -0
@@ 8,6 8,8 @@ services:
      - "8090:80"
    volumes:
      - .:/app
    environment:
      FENNEL_OPENRESTY_JWT_SECRET: Test_Secret!
    networks:
      nw:
        ipv4_address: 172.28.0.101

A modules/lua-resty-http => modules/lua-resty-http +1 -0
@@ 0,0 1,1 @@
Subproject commit f71e9708a3fd0ff4179d4b0d770c91fcf98d0042

M nginx/conf/nginx.conf => nginx/conf/nginx.conf +1 -0
@@ 1,4 1,5 @@
worker_processes 1;
env FENNEL_OPENRESTY_JWT_SECRET;
events {
    worker_connections 1024;
}

A server/jwt.fnl => server/jwt.fnl +15 -0
@@ 0,0 1,15 @@
(local jwt (require :resty.jwt))
(local jwt-secret (os.getenv :FENNEL_OPENRESTY_JWT_SECRET))

(fn get-jwt-token [{:id id :email email}]
  (: jwt :sign jwt-secret
     {:header {:typ :JWT :alg :HS512}
      :payload {:sub id :email email :iat (ngx.time)}}))

(fn verify-jwt-token [token]
  (let [jwt-obj (: jwt :load_jwt token)
        verified? (: jwt :verify_jwt_obj jwt-secret jwt-obj)]
    (when verified? jwt-obj.payload)))

{:get-token get-jwt-token
 :verify-token verify-jwt-token}

M server/server.fnl => server/server.fnl +67 -2
@@ 1,8 1,73 @@
(global ngx ngx)
(local pgmoon (require :pgmoon))
(local html (require :server.html))
(local cjson (require :cjson))
(local html (fn [...] (.. "<!doctype html>" ((require :server.html) ...))))
(global _G (. (getmetatable _G) :__index))
(local fennel (require :modules.fennel.fennel))
(local fennelview (require :modules.fennel.fennelview))
(local lume (require :modules.lume.lume))
(local jwt (require :server.jwt))

(ngx.say (.. "<!doctype html>\n" (html [:div {} :WTF])))
(local pg (pgmoon.new (require :db.config)))
(local pg-status (assert (: pg :connect)))

(local (h h-err) (ngx.req.get_headers))
(local method (ngx.req.get_method))

(fn say-serialized [tab]
  (if (and h (= h.Accept "text/x-fennelview"))
      (do (set ngx.header.Content-Type "text/x-fennelview")
          (ngx.say (fennelview tab {:indent ""})))
      (do (set ngx.header.Content-Type "application/json")
          (ngx.say (cjson.encode tab)))))

(fn index-route []
  (say-serialized {:authenticate :/api/authenticate}))

(fn unknown-route []
  (let [message (.. "Error 404 - unknown route " ngx.var.uri " for method " method)
        response {:error {:message message :code 404}}]
    (set ngx.status ngx.HTTP_NOT_FOUND)
    (say-serialized response)))

;; returns a user id if successful
(fn authenticate-user-by-email [{:email email :password password}]
  (let [esc_email (: pg :escape_literal email)
        esc_password (: pg :escape_literal password)
        query (.. "select id, email from users where"
                     " email=lower(" esc_email ") and"
                     " password=crypt(" esc_password ", password)"
                     " limit 1;")
        result (: pg :query query)]
    (when (and result (. result 1)) (. result 1))))

(local authenticate-wrong-method-error-message
       (.. "Method not allowed - use a POST with a JSON body including"
           " 'email' and 'password' to log in and receive a JWT which"
           " can be used as a bearer token to authenticate with the rest"
           " of the API."))

(fn authenticate-route []
  (match method
    :GET (do (set ngx.status ngx.HTTP_NOT_ALLOWED)
             (say-serialized
              {:error {:message authenticate-wrong-method-error-message
                       :code 405}}))
    :POST (do (ngx.req.read_body)
              (let [raw-body (ngx.req.get_body_data)
                    body (if raw-body (cjson.decode raw-body) {})]
                (if (and body.email body.password)
                    (let [user (authenticate-user-by-email body)]
                      (say-serialized (jwt.get-token user)))
                    (do (set ngx.status ngx.BAD_REQUEST)
                        (say-serialized {:error {:message "Bad request" :code 400}})))))
    _ (unknown-route)))

((fn router [uri]
   (let [path (lume.slice (lume.split uri :/) 2)
         route (match path
                 [:api :authenticate nil] authenticate-route
                 [:api nil] index-route
                 _ unknown-route)]
     (route)))
 ngx.var.uri)

M server/wss.fnl => server/wss.fnl +2 -8
@@ 1,15 1,9 @@
(global ngx ngx)
(local server (require :resty.websocket.server))

(local pgmoon (require :pgmoon))
(local jwt (require :server.jwt))

(local pg
       (pgmoon.new
        {:host :172.28.0.102
         :port :5432
         :database :fennel-openresty
         :user :fennel-openresty
         :password "go fennel!"}))
(local pg (pgmoon.new (require :db.config)))
(local (pg-status pg-err) (: pg :connect))
((fn [] (when (not pg-status)
          (ngx.log ngx.ERR (.. "error connecting to db: " (tostring pg-err))))))