2020-07-22 00:59:25 +02:00
|
|
|
(in-package :panettone)
|
|
|
|
(declaim (optimize (safety 3)))
|
|
|
|
|
|
|
|
;;;
|
|
|
|
;;; Data model
|
|
|
|
;;;
|
|
|
|
|
|
|
|
(defclass/std user ()
|
|
|
|
((cn dn mail displayname :type string)))
|
|
|
|
|
|
|
|
;;;
|
|
|
|
;;; LDAP integration
|
|
|
|
;;;
|
|
|
|
|
|
|
|
(defvar *ldap* nil
|
|
|
|
"The ldap connection")
|
|
|
|
|
2020-07-22 01:36:21 +02:00
|
|
|
(defun connect-ldap (&key
|
|
|
|
(host "localhost")
|
|
|
|
(port 389))
|
|
|
|
(setq *ldap* (ldap:new-ldap :host host :port port)))
|
2020-07-22 00:59:25 +02:00
|
|
|
|
|
|
|
(defun ldap-entry->user (entry)
|
|
|
|
(apply
|
|
|
|
#'make-instance
|
|
|
|
'user
|
|
|
|
:dn (ldap:dn entry)
|
|
|
|
(alexandria:mappend
|
|
|
|
(lambda (field)
|
|
|
|
(list field (car (ldap:attr-value entry field))))
|
|
|
|
(list :mail
|
|
|
|
:cn
|
|
|
|
:displayname))))
|
|
|
|
|
|
|
|
(defun find-user/ldap (username)
|
|
|
|
(check-type username (simple-array character (*)))
|
|
|
|
(ldap:search
|
|
|
|
*ldap*
|
|
|
|
`(and (= objectClass organizationalPerson)
|
2020-07-22 04:12:02 +02:00
|
|
|
(or
|
|
|
|
(= cn ,username)
|
|
|
|
(= dn ,username)))
|
2020-07-22 00:59:25 +02:00
|
|
|
;; TODO(grfn): make this configurable
|
|
|
|
:base "ou=users,dc=tvl,dc=fyi")
|
|
|
|
(ldap:next-search-result *ldap*))
|
|
|
|
|
|
|
|
(defun find-user (username)
|
|
|
|
(check-type username (simple-array character (*)))
|
2020-07-22 04:12:02 +02:00
|
|
|
(when-let ((ldap-entry (find-user/ldap username)))
|
|
|
|
(ldap-entry->user ldap-entry)))
|
|
|
|
|
|
|
|
(defun find-user-by-dn (dn)
|
|
|
|
(ldap:search *ldap* `(= objectClass organizationalPerson)
|
|
|
|
:base dn
|
|
|
|
:scope 'ldap:base)
|
|
|
|
(when-let ((ldap-entry (ldap:next-search-result *ldap*)))
|
|
|
|
(ldap-entry->user ldap-entry)))
|
|
|
|
|
|
|
|
(comment
|
|
|
|
(user-by-dn "cn=glittershark,ou=users,dc=tvl,dc=fyi")
|
|
|
|
)
|
2020-07-22 00:59:25 +02:00
|
|
|
|
|
|
|
(defun authenticate-user (user-or-username password)
|
|
|
|
"Checks the given USER-OR-USERNAME has the given PASSWORD, by making a bind
|
|
|
|
request against the ldap server at *ldap*. Returns the user if authentication is
|
|
|
|
successful, `nil' otherwise"
|
2020-07-24 00:42:15 +02:00
|
|
|
(when-let ((user (if (typep user-or-username 'user) user-or-username
|
|
|
|
(find-user user-or-username))))
|
|
|
|
(let ((dn (dn user)))
|
2020-07-26 21:33:27 +02:00
|
|
|
(let ((code-sym
|
|
|
|
(nth-value 1 (ldap:bind
|
|
|
|
(ldap:new-ldap :host (ldap:host *ldap*)
|
|
|
|
:port (ldap:port *ldap*)
|
|
|
|
:user dn
|
|
|
|
:pass password)))))
|
2020-07-24 00:42:15 +02:00
|
|
|
(when (equalp code-sym 'trivial-ldap:success)
|
|
|
|
user)))))
|
2020-07-22 00:59:25 +02:00
|
|
|
|
2020-07-22 04:12:02 +02:00
|
|
|
(defun author (object)
|
|
|
|
(find-user-by-dn (author-dn object)))
|
|
|
|
|
2020-07-22 00:59:25 +02:00
|
|
|
;;;
|
|
|
|
;;; Views
|
|
|
|
;;;
|
|
|
|
|
|
|
|
(defvar *title* "Panettone")
|
|
|
|
|
2020-07-24 02:53:36 +02:00
|
|
|
(defvar *user* nil)
|
|
|
|
|
2020-07-24 23:49:52 +02:00
|
|
|
(setf (who:html-mode) :html5)
|
2020-07-22 00:59:25 +02:00
|
|
|
|
2020-07-26 21:33:27 +02:00
|
|
|
(defun render/footer-nav ()
|
2020-07-24 02:53:36 +02:00
|
|
|
(who:with-html-output (*standard-output*)
|
|
|
|
(:footer
|
|
|
|
(:nav
|
|
|
|
(if (find (hunchentoot:request-uri*)
|
|
|
|
(list "/" "/issues/closed")
|
|
|
|
:test #'string=)
|
|
|
|
(who:htm (:span :class "placeholder"))
|
|
|
|
(who:htm (:a :href "/" "All Issues")))
|
|
|
|
(if *user*
|
|
|
|
(who:htm
|
|
|
|
(:form :class "form-link log-out"
|
|
|
|
:method "post"
|
|
|
|
:action "/logout"
|
|
|
|
(:input :type "submit" :value "Log Out")))
|
|
|
|
(who:htm
|
2020-07-29 00:38:27 +02:00
|
|
|
(:a :href
|
|
|
|
(format nil
|
|
|
|
"/login?original-uri=~A"
|
|
|
|
(drakma:url-encode (hunchentoot:request-uri*)
|
|
|
|
:utf-8))
|
|
|
|
"Log In")))))))
|
2020-07-24 02:53:36 +02:00
|
|
|
|
|
|
|
(defmacro render ((&key (footer t)) &body body)
|
2020-07-22 00:59:25 +02:00
|
|
|
`(who:with-html-output-to-string (*standard-output* nil :prologue t)
|
2020-07-24 23:49:52 +02:00
|
|
|
(:html
|
|
|
|
:lang "en"
|
|
|
|
(:head
|
|
|
|
(:title (who:esc *title*))
|
2020-07-24 03:07:49 +02:00
|
|
|
(:link :rel "stylesheet" :type "text/css" :href "/main.css")
|
|
|
|
(:meta :name "viewport"
|
|
|
|
:content "width=device-width,initial-scale=1"))
|
2020-07-24 23:49:52 +02:00
|
|
|
(:body
|
|
|
|
(:div
|
|
|
|
:class "content"
|
|
|
|
,@body
|
|
|
|
(when ,footer
|
|
|
|
(render/footer-nav)))))))
|
2020-07-22 00:59:25 +02:00
|
|
|
|
2020-07-24 00:50:13 +02:00
|
|
|
(defun render/alert (message)
|
|
|
|
"Render an alert box for MESSAGE, if non-null"
|
|
|
|
(check-type message (or null string))
|
|
|
|
(who:with-html-output (*standard-output*)
|
|
|
|
(when message
|
|
|
|
(who:htm (:div :class "alert" (who:esc message))))))
|
|
|
|
|
2020-07-24 01:59:15 +02:00
|
|
|
(defun render/login (&key message (original-uri "/"))
|
2020-07-24 02:53:36 +02:00
|
|
|
(render (:footer nil)
|
2020-07-23 00:16:58 +02:00
|
|
|
(:div
|
|
|
|
:class "login-form"
|
|
|
|
(:header
|
|
|
|
(:h1 "Login"))
|
|
|
|
(:main
|
|
|
|
:class "login-form"
|
2020-07-24 00:50:13 +02:00
|
|
|
(render/alert message)
|
2020-07-23 00:16:58 +02:00
|
|
|
(:form
|
|
|
|
:method :post :action "/login"
|
2020-07-24 01:59:15 +02:00
|
|
|
(:input :type "hidden" :name "original-uri"
|
|
|
|
:value original-uri)
|
2020-07-23 00:16:58 +02:00
|
|
|
(:div
|
|
|
|
(:label :for "username"
|
|
|
|
"Username")
|
|
|
|
(:input :type "text"
|
|
|
|
:name "username"
|
|
|
|
:id "username"
|
|
|
|
:placeholder "username"))
|
|
|
|
(:div
|
|
|
|
(:label :for "password"
|
|
|
|
"Password")
|
|
|
|
(:input :type "password"
|
|
|
|
:name "password"
|
|
|
|
:id "password"
|
|
|
|
:placeholder "password"))
|
|
|
|
(:input :type "submit"
|
|
|
|
:value "Submit"))))))
|
|
|
|
|
|
|
|
(defun created-by-at (issue)
|
2020-07-26 21:33:27 +02:00
|
|
|
(check-type issue model:issue)
|
2020-07-23 00:16:58 +02:00
|
|
|
(who:with-html-output (*standard-output*)
|
|
|
|
(:span :class "created-by-at"
|
|
|
|
"Opened by "
|
|
|
|
(:span :class "username"
|
|
|
|
(who:esc
|
|
|
|
(or
|
|
|
|
(when-let ((author (author issue)))
|
|
|
|
(displayname author))
|
|
|
|
"someone")))
|
|
|
|
" at "
|
|
|
|
(:span :class "timestamp"
|
|
|
|
(who:esc
|
|
|
|
(format-dottime (created-at issue)))))))
|
2020-07-22 00:59:25 +02:00
|
|
|
|
2020-07-23 05:11:10 +02:00
|
|
|
(defun render/issue-list (&key issues)
|
|
|
|
(who:with-html-output (*standard-output*)
|
|
|
|
(:ol
|
|
|
|
:class "issue-list"
|
|
|
|
(dolist (issue issues)
|
2020-07-26 21:33:27 +02:00
|
|
|
(let ((issue-id (model:id issue)))
|
2020-07-23 05:11:10 +02:00
|
|
|
(who:htm
|
|
|
|
(:li
|
|
|
|
(:a :href (format nil "/issues/~A" issue-id)
|
|
|
|
(:p
|
|
|
|
(:span :class "issue-subject"
|
|
|
|
(who:esc (subject issue))))
|
|
|
|
(:span :class "issue-number"
|
|
|
|
(who:esc (format nil "#~A" issue-id)))
|
|
|
|
" - "
|
|
|
|
(created-by-at issue)
|
|
|
|
(let ((num-comments (length (issue-comments issue))))
|
|
|
|
(unless (zerop num-comments)
|
|
|
|
(who:htm
|
|
|
|
(:span :class "comment-count"
|
|
|
|
" - "
|
|
|
|
(who:esc
|
|
|
|
(format nil "~A comment~:p" num-comments))))))))))))))
|
|
|
|
|
2020-07-22 00:59:25 +02:00
|
|
|
(defun render/index (&key issues)
|
2020-07-24 02:53:36 +02:00
|
|
|
(render ()
|
2020-07-23 00:16:58 +02:00
|
|
|
(:header
|
|
|
|
(:h1 "Issues")
|
2020-07-24 02:59:48 +02:00
|
|
|
(when *user*
|
|
|
|
(who:htm
|
|
|
|
(:a
|
|
|
|
:class "new-issue"
|
|
|
|
:href "/issues/new" "New Issue"))))
|
2020-07-23 00:16:58 +02:00
|
|
|
(:main
|
2020-07-23 05:11:10 +02:00
|
|
|
(:div
|
|
|
|
:class "issue-links"
|
|
|
|
(:a :href "/issues/closed" "View closed issues"))
|
|
|
|
(render/issue-list :issues issues))))
|
|
|
|
|
|
|
|
(defun render/closed-issues (&key issues)
|
2020-07-24 02:53:36 +02:00
|
|
|
(render ()
|
2020-07-23 05:11:10 +02:00
|
|
|
(:header
|
|
|
|
(:h1 "Closed issues"))
|
|
|
|
(:main
|
|
|
|
(:div
|
|
|
|
:class "issue-links"
|
|
|
|
(:a :href "/" "View open isues"))
|
|
|
|
(render/issue-list :issues issues))))
|
2020-07-22 00:59:25 +02:00
|
|
|
|
2020-07-24 00:50:13 +02:00
|
|
|
(defun render/new-issue (&optional message)
|
2020-07-24 02:53:36 +02:00
|
|
|
(render ()
|
2020-07-23 00:16:58 +02:00
|
|
|
(:header
|
|
|
|
(:h1 "New Issue"))
|
|
|
|
(:main
|
2020-07-24 00:50:13 +02:00
|
|
|
(render/alert message)
|
2020-07-23 00:16:58 +02:00
|
|
|
(:form :method "post"
|
|
|
|
:action "/issues"
|
|
|
|
:class "issue-form"
|
|
|
|
(:div
|
|
|
|
(:input :type "text"
|
|
|
|
:id "subject"
|
|
|
|
:name "subject"
|
|
|
|
:placeholder "Subject"))
|
|
|
|
|
|
|
|
(:div
|
|
|
|
(:textarea :name "body"
|
|
|
|
:placeholder "Description"
|
|
|
|
:rows 10))
|
|
|
|
|
|
|
|
(:input :type "submit"
|
|
|
|
:value "Create Issue")))))
|
2020-07-22 04:12:02 +02:00
|
|
|
|
2020-07-23 03:43:30 +02:00
|
|
|
(defun render/new-comment (issue-id)
|
|
|
|
(who:with-html-output (*standard-output*)
|
|
|
|
(:form
|
|
|
|
:class "new-comment"
|
|
|
|
:method "post"
|
|
|
|
:action (format nil "/issues/~A/comments" issue-id)
|
|
|
|
(:div
|
|
|
|
(:textarea :name "body"
|
|
|
|
:placeholder "Leave a comment"
|
|
|
|
:rows 5))
|
|
|
|
(:input :type "submit"
|
|
|
|
:value "Comment"))))
|
2020-07-22 04:12:02 +02:00
|
|
|
|
2020-07-22 00:59:25 +02:00
|
|
|
(defun render/issue (issue)
|
2020-07-26 21:33:27 +02:00
|
|
|
(check-type issue model:issue)
|
|
|
|
(let ((issue-id (id issue))
|
2020-07-23 05:11:10 +02:00
|
|
|
(issue-status (status issue)))
|
2020-07-24 02:53:36 +02:00
|
|
|
(render ()
|
2020-07-23 05:11:10 +02:00
|
|
|
(:header
|
|
|
|
(:h1 (who:esc (subject issue)))
|
|
|
|
(:div :class "issue-number"
|
|
|
|
(who:esc (format nil "#~A" issue-id))))
|
|
|
|
(:main
|
|
|
|
(:div
|
|
|
|
:class "issue-info"
|
|
|
|
(created-by-at issue)
|
|
|
|
|
2020-07-24 02:59:48 +02:00
|
|
|
(when *user*
|
|
|
|
(who:htm
|
|
|
|
(:form :class "set-issue-status"
|
|
|
|
:method "post"
|
|
|
|
:action (format nil "/issues/~A/~A"
|
|
|
|
issue-id
|
|
|
|
(case issue-status
|
|
|
|
(:open "close")
|
|
|
|
(:closed "open")))
|
|
|
|
(:input :type "submit"
|
|
|
|
:class (case issue-status
|
|
|
|
(:open "close-issue")
|
|
|
|
(:closed "open-issue"))
|
|
|
|
:value (case issue-status
|
|
|
|
(:open "Close")
|
|
|
|
(:closed "Reopen")))))))
|
2020-07-23 05:11:10 +02:00
|
|
|
(:p (who:esc (body issue)))
|
|
|
|
(let ((comments (issue-comments issue)))
|
|
|
|
(who:htm
|
|
|
|
(:div
|
|
|
|
:class "issue-comments"
|
|
|
|
(dolist (comment comments)
|
|
|
|
(let ((author (author comment)))
|
|
|
|
(who:htm
|
|
|
|
(:div
|
|
|
|
:class "comment"
|
|
|
|
(:p (who:esc (body comment)))
|
|
|
|
(:p
|
|
|
|
:class "comment-info"
|
|
|
|
(:span :class "username"
|
|
|
|
(who:esc (displayname author))
|
|
|
|
" at "
|
|
|
|
(who:esc (format-dottime (created-at comment)))))))))
|
2020-07-24 02:59:48 +02:00
|
|
|
(when *user*
|
2020-07-26 21:33:27 +02:00
|
|
|
(render/new-comment (id issue))))))))))
|
2020-07-22 00:59:25 +02:00
|
|
|
|
|
|
|
(defun render/not-found (entity-type)
|
2020-07-24 02:53:36 +02:00
|
|
|
(render ()
|
2020-07-22 00:59:25 +02:00
|
|
|
(:h1 (who:esc entity-type) "Not Found")))
|
|
|
|
|
|
|
|
;;;
|
|
|
|
;;; HTTP handlers
|
|
|
|
;;;
|
|
|
|
|
2020-07-24 02:59:48 +02:00
|
|
|
(defun @auth-optional (next)
|
|
|
|
(let ((*user* (hunchentoot:session-value 'user)))
|
|
|
|
(funcall next)))
|
|
|
|
|
2020-07-22 00:59:25 +02:00
|
|
|
(defun @auth (next)
|
|
|
|
(if-let ((*user* (hunchentoot:session-value 'user)))
|
|
|
|
(funcall next)
|
2020-07-24 01:59:15 +02:00
|
|
|
(hunchentoot:redirect
|
|
|
|
(format nil "/login?original-uri=~A"
|
|
|
|
(drakma:url-encode
|
|
|
|
(hunchentoot:request-uri*)
|
|
|
|
:utf-8)))))
|
|
|
|
|
2020-07-26 21:33:27 +02:00
|
|
|
(defun @txn (next)
|
|
|
|
(pomo:with-transaction ()
|
|
|
|
(catch
|
|
|
|
;; 'hunchentoot:handler-done is unexported, but is used by functions
|
|
|
|
;; like hunchentoot:redirect to nonlocally abort the request handler -
|
|
|
|
;; this doesn't mean an error occurred, so we need to catch it here to
|
|
|
|
;; make the transaction still get committed
|
|
|
|
(intern "HANDLER-DONE" "HUNCHENTOOT")
|
|
|
|
(funcall next))))
|
|
|
|
|
|
|
|
(defun @handle-issue-not-found (next)
|
|
|
|
(handler-case (funcall next)
|
|
|
|
(issue-not-found (err)
|
|
|
|
(render/not-found
|
|
|
|
(format nil "Issue #~A" (model:id err))))))
|
|
|
|
|
2020-07-24 01:59:15 +02:00
|
|
|
(defroute login-form ("/login" :method :get)
|
|
|
|
(original-uri)
|
2020-07-22 00:59:25 +02:00
|
|
|
(if (hunchentoot:session-value 'user)
|
2020-07-24 01:59:15 +02:00
|
|
|
(hunchentoot:redirect (or original-uri "/"))
|
|
|
|
(render/login :original-uri original-uri)))
|
2020-07-22 00:59:25 +02:00
|
|
|
|
|
|
|
(defroute submit-login ("/login" :method :post)
|
2020-07-24 01:59:15 +02:00
|
|
|
(&post original-uri username password)
|
2020-07-22 00:59:25 +02:00
|
|
|
(if-let ((user (authenticate-user username password)))
|
|
|
|
(progn
|
|
|
|
(setf (hunchentoot:session-value 'user) user)
|
2020-07-24 01:59:15 +02:00
|
|
|
(hunchentoot:redirect (or original-uri "/")))
|
|
|
|
(render/login :message "Invalid credentials")))
|
2020-07-22 00:59:25 +02:00
|
|
|
|
2020-07-24 02:53:36 +02:00
|
|
|
(defroute logout ("/logout" :method :post) ()
|
|
|
|
(hunchentoot:delete-session-value 'user)
|
|
|
|
(hunchentoot:redirect "/"))
|
|
|
|
|
2020-07-24 02:59:48 +02:00
|
|
|
(defroute index ("/" :decorators (@auth-optional)) ()
|
2020-07-26 21:33:27 +02:00
|
|
|
(let ((issues (model:list-issues :status :open)))
|
2020-07-22 00:59:25 +02:00
|
|
|
(render/index :issues issues)))
|
|
|
|
|
2020-07-24 02:59:48 +02:00
|
|
|
(defroute handle-closed-issues
|
|
|
|
("/issues/closed" :decorators (@auth-optional)) ()
|
2020-07-26 21:33:27 +02:00
|
|
|
(let ((issues (model:list-issues :status :closed)))
|
2020-07-23 05:11:10 +02:00
|
|
|
(render/closed-issues :issues issues)))
|
|
|
|
|
2020-07-22 00:59:25 +02:00
|
|
|
(defroute new-issue ("/issues/new" :decorators (@auth)) ()
|
|
|
|
(render/new-issue))
|
|
|
|
|
|
|
|
(defroute handle-create-issue
|
2020-07-26 21:33:27 +02:00
|
|
|
("/issues" :method :post :decorators (@auth @txn))
|
2020-07-22 00:59:25 +02:00
|
|
|
(&post subject body)
|
2020-07-24 00:50:13 +02:00
|
|
|
(if (string= subject "")
|
|
|
|
(render/new-issue "Subject is required")
|
|
|
|
(progn
|
2020-07-26 21:33:27 +02:00
|
|
|
(model:create-issue :subject subject
|
|
|
|
:body body
|
|
|
|
:author-dn (dn *user*))
|
2020-07-24 00:50:13 +02:00
|
|
|
(hunchentoot:redirect "/"))))
|
2020-07-22 00:59:25 +02:00
|
|
|
|
2020-07-26 21:33:27 +02:00
|
|
|
(defroute show-issue
|
2020-07-28 03:27:32 +02:00
|
|
|
("/issues/:id" :decorators (@auth-optional @handle-issue-not-found))
|
2020-07-22 00:59:25 +02:00
|
|
|
(&path (id 'integer))
|
|
|
|
(handler-case
|
2020-07-26 21:33:27 +02:00
|
|
|
(let* ((issue (model:get-issue id))
|
2020-07-24 01:28:39 +02:00
|
|
|
(*title* (format nil "~A | Panettone"
|
|
|
|
(subject issue))))
|
|
|
|
(render/issue issue))
|
2020-07-22 00:59:25 +02:00
|
|
|
(issue-not-found (_)
|
2020-07-26 21:33:27 +02:00
|
|
|
(declare (ignore _))
|
2020-07-22 00:59:25 +02:00
|
|
|
(render/not-found "Issue"))))
|
|
|
|
|
2020-07-23 03:43:30 +02:00
|
|
|
(defroute handle-create-comment
|
2020-07-26 21:33:27 +02:00
|
|
|
("/issues/:id/comments"
|
|
|
|
:decorators (@auth @handle-issue-not-found @txn)
|
|
|
|
:method :post)
|
2020-07-23 03:43:30 +02:00
|
|
|
(&path (id 'integer) &post body)
|
2020-07-24 00:56:25 +02:00
|
|
|
(flet ((redirect-to-issue ()
|
|
|
|
(hunchentoot:redirect (format nil "/issues/~A" id))))
|
2020-07-26 21:33:27 +02:00
|
|
|
(cond
|
|
|
|
((string= body "")
|
|
|
|
(redirect-to-issue))
|
|
|
|
(:else
|
|
|
|
(model:create-issue-comment
|
|
|
|
:issue-id id
|
|
|
|
:body body
|
|
|
|
:author-dn (dn *user*))
|
|
|
|
(redirect-to-issue)))))
|
2020-07-23 03:43:30 +02:00
|
|
|
|
2020-07-23 05:11:10 +02:00
|
|
|
(defroute close-issue
|
2020-07-26 21:33:27 +02:00
|
|
|
("/issues/:id/close" :decorators (@auth @handle-issue-not-found @txn)
|
2020-07-23 05:11:10 +02:00
|
|
|
:method :post)
|
|
|
|
(&path (id 'integer))
|
2020-07-26 21:33:27 +02:00
|
|
|
(model:set-issue-status id :closed)
|
2020-07-23 05:11:10 +02:00
|
|
|
(hunchentoot:redirect (format nil "/issues/~A" id)))
|
|
|
|
|
|
|
|
(defroute open-issue
|
|
|
|
("/issues/:id/open" :decorators (@auth)
|
|
|
|
:method :put)
|
|
|
|
(&path (id 'integer))
|
2020-07-26 21:33:27 +02:00
|
|
|
(model:set-issue-status id :open)
|
2020-07-23 05:11:10 +02:00
|
|
|
(hunchentoot:redirect (format nil "/issues/~A" id)))
|
|
|
|
|
2020-07-23 00:16:58 +02:00
|
|
|
(defroute styles ("/main.css") ()
|
|
|
|
(setf (hunchentoot:content-type*) "text/css")
|
|
|
|
(apply #'lass:compile-and-write panettone.css:styles))
|
|
|
|
|
2020-07-22 00:59:25 +02:00
|
|
|
(defvar *acceptor* nil
|
|
|
|
"Hunchentoot acceptor for Panettone's web server.")
|
|
|
|
|
2020-07-26 21:33:27 +02:00
|
|
|
(defun migrate-db ()
|
2020-07-28 06:09:36 +02:00
|
|
|
"Migrate the database to the latest version of the schema"
|
|
|
|
(model:ddl/init))
|
2020-07-26 21:33:27 +02:00
|
|
|
|
2020-07-28 06:09:36 +02:00
|
|
|
(defun start-panettone (&key port
|
2020-07-22 01:36:21 +02:00
|
|
|
(ldap-host "localhost")
|
2020-07-26 21:33:27 +02:00
|
|
|
(ldap-port 389)
|
|
|
|
postgres-params)
|
2020-07-22 01:36:21 +02:00
|
|
|
(connect-ldap :host ldap-host
|
|
|
|
:port ldap-port)
|
2020-07-26 21:33:27 +02:00
|
|
|
|
|
|
|
(apply #'model:connect-postgres postgres-params)
|
|
|
|
(migrate-db)
|
2020-07-22 00:59:25 +02:00
|
|
|
|
|
|
|
(setq *acceptor*
|
|
|
|
(make-instance 'easy-routes:routes-acceptor :port port))
|
|
|
|
(hunchentoot:start *acceptor*))
|
|
|
|
|
|
|
|
(defun main ()
|
2020-07-22 01:36:21 +02:00
|
|
|
(let ((port (integer-env "PANETTONE_PORT" :default 6161))
|
2020-07-28 06:09:36 +02:00
|
|
|
(ldap-port (integer-env "LDAP_PORT" :default 389)))
|
2020-07-26 23:42:39 +02:00
|
|
|
(setq hunchentoot:*show-lisp-backtraces-p* nil)
|
|
|
|
(setq hunchentoot:*log-lisp-backtraces-p* nil)
|
2020-07-22 00:59:25 +02:00
|
|
|
(start-panettone :port port
|
2020-07-22 01:36:21 +02:00
|
|
|
:ldap-port ldap-port)
|
2020-07-22 00:59:25 +02:00
|
|
|
(sb-thread:join-thread
|
|
|
|
(find-if (lambda (th)
|
|
|
|
(string= (sb-thread:thread-name th)
|
|
|
|
(format nil "hunchentoot-listener-*:~A" port)))
|
|
|
|
(sb-thread:list-all-threads)))))
|
|
|
|
|
|
|
|
(comment
|
2020-07-22 04:12:02 +02:00
|
|
|
(setq hunchentoot:*catch-errors-p* nil)
|
2020-07-22 00:59:25 +02:00
|
|
|
(start-panettone :port 6161
|
2020-07-22 01:36:21 +02:00
|
|
|
:ldap-port 3899)
|
2020-07-22 00:59:25 +02:00
|
|
|
)
|