;; (in-package :cl-user) (defpackage hot-line.web (:use #:cl #:caveman2 #:hot-line.config #:hot-line.view #:hot-line.db #:datafly #:sxql #:app-constants #:local-time #:sqlite #:cl-pass ;; #:validation #:authentication #:user-management #:hermetic #:storage-management #:convert #:pagination #:routing #:storage) (:export :*web*)) (in-package :hot-line.web) ;; for @route annotation (syntax:use-syntax :annot) ;; ;; Application (defclass () ()) (defvar *web* (make-instance ')) (clear-routing-rules *web*) ;; ;; Routing rules (defroute "/" () (cond ((not (hermetic:logged-in-p)) (render "/index.html" `(:token ,(authentication:csrf-token)))) (t (render "/index.html" `(:user ,(authentication:get-current-user) :token ,(authentication:csrf-token) :roles ,(authentication:get-user-roles)))))) (defroute "/about" () (cond ((not (hermetic:logged-in-p)) (render "about.html" `(:token ,(authentication:csrf-token)))) (t (render "about.html" `(:user ,(authentication:get-current-user) :token ,(authentication:csrf-token) :roles ,(authentication:get-user-roles)))))) (defroute "/privacy" () (cond ((not (hermetic:logged-in-p)) (render "privacy.html" `(:token ,(authentication:csrf-token)))) (t (render "privacy.html" `(:user ,(authentication:get-current-user) :token ,(authentication:csrf-token) :roles ,(authentication:get-user-roles)))))) (defroute "/navigation" () (cond ((not (hermetic:logged-in-p)) (render "/nav-menu.html" `(:token ,(authentication:csrf-token)))) (t (render "/nav-menu.html" `(:user ,(authentication:get-current-user) :token ,(authentication:csrf-token) :roles ,(authentication:get-user-roles)))))) (defroute ("/sign-up" :method :get) () (if (hermetic:logged-in-p) `(301 (:location "/dashboard")) (render "sign-up.html" `(:token ,(authentication:csrf-token))))) (defroute ("/sign-up" :method :post) (&key method) (destructuring-bind (&key authenticity-token &allow-other-keys) (authentication:request-params (lack.request:request-body-parameters ningle:*request*)) (cond ((not (string= authenticity-token (authentication:csrf-token))) '(403 (:content-type "text/plain") ("Denied"))) ((hermetic:logged-in-p) '(301 (:location "/dashboard"))) ((string= "sign-up-user" method) (routing:sign-up-user (lack.request:request-body-parameters ningle:*request*))) (t `(400 (:content-type "text/plain") (,(format nil "Unknown method ~S" method))))))) ;; Admin/User Section (defroute "/login" () (if (hermetic:logged-in-p) `(301 (:location "/dashboard")) (render "user/log-in.html" `(:token ,(authentication:csrf-token))))) (defroute ("/login" :method :post) (&key method) (cond ((string= "login" method) (routing:attempt-login (lack.request:request-body-parameters ningle:*request*))) (t `(400 (:content-type "text/plain") (,(format nil "Unknown method ~S" method)))))) (defroute ("/logout" :method :post) (&key method) (cond ((string= "logout" method) (routing:log-out (lack.request:request-body-parameters ningle:*request*))) (t `(400 (:content-type "text/plain") (,(format nil "Unknown method ~S" method)))))) (defroute ("/users") () (cond ((not (hermetic:logged-in-p)) (on-exception *web* 404)) ((equal +true+ (user::is-administrator-p (authentication:get-current-user))) (render "user/index.html" `(:user ,(authentication:get-current-user) :users ,(user-management:get-all-users) :user-count ,(user-management:get-total-user-count) :categories ,(db-management:get-distinct-column-totals "user" "administrator") :token ,(authentication:csrf-token) :roles ,(authentication:get-user-roles)))) (t (on-exception *web* 404)))) (defroute ("/user/add") () (cond ((not (hermetic:logged-in-p)) (on-exception *web* 404)) ((equal +true+ (user::is-administrator-p (authentication:get-current-user))) (render "user/add.html" `(:user ,(authentication:get-current-user) :token ,(authentication:csrf-token) :roles ,(authentication:get-user-roles)))) (t (on-exception *web* 404)))) (defroute ("/user/edit/:username") (&key username) (cond ((or (not (hermetic:logged-in-p)) (null (user-management:user-in-db-p :username username))) (on-exception *web* 404)) ((or (and (not (null (user-management:user-in-db-p :username username))) (equal +true+ (user::is-administrator-p (authentication:get-current-user)))) (string= username (user::username-of (authentication:get-current-user)))) `(200 () (, (render "user/edit.html" `(:user-to-edit ,(user-management:user-in-db-p :username username) :user ,(authentication:get-current-user) :roles ,(authentication:get-user-roles) :token ,(authentication:csrf-token) :roles ,(authentication:get-user-roles) :session ,ningle:*session*))))) (t (on-exception *web* 404)))) (defroute ("/user" :method :post) (&key method) (destructuring-bind (&key authenticity-token &allow-other-keys) (authentication:request-params (lack.request:request-body-parameters ningle:*request*)) (cond ((not (string= authenticity-token (authentication:csrf-token))) '(403 (:content-type "text/plain") ("Denied"))) ((not (hermetic:logged-in-p)) '(303 (:location "/login"))) ((string= "add" method) (routing:add-user (lack.request:request-body-parameters ningle:*request*))) ((string= "update-role" method) (routing:update-role (lack.request:request-body-parameters ningle:*request*))) ((string= "update-display-name" method) (routing:update-display-name (lack.request:request-body-parameters ningle:*request*))) ((string= "update-password" method) (routing:update-password (lack.request:request-body-parameters ningle:*request*))) ((string= "delete-user" method) (routing:delete-user (lack.request:request-body-parameters ningle:*request*))) (t `(400 (:content-type "text/plain") (,(format nil "Unknown method ~S" method))))))) (defroute "/dashboard" () (if (not (hermetic:logged-in-p)) '(303 (:location "/login")) (let* ((current-user (authentication:get-current-user)) (username (user::username-of current-user))) (render "user/dashboard.html" `(:user ,current-user :token ,(authentication:csrf-token) :roles ,(authentication:get-user-roles) :storage-files ,(reverse (storage:get-file-names (storage:get-files-in-directory username "")))))))) (defroute "/storage/download/:username/:filename" (&key username filename) (if (and (hermetic:logged-in-p) (string= username (user::username-of (authentication:get-current-user)))) (if (storage:file-exists-p username "" filename) `(200 (:content-type "octet/stream") ,(storage:open-binary-file username "" filename))) (on-exception *web* 404))) (defroute ("/storage" :method :POST) (&key method) (destructuring-bind (&key filename authenticity-token &allow-other-keys) (authentication:request-params (lack.request:request-body-parameters ningle:*request*)) (cond ((not (string= authenticity-token (authentication:csrf-token))) '(403 (:content-type "text/plain") ("Denied"))) ((validation:string-is-nil-or-empty-p filename) (hot-line.web::on-exception hot-line.web:*web* 404)) ((not (hermetic:logged-in-p)) '(303 (:location "/login"))) ((string= "delete-storage-file" method) (routing:delete-storage-file (lack.request:request-body-parameters ningle:*request*))) (t (on-exception *web* 404))))) ;; This is where the chart stuff starts... (defroute "/chart/add" () (if (not (hermetic:logged-in-p)) '(303 (:location "/login")) (progn (let* ((current-user (authentication:get-current-user)) (username (user::username-of current-user))) (render "chart/add.html" `(:user ,current-user :token ,(authentication:csrf-token) :roles ,(authentication:get-user-roles))))))) (defroute ("/chartify" :method :post) (&key method) (destructuring-bind (&key authenticity-token &allow-other-keys) (authentication:request-params (lack.request:request-body-parameters ningle:*request*)) (cond ((not (string= authenticity-token (authentication:csrf-token))) '(403 (:content-type "text/plain") ("Denied"))) ((not (hermetic:logged-in-p)) '(303 (:location "/login"))) ((string= "create-chart" method) (routing:create-chart (lack.request:request-body-parameters ningle:*request*))) (t `(400 (:content-type "text/plain") (,(format nil "Unknown method ~S" method))))))) ;; ;; Error pages (defmethod on-exception ((app ) (code (eql 404))) (declare (ignore app)) (merge-pathnames #P"_errors/404.html" *template-directory*))