mirror of
https://github.com/penpot/penpot.git
synced 2025-05-16 02:17:11 +02:00
♻️ Refactor prepl interface
Make prepl to be json message based protocol instead of clojure expression. This facilitates implementing internal RPC over socket server.
This commit is contained in:
parent
62a12a64a3
commit
2df6f2b8b1
3 changed files with 115 additions and 50 deletions
|
@ -35,40 +35,35 @@ def get_prepl_conninfo():
|
||||||
|
|
||||||
return host, port
|
return host, port
|
||||||
|
|
||||||
def send_eval(expr):
|
def send(data):
|
||||||
host, port = get_prepl_conninfo()
|
host, port = get_prepl_conninfo()
|
||||||
|
with socket.create_connection((host, port)) as s:
|
||||||
|
f = s.makefile(mode="rw")
|
||||||
|
|
||||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
json.dump(data, f)
|
||||||
s.connect((host, port))
|
f.write("\n")
|
||||||
s.send(expr.encode("utf-8"))
|
f.flush()
|
||||||
s.send(b":repl/quit\n\n")
|
|
||||||
|
|
||||||
with s.makefile() as f:
|
while True:
|
||||||
while True:
|
line = f.readline()
|
||||||
line = f.readline()
|
result = json.loads(line)
|
||||||
result = json.loads(line)
|
tag = result.get("tag", None)
|
||||||
tag = result.get("tag", None)
|
|
||||||
if tag == "ret":
|
|
||||||
return result.get("val", None), result.get("exception", None)
|
|
||||||
elif tag == "out":
|
|
||||||
print(result.get("val"), end="")
|
|
||||||
else:
|
|
||||||
raise RuntimeError("unexpected response from PREPL")
|
|
||||||
|
|
||||||
def encode(val):
|
if tag == "ret":
|
||||||
return json.dumps(json.dumps(val))
|
return result.get("val", None), result.get("err", None)
|
||||||
|
elif tag == "out":
|
||||||
|
print(result.get("val"), end="")
|
||||||
|
else:
|
||||||
|
raise RuntimeError("unexpected response from PREPL")
|
||||||
|
|
||||||
def print_error(res):
|
def print_error(error):
|
||||||
for error in res["via"]:
|
print("ERR:", error["hint"])
|
||||||
print("ERR:", error["message"])
|
|
||||||
break
|
|
||||||
|
|
||||||
def run_cmd(params):
|
def run_cmd(params):
|
||||||
try:
|
try:
|
||||||
expr = "(app.srepl.cli/exec {})".format(encode(params))
|
res, err = send(params)
|
||||||
res, failed = send_eval(expr)
|
if err:
|
||||||
if failed:
|
print_error(err)
|
||||||
print_error(res)
|
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
@ -96,7 +91,7 @@ def update_profile(email, fullname, password, is_active):
|
||||||
"email": email,
|
"email": email,
|
||||||
"fullname": fullname,
|
"fullname": fullname,
|
||||||
"password": password,
|
"password": password,
|
||||||
"is_active": is_active
|
"isActive": is_active
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +133,7 @@ def derive_password(password):
|
||||||
params = {
|
params = {
|
||||||
"cmd": "derive-password",
|
"cmd": "derive-password",
|
||||||
"params": {
|
"params": {
|
||||||
"password": password,
|
"password": password
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,13 +6,17 @@
|
||||||
|
|
||||||
(ns app.srepl
|
(ns app.srepl
|
||||||
"Server Repl."
|
"Server Repl."
|
||||||
|
(:refer-clojure :exclude [read-line])
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.exceptions :as ex]
|
||||||
|
[app.common.json :as json]
|
||||||
[app.common.logging :as l]
|
[app.common.logging :as l]
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.srepl.cli]
|
[app.srepl.cli :as cli]
|
||||||
[app.srepl.main]
|
[app.srepl.main]
|
||||||
[app.util.json :as json]
|
|
||||||
[app.util.locks :as locks]
|
[app.util.locks :as locks]
|
||||||
|
[app.util.time :as dt]
|
||||||
|
[clojure.core :as c]
|
||||||
[clojure.core.server :as ccs]
|
[clojure.core.server :as ccs]
|
||||||
[clojure.main :as cm]
|
[clojure.main :as cm]
|
||||||
[integrant.core :as ig]))
|
[integrant.core :as ig]))
|
||||||
|
@ -28,17 +32,80 @@
|
||||||
:init repl-init
|
:init repl-init
|
||||||
:read ccs/repl-read))
|
:read ccs/repl-read))
|
||||||
|
|
||||||
|
(defn- ex->data
|
||||||
|
[cause phase]
|
||||||
|
(let [data (ex-data cause)
|
||||||
|
explain (ex/explain data)]
|
||||||
|
(cond-> {:phase phase
|
||||||
|
:code (get data :code :unknown)
|
||||||
|
:type (get data :type :unknown)
|
||||||
|
:hint (or (get data :hint) (ex-message cause))}
|
||||||
|
(some? explain)
|
||||||
|
(assoc :explain explain))))
|
||||||
|
|
||||||
|
(defn read-line
|
||||||
|
[]
|
||||||
|
(if-let [line (c/read-line)]
|
||||||
|
(try
|
||||||
|
(l/dbg :hint "decode" :data line)
|
||||||
|
(json/decode line :key-fn json/read-kebab-key)
|
||||||
|
(catch Throwable _cause
|
||||||
|
(l/warn :hint "unable to decode data" :data line)
|
||||||
|
nil))
|
||||||
|
::eof))
|
||||||
|
|
||||||
(defn json-repl
|
(defn json-repl
|
||||||
[]
|
[]
|
||||||
(let [out *out*
|
(let [lock (locks/create)
|
||||||
lock (locks/create)]
|
out *out*
|
||||||
(ccs/prepl *in*
|
|
||||||
(fn [m]
|
out-fn
|
||||||
(binding [*out* out,
|
(fn [m]
|
||||||
*flush-on-newline* true,
|
(locks/locking lock
|
||||||
*print-readably* true]
|
(binding [*out* out]
|
||||||
(locks/locking lock
|
(l/warn :hint "write" :data m)
|
||||||
(println (json/encode-str m))))))))
|
(println (json/encode m :key-fn json/write-camel-key)))))
|
||||||
|
|
||||||
|
tapfn
|
||||||
|
(fn [val]
|
||||||
|
(out-fn {:tag :tap :val val}))]
|
||||||
|
|
||||||
|
(binding [*out* (PrintWriter-on #(out-fn {:tag :out :val %1}) nil true)
|
||||||
|
*err* (PrintWriter-on #(out-fn {:tag :err :val %1}) nil true)]
|
||||||
|
(try
|
||||||
|
(add-tap tapfn)
|
||||||
|
(loop []
|
||||||
|
(when (try
|
||||||
|
(let [data (read-line)
|
||||||
|
tpoint (dt/tpoint)]
|
||||||
|
|
||||||
|
(l/dbg :hint "received" :data (if (= data ::eof) "EOF" data))
|
||||||
|
|
||||||
|
(try
|
||||||
|
(when-not (= data ::eof)
|
||||||
|
(when-not (nil? data)
|
||||||
|
(let [result (cli/exec data)
|
||||||
|
elapsed (tpoint)]
|
||||||
|
(l/warn :hint "result" :data result)
|
||||||
|
(out-fn {:tag :ret
|
||||||
|
:val (if (instance? Throwable result)
|
||||||
|
(Throwable->map result)
|
||||||
|
result)
|
||||||
|
:elapsed (inst-ms elapsed)})))
|
||||||
|
true)
|
||||||
|
(catch Throwable cause
|
||||||
|
(let [elapsed (tpoint)]
|
||||||
|
(out-fn {:tag :ret
|
||||||
|
:err (ex->data cause :eval)
|
||||||
|
:elapsed (inst-ms elapsed)})
|
||||||
|
true))))
|
||||||
|
(catch Throwable cause
|
||||||
|
(out-fn {:tag :ret
|
||||||
|
:err (ex->data cause :read)})
|
||||||
|
true))
|
||||||
|
(recur)))
|
||||||
|
(finally
|
||||||
|
(remove-tap tapfn))))))
|
||||||
|
|
||||||
;; --- State initialization
|
;; --- State initialization
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.rpc.commands.auth :as cmd.auth]
|
[app.rpc.commands.auth :as cmd.auth]
|
||||||
[app.rpc.commands.profile :as cmd.profile]
|
[app.rpc.commands.profile :as cmd.profile]
|
||||||
[app.util.json :as json]
|
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
|
@ -28,12 +27,11 @@
|
||||||
"Entry point with external tools integrations that uses PREPL
|
"Entry point with external tools integrations that uses PREPL
|
||||||
interface for interacting with running penpot backend."
|
interface for interacting with running penpot backend."
|
||||||
[data]
|
[data]
|
||||||
(let [data (json/decode data)]
|
(-> {::cmd (get data :cmd)}
|
||||||
(-> {::cmd (keyword (:cmd data "default"))}
|
(merge (:params data))
|
||||||
(merge (:params data))
|
(exec-command)))
|
||||||
(exec-command))))
|
|
||||||
|
|
||||||
(defmethod exec-command :create-profile
|
(defmethod exec-command "create-profile"
|
||||||
[{:keys [fullname email password is-active]
|
[{:keys [fullname email password is-active]
|
||||||
:or {is-active true}}]
|
:or {is-active true}}]
|
||||||
(some-> (get-current-system)
|
(some-> (get-current-system)
|
||||||
|
@ -49,7 +47,7 @@
|
||||||
(->> (cmd.auth/create-profile! conn params)
|
(->> (cmd.auth/create-profile! conn params)
|
||||||
(cmd.auth/create-profile-rels! conn)))))))
|
(cmd.auth/create-profile-rels! conn)))))))
|
||||||
|
|
||||||
(defmethod exec-command :update-profile
|
(defmethod exec-command "update-profile"
|
||||||
[{:keys [fullname email password is-active]}]
|
[{:keys [fullname email password is-active]}]
|
||||||
(some-> (get-current-system)
|
(some-> (get-current-system)
|
||||||
(db/tx-run!
|
(db/tx-run!
|
||||||
|
@ -70,7 +68,12 @@
|
||||||
:deleted-at nil})]
|
:deleted-at nil})]
|
||||||
(pos? (db/get-update-count res)))))))))
|
(pos? (db/get-update-count res)))))))))
|
||||||
|
|
||||||
(defmethod exec-command :delete-profile
|
(defmethod exec-command "echo"
|
||||||
|
[params]
|
||||||
|
params)
|
||||||
|
|
||||||
|
|
||||||
|
(defmethod exec-command "delete-profile"
|
||||||
[{:keys [email soft]}]
|
[{:keys [email soft]}]
|
||||||
(when-not email
|
(when-not email
|
||||||
(ex/raise :type :assertion
|
(ex/raise :type :assertion
|
||||||
|
@ -88,7 +91,7 @@
|
||||||
{:email email}))]
|
{:email email}))]
|
||||||
(pos? (db/get-update-count res)))))))
|
(pos? (db/get-update-count res)))))))
|
||||||
|
|
||||||
(defmethod exec-command :search-profile
|
(defmethod exec-command "search-profile"
|
||||||
[{:keys [email]}]
|
[{:keys [email]}]
|
||||||
(when-not email
|
(when-not email
|
||||||
(ex/raise :type :assertion
|
(ex/raise :type :assertion
|
||||||
|
@ -102,7 +105,7 @@
|
||||||
" where email similar to ? order by created_at desc limit 100")]
|
" where email similar to ? order by created_at desc limit 100")]
|
||||||
(db/exec! conn [sql email]))))))
|
(db/exec! conn [sql email]))))))
|
||||||
|
|
||||||
(defmethod exec-command :derive-password
|
(defmethod exec-command "derive-password"
|
||||||
[{:keys [password]}]
|
[{:keys [password]}]
|
||||||
(auth/derive-password password))
|
(auth/derive-password password))
|
||||||
|
|
||||||
|
@ -110,4 +113,4 @@
|
||||||
[{:keys [::cmd]}]
|
[{:keys [::cmd]}]
|
||||||
(ex/raise :type :internal
|
(ex/raise :type :internal
|
||||||
:code :not-implemented
|
:code :not-implemented
|
||||||
:hint (str/ffmt "command '%' not implemented" (name cmd))))
|
:hint (str/ffmt "command '%' not implemented" cmd)))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue