diff --git a/backend/resources/app/templates/debug.tmpl b/backend/resources/app/templates/debug.tmpl index 15824e626..0416a045d 100644 --- a/backend/resources/app/templates/debug.tmpl +++ b/backend/resources/app/templates/debug.tmpl @@ -6,11 +6,17 @@ Debug Main Page {% block content %} - Debug INDEX: - [ERRORS] + + ADMIN DEBUG INTERFACE + - + + + Error reports + CLICK HERE TO SEE THE ERROR REPORTS + + Download file data: Given an FILE-ID, downloads the file data as file. The file data is encoded using transit. @@ -37,9 +43,42 @@ Debug Main Page - + + + + + + Profile Management + + + + + + + Are you sure? + + + + This is a just a security double check for prevent non intentional submits. + + + + + + + + + + + + + + + + + @@ -123,5 +162,35 @@ Debug Main Page + + + + Reset file version + Allows reset file data version to a specific number/ + + + + + + + + + + + Are you sure? + + + + This is a just a security double check for prevent non intentional submits. + + + + + + + + + + {% endblock %} diff --git a/backend/resources/app/templates/styles.css b/backend/resources/app/templates/styles.css index 410734adb..93ef2c490 100644 --- a/backend/resources/app/templates/styles.css +++ b/backend/resources/app/templates/styles.css @@ -116,29 +116,50 @@ nav > div:not(:last-child) { width: unset; } -.index { +.dashboard { margin-top: 40px; display: flex; } -.index > section { - padding: 10px; - background-color: #e3e3e3; +.widget { max-width: 400px; margin: 5px; height: fit-content; } -.index fieldset:not(:first-child) { +.widget input[type=submit] { + outline: none; + border: 1px solid gray; + border-radius: 2px; + padding: 3px 5px; +} + +.widget input[type=submit].danger { + outline: none; + border: 1px solid red; + border-radius: 2px; + padding: 3px 5px; +} + + + +.widget > fieldset { + padding: 10px; + background-color: #f9f9f9; +} + +.widget > fieldset:not(:last-child) { + margin-bottom: 10px; +} + + + +.dashboard fieldset:not(:first-child) { margin-top: 15px; } -/* .index > section:not(:last-child) { */ -/* margin-bottom: 10px; */ -/* } */ - -.index > section > h2 { +.widget > h2 { margin-top: 0px; } diff --git a/backend/src/app/http/debug.clj b/backend/src/app/http/debug.clj index f7f2fb1d8..a61017edf 100644 --- a/backend/src/app/http/debug.clj +++ b/backend/src/app/http/debug.clj @@ -7,6 +7,7 @@ (ns app.http.debug (:refer-clojure :exclude [error-handler]) (:require + [app.common.data :as d] [app.common.exceptions :as ex] [app.common.logging :as l] [app.common.pprint :as pp] @@ -14,9 +15,12 @@ [app.config :as cf] [app.db :as db] [app.http.session :as session] + [app.main :as-alias main] + [app.rpc.commands.auth :as auth] [app.rpc.commands.binfile :as binf] [app.rpc.commands.files-create :refer [create-file]] [app.rpc.commands.profile :as profile] + [app.srepl.helpers :as srepl] [app.storage :as-alias sto] [app.util.blob :as blob] [app.util.template :as tmpl] @@ -34,15 +38,19 @@ ;; (selmer.parser/cache-off!) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; HELPERS +;; INDEX ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn authorized? - [pool {:keys [::session/profile-id]}] - (or (= "devenv" (cf/get :host)) - (let [profile (ex/ignoring (profile/get-profile pool profile-id)) - admins (or (cf/get :admins) #{})] - (contains? admins (:email profile))))) +(defn index-handler + [_cfg _request] + {::yrs/status 200 + ::yrs/headers {"content-type" "text/html"} + ::yrs/body (-> (io/resource "app/templates/debug.tmpl") + (tmpl/render {}))}) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; FILE CHANGES +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn prepare-response [body] @@ -59,24 +67,6 @@ ::yrs/body body ::yrs/headers headers})) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; INDEX -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn index-handler - [{:keys [::db/pool]} request] - (when-not (authorized? pool request) - (ex/raise :type :authentication - :code :only-admins-allowed)) - {::yrs/status 200 - ::yrs/headers {"content-type" "text/html"} - ::yrs/body (-> (io/resource "app/templates/debug.tmpl") - (tmpl/render {}))}) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; FILE CHANGES -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - (def sql:retrieve-range-of-changes "select revn, changes from file_change where file_id=? and revn >= ? and revn <= ? order by revn") @@ -85,10 +75,6 @@ (defn- retrieve-file-data [{:keys [::db/pool]} {:keys [params ::session/profile-id] :as request}] - (when-not (authorized? pool request) - (ex/raise :type :authentication - :code :only-admins-allowed)) - (let [file-id (some-> params :file-id parse-uuid) revn (some-> params :revn parse-long) filename (str file-id)] @@ -178,10 +164,6 @@ (defn file-changes-handler [{:keys [::db/pool]} {:keys [params] :as request}] - (when-not (authorized? pool request) - (ex/raise :type :authentication - :code :only-admins-allowed)) - (letfn [(retrieve-changes [file-id revn] (if (str/includes? revn ":") (let [[start end] (->> (str/split revn #":") @@ -251,10 +233,6 @@ (assoc :created-at (dt/format-instant created-at :rfc1123)))))) ] - (when-not (authorized? pool request) - (ex/raise :type :authentication - :code :only-admins-allowed)) - (if-let [report (get-report request)] (let [result (case (:version report) 1 (render-template-v1 report) @@ -275,10 +253,7 @@ LIMIT 200") (defn error-list-handler - [{:keys [::db/pool]} request] - (when-not (authorized? pool request) - (ex/raise :type :authentication - :code :only-admins-allowed)) + [{:keys [::db/pool]} _request] (let [items (->> (db/exec! pool [sql:error-reports]) (map #(update % :created-at dt/format-instant :rfc1123)))] {::yrs/status 200 @@ -363,6 +338,93 @@ ::yrs/headers {"content-type" "text/plain"} ::yrs/body "OK"})) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; ACTIONS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- resend-email-notification + [{:keys [::db/pool ::main/props] :as cfg} {:keys [params] :as request}] + + (when-not (contains? params :force) + (ex/raise :type :validation + :code :missing-force + :hint "missing force checkbox")) + + (let [profile (some->> params :email (profile/get-profile-by-email pool))] + + (when-not profile + (ex/raise :type :validation + :code :missing-profile + :hint "unable to find profile by email")) + + (cond + (contains? params :block) + (do + (db/update! pool :profile {:is-blocked true} {:id (:id profile)}) + (db/delete! pool :http-session {:profile-id (:id profile)}) + + {::yrs/status 200 + ::yrs/headers {"content-type" "text/plain"} + ::yrs/body (str/ffmt "PROFILE '%' BLOCKED" (:email profile))}) + + (contains? params :unblock) + (do + (db/update! pool :profile {:is-blocked false} {:id (:id profile)}) + {::yrs/status 200 + ::yrs/headers {"content-type" "text/plain"} + ::yrs/body (str/ffmt "PROFILE '%' UNBLOCKED" (:email profile))}) + + (contains? params :resend) + (if (:is-blocked profile) + {::yrs/status 200 + ::yrs/headers {"content-type" "text/plain"} + ::yrs/body "PROFILE ALREADY BLOCKED"} + (do + (auth/send-email-verification! pool props profile) + {::yrs/status 200 + ::yrs/headers {"content-type" "text/plain"} + ::yrs/body (str/ffmt "RESENDED FOR '%'" (:email profile))})) + + :else + (do + (db/update! pool :profile {:is-active true} {:id (:id profile)}) + {::yrs/status 200 + ::yrs/headers {"content-type" "text/plain"} + ::yrs/body (str/ffmt "PROFILE '%' ACTIVATED" (:email profile))})))) + + +(defn- reset-file-data-version + [cfg {:keys [params] :as request}] + (let [file-id (some-> params :file-id d/parse-uuid) + version (some-> params :version d/parse-integer)] + + (when-not (contains? params :force) + (ex/raise :type :validation + :code :missing-force + :hint "missing force checkbox")) + + (when (nil? file-id) + (ex/raise :type :validation + :code :invalid-file-id + :hint "provided invalid file id")) + + (when (nil? version) + (ex/raise :type :validation + :code :invalid-version + :hint "provided invalid version")) + + (srepl/update-file! cfg + :id file-id + :update-fn (fn [file] + (update file :data assoc :version version)) + :migrate? false + :inc-revn? false + :save? true) + {::yrs/status 200 + ::yrs/headers {"content-type" "text/plain"} + ::yrs/body "OK"})) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; OTHER SMALL VIEWS/HANDLERS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -397,6 +459,13 @@ ;; INIT ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn authorized? + [pool {:keys [::session/profile-id]}] + (or (= "devenv" (cf/get :host)) + (let [profile (ex/ignoring (profile/get-profile pool profile-id)) + admins (or (cf/get :admins) #{})] + (contains? admins (:email profile))))) + (def with-authorization {:compile (fn [& _] @@ -420,6 +489,10 @@ ["/changelog" {:handler (partial changelog-handler cfg)}] ["/error/:id" {:handler (partial error-handler cfg)}] ["/error" {:handler (partial error-list-handler cfg)}] + ["/actions/resend-email-verification" + {:handler (partial resend-email-notification cfg)}] + ["/actions/reset-file-data-version" + {:handler (partial reset-file-data-version cfg)}] ["/file/export" {:handler (partial export-handler cfg)}] ["/file/import" {:handler (partial import-handler cfg)}] ["/file/data" {:handler (partial file-data-handler cfg)}] diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 0f1eb8854..3a0bee728 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -284,11 +284,13 @@ ::http.ws/routes (ig/ref ::http.ws/routes) ::http.awsns/routes (ig/ref ::http.awsns/routes)} - :app.http.debug/routes + ::http.debug/routes {::db/pool (ig/ref ::db/pool) ::wrk/executor (ig/ref ::wrk/executor) ::session/manager (ig/ref ::session/manager) - ::sto/storage (ig/ref ::sto/storage)} + ::sto/storage (ig/ref ::sto/storage) + ::props (ig/ref ::setup/props)} + ::http.ws/routes {::db/pool (ig/ref ::db/pool)