diff --git a/backend/scripts/manage.py b/backend/scripts/manage.py index d3971e68d..f731319c9 100755 --- a/backend/scripts/manage.py +++ b/backend/scripts/manage.py @@ -35,40 +35,35 @@ def get_prepl_conninfo(): return host, port -def send_eval(expr): +def send(data): 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: - s.connect((host, port)) - s.send(expr.encode("utf-8")) - s.send(b":repl/quit\n\n") + json.dump(data, f) + f.write("\n") + f.flush() - with s.makefile() as f: - while True: - line = f.readline() - result = json.loads(line) - 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") + while True: + line = f.readline() + result = json.loads(line) + tag = result.get("tag", None) -def encode(val): - return json.dumps(json.dumps(val)) + if tag == "ret": + 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): - for error in res["via"]: - print("ERR:", error["message"]) - break +def print_error(error): + print("ERR:", error["hint"]) def run_cmd(params): try: - expr = "(app.srepl.cli/exec {})".format(encode(params)) - res, failed = send_eval(expr) - if failed: - print_error(res) + res, err = send(params) + if err: + print_error(err) sys.exit(-1) return res @@ -96,7 +91,7 @@ def update_profile(email, fullname, password, is_active): "email": email, "fullname": fullname, "password": password, - "is_active": is_active + "isActive": is_active } } @@ -138,7 +133,7 @@ def derive_password(password): params = { "cmd": "derive-password", "params": { - "password": password, + "password": password } } diff --git a/backend/src/app/srepl.clj b/backend/src/app/srepl.clj index fb53ca1e2..db28fd038 100644 --- a/backend/src/app/srepl.clj +++ b/backend/src/app/srepl.clj @@ -6,13 +6,17 @@ (ns app.srepl "Server Repl." + (:refer-clojure :exclude [read-line]) (:require + [app.common.exceptions :as ex] + [app.common.json :as json] [app.common.logging :as l] [app.config :as cf] - [app.srepl.cli] + [app.srepl.cli :as cli] [app.srepl.main] - [app.util.json :as json] [app.util.locks :as locks] + [app.util.time :as dt] + [clojure.core :as c] [clojure.core.server :as ccs] [clojure.main :as cm] [integrant.core :as ig])) @@ -28,17 +32,80 @@ :init repl-init :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 [] - (let [out *out* - lock (locks/create)] - (ccs/prepl *in* - (fn [m] - (binding [*out* out, - *flush-on-newline* true, - *print-readably* true] - (locks/locking lock - (println (json/encode-str m)))))))) + (let [lock (locks/create) + out *out* + + out-fn + (fn [m] + (locks/locking lock + (binding [*out* out] + (l/warn :hint "write" :data 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 diff --git a/backend/src/app/srepl/cli.clj b/backend/src/app/srepl/cli.clj index 25cf50d76..5dfb786ec 100644 --- a/backend/src/app/srepl/cli.clj +++ b/backend/src/app/srepl/cli.clj @@ -13,7 +13,6 @@ [app.db :as db] [app.rpc.commands.auth :as cmd.auth] [app.rpc.commands.profile :as cmd.profile] - [app.util.json :as json] [app.util.time :as dt] [cuerdas.core :as str])) @@ -28,12 +27,11 @@ "Entry point with external tools integrations that uses PREPL interface for interacting with running penpot backend." [data] - (let [data (json/decode data)] - (-> {::cmd (keyword (:cmd data "default"))} - (merge (:params data)) - (exec-command)))) + (-> {::cmd (get data :cmd)} + (merge (:params data)) + (exec-command))) -(defmethod exec-command :create-profile +(defmethod exec-command "create-profile" [{:keys [fullname email password is-active] :or {is-active true}}] (some-> (get-current-system) @@ -49,7 +47,7 @@ (->> (cmd.auth/create-profile! conn params) (cmd.auth/create-profile-rels! conn))))))) -(defmethod exec-command :update-profile +(defmethod exec-command "update-profile" [{:keys [fullname email password is-active]}] (some-> (get-current-system) (db/tx-run! @@ -70,7 +68,12 @@ :deleted-at nil})] (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]}] (when-not email (ex/raise :type :assertion @@ -88,7 +91,7 @@ {:email email}))] (pos? (db/get-update-count res))))))) -(defmethod exec-command :search-profile +(defmethod exec-command "search-profile" [{:keys [email]}] (when-not email (ex/raise :type :assertion @@ -102,7 +105,7 @@ " where email similar to ? order by created_at desc limit 100")] (db/exec! conn [sql email])))))) -(defmethod exec-command :derive-password +(defmethod exec-command "derive-password" [{:keys [password]}] (auth/derive-password password)) @@ -110,4 +113,4 @@ [{:keys [::cmd]}] (ex/raise :type :internal :code :not-implemented - :hint (str/ffmt "command '%' not implemented" (name cmd)))) + :hint (str/ffmt "command '%' not implemented" cmd)))