0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-17 18:21:23 -05:00

♻️ Refactor error handling.

This commit is contained in:
Andrey Antukh 2021-01-22 14:33:18 +01:00 committed by Alonso Torres
parent b4ba9d4375
commit bea093e8da
17 changed files with 578 additions and 334 deletions

View file

@ -10,20 +10,25 @@
(ns app.http.errors
"A errors handling for the http server."
(:require
[clojure.pprint :refer [pprint]]
[app.common.exceptions :as ex]
[clojure.tools.logging :as log]
[cuerdas.core :as str]
[expound.alpha :as expound]))
(defn get-context-string
[request edata]
(str "=| uri: " (pr-str (:uri request)) "\n"
"=| method: " (pr-str (:request-method request)) "\n"
"=| params: " (pr-str (:params request)) "\n"
"=| params: \n"
(with-out-str
(pprint (:params request)))
"\n"
(when (map? edata)
(str "=| ex-data: " (pr-str edata) "\n"))
(str "=| ex-data: \n"
(with-out-str
(pprint edata))))
"\n"))
@ -33,69 +38,60 @@
(or (:type edata)
(class err)))))
(defmethod handle-exception :authorization
[err _]
{:status 403
:body (ex-data err)})
(defmethod handle-exception :authentication
[err _]
{:status 401
:body (ex-data err)})
{:status 401 :body (ex-data err)})
(defn- explain-error
[error]
(with-out-str
(expound/printer (:data error))))
(defmethod handle-exception :validation
[err req]
(let [header (get-in req [:headers "accept"])
edata (ex-data err)]
(cond
(= :spec-validation (:code edata))
(if (str/starts-with? header "text/html")
{:status 400
:headers {"content-type" "text/html"}
:body (str "<pre style='font-size:16px'>"
(with-out-str
(expound/printer (:data edata)))
"</pre>\n")}
{:status 400
:body (assoc edata :explain (with-out-str (expound/printer (:data edata))))})
:else
(if (and (= :spec-validation (:code edata))
(str/starts-with? header "text/html"))
{:status 400
:body edata})))
:headers {"content-type" "text/html"}
:body (str "<pre style='font-size:16px'>"
(explain-error edata)
"</pre>\n")}
{:status 400
:body (cond-> edata
(map? (:data edata))
(-> (assoc :explain (explain-error edata))
(dissoc :data)))})))
(defmethod handle-exception :assertion
[error request]
(let [edata (ex-data error)]
(log/error error
(str "Assertion error\n"
(get-context-string request edata)
(with-out-str (expound/printer (:data edata)))))
(str "Internal error: assertion\n"
(get-context-string request edata)
(explain-error edata)))
{:status 500
:body (assoc edata :explain (with-out-str (expound/printer (:data edata))))}))
:body {:type :server-error
:data (-> edata
(assoc :explain (explain-error edata))
(dissoc :data))}}))
(defmethod handle-exception :not-found
[err _]
(let [response (ex-data err)]
{:status 404
:body response}))
(defmethod handle-exception :service-error
[err req]
(handle-exception (.getCause ^Throwable err) req))
{:status 404 :body (ex-data err)})
(defmethod handle-exception :default
[error request]
(let [edata (ex-data error)]
(log/error error
(str "Internal Error\n"
(get-context-string request edata)))
(if (nil? edata)
{:status 500
:body {:type :server-error
:hint (ex-message error)}}
{:status 500
:body (dissoc edata :data)})))
(str "Internal Error: "
(ex-message error)
(get-context-string request edata)))
{:status 500
:body {:type :server-error
:hint (ex-message error)
:data edata}}))
(defn handle
[error req]

View file

@ -108,7 +108,8 @@
(proj/check-edition-permissions! conn profile-id id)
(db/update! conn :project
{:name name}
{:id id})))
{:id id})
nil))
;; --- Mutation: Delete Project

View file

