From bb5f8fb4820937ca4a979e87dee60df42b1705e1 Mon Sep 17 00:00:00 2001 From: Craig Oates Date: Sun, 18 Sep 2022 23:13:33 +0100 Subject: [PATCH] update web.lisp, mostly around site settings and first-run set-up. --- src/web.lisp | 299 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 245 insertions(+), 54 deletions(-) diff --git a/src/web.lisp b/src/web.lisp index aafbc2c..63b22da 100644 --- a/src/web.lisp +++ b/src/web.lisp @@ -13,6 +13,7 @@ #:hermetic #:auth #:utils + #:validation #:user #:nera-db) (:export #:*web*)) @@ -32,14 +33,17 @@ ;; Routing rules (defroute "/" () - (render #P"index.html" - (append (if (hermetic:logged-in-p) - (auth:auth-user-data)) - `(:alert ,(utils:get-and-reset-alert) - :content ,(storage:open-text-file - "" "pages" - (site-settings::home-page-of - (nera:get-site-settings))))))) + (if (not (uiop:file-exists-p (ritherdon-archive.config:database-name))) + (redirect "/setup") + (render #P"index.html" + (append (if (hermetic:logged-in-p) + (auth:auth-user-data)) + `(:alert ,(utils:get-and-reset-alert) + :nav-menu ,(nera:nav-menu-slugs) + :content ,(storage:open-text-file + "" "pages" + (site-settings::home-page-of + (nera:get-site-settings)))))))) (defroute "/setup" () ;; If there is no database, there is no user, hence no more checks. @@ -57,6 +61,7 @@ `(,+service-unavailable+ (:location "/"))) (t (nera-db:init-db (lack.request:request-body-parameters ningle:*request*)) + (storage:init-storage) ;; Redirect to /setup-complete? (utils:set-alert "All set-up. Log-in to continue.") (redirect "/login"))))) @@ -67,16 +72,16 @@ (utils:set-alert "This feature has been disabled.") (redirect "/")) (hermetic:auth (:logged-in) - ;; Hermetic Authorised (Logged in users can't sign-up). - (progn - (utils:set-alert - "You need to be signed out to create a new account.") - (redirect "/dashboard")) - ;; Hermetic Not Authorised - (let ((alert (utils:get-and-reset-alert))) - (render "sign-up.html" - `(:token ,(auth:csrf-token) - :alert ,alert)))))) + ;; Hermetic Authorised (Logged in users can't sign-up). + (progn + (utils:set-alert + "You need to be signed out to create a new account.") + (redirect "/dashboard")) + ;; Hermetic Not Authorised + (let ((alert (utils:get-and-reset-alert))) + (render "sign-up.html" + `(:token ,(auth:csrf-token) + :alert ,alert)))))) (defroute ("/sign-up" :method :POST) () (destructuring-bind @@ -108,7 +113,6 @@ (utils:set-alert "Account created.") (redirect "/login"))))))) - (defroute ("/login" :method :GET) () (hermetic:auth (:logged-in) ;; Authorised @@ -136,7 +140,7 @@ (gethash :id ningle:*session*) (nera:get-user-id username) ;; Set the users password (for session) (gethash :password ningle:*session*) password) - (redirect "/dashboard")) + (redirect "/dashboard")) ;; Failed log-in attempt. (progn (utils:set-alert "Incorrect details provided.") (redirect "/login")) @@ -172,39 +176,59 @@ (redirect "/login")))) (defroute ("/site-settings" :method :GET) () - (hermetic:auth (:administrator) - ;; Authorised - (render #P"user/site-settings.html" - (append (auth:auth-user-data) - `(:alert ,(utils:get-and-reset-alert) - :pages ,(storage:get-file-names - (storage:get-files-in-directory - "" "pages")) - :settings ,(nera:get-site-settings)))) - ;; Not Authorised - (progn - (utils:set-alert "You are not authorised to view this page.") - (redirect "/login")))) - -(defroute ("/site-settings/sign-up" :method :POST) () + (hermetic:auth + (:administrator) + ;; Authorised + (render #P"user/site-settings.html" + (append (auth:auth-user-data) + `(:alert ,(utils:get-and-reset-alert) + :pages ,(nera:get-all-pages) + :site-wide-snippet ,(storage:open-text-file-with-raw-path + "static/site-wide-snippet") + :settings ,(nera:get-site-settings)))) + ;; Not Authorised + (progn + (utils:set-alert "You are not authorised to view this page.") + (redirect "/login")))) + +(defroute ("/site-settings/update-sign-up" :method :POST) () (destructuring-bind (&key enable-sign-up authenticity-token &allow-other-keys) (utils:request-params (lack.request:request-body-parameters ningle:*request*)) + (if (not (string= authenticity-token (auth:csrf-token))) + `(,+forbidden+ (:content-type "text/plain") ("Denied")) + (hermetic:auth (:administrator) + ;; Authorised + (progn + (nera:update-enable-sign-on-settings enable-sign-up) + (utils:set-alert "Enable Sign-Up setting saved.") + (redirect "/site-settings")) + ;; Not Authorised + (progn + (utils:set-alert "You are not authorised to view this page.") + (redirect "/login")))))) + +(defroute ("/site-settings/update-nav-menu" :method :POST) (&key _parsed) + (destructuring-bind + (&key authenticity-token &allow-other-keys) + (utils:request-params + (lack.request:request-body-parameters ningle:*request*)) (if (not (string= authenticity-token (auth:csrf-token))) `(,+forbidden+ (:content-type "text/plain") ("Denied")) (hermetic:auth (:administrator) ;; Authorised (progn - (nera:update-enable-sign-on-settings enable-sign-up) - (utils:set-alert "Enable Sign-Up setting saved.") + (format t "~a" (cdr (assoc "page" _parsed :test #'string=))) + (nera:update-nav-menu _parsed) + (utils:set-alert "Nav. menu updated.") (redirect "/site-settings")) ;; Not Authorised (progn (utils:set-alert "You are not authorised to view this page.") (redirect "/login")))))) -(defroute ("/site-settings/home-page" :method :POST) () +(defroute ("/site-settings/update-home-page" :method :POST) () (destructuring-bind (&key set-home-page authenticity-token &allow-other-keys) (utils:request-params @@ -226,6 +250,122 @@ (utils:set-alert "You are not authorised to view this page.") (redirect "/login")))))) +(defroute ("/site-settings/update-site-name" :method :POST) () + (destructuring-bind + (&key site-name authenticity-token &allow-other-keys) + (utils:request-params + (lack.request:request-body-parameters ningle:*request*)) + (if (not (string= authenticity-token (auth:csrf-token))) + `(,+forbidden+ (:content-type "text/plain") ("Denied")) + (hermetic:auth (:administrator) + ;; Authorised + (cond ((utils:string-is-nil-or-empty? site-name) + (utils:set-alert "No value provided. Site name not changed.") + (redirect "/site-settings")) + (t + (nera:update-site-name site-name) + (utils:set-alert "Site name updated.") + (redirect "/site-settings"))) + ;; Not Authorised + (progn + (utils:set-alert "You are not authorised to view this page.") + (redirect "/login")))))) + +(defroute ("/site-settings/update-code-snippet" :method :POST) () + (destructuring-bind + (&key code-snippet authenticity-token &allow-other-keys) + (utils:request-params + (lack.request:request-body-parameters ningle:*request*)) + (if (not (string= authenticity-token (auth:csrf-token))) + `(,+forbidden+ (:content-type "text/plain") ("Denied")) + (hermetic:auth (:administrator) + ;; Authorised + (progn + (storage:store-text-with-raw-path + "static/site-wide-snippet" code-snippet) + (utils:set-alert "Site-Wide snippet updated.") + (redirect "/site-settings")) + ;; Not Authorised + (progn + (utils:set-alert "You are not authorised to view this page.") + (redirect "/login")))))) + +(defroute ("/site-settings/update-favicon" :method :POST) () + (destructuring-bind + (&key favicon-file authenticity-token &allow-other-keys) + (utils:request-params (lack.request:request-body-parameters ningle:*request*)) + (if (not (string= authenticity-token (auth:csrf-token))) + `(,+forbidden+ (:content-type "text/plain") ("Denied")) + (hermetic:auth + (:administrator) + ;; Authorised + (cond ((utils:string-is-nil-or-empty? (cadr favicon-file)) + (utils:set-alert "No file provided. Favicon not uploaded.") + (redirect "/site-settings")) + (t (storage:store-with-raw-path + "static/images/favicon.png" favicon-file) + (utils:set-alert "Favicon saved.") + (utils:run-bash-command + (format nil "convert ~a -resize 192x192\\> ~a" + (merge-pathnames + #P"static/images/favicon.png" + ritherdon-archive.config:*application-root*) + (merge-pathnames + #P"static/images/favicon.png" + ritherdon-archive.config:*application-root*))) + (redirect "/site-settings"))) + ;; Not Authorised + (progn + (utils:set-alert "You are not authorised to view this page.") + (redirect "/login")))))) + +(defroute ("/site-settings/update-enable-site-logo" :method :POST) () + (destructuring-bind + (&key enable-site-logo authenticity-token &allow-other-keys) + (utils:request-params + (lack.request:request-body-parameters ningle:*request*)) + (if (not (string= authenticity-token (auth:csrf-token))) + `(,+forbidden+ (:content-type "text/plain") ("Denied")) + (hermetic:auth (:administrator) + ;; Authorised + (progn + (nera:update-enable-site-logo-setting enable-site-logo) + (utils:set-alert "Enable Site Logo setting saved.") + (redirect "/site-settings")) + ;; Not Authorised + (progn + (utils:set-alert "You are not authorised to view this page.") + (redirect "/login")))))) + +(defroute ("/site-settings/update-site-logo" :method :POST) () + (destructuring-bind + (&key site-logo authenticity-token &allow-other-keys) + (utils:request-params (lack.request:request-body-parameters ningle:*request*)) + (if (not (string= authenticity-token (auth:csrf-token))) + `(,+forbidden+ (:content-type "text/plain") ("Denied")) + (hermetic:auth + (:administrator) + ;; Authorised + (cond ((utils:string-is-nil-or-empty? (cadr site-logo)) + (utils:set-alert "No file provided. Site logo not uploaded.") + (redirect "/site-settings")) + (t (storage:store-with-raw-path + "static/images/site-logo.png" site-logo) + (utils:set-alert "Site logo saved.") + (utils:run-bash-command + (format nil "convert ~a -resize 500x500\\> ~a" + (merge-pathnames + #P"static/images/site-logo.png" + ritherdon-archive.config:*application-root*) + (merge-pathnames + #P"static/images/site-logo.png" + ritherdon-archive.config:*application-root*))) + (redirect "/site-settings"))) + ;; Not Authorised + (progn + (utils:set-alert "You are not authorised to view this page.") + (redirect "/login")))))) + (defroute ("/users" :method :GET) () (hermetic:auth (:administrator) ;; Authorised @@ -423,6 +563,7 @@ "pages" (utils:slugify title) page-content) + (nera:create-page title (utils:slugify title) +false+ +false+) (utils:set-alert "Page created.") (redirect "/dashboard"))) ;; Not Authorised @@ -454,6 +595,26 @@ (storage:get-files-in-directory "" "pages"))))))) +(defroute ("/view/pages" :method :GET) () + (let ((alert (utils:get-and-reset-alert))) + (render #P"pages.html" + (append (if (hermetic:logged-in-p) + (auth:auth-user-data)) + `(:alert ,alert + :pages ,(storage:get-file-names + (storage:get-files-in-directory + "" "pages"))))))) + +(defroute ("/view/archive" :method :GET) () + (let ((alert (utils:get-and-reset-alert))) + (render #P"pages.html" + (append (if (hermetic:logged-in-p) + (auth:auth-user-data)) + `(:alert ,alert + :pages ,(storage:get-file-names + (storage:get-files-in-directory + "" "pages"))))))) + (defroute ("/view/page/:slug" :method :GET) (&key slug) (let ((alert (utils:get-and-reset-alert))) (if (storage:file-exists-p "" "pages" slug) @@ -461,6 +622,7 @@ (append (if (hermetic:logged-in-p) (auth:auth-user-data)) `(:alert ,alert + :db-data ,(nera:get-page slug) :data ,(storage:open-text-file "" "pages" slug)))) (on-exception *web* 404)))) @@ -472,7 +634,7 @@ (render "/user/edit-page.html" (append (auth:auth-user-data) `(:alert ,alert - :title ,slug + :db-data ,(nera:get-page slug) :data ,(storage:open-text-file "" "pages" slug))))) ;; Not Authorised @@ -482,7 +644,7 @@ (defroute ("/edit/page" :method :POST) () (destructuring-bind - (&key title page-content authenticity-token &allow-other-keys) + (&key slug page-content authenticity-token &allow-other-keys) (utils:request-params (lack.request:request-body-parameters ningle:*request*)) (format t "~a" page-content) (cond ((not (string= authenticity-token (auth:csrf-token))) @@ -490,15 +652,13 @@ (t (hermetic:auth (:logged-in) ;; Authorised - (cond ((utils:string-is-nil-or-empty? title) - (utils:set-alert "Cannot find file. Unable to save page.") + (cond ((utils:string-is-nil-or-empty? slug) + (utils:set-alert "Page cannot be found.") (redirect "/user/pages")) (t (storage:store-text "" ; `USERNAME' blank because it's not used/needed. - "pages" - (utils:slugify title) - page-content) + "pages" slug page-content) (utils:set-alert "Page updated.") (redirect "/user/pages"))) ;; Not Authorised @@ -507,24 +667,29 @@ (defroute ("/rename/page" :method :POST) () (destructuring-bind - (&key title new-title authenticity-token &allow-other-keys) + (&key id slug new-title authenticity-token &allow-other-keys) (utils:request-params (lack.request:request-body-parameters ningle:*request*)) (cond ((not (string= authenticity-token (auth:csrf-token))) `(,+forbidden+ (:content-type "text/plain") ("Denied"))) (t (hermetic:auth (:logged-in) ;; Authorised - (cond ((utils:string-is-nil-or-empty? title) + (cond ((utils:string-is-nil-or-empty? slug) (utils:set-alert "Cannot find file. Unable to save changes.") (redirect "/user/pages")) ((utils:string-is-nil-or-empty? new-title) (utils:set-alert "No title provided. Unable to save changes.") - (redirect (format nil "/edit/page/~a" title))) + (redirect (format nil "/edit/page/~a" slug))) + + ((utils:string-is-nil-or-empty? id) + (utils:set-alert "No Id. found. Cannot find page in database.") + (redirect "/user/pages")) (t (storage:rename-content-file "" ; `USERNAME' blank because it's not used/needed. - "pages" title (utils:slugify new-title)) + "pages" slug (utils:slugify new-title)) + (nera:update-page id new-title (utils:slugify new-title)) (utils:set-alert "File name changed.") (redirect (format nil "/edit/page/~a" (utils:slugify new-title))))) @@ -532,26 +697,52 @@ (progn (utils:set-alert "You are not logged in.") (redirect "/login"))))))) +(defroute ("/page/set-nav-menu" :method :POST) () + (destructuring-bind + (&key slug show-in-nav authenticity-token &allow-other-keys) + (utils:request-params (lack.request:request-body-parameters ningle:*request*)) + (cond ((not (string= authenticity-token (auth:csrf-token))) + `(,+forbidden+ (:content-type "text/plain") ("Denied"))) + (t (hermetic:auth + (:logged-in) + ;; Authorised + (cond ((utils:string-is-nil-or-empty? slug) + (utils:set-alert "Cannot find page in database.") + (redirect "/user/pages")) + + (t (nera:update-nav-menu + slug (if (null show-in-nav) +false+ +true+)) + (utils:set-alert "Updated name changed.") + (redirect (format nil "/edit/page/~a" slug)))) + ;; Not Authorised + (progn (utils:set-alert "You are not logged in.") + (redirect "/login"))))))) + (defroute ("/page/delete" :method :POST) () (destructuring-bind - (&key title authenticity-token &allow-other-keys) + (&key slug authenticity-token &allow-other-keys) (utils:request-params (lack.request:request-body-parameters ningle:*request*)) (cond ((not (string= authenticity-token (auth:csrf-token))) `(,+forbidden+ (:content-type "text/plain") ("Denied"))) (t (hermetic:auth (:administrator) ;; Authorised - (cond ((utils:string-is-nil-or-empty? title) - (utils:set-alert "No file name provided. Nothing deleted.") + (cond ((utils:string-is-nil-or-empty? slug) + (utils:set-alert "Cannot find page in database. Nothing deleted.") (redirect "/user/pages")) - ((null (storage:file-exists-p "" "pages" title)) + ((null (storage:file-exists-p "" "pages" slug)) (utils:set-alert "Unable to find page. Nothing deleted.") (redirect "/user/pages")) + + ((eql (pages::can-delete-p (nera:get-page slug)) +false+) + (utils:set-alert "File not allowed to be deleted.") + (redirect "/user/pages")) (t (storage:remove-file "" ; `USERNAME' blank because it's not used/needed. - "pages" title) + "pages" slug) + (nera:delete-page :slug slug) (utils:set-alert "Page deleted.") (redirect "/user/pages"))) ;; Not Authorised