A website for producing interactive charts without writing a single line of code. Built with Common Lisp and Python.
https://charts.craigoates.net
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
256 lines
8.8 KiB
256 lines
8.8 KiB
;; (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 <web> (<app>) ()) |
|
(defvar *web* (make-instance '<web>)) |
|
(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 <web>) (code (eql 404))) |
|
(declare (ignore app)) |
|
(merge-pathnames #P"_errors/404.html" |
|
*template-directory*))
|
|
|