@ -71,8 +71,8 @@
[conn file-id page-id token]
(let [sql "select exists(select 1 from file_share_token where file_id=? and page_id=? and token=?) as exists"]
(when-not (:exists (db/exec-one! conn [sql file-id page-id token]))
(ex/raise :type :authorization
:code :unauthorized-token))))
(ex/raise :type :not-found
:code :object-not-found))))
(defn retrieve-shared-token
[conn file-id page-id]

View file

@ -140,13 +140,14 @@
[spec x message context]
(if (s/valid? spec x)
x
(let [data (s/explain-data spec x)
hint (with-out-str (s/explain-out data))]
(let [data (s/explain-data spec x)
explain (with-out-str (s/explain-out data))]
(ex/raise :type :assertion
:code :spec-validation
:hint message
:data data
:hint hint
:explain explain
:context context
:message message
#?@(:cljs [:stack (.-stack (ex-info message {}))])))))
@ -181,13 +182,13 @@
[spec data]
(let [result (s/conform spec data)]
(when (= result ::s/invalid)
(let [data (s/explain-data spec data)
hint (with-out-str
(s/explain-out data))]
(let [data (s/explain-data spec data)
explain (with-out-str
(s/explain-out data))]
(throw (ex/error :type :validation
:code :spec-validation
:data data
:hint hint))))
:explain explain
:data data))))
result))
(defmacro instrument!

View file

@ -18,7 +18,7 @@
}
},
"auth.create-demo-account" : {
"used-in" : [ "src/app/main/ui/auth/login.cljs:160", "src/app/main/ui/auth/register.cljs:136" ],
"used-in" : [ "src/app/main/ui/auth/register.cljs:136", "src/app/main/ui/auth/login.cljs:160" ],
"translations" : {
"en" : "Create demo account",
"fr" : "Vous voulez juste essayer?",
@ -27,7 +27,7 @@
}
},
"auth.create-demo-profile" : {
"used-in" : [ "src/app/main/ui/auth/login.cljs:157", "src/app/main/ui/auth/register.cljs:133" ],
"used-in" : [ "src/app/main/ui/auth/register.cljs:133", "src/app/main/ui/auth/login.cljs:157" ],
"translations" : {
"en" : "Just wanna try it?",
"fr" : "Vous voulez juste essayer?",
@ -45,7 +45,7 @@
}
},
"auth.email" : {
"used-in" : [ "src/app/main/ui/auth/login.cljs:99", "src/app/main/ui/auth/register.cljs:101", "src/app/main/ui/auth/recovery_request.cljs:47" ],
"used-in" : [ "src/app/main/ui/auth/register.cljs:101", "src/app/main/ui/auth/recovery_request.cljs:47", "src/app/main/ui/auth/login.cljs:99" ],
"translations" : {
"en" : "Email",
"fr" : "Adresse email",
@ -196,7 +196,7 @@
}
},
"auth.password" : {
"used-in" : [ "src/app/main/ui/auth/login.cljs:106", "src/app/main/ui/auth/register.cljs:106" ],
"used-in" : [ "src/app/main/ui/auth/register.cljs:106", "src/app/main/ui/auth/login.cljs:106" ],
"translations" : {
"en" : "Password",
"fr" : "Mot de passe",
@ -259,7 +259,7 @@
}
},
"auth.register-submit" : {
"used-in" : [ "src/app/main/ui/auth/login.cljs:134", "src/app/main/ui/auth/register.cljs:110" ],
"used-in" : [ "src/app/main/ui/auth/register.cljs:110", "src/app/main/ui/auth/login.cljs:134" ],
"translations" : {
"en" : "Create an account",
"fr" : "Créer un compte",
@ -295,7 +295,7 @@
}
},
"dashboard.add-shared" : {
"used-in" : [ "src/app/main/ui/workspace/header.cljs:225", "src/app/main/ui/dashboard/grid.cljs:180" ],
"used-in" : [ "src/app/main/ui/workspace/header.cljs:225", "src/app/main/ui/dashboard/grid.cljs:182" ],
"translations" : {
"en" : "Add as Shared Library",
"fr" : "",
@ -343,7 +343,7 @@
}
},
"dashboard.empty-files" : {
"used-in" : [ "src/app/main/ui/dashboard/grid.cljs:187" ],
"used-in" : [ "src/app/main/ui/dashboard/grid.cljs:189" ],
"translations" : {
"en" : "You still have no files here",
"fr" : "Vous n'avez encore aucun fichier ici",
@ -517,7 +517,7 @@
}
},
"dashboard.num-of-members" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:299" ],
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:301" ],
"translations" : {
"en" : "%s members",
"es" : "%s integrantes"
@ -542,7 +542,7 @@
}
},
"dashboard.promote-to-owner" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:196" ],
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:198" ],
"translations" : {
"en" : "Promote to owner",
"es" : "Promover a dueño"
@ -558,7 +558,7 @@
}
},
"dashboard.remove-shared" : {
"used-in" : [ "src/app/main/ui/workspace/header.cljs:223", "src/app/main/ui/dashboard/grid.cljs:179" ],
"used-in" : [ "src/app/main/ui/workspace/header.cljs:223", "src/app/main/ui/dashboard/grid.cljs:181" ],
"translations" : {
"en" : "Remove as Shared Library",
"fr" : "",
@ -603,7 +603,7 @@
}
},
"dashboard.show-all-files" : {
"used-in" : [ "src/app/main/ui/dashboard/grid.cljs:250" ],
"used-in" : [ "src/app/main/ui/dashboard/grid.cljs:252" ],
"translations" : {
"en" : "Show all files",
"es" : "Ver todos los ficheros"
@ -626,21 +626,21 @@
}
},
"dashboard.team-info" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:282" ],
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:284" ],
"translations" : {
"en" : "Team info",
"es" : "Información del equipo"
}
},
"dashboard.team-members" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:293" ],
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:295" ],
"translations" : {
"en" : "Team members",
"es" : "Integrantes del equipo"
}
},
"dashboard.team-projects" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:302" ],
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:304" ],
"translations" : {
"en" : "Team projects",
"es" : "Proyectos del equipo"
@ -674,7 +674,7 @@
}
},
"dashboard.update-settings" : {
"used-in" : [ "src/app/main/ui/settings/options.cljs:72", "src/app/main/ui/settings/profile.cljs:82", "src/app/main/ui/settings/password.cljs:96" ],
"used-in" : [ "src/app/main/ui/settings/profile.cljs:82", "src/app/main/ui/settings/password.cljs:96", "src/app/main/ui/settings/options.cljs:72" ],
"translations" : {
"en" : "Update settings",
"fr" : "Mettre à jour les paramètres",
@ -796,7 +796,7 @@
}
},
"errors.clipboard-not-implemented" : {
"used-in" : [ "src/app/main/data/workspace.cljs:1251" ],
"used-in" : [ "src/app/main/data/workspace.cljs:1375" ],
"translations" : {
"en" : "Your browser cannot do this operation, please use Ctrl-V",
"fr" : "",
@ -805,7 +805,7 @@
}
},
"errors.email-already-exists" : {
"used-in" : [ "src/app/main/ui/settings/change_email.cljs:47", "src/app/main/ui/auth/verify_token.cljs:80" ],
"used-in" : [ "src/app/main/ui/auth/verify_token.cljs:80", "src/app/main/ui/settings/change_email.cljs:47" ],
"translations" : {
"en" : "Email already used",
"fr" : "Adresse e-mail déjà utilisée",
@ -832,7 +832,7 @@
}
},
"errors.generic" : {
"used-in" : [ "src/app/main/ui/settings/options.cljs:32", "src/app/main/ui/settings/profile.cljs:42", "src/app/main/ui/auth/verify_token.cljs:89" ],
"used-in" : [ "src/app/main/ui/auth/verify_token.cljs:89", "src/app/main/ui/settings/profile.cljs:42", "src/app/main/ui/settings/options.cljs:32" ],
"translations" : {
"en" : "Something wrong has happened.",
"fr" : "Quelque chose c'est mal passé.",
@ -859,7 +859,7 @@
}
},
"errors.media-type-mismatch" : {
"used-in" : [ "src/app/main/data/media.cljs:78", "src/app/main/data/workspace/persistence.cljs:394" ],
"used-in" : [ "src/app/main/data/workspace/persistence.cljs:381", "src/app/main/data/media.cljs:78" ],
"translations" : {
"en" : "Seems that the contents of the image does not match the file extension.",
"fr" : "",
@ -868,7 +868,7 @@
}
},
"errors.media-type-not-allowed" : {
"used-in" : [ "src/app/main/data/media.cljs:75", "src/app/main/data/workspace/persistence.cljs:391" ],
"used-in" : [ "src/app/main/data/workspace/persistence.cljs:378", "src/app/main/data/media.cljs:75" ],
"translations" : {
"en" : "Seems that this is not a valid image.",
"fr" : "",
@ -913,7 +913,7 @@
}
},
"errors.unexpected-error" : {
"used-in" : [ "src/app/main/data/media.cljs:81", "src/app/main/ui/auth/register.cljs:45", "src/app/main/ui/workspace/sidebar/options/exports.cljs:66", "src/app/main/ui/handoff/exports.cljs:41" ],
"used-in" : [ "src/app/main/data/media.cljs:81", "src/app/main/ui/workspace/sidebar/options/exports.cljs:66", "src/app/main/ui/auth/register.cljs:45", "src/app/main/ui/handoff/exports.cljs:41" ],
"translations" : {
"en" : "An unexpected error occurred.",
"fr" : "Une erreur inattendue c'est produite",
@ -986,21 +986,21 @@
}
},
"handoff.attributes.image.download" : {
"used-in" : [ "src/app/main/ui/handoff/attributes/image.cljs:45" ],
"used-in" : [ "src/app/main/ui/handoff/attributes/image.cljs:61" ],
"translations" : {
"en" : "Dowload source image",
"es" : "Descargar imagen original"
}
},
"handoff.attributes.image.height" : {
"used-in" : [ "src/app/main/ui/handoff/attributes/image.cljs:37" ],
"used-in" : [ "src/app/main/ui/handoff/attributes/image.cljs:49" ],
"translations" : {
"en" : "Height",
"es" : "Altura"
}
},
"handoff.attributes.image.width" : {
"used-in" : [ "src/app/main/ui/handoff/attributes/image.cljs:32" ],
"used-in" : [ "src/app/main/ui/handoff/attributes/image.cljs:44" ],
"translations" : {
"en" : "Width",
"es" : "Ancho"
@ -1368,7 +1368,7 @@
"unused" : true
},
"labels.admin" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:85", "src/app/main/ui/dashboard/team.cljs:174", "src/app/main/ui/dashboard/team.cljs:190" ],
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:85", "src/app/main/ui/dashboard/team.cljs:176", "src/app/main/ui/dashboard/team.cljs:192" ],
"translations" : {
"en" : "Admin",
"es" : "Administración"
@ -1381,6 +1381,18 @@
"es" : "Todo"
}
},
"labels.bad-gateway.desc-message" : {
"used-in" : [ "src/app/main/ui/static.cljs:58" ],
"translations" : {
"en" : "Looks like you need to wait a bit and retry; we are performing small maintenance of our servers."
}
},
"labels.bad-gateway.main-message" : {
"used-in" : [ "src/app/main/ui/static.cljs:57" ],
"translations" : {
"en" : "Bad Gateway"
}
},
"labels.cancel" : {
"used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:201" ],
"translations" : {
@ -1414,7 +1426,7 @@
}
},
"labels.delete" : {
"used-in" : [ "src/app/main/ui/dashboard/files.cljs:85", "src/app/main/ui/dashboard/grid.cljs:177" ],
"used-in" : [ "src/app/main/ui/dashboard/grid.cljs:179", "src/app/main/ui/dashboard/files.cljs:85" ],
"translations" : {
"en" : "Delete",
"fr" : "Supprimer",
@ -1453,14 +1465,14 @@
}
},
"labels.editor" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:86", "src/app/main/ui/dashboard/team.cljs:177", "src/app/main/ui/dashboard/team.cljs:191" ],
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:86", "src/app/main/ui/dashboard/team.cljs:179", "src/app/main/ui/dashboard/team.cljs:193" ],
"translations" : {
"en" : "Editor",
"es" : "Editor"
}
},
"labels.email" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:112", "src/app/main/ui/dashboard/team.cljs:215" ],
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:114", "src/app/main/ui/dashboard/team.cljs:217" ],
"translations" : {
"en" : "Email",
"fr" : "Adresse email",
@ -1478,7 +1490,7 @@
}
},
"labels.hide-resolved-comments" : {
"used-in" : [ "src/app/main/ui/viewer/header.cljs:175", "src/app/main/ui/workspace/comments.cljs:129" ],
"used-in" : [ "src/app/main/ui/workspace/comments.cljs:129", "src/app/main/ui/viewer/header.cljs:175" ],
"translations" : {
"en" : "Hide resolved comments",
"es" : "Ocultar comentarios resueltos"
@ -1503,14 +1515,14 @@
}
},
"labels.members" : {
"used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:297", "src/app/main/ui/dashboard/team.cljs:60", "src/app/main/ui/dashboard/team.cljs:66" ],
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:60", "src/app/main/ui/dashboard/team.cljs:66", "src/app/main/ui/dashboard/sidebar.cljs:297" ],
"translations" : {
"en" : "Members",
"es" : "Integrantes"
}
},
"labels.name" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:214" ],
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:216" ],
"translations" : {
"en" : "Name",
"fr" : "Nom",
@ -1534,15 +1546,33 @@
"es" : "No tienes notificaciones de comentarios pendientes"
}
},
"labels.not-found.auth-info" : {
"used-in" : [ "src/app/main/ui/static.cljs:42" ],
"translations" : {
"en" : "Youre signed in as"
}
},
"labels.not-found.desc-message" : {
"used-in" : [ "src/app/main/ui/static.cljs:40" ],
"translations" : {
"en" : "This page might not exist or you dont have permissions to access to it."
}
},
"labels.not-found.main-message" : {
"used-in" : [ "src/app/main/ui/static.cljs:39" ],
"translations" : {
"en" : "Oops!"
}
},
"labels.num-of-files" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:308" ],
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:310" ],
"translations" : {
"en" : [ "1 file", "%s files" ],
"es" : [ "1 archivo", "%s archivos" ]
}
},
"labels.num-of-projects" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:305" ],
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:307" ],
"translations" : {
"en" : [ "1 project", "%s projects" ],
"es" : [ "1 proyecto", "%s proyectos" ]
@ -1565,7 +1595,7 @@
}
},
"labels.owner" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:171", "src/app/main/ui/dashboard/team.cljs:296" ],
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:173", "src/app/main/ui/dashboard/team.cljs:298" ],
"translations" : {
"en" : "Owner",
"es" : "Dueño"
@ -1581,7 +1611,7 @@
}
},
"labels.permissions" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:216" ],
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:218" ],
"translations" : {
"en" : "Permissions",
"es" : "Permisos"
@ -1606,7 +1636,7 @@
}
},
"labels.remove" : {
"used-in" : [ "src/app/main/ui/workspace/libraries.cljs:92", "src/app/main/ui/dashboard/team.cljs:202" ],
"used-in" : [ "src/app/main/ui/workspace/libraries.cljs:92", "src/app/main/ui/dashboard/team.cljs:204" ],
"translations" : {
"en" : "Remove",
"fr" : "",
@ -1615,12 +1645,18 @@
}
},
"labels.rename" : {
"used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:300", "src/app/main/ui/dashboard/files.cljs:84", "src/app/main/ui/dashboard/grid.cljs:176" ],
"used-in" : [ "src/app/main/ui/dashboard/grid.cljs:178", "src/app/main/ui/dashboard/sidebar.cljs:300", "src/app/main/ui/dashboard/files.cljs:84" ],
"translations" : {
"en" : "Rename",
"es" : "Renombrar"
}
},
"labels.retry" : {
"used-in" : [ "src/app/main/ui/static.cljs:62", "src/app/main/ui/static.cljs:79" ],
"translations" : {
"en" : "Retry"
}
},
"labels.role" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:84" ],
"translations" : {
@ -1628,8 +1664,20 @@
"es" : "Cargo"
}
},
"labels.service-unavailable.desc-message" : {
"used-in" : [ "src/app/main/ui/static.cljs:75" ],
"translations" : {
"en" : "We are in programmed maintenance of our systems."
}
},
"labels.service-unavailable.main-message" : {
"used-in" : [ "src/app/main/ui/static.cljs:74" ],
"translations" : {
"en" : "Service Unavailable"
}
},
"labels.settings" : {
"used-in" : [ "src/app/main/ui/settings/sidebar.cljs:80", "src/app/main/ui/dashboard/sidebar.cljs:298", "src/app/main/ui/dashboard/team.cljs:61", "src/app/main/ui/dashboard/team.cljs:68" ],
"used-in" : [ "src/app/main/ui/settings/sidebar.cljs:80", "src/app/main/ui/dashboard/team.cljs:61", "src/app/main/ui/dashboard/team.cljs:68", "src/app/main/ui/dashboard/sidebar.cljs:298" ],
"translations" : {
"en" : "Settings",
"fr" : "Settings",
@ -1647,19 +1695,28 @@
}
},
"labels.show-all-comments" : {
"used-in" : [ "src/app/main/ui/viewer/header.cljs:163", "src/app/main/ui/workspace/comments.cljs:117" ],
"used-in" : [ "src/app/main/ui/workspace/comments.cljs:117", "src/app/main/ui/viewer/header.cljs:163" ],
"translations" : {
"en" : "Show all comments",
"es" : "Mostrar todos los comentarios"
}
},
"labels.show-your-comments" : {
"used-in" : [ "src/app/main/ui/viewer/header.cljs:168", "src/app/main/ui/workspace/comments.cljs:122" ],
"used-in" : [ "src/app/main/ui/workspace/comments.cljs:122", "src/app/main/ui/viewer/header.cljs:168" ],
"translations" : {
"en" : "Show only yours comments",
"es" : "Mostrar sólo tus comentarios"
}
},
"labels.sign-out" : {
"used-in" : [ "src/app/main/ui/static.cljs:45" ],
"translations" : {
"en" : "Sign out",
"fr" : "Quitter",
"ru" : "Выход",
"es" : "Salir"
}
},
"labels.update" : {
"used-in" : [ "src/app/main/ui/settings/profile.cljs:104" ],
"translations" : {
@ -1670,7 +1727,7 @@
}
},
"labels.viewer" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:87", "src/app/main/ui/dashboard/team.cljs:180", "src/app/main/ui/dashboard/team.cljs:192" ],
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:87", "src/app/main/ui/dashboard/team.cljs:182", "src/app/main/ui/dashboard/team.cljs:194" ],
"translations" : {
"en" : "Viewer",
"es" : "Visualizador"
@ -1684,7 +1741,7 @@
}
},
"media.loading" : {
"used-in" : [ "src/app/main/data/media.cljs:60", "src/app/main/data/workspace/persistence.cljs:472", "src/app/main/data/workspace/persistence.cljs:527" ],
"used-in" : [ "src/app/main/data/workspace/persistence.cljs:459", "src/app/main/data/workspace/persistence.cljs:514", "src/app/main/data/media.cljs:60" ],
"translations" : {
"en" : "Loading image...",
"fr" : "Chargement de l'image...",
@ -1852,13 +1909,13 @@
}
},
"modals.delete-page.body" : {
"used-in" : [ "src/app/main/ui/workspace/sidebar/sitemap.cljs:45" ],
"used-in" : [ "src/app/main/ui/workspace/sidebar/sitemap.cljs:60" ],
"translations" : {
"en" : "Are you sure you want to delete this page?"
}
},
"modals.delete-page.title" : {
"used-in" : [ "src/app/main/ui/workspace/sidebar/sitemap.cljs:44" ],
"used-in" : [ "src/app/main/ui/workspace/sidebar/sitemap.cljs:59" ],
"translations" : {
"en" : "Delete page"
}
@ -1906,28 +1963,28 @@
}
},
"modals.delete-team-member-confirm.accept" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:160" ],
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:162" ],
"translations" : {
"en" : "Delete member",
"es" : "Eliminando miembro"
}
},
"modals.delete-team-member-confirm.message" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:159" ],
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:161" ],
"translations" : {
"en" : "Are you sure you want to delete this member from the team?",
"es" : "¿Seguro que quieres eliminar este integrante del equipo?"
}
},
"modals.delete-team-member-confirm.title" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:158" ],
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:160" ],
"translations" : {
"en" : "Delete team member",
"es" : "Eliminar integrante del equipo"
}
},
"modals.invite-member.title" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:108" ],
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:110" ],
"translations" : {
"en" : "Invite to join the team",
"es" : "Invitar a unirse al equipo"
@ -1990,21 +2047,21 @@
}
},
"modals.promote-owner-confirm.accept" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:147" ],
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:149" ],
"translations" : {
"en" : "Promote",
"es" : "Promocionar"
}
},
"modals.promote-owner-confirm.message" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:146" ],
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:148" ],
"translations" : {
"en" : "Are you sure you want to promote this user to owner?",
"es" : "¿Seguro que quieres promocionar este usuario a dueño?"
}
},
"modals.promote-owner-confirm.title" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:145" ],
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:147" ],
"translations" : {
"en" : "Promote to owner",
"es" : "Promocionar a dueño"
@ -2047,7 +2104,7 @@
}
},
"notifications.profile-saved" : {
"used-in" : [ "src/app/main/ui/settings/options.cljs:36", "src/app/main/ui/settings/profile.cljs:38" ],
"used-in" : [ "src/app/main/ui/settings/profile.cljs:38", "src/app/main/ui/settings/options.cljs:36" ],
"translations" : {
"en" : "Profile saved successfully!",
"fr" : "Profil enregistré avec succès!",
@ -2056,7 +2113,7 @@
}
},
"notifications.validation-email-sent" : {
"used-in" : [ "src/app/main/ui/settings/change_email.cljs:56", "src/app/main/ui/auth/register.cljs:54" ],
"used-in" : [ "src/app/main/ui/auth/register.cljs:54", "src/app/main/ui/settings/change_email.cljs:56" ],
"translations" : {
"en" : "Verification email sent to %s. Check your email!",
"es" : "Verificación de email enviada a %s. Comprueba tu correo."
@ -2072,7 +2129,7 @@
}
},
"settings.multiple" : {
"used-in" : [ "src/app/main/ui/workspace/sidebar/options/shadow.cljs:213", "src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:161", "src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:170", "src/app/main/ui/workspace/sidebar/options/typography.cljs:99", "src/app/main/ui/workspace/sidebar/options/typography.cljs:149", "src/app/main/ui/workspace/sidebar/options/typography.cljs:162", "src/app/main/ui/workspace/sidebar/options/blur.cljs:79", "src/app/main/ui/workspace/sidebar/options/stroke.cljs:154" ],
"used-in" : [ "src/app/main/ui/workspace/sidebar/options/stroke.cljs:154", "src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:161", "src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:170", "src/app/main/ui/workspace/sidebar/options/typography.cljs:99", "src/app/main/ui/workspace/sidebar/options/typography.cljs:149", "src/app/main/ui/workspace/sidebar/options/typography.cljs:162", "src/app/main/ui/workspace/sidebar/options/shadow.cljs:213", "src/app/main/ui/workspace/sidebar/options/blur.cljs:79" ],
"translations" : {
"en" : "Mixed",
"fr" : null,
@ -2351,7 +2408,7 @@
}
},
"workspace.assets.delete" : {
"used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:140", "src/app/main/ui/workspace/sidebar/assets.cljs:260", "src/app/main/ui/workspace/sidebar/assets.cljs:375", "src/app/main/ui/workspace/sidebar/assets.cljs:504" ],
"used-in" : [ "src/app/main/ui/workspace/sidebar/sitemap.cljs:151", "src/app/main/ui/workspace/sidebar/assets.cljs:140", "src/app/main/ui/workspace/sidebar/assets.cljs:260", "src/app/main/ui/workspace/sidebar/assets.cljs:375", "src/app/main/ui/workspace/sidebar/assets.cljs:504" ],
"translations" : {
"en" : "Delete",
"fr" : "",
@ -2360,7 +2417,7 @@
}
},
"workspace.assets.duplicate" : {
"used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:139" ],
"used-in" : [ "src/app/main/ui/workspace/sidebar/sitemap.cljs:155", "src/app/main/ui/workspace/sidebar/assets.cljs:139" ],
"translations" : {
"en" : "Duplicate",
"fr" : "",
@ -2414,7 +2471,7 @@
}
},
"workspace.assets.rename" : {
"used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:138", "src/app/main/ui/workspace/sidebar/assets.cljs:259", "src/app/main/ui/workspace/sidebar/assets.cljs:373", "src/app/main/ui/workspace/sidebar/assets.cljs:502" ],
"used-in" : [ "src/app/main/ui/workspace/sidebar/sitemap.cljs:154", "src/app/main/ui/workspace/sidebar/assets.cljs:138", "src/app/main/ui/workspace/sidebar/assets.cljs:259", "src/app/main/ui/workspace/sidebar/assets.cljs:373", "src/app/main/ui/workspace/sidebar/assets.cljs:502" ],
"translations" : {
"en" : "Rename",
"fr" : "",
@ -2502,14 +2559,14 @@
}
},
"workspace.gradients.linear" : {
"used-in" : [ "src/app/main/data/workspace/libraries.cljs:43", "src/app/main/ui/components/color_bullet.cljs:31" ],
"used-in" : [ "src/app/main/data/workspace/libraries.cljs:72", "src/app/main/ui/components/color_bullet.cljs:31" ],
"translations" : {
"en" : "Linear gradient",
"es" : "Degradado lineal"
}
},
"workspace.gradients.radial" : {
"used-in" : [ "src/app/main/data/workspace/libraries.cljs:44", "src/app/main/ui/components/color_bullet.cljs:32" ],
"used-in" : [ "src/app/main/data/workspace/libraries.cljs:73", "src/app/main/ui/components/color_bullet.cljs:32" ],
"translations" : {
"en" : "Radial gradient",
"es" : "Degradado radial"
@ -2706,21 +2763,21 @@
}
},
"workspace.libraries.colors.big-thumbnails" : {
"used-in" : [ "src/app/main/ui/workspace/colorpalette.cljs:170" ],
"used-in" : [ "src/app/main/ui/workspace/colorpalette.cljs:171" ],
"translations" : {
"en" : "Big thumbnails",
"es" : "Miniaturas grandes"
}
},
"workspace.libraries.colors.file-library" : {
"used-in" : [ "src/app/main/ui/workspace/colorpicker/libraries.cljs:89", "src/app/main/ui/workspace/colorpalette.cljs:148" ],
"used-in" : [ "src/app/main/ui/workspace/colorpicker/libraries.cljs:89", "src/app/main/ui/workspace/colorpalette.cljs:149" ],
"translations" : {
"en" : "File library",
"es" : "Biblioteca del archivo"
}
},
"workspace.libraries.colors.recent-colors" : {
"used-in" : [ "src/app/main/ui/workspace/colorpicker/libraries.cljs:88", "src/app/main/ui/workspace/colorpalette.cljs:158" ],
"used-in" : [ "src/app/main/ui/workspace/colorpicker/libraries.cljs:88", "src/app/main/ui/workspace/colorpalette.cljs:159" ],
"translations" : {
"en" : "Recent colors",
"es" : "Colores recientes"
@ -2734,7 +2791,7 @@
}
},
"workspace.libraries.colors.small-thumbnails" : {
"used-in" : [ "src/app/main/ui/workspace/colorpalette.cljs:175" ],
"used-in" : [ "src/app/main/ui/workspace/colorpalette.cljs:176" ],
"translations" : {
"en" : "Small thumbnails",
"es" : "Miniaturas pequeñas"
@ -3251,7 +3308,7 @@
}
},
"workspace.options.position" : {
"used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame.cljs:118", "src/app/main/ui/workspace/sidebar/options/measures.cljs:146" ],
"used-in" : [ "src/app/main/ui/workspace/sidebar/options/measures.cljs:146", "src/app/main/ui/workspace/sidebar/options/frame.cljs:118" ],
"translations" : {
"en" : "Position",
"fr" : "Position",
@ -3384,7 +3441,7 @@
}
},
"workspace.options.size" : {
"used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame.cljs:93", "src/app/main/ui/workspace/sidebar/options/measures.cljs:118" ],
"used-in" : [ "src/app/main/ui/workspace/sidebar/options/measures.cljs:118", "src/app/main/ui/workspace/sidebar/options/frame.cljs:93" ],
"translations" : {
"en" : "Size",
"fr" : "Taille",
@ -3594,7 +3651,7 @@
}
},
"workspace.options.text-options.none" : {
"used-in" : [ "src/app/main/ui/workspace/sidebar/options/typography.cljs:176", "src/app/main/ui/workspace/sidebar/options/text.cljs:153" ],
"used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:153", "src/app/main/ui/workspace/sidebar/options/typography.cljs:176" ],
"translations" : {
"en" : "None",
"fr" : "Aucune",
@ -3733,7 +3790,7 @@
}
},
"workspace.shape.menu.detach-instance" : {
"used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:163", "src/app/main/ui/workspace/context_menu.cljs:173", "src/app/main/ui/workspace/sidebar/options/component.cljs:79", "src/app/main/ui/workspace/sidebar/options/component.cljs:84" ],
"used-in" : [ "src/app/main/ui/workspace/sidebar/options/component.cljs:79", "src/app/main/ui/workspace/sidebar/options/component.cljs:84", "src/app/main/ui/workspace/context_menu.cljs:163", "src/app/main/ui/workspace/context_menu.cljs:173" ],
"translations" : {
"en" : "Detach instance",
"es" : "Desacoplar instancia"
@ -3761,7 +3818,7 @@
}
},
"workspace.shape.menu.go-master" : {
"used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:177", "src/app/main/ui/workspace/sidebar/options/component.cljs:86" ],
"used-in" : [ "src/app/main/ui/workspace/sidebar/options/component.cljs:86", "src/app/main/ui/workspace/context_menu.cljs:177" ],
"translations" : {
"en" : "Go to master component file",
"es" : "Ir al archivo del componente maestro"
@ -3803,7 +3860,7 @@
}
},
"workspace.shape.menu.reset-overrides" : {
"used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:165", "src/app/main/ui/workspace/context_menu.cljs:175", "src/app/main/ui/workspace/sidebar/options/component.cljs:80", "src/app/main/ui/workspace/sidebar/options/component.cljs:85" ],
"used-in" : [ "src/app/main/ui/workspace/sidebar/options/component.cljs:80", "src/app/main/ui/workspace/sidebar/options/component.cljs:85", "src/app/main/ui/workspace/context_menu.cljs:165", "src/app/main/ui/workspace/context_menu.cljs:175" ],
"translations" : {
"en" : "Reset overrides",
"es" : "Deshacer modificaciones"
@ -3817,7 +3874,7 @@
}
},
"workspace.shape.menu.show-master" : {
"used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:169", "src/app/main/ui/workspace/sidebar/options/component.cljs:82" ],
"used-in" : [ "src/app/main/ui/workspace/sidebar/options/component.cljs:82", "src/app/main/ui/workspace/context_menu.cljs:169" ],
"translations" : {
"en" : "Show master component",
"es" : "Ver componente maestro"
@ -3845,7 +3902,7 @@
}
},
"workspace.shape.menu.update-master" : {
"used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:167", "src/app/main/ui/workspace/sidebar/options/component.cljs:81" ],
"used-in" : [ "src/app/main/ui/workspace/sidebar/options/component.cljs:81", "src/app/main/ui/workspace/context_menu.cljs:167" ],
"translations" : {
"en" : "Update master component",
"es" : "Actualizar componente maestro"
@ -3861,7 +3918,7 @@
"unused" : true
},
"workspace.sidebar.sitemap" : {
"used-in" : [ "src/app/main/ui/workspace/sidebar/sitemap.cljs:171" ],
"used-in" : [ "src/app/main/ui/workspace/sidebar/sitemap.cljs:207" ],
"translations" : {
"en" : "Pages",
"fr" : "Pages",
@ -4244,7 +4301,7 @@
}
},
"workspace.updates.dismiss" : {
"used-in" : [ "src/app/main/data/workspace/libraries.cljs:649" ],
"used-in" : [ "src/app/main/data/workspace/libraries.cljs:697" ],
"translations" : {
"en" : "Dismiss",
"fr" : "",
@ -4253,7 +4310,7 @@
}
},
"workspace.updates.there-are-updates" : {
"used-in" : [ "src/app/main/data/workspace/libraries.cljs:645" ],
"used-in" : [ "src/app/main/data/workspace/libraries.cljs:693" ],
"translations" : {
"en" : "There are updates in shared libraries",
"fr" : "",
@ -4262,7 +4319,7 @@
}
},
"workspace.updates.update" : {
"used-in" : [ "src/app/main/data/workspace/libraries.cljs:647" ],
"used-in" : [ "src/app/main/data/workspace/libraries.cljs:695" ],
"translations" : {
"en" : "Update",
"fr" : "",

View file

@ -12,22 +12,22 @@
//
//#################################################
@import 'common/dependencies/colors';
@import 'common/dependencies/helpers';
@import 'common/dependencies/mixin';
@import 'common/dependencies/fonts';
@import 'common/dependencies/reset';
@import 'common/dependencies/animations';
@import 'common/dependencies/z-index';
@import 'common/dependencies/highlightjs-theme';
@import "common/dependencies/colors";
@import "common/dependencies/helpers";
@import "common/dependencies/mixin";
@import "common/dependencies/fonts";
@import "common/dependencies/reset";
@import "common/dependencies/animations";
@import "common/dependencies/z-index";
@import "common/dependencies/highlightjs-theme";
//#################################################
// Layouts
//#################################################
@import 'common/base';
@import 'main/layouts/login';
@import 'main/layouts/main-layout';
@import "common/base";
@import "main/layouts/login";
@import "main/layouts/main-layout";
@import "main/layouts/not-found";
@import "main/layouts/viewer";
@import "main/layouts/handoff";
@ -36,12 +36,12 @@
// Commons
//#################################################
@import 'common/framework';
@import 'main/partials/modal';
@import 'main/partials/forms';
@import "common/framework";
@import "main/partials/modal";
@import "main/partials/forms";
@import "main/partials/texts";
@import 'main/partials/context-menu';
@import 'main/partials/dropdown';
@import "main/partials/context-menu";
@import "main/partials/dropdown";
//#################################################
// Partials
@ -51,35 +51,36 @@
@import "main/partials/viewer-header";
@import "main/partials/viewer-thumbnails";
@import "main/partials/zoom-widget";
@import 'main/partials/activity-bar';
@import 'main/partials/color-palette';
@import 'main/partials/colorpicker';
@import 'main/partials/dashboard';
@import 'main/partials/dashboard-header';
@import 'main/partials/dashboard-grid';
@import 'main/partials/dashboard-sidebar';
@import 'main/partials/dashboard-team';
@import 'main/partials/dashboard-settings';
@import 'main/partials/debug-icons-preview';
@import 'main/partials/editable-label';
@import 'main/partials/left-toolbar';
@import 'main/partials/loader';
@import 'main/partials/project-bar';
@import 'main/partials/sidebar';
@import 'main/partials/sidebar-align-options';
@import 'main/partials/sidebar-assets';
@import 'main/partials/sidebar-document-history';
@import 'main/partials/sidebar-element-options';
@import 'main/partials/sidebar-icons';
@import 'main/partials/sidebar-interactions';
@import 'main/partials/sidebar-layers';
@import 'main/partials/sidebar-sitemap';
@import 'main/partials/sidebar-tools';
@import 'main/partials/tab-container';
@import 'main/partials/tool-bar';
@import 'main/partials/user-settings';
@import 'main/partials/workspace';
@import 'main/partials/workspace-header';
@import 'main/partials/comments';
@import 'main/partials/color-bullet';
@import "main/partials/activity-bar";
@import "main/partials/color-palette";
@import "main/partials/colorpicker";
@import "main/partials/dashboard";
@import "main/partials/dashboard-header";
@import "main/partials/dashboard-grid";
@import "main/partials/dashboard-sidebar";
@import "main/partials/dashboard-team";
@import "main/partials/dashboard-settings";
@import "main/partials/debug-icons-preview";
@import "main/partials/editable-label";
@import "main/partials/left-toolbar";
@import "main/partials/loader";
@import "main/partials/project-bar";
@import "main/partials/sidebar";
@import "main/partials/sidebar-align-options";
@import "main/partials/sidebar-assets";
@import "main/partials/sidebar-document-history";
@import "main/partials/sidebar-element-options";
@import "main/partials/sidebar-icons";
@import "main/partials/sidebar-interactions";
@import "main/partials/sidebar-layers";
@import "main/partials/sidebar-sitemap";
@import "main/partials/sidebar-tools";
@import "main/partials/tab-container";
@import "main/partials/tool-bar";
@import "main/partials/user-settings";
@import "main/partials/workspace";
@import "main/partials/workspace-header";
@import "main/partials/comments";
@import "main/partials/color-bullet";
@import "main/partials/handoff";
@import "main/partials/exception-page";

View file

@ -29,34 +29,55 @@
justify-content: center;
align-items: center;
.error-img {
.container {
max-width: 600px;
}
.image {
align-items: center;
display: flex;
justify-content: center;
margin-bottom: 2rem;
svg {
height: 320px;
width: 200px;
height: 220px;
width: 220px;
}
}
.main-message {
color: $color-black;
font-size: 11rem;
line-height: 200px;
font-size: 5rem;
line-height: 150px;
text-align: center;
}
.desc-message {
color: $color-black;
font-size: 2.2rem;
font-size: 1.6rem;
font-weight: 300;
text-align: center;
}
.btn-primary {
margin-top: $x-big;
.sign-info {
margin-top: 20px;
color: $color-black;
font-size: 1rem;
font-weight: 200;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
b {
font-weight: 400;
}
.btn-primary {
margin-top: 15px;
}
}
}

View file

@ -0,0 +1,83 @@
.exception-layout {
display: grid;
grid-template-rows: 120px auto;
grid-template-columns: 1fr;
}
.exception-header {
grid-column: 1 / span 1;
grid-row: 1 / span 1;
display: flex;
align-items: center;
padding: 32px;
svg {
height: 55px;
width: 170px;
}
}
.exception-content {
grid-column: 1 / span 1;
grid-row: 1 / span 2;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
.container {
max-width: 600px;
}
.image {
align-items: center;
display: flex;
justify-content: center;
margin-bottom: 2rem;
svg {
height: 220px;
width: 220px;
}
}
.main-message {
color: $color-black;
font-size: 5rem;
line-height: 150px;
text-align: center;
}
.desc-message {
color: $color-black;
font-size: 1.6rem;
font-weight: 300;
text-align: center;
}
.sign-info {
margin-top: 20px;
color: $color-black;
font-size: 1rem;
font-weight: 200;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
b {
font-weight: 400;
}
.btn-primary {
margin-top: 15px;
}
}
}

View file

@ -81,7 +81,7 @@
(rt/initialize-history on-navigate))
(st/emit! udu/fetch-profile)
(mf/mount (mf/element ui/app-wrapper) (dom/get-element "app"))
(mf/mount (mf/element ui/app) (dom/get-element "app"))
(mf/mount (mf/element modal) (dom/get-element "modal")))
(defn ^:export init

View file

@ -129,3 +129,13 @@
:actions actions
:tag tag})))
(defn assign-exception
[{:keys [type] :as error}]
(us/assert (s/nilable map?) error)
(us/assert (s/nilable ::us/keyword) type)
(ptk/reify ::assign-exception
ptk/UpdateEvent
(update [_ state]
(if (nil? error)
(dissoc state :exception)
(assoc state :exception error)))))

View file

@ -130,13 +130,11 @@
(rx/map #(shapes-changes-persisted file-id %))))))
on-error
(fn [{:keys [type status] :as error}]
(if (and (= :server-error type)
(= 502 status))
(fn [{:keys [type] :as error}]
(if (or (= :bad-gateway type)
(= :service-unavailable type))
(rx/of (update-persistence-status {:status :error :reason type}))
(rx/of update-persistence-queue
(update-persistence-status {:status :error :reason type}))))]
(rx/throw error)))]
(when (= file-id (:id file))
(->> (rp/mutation :update-file params)
@ -219,18 +217,7 @@
(rp/query :project {:id project-id})
(rp/query :file-libraries {:file-id file-id}))
(rx/first)
(rx/map (fn [bundle] (apply bundle-fetched bundle)))
(rx/catch (fn [{:keys [type code] :as error}]
(cond
(= :not-found type)
(rx/of (rt/nav' :not-found))
(and (= :authentication type)
(= :unauthorized code))
(rx/of (rt/nav' :not-authorized))
:else
(throw error))))))))
(rx/map (fn [bundle] (apply bundle-fetched bundle)))))))
(defn- bundle-fetched
[file users project libraries]

View file

@ -31,6 +31,9 @@
(def profile
(l/derived :profile st/state))
(def exception
(l/derived :exception st/state))
;; ---- Dashboard refs
(def dashboard-local

View file

@ -15,34 +15,32 @@
[app.util.http-api :as http]))
(defn- handle-response
[response]
[{:keys [status body] :as response}]
(cond
(http/success? response)
(rx/of (:body response))
(= 204 status)
(rx/empty)
(= (:status response) 400)
(rx/throw (:body response))
(= 502 status)
(rx/throw {:type :bad-gateway})
(= (:status response) 401)
(rx/throw {:type :authentication
:code :not-authenticated})
(= (:status response) 403)
(rx/throw {:type :authorization
:code :not-authorized})
(= (:status response) 404)
(rx/throw (:body response))
(= 503 status)
(rx/throw {:type :service-unavailable})
(= 0 (:status response))
(rx/throw {:type :offline})
(and (= 200 status)
(coll? body))
(rx/of body)
(and (>= status 400)
(map? body))
(rx/throw body)
:else
(rx/throw (merge {:type :server-error
:status (:status response)}
(:body response)))))
(rx/throw {:type :unexpected-error
:status status
:data body})))
(defn send-query!
[id params]

View file

@ -26,6 +26,12 @@
(defonce state (ptk/store {:resolve ptk/resolve}))
(defonce stream (ptk/input-stream state))
(defn ^boolean is-logged?
[pdata]
(and (some? pdata)
(uuid? (:id pdata))
(not= uuid/zero (:id pdata))))
(when *assert*
(defonce debug-subscription
(->> stream

View file

@ -28,7 +28,7 @@
[app.main.ui.messages :as msgs]
[app.main.ui.render :as render]
[app.main.ui.settings :as settings]
[app.main.ui.static :refer [not-found-page not-authorized-page]]
[app.main.ui.static :as static]
[app.main.ui.viewer :refer [viewer-page]]
[app.main.ui.handoff :refer [handoff]]
[app.main.ui.workspace :as workspace]
@ -37,6 +37,7 @@
[app.util.router :as rt]
[cuerdas.core :as str]
[cljs.spec.alpha :as s]
[cljs.pprint :refer [pprint]]
[expound.alpha :as expound]
[potok.core :as ptk]
[rumext.alpha :as mf]))
@ -81,9 +82,6 @@
:conform {:path-params ::viewer-path-params
:query-params ::viewer-query-params}}]
["/not-found" :not-found]
["/not-authorized" :not-authorized]
(when *assert*
["/debug/icons-preview" :debug-icons-preview])
@ -100,19 +98,15 @@
["/workspace/:project-id/:file-id" :workspace]])
(mf/defc app-error
(mf/defc on-main-error
[{:keys [error] :as props}]
(let [data (ex-data error)]
(case (:type data)
:not-found [:& not-found-page {:error data}]
(do
(ptk/handle-error error)
[:span "Internal application errror"]))))
(ptk/handle-error error)
[:span "Internal application errror"]))
(mf/defc app
{::mf/wrap [#(mf/catch % {:fallback app-error})]}
(mf/defc main-page
{::mf/wrap [#(mf/catch % {:fallback on-main-error})]}
[{:keys [route] :as props}]
[:& (mf/provider ctx/current-route) {:value route}
(case (get-in route [:data :name])
(:auth-login
@ -189,67 +183,71 @@
:page-id page-id
:layout-name (keyword layout-name)
:key file-id}])
:not-authorized
[:& not-authorized-page]
:not-found
[:& not-found-page]
nil)])
(mf/defc app-wrapper
(mf/defc app
[]
(let [route (mf/deref refs/route)]
[:*
[:& msgs/notifications]
(when route
[:& app {:route route}])]))
(let [route (mf/deref refs/route)
edata (mf/deref refs/exception)]
[:& (mf/provider ctx/current-route) {:value route}
(if edata
[:& static/exception-page {:data edata}]
[:*
[:& msgs/notifications]
(when route
[:& main-page {:route route}])])]))
;; --- Error Handling
;; That are special case server-errors that should be treated
;; differently.
(derive :not-found ::exceptional-state)
(derive :bad-gateway ::exceptional-state)
(derive :service-unavailable ::exceptional-state)
(defmethod ptk/handle-error ::exceptional-state
[{:keys [status] :as error}]
(ts/schedule
(st/emitf (dm/assign-exception error))))
;; We receive a explicit authentication error; this explicitly clears
;; all profile data and redirect the user to the login page.
(defmethod ptk/handle-error :authentication
[error]
(ts/schedule (st/emitf (logout))))
;; Error that happens on an active bussines model validation does not
;; passes an validation (example: profile can't leave a team). From
;; the user perspective a error flash message should be visualized but
;; user can continue operate on the application.
(defmethod ptk/handle-error :validation
[error]
(ts/schedule
(st/emitf (dm/show {:content "Unexpected validation error (server side)."
:type :error
:timeout 5000})))
(when-let [explain (:hint-verbose error)]
(js/console.group "Server Error")
(js/console.error (if (map? error) (pr-str error) error))
(js/console.error explain)
(js/console.endGroup "Server Error")))
(st/emitf
(dm/show {:content "Unexpected validation error (server side)."
:type :error
:timeout 3000})))
(defmethod ptk/handle-error :spec-validation
[error]
(ts/schedule
(st/emitf (dm/show {:content "Unexpected validation error (server side)."
:type :error
:timeout 5000})))
;; Print to the console some debug info.
(js/console.group "Server Error")
(js/console.info
(with-out-str
(pprint (dissoc error :explain))))
(when-let [explain (:explain error)]
(js/console.group "Server Error")
(js/console.error (if (map? error) (pr-str error) error))
(js/console.error explain)
(js/console.endGroup "Server Error")))
(defmethod ptk/handle-error :authentication
[error]
(ts/schedule 0 #(st/emit! (logout))))
(defmethod ptk/handle-error :authorization
[error]
(ts/schedule
(st/emitf (dm/show {:content "Not authorized to see this content."
:timeout 2000
:type :error}))))
(js/console.error explain))
(js/console.endGroup "Server Error"))
;; This is a pure frontend error that can be caused by an active
;; assertion (assertion that is preserved on production builds). From
;; the user perspective this should be treated as internal error.
(defmethod ptk/handle-error :assertion
[{:keys [data stack message context] :as error}]
(ts/schedule
(st/emitf (dm/show {:content "Internal assertion error."
(st/emitf (dm/show {:content "Internal error: assertion."
:type :error
:timeout 2000})))
:timeout 3000})))
;; Print to the console some debugging info
(js/console.group message)
(js/console.info (str/format "ns: '%s'\nname: '%s'\nfile: '%s:%s'"
(:ns context)
@ -259,48 +257,49 @@
(js/console.groupCollapsed "Stack Trace")
(js/console.info stack)
(js/console.groupEnd "Stack Trace")
(js/console.error (with-out-str (expound/printer data)))
(js/console.groupEnd message))
;; This happens when the backed server fails to process the
;; request. This can be caused by an internal assertion or any other
;; uncontrolled error.
(defmethod ptk/handle-error :server-error
[{:keys [data] :as error}]
(ts/schedule
(st/emitf (dm/show
{:content "Something wrong has happened (on backend)."
:type :error
:timeout 3000})))
(js/console.group "Internal Server Error:")
(js/console.error "hint:" (or (:hint data) (:message data)))
(js/console.info
(with-out-str
(pprint (dissoc data :explain))))
(when-let [explain (:explain data)]
(js/console.error explain))
(js/console.groupEnd "Internal Server Error:"))
(defmethod ptk/handle-error :default
[error]
(if (instance? ExceptionInfo error)
(ptk/handle-error (ex-data error))
(do
(js/console.group "Generic Error")
(ts/schedule
(st/emitf (dm/show
{:content "Something wrong has happened."
:type :error
:timeout 3000})))
(js/console.group "Internal error:")
(js/console.log "hint:" (or (ex-message error)
(:hint error)
(:message error)))
(ex/ignoring
(js/console.error "repr: " (pr-str error))
(js/console.error "data: " (clj->js error))
(js/console.error "stack:" (.-stack error)))
(js/console.groupEnd "Generic error")
(ts/schedule (st/emitf (dm/show
{:content "Something wrong has happened."
:type :error
:timeout 3000}))))))
(js/console.groupEnd "Internal error:"))))
(defmethod ptk/handle-error :server-error
[{:keys [status] :as error}]
(cond
(= status 429)
(ts/schedule
(st/emitf (dm/show {:content "Too many requests, wait a little bit and retry."
:type :error
:timeout 5000})))
:else
(ts/schedule
(st/emitf (dm/show {:content "Unable to connect to backend, wait a little bit and refresh."
:type :error})))))
(defmethod ptk/handle-error :not-found
[{:keys [status] :as error}]
(ts/schedule
(st/emitf (dm/show {:content "Resource not found."
:type :warning}))))
(defonce uncaught-error-handler
(letfn [(on-error [event]

View file

@ -11,27 +11,104 @@
(:require
[cljs.spec.alpha :as s]
[rumext.alpha :as mf]
[app.main.ui.context :as ctx]
[app.main.data.auth :as da]
[app.main.data.messages :as dm]
[app.main.store :as st]
[app.main.refs :as refs]
[cuerdas.core :as str]
[app.util.i18n :refer [tr]]
[app.util.router :as rt]
[app.main.ui.icons :as i]))
(mf/defc not-found-page
[{:keys [error] :as props}]
[:section.not-found-layout
[:div.not-found-header i/logo]
[:div.not-found-content
[:div.message-container
[:div.error-img i/icon-empty]
[:div.main-message "404"]
[:div.desc-message "Oops! Page not found"]
[:a.btn-primary.btn-small "Go back"]]]])
(defn- go-to-dashboard
[profile]
(let [team-id (:default-team-id profile)]
(st/emit! (rt/nav :dashboard-projects {:team-id team-id}))))
(mf/defc not-authorized-page
(mf/defc not-found
[{:keys [error] :as props}]
[:section.not-found-layout
[:div.not-found-header i/logo]
[:div.not-found-content
[:div.message-container
[:div.error-img i/icon-lock]
[:div.main-message "403"]
[:div.desc-message "Sorry, you are not authorized to access this page."]
#_[:a.btn-primary.btn-small "Go back"]]]])
(let [profile (mf/deref refs/profile)]
[:section.exception-layout
[:div.exception-header
{:on-click (partial go-to-dashboard profile)}
i/logo]
[:div.exception-content
[:div.container
[:div.image i/icon-empty]
[:div.main-message (tr "labels.not-found.main-message")]
[:div.desc-message (tr "labels.not-found.desc-message")]
[:div.sign-info
[:span (tr "labels.not-found.auth-info") " " [:b (:email profile)]]
[:a.btn-primary.btn-small
{:on-click (st/emitf (da/logout))}
(tr "labels.sign-out")]]]]]))
(mf/defc bad-gateway
[{:keys [error] :as props}]
(let [profile (mf/deref refs/profile)]
[:section.exception-layout
[:div.exception-header
{:on-click (partial go-to-dashboard profile)}
i/logo]
[:div.exception-content
[:div.container
[:div.image i/icon-empty]
[:div.main-message (tr "labels.bad-gateway.main-message")]
[:div.desc-message (tr "labels.bad-gateway.desc-message")]
[:div.sign-info
[:a.btn-primary.btn-small
{:on-click (st/emitf #(dissoc % :exception))}
(tr "labels.retry")]]]]]))
(mf/defc service-unavailable
[{:keys [error] :as props}]
(let [profile (mf/deref refs/profile)]
[:section.exception-layout
[:div.exception-header
{:on-click (partial go-to-dashboard profile)}
i/logo]
[:div.exception-content
[:div.container
[:div.image i/icon-empty]
[:div.main-message (tr "labels.service-unavailable.main-message")]
[:div.desc-message (tr "labels.service-unavailable.desc-message")]
[:div.sign-info
[:a.btn-primary.btn-small
{:on-click (st/emitf #(dissoc % :exception))}
(tr "labels.retry")]]]]]))
(mf/defc internal-error
[props]
(let [profile (mf/deref refs/profile)]
[:section.exception-layout
[:div.exception-header
{:on-click (partial go-to-dashboard profile)}
i/logo]
[:div.exception-content
[:div.container
[:div.image i/icon-empty]
[:div.main-message "Internal Error"]
[:div.desc-message "Something bad happended on backend servers. Please retry the operation and if the problem persists, contact with support."]
[:div.sign-info
[:a.btn-primary.btn-small
{:on-click (st/emitf (dm/assign-exception nil))}
(tr "labels.retry")]]]]]))
(mf/defc exception-page
[{:keys [data] :as props}]
(case (:type data)
:not-found
[:& not-found]
:bad-gateway
[:& bad-gateway]
:service-unavailable
[:& service-unavailable]
:server-error
[:& internal-error]
nil))

View file

@ -10,15 +10,15 @@
(ns app.util.router
(:refer-clojure :exclude [resolve])
(:require
[app.common.data :as d]
[app.config :as cfg]
[app.util.browser-history :as bhistory]
[app.util.timers :as ts]
[beicon.core :as rx]
[cuerdas.core :as str]
[goog.events :as e]
[potok.core :as ptk]
[reitit.core :as r]
[app.common.data :as d]
[app.config :as cfg]
[app.util.browser-history :as bhistory]
[app.util.timers :as ts])
[reitit.core :as r])
(:import
goog.Uri
goog.Uri.QueryData))
@ -92,6 +92,10 @@
;; --- Navigate (Event)
(deftype Navigate [id params qparams replace]
ptk/UpdateEvent
(update [_ state]
(dissoc state :exception))
ptk/EffectEvent
(effect [_ state stream]
(let [router (:router state)