feat(grfn/bbbg): Add attendee checks
Change-Id: I7f96597ab3f0552cdecd0abac1ef50a68d3e0b7b Reviewed-on: https://cl.tvl.fyi/c/depot/+/4508 Tested-by: BuildkiteCI Reviewed-by: grfn <grfn@gws.fyi> Autosubmit: grfn <grfn@gws.fyi>
This commit is contained in:
		
							parent
							
								
									4ad94b9cf8
								
							
						
					
					
						commit
						4643585e01
					
				
					 13 changed files with 333 additions and 7 deletions
				
			
		| 
						 | 
				
			
			@ -40,11 +40,13 @@
 | 
			
		|||
  yogthos/config {:mvn/version "1.1.8"}
 | 
			
		||||
  clojure.java-time/clojure.java-time {:mvn/version "0.3.3"}
 | 
			
		||||
  cheshire/cheshire {:mvn/version "5.10.1"}
 | 
			
		||||
  org.apache.commons/commons-lang3 {:mvn/version "3.11"}
 | 
			
		||||
 | 
			
		||||
  ;; Spec
 | 
			
		||||
  org.clojure/spec.alpha {:mvn/version "0.3.214"}
 | 
			
		||||
  org.clojure/core.specs.alpha {:mvn/version "0.2.62"}
 | 
			
		||||
  expound/expound {:mvn/version "0.8.10"}}
 | 
			
		||||
  expound/expound {:mvn/version "0.8.10"}
 | 
			
		||||
  org.clojure/test.check {:mvn/version "1.1.0"}}
 | 
			
		||||
 | 
			
		||||
 :paths
 | 
			
		||||
 ["src"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -119,6 +119,19 @@ let repos = [
 | 
			
		|||
    paths = [ src ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  rec {
 | 
			
		||||
    name = "commons-lang3/org.apache.commons";
 | 
			
		||||
    src = fetchMavenArtifact {
 | 
			
		||||
      inherit repos;
 | 
			
		||||
      artifactId = "commons-lang3";
 | 
			
		||||
      groupId = "org.apache.commons";
 | 
			
		||||
      sha512 = "c1f6b5cb9ac47cfb612423a71b347568f3697cf88018b5808678be5234c50b22888db23cb833b7d8d458d39707ab9e4d839107d1d3306de2e4e422010c95180f";
 | 
			
		||||
      version = "3.11";
 | 
			
		||||
      
 | 
			
		||||
    };
 | 
			
		||||
    paths = [ src ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  rec {
 | 
			
		||||
    name = "tools.logging/org.clojure";
 | 
			
		||||
    src = fetchMavenArtifact {
 | 
			
		||||
| 
						 | 
				
			
			@ -1198,6 +1211,19 @@ let repos = [
 | 
			
		|||
    paths = [ src ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  rec {
 | 
			
		||||
    name = "test.check/org.clojure";
 | 
			
		||||
    src = fetchMavenArtifact {
 | 
			
		||||
      inherit repos;
 | 
			
		||||
      artifactId = "test.check";
 | 
			
		||||
      groupId = "org.clojure";
 | 
			
		||||
      sha512 = "68caa189e7292da5dfde92d795ce35ff1980108a579dc11ca618bf0e480101b8ded16fc8ab816b30f364afbccbb8f02fc9194271e7f5323b8042d468164ecb64";
 | 
			
		||||
      version = "1.1.0";
 | 
			
		||||
      
 | 
			
		||||
    };
 | 
			
		||||
    paths = [ src ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  rec {
 | 
			
		||||
    name = "ring-servlet/ring";
 | 
			
		||||
    src = fetchMavenArtifact {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
DROP TABLE "attendee_check";
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
CREATE TABLE attendee_check (
 | 
			
		||||
    "id" UUID PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
 | 
			
		||||
    "attendee_id" UUID NOT NULL REFERENCES attendee ("id"),
 | 
			
		||||
    "user_id" UUID NOT NULL REFERENCES "public"."user" ("id"),
 | 
			
		||||
    "last_dose_at" DATE,
 | 
			
		||||
    "checked_at" TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now()
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										4
									
								
								users/grfn/bbbg/src/bbbg/attendee_check.clj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								users/grfn/bbbg/src/bbbg/attendee_check.clj
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,4 @@
 | 
			
		|||
(ns bbbg.attendee-check
 | 
			
		||||
  (:require [clojure.spec.alpha :as s]))
 | 
			
		||||
 | 
			
		||||
(s/def ::id uuid?)
 | 
			
		||||
| 
						 | 
				
			
			@ -355,8 +355,6 @@
 | 
			
		|||
 | 
			
		||||
(comment
 | 
			
		||||
  (def db (:db bbbg.core/system))
 | 
			
		||||
  (generate-migration db "init-schema")
 | 
			
		||||
  (generate-migration db "add-attendee-checks")
 | 
			
		||||
  (migrate! db)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  )
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -55,5 +55,6 @@
 | 
			
		|||
 | 
			
		||||
  (db/list db (with-stats))
 | 
			
		||||
 | 
			
		||||
  (map? db)
 | 
			
		||||
  (db/insert! db :attendee {::attendee/meetup-name "Rando Guy"
 | 
			
		||||
                            ::attendee/discord-name "rando"})
 | 
			
		||||
  )
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										46
									
								
								users/grfn/bbbg/src/bbbg/db/attendee_check.clj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								users/grfn/bbbg/src/bbbg/db/attendee_check.clj
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,46 @@
 | 
			
		|||
(ns bbbg.db.attendee-check
 | 
			
		||||
  (:require
 | 
			
		||||
   [bbbg.attendee :as attendee]
 | 
			
		||||
   [bbbg.attendee-check :as attendee-check]
 | 
			
		||||
   [bbbg.db :as db]
 | 
			
		||||
   [bbbg.user :as user]
 | 
			
		||||
   [bbbg.util.core :as u]))
 | 
			
		||||
 | 
			
		||||
(defn attendees-with-last-checks
 | 
			
		||||
  [db attendees]
 | 
			
		||||
  (let [ids (map ::attendee/id attendees)
 | 
			
		||||
        checks
 | 
			
		||||
        (db/list db {:select [:attendee-check.*]
 | 
			
		||||
                     :from [:attendee-check]
 | 
			
		||||
                     :join [[{:select [:%max.attendee-check.checked-at
 | 
			
		||||
                                       :attendee-check.attendee-id]
 | 
			
		||||
                              :from [:attendee-check]
 | 
			
		||||
                              :group-by [:attendee-check.attendee-id]
 | 
			
		||||
                              :where [:in :attendee-check.attendee-id ids]}
 | 
			
		||||
                             :last-check]
 | 
			
		||||
                            [:=
 | 
			
		||||
                             :attendee-check.attendee-id
 | 
			
		||||
                             :last-check.attendee-id]]})
 | 
			
		||||
        users (u/key-by
 | 
			
		||||
               ::user/id
 | 
			
		||||
               (db/list db {:select [:public.user.*]
 | 
			
		||||
                            :from [:public.user]
 | 
			
		||||
                            :where [:in :id (map ::user/id checks)]}))
 | 
			
		||||
        checks (map #(assoc % :user (users (::user/id %))) checks)
 | 
			
		||||
        attendee-id->check (u/key-by ::attendee/id checks)]
 | 
			
		||||
    (map #(assoc % :last-check (attendee-id->check (::attendee/id %)))
 | 
			
		||||
         attendees)))
 | 
			
		||||
 | 
			
		||||
(comment
 | 
			
		||||
  (def db (:db bbbg.core/system))
 | 
			
		||||
 | 
			
		||||
  (attendees-with-last-checks
 | 
			
		||||
   db
 | 
			
		||||
   (db/list db :attendee)
 | 
			
		||||
   )
 | 
			
		||||
 | 
			
		||||
  (db/insert! db :attendee-check
 | 
			
		||||
              {::attendee/id #uuid "58bcd372-ff6e-49df-b280-23d24c5ba0f0"
 | 
			
		||||
               ::user/id #uuid "303fb606-5ef0-4682-ad7d-6429c670cd78"
 | 
			
		||||
               ::attendee-check/last-dose-at "2021-12-19"})
 | 
			
		||||
  )
 | 
			
		||||
							
								
								
									
										55
									
								
								users/grfn/bbbg/src/bbbg/handlers/attendee_checks.clj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								users/grfn/bbbg/src/bbbg/handlers/attendee_checks.clj
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,55 @@
 | 
			
		|||
(ns bbbg.handlers.attendee-checks
 | 
			
		||||
  (:require
 | 
			
		||||
   [bbbg.attendee :as attendee]
 | 
			
		||||
   [bbbg.db :as db]
 | 
			
		||||
   [bbbg.handlers.core :refer [page-response wrap-auth-required]]
 | 
			
		||||
   [bbbg.util.display :refer [format-date]]
 | 
			
		||||
   [compojure.coercions :refer [as-uuid]]
 | 
			
		||||
   [compojure.core :refer [context GET POST]]
 | 
			
		||||
   [ring.util.response :refer [not-found]]
 | 
			
		||||
   [bbbg.attendee-check :as attendee-check]
 | 
			
		||||
   [bbbg.user :as user]))
 | 
			
		||||
 | 
			
		||||
(defn- edit-attendee-checks-page [{:keys [existing-check]
 | 
			
		||||
                                   attendee-id ::attendee/id}]
 | 
			
		||||
  [:div
 | 
			
		||||
   (when existing-check
 | 
			
		||||
     [:p
 | 
			
		||||
      "Already checked on "
 | 
			
		||||
      (-> existing-check ::attendee-check/checked-at format-date)
 | 
			
		||||
      " by "
 | 
			
		||||
      (::user/username existing-check)])
 | 
			
		||||
   [:form {:method :post
 | 
			
		||||
           :action (str "/attendees/" attendee-id "/checks")}
 | 
			
		||||
    [:div.form-group
 | 
			
		||||
     [:label
 | 
			
		||||
      "Last Dose"
 | 
			
		||||
      [:input {:type :date
 | 
			
		||||
               :name :last-dose-at}]]]
 | 
			
		||||
    [:div.form-group
 | 
			
		||||
     [:input {:type :submit
 | 
			
		||||
              :value "Mark Checked"}]]]])
 | 
			
		||||
 | 
			
		||||
(defn attendee-checks-routes [{:keys [db]}]
 | 
			
		||||
  (wrap-auth-required
 | 
			
		||||
   (context "/attendees/:attendee-id/checks" [attendee-id :<< as-uuid]
 | 
			
		||||
     (GET "/edit" []
 | 
			
		||||
       (if (db/exists? db {:select [1]
 | 
			
		||||
                           :from [:attendee]
 | 
			
		||||
                           :where [:= :id attendee-id]})
 | 
			
		||||
         (let [existing-check (db/fetch
 | 
			
		||||
                               db
 | 
			
		||||
                               {:select [:attendee-check.*
 | 
			
		||||
                                         :public.user.*]
 | 
			
		||||
                                :from [:attendee-check]
 | 
			
		||||
                                :join [:public.user
 | 
			
		||||
                                       [:=
 | 
			
		||||
                                        :attendee-check.user-id
 | 
			
		||||
                                        :public.user.id]]
 | 
			
		||||
                                :where [:= :attendee-id attendee-id]})]
 | 
			
		||||
           (page-response
 | 
			
		||||
            (edit-attendee-checks-page
 | 
			
		||||
             {:existing-check existing-check
 | 
			
		||||
              ::attendee/id attendee-id})))
 | 
			
		||||
         (not-found "Attendee not found")))
 | 
			
		||||
     (POST "/" []))))
 | 
			
		||||
| 
						 | 
				
			
			@ -1,18 +1,23 @@
 | 
			
		|||
(ns bbbg.handlers.attendees
 | 
			
		||||
  (:require
 | 
			
		||||
   [bbbg.attendee :as attendee]
 | 
			
		||||
   [bbbg.attendee-check :as attendee-check]
 | 
			
		||||
   [bbbg.db :as db]
 | 
			
		||||
   [bbbg.db.attendee :as db.attendee]
 | 
			
		||||
   [bbbg.db.attendee-check :as db.attendee-check]
 | 
			
		||||
   [bbbg.db.event :as db.event]
 | 
			
		||||
   [bbbg.event :as event]
 | 
			
		||||
   [bbbg.handlers.core :refer [page-response wrap-auth-required]]
 | 
			
		||||
   [bbbg.user :as user]
 | 
			
		||||
   [bbbg.util.display :refer [format-date]]
 | 
			
		||||
   [bbbg.views.flash :as flash]
 | 
			
		||||
   [cheshire.core :as json]
 | 
			
		||||
   [compojure.coercions :refer [as-uuid]]
 | 
			
		||||
   [compojure.core :refer [GET POST routes]]
 | 
			
		||||
   [honeysql.helpers :refer [merge-where]]
 | 
			
		||||
   [ring.util.response :refer [content-type redirect response not-found]])
 | 
			
		||||
  (:import java.util.UUID))
 | 
			
		||||
   [ring.util.response :refer [content-type not-found redirect response]])
 | 
			
		||||
  (:import
 | 
			
		||||
   java.util.UUID))
 | 
			
		||||
 | 
			
		||||
(defn- attendees-page [{:keys [attendees q edit-notes]}]
 | 
			
		||||
  [:div
 | 
			
		||||
| 
						 | 
				
			
			@ -30,6 +35,7 @@
 | 
			
		|||
      [:th "Events RSVPd"]
 | 
			
		||||
      [:th "Events Attended"]
 | 
			
		||||
      [:th "No-Shows"]
 | 
			
		||||
      [:th "Last Vaccination Check"]
 | 
			
		||||
      [:th "Notes"]]]
 | 
			
		||||
    [:tbody
 | 
			
		||||
     (for [attendee attendees
 | 
			
		||||
| 
						 | 
				
			
			@ -40,6 +46,15 @@
 | 
			
		|||
        [:td (:events-rsvpd attendee)]
 | 
			
		||||
        [:td (:events-attended attendee)]
 | 
			
		||||
        [:td (:no-shows attendee)]
 | 
			
		||||
        (if-let [last-check (:last-check attendee)]
 | 
			
		||||
          [:td (str (-> last-check
 | 
			
		||||
                        ::attendee-check/checked-at
 | 
			
		||||
                        format-date)
 | 
			
		||||
                    ", by "
 | 
			
		||||
                    (get-in last-check [:user ::user/username]))]
 | 
			
		||||
          [:td "Not Checked"
 | 
			
		||||
           [:a {:href (str "/attendees/" id "/checks/edit")}
 | 
			
		||||
            "Edit"]])
 | 
			
		||||
        (if (= edit-notes id)
 | 
			
		||||
          [:td
 | 
			
		||||
           [:form.organizer-notes {:method :post
 | 
			
		||||
| 
						 | 
				
			
			@ -59,6 +74,9 @@
 | 
			
		|||
     (GET "/attendees" [q edit-notes]
 | 
			
		||||
       (let [attendees (db/list db (cond-> (db.attendee/with-stats)
 | 
			
		||||
                                     q (db.attendee/search q)))
 | 
			
		||||
             attendees (db.attendee-check/attendees-with-last-checks
 | 
			
		||||
                        db
 | 
			
		||||
                        attendees)
 | 
			
		||||
             edit-notes (some-> edit-notes UUID/fromString)]
 | 
			
		||||
         (page-response (attendees-page {:attendees attendees
 | 
			
		||||
                                         :q q
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										17
									
								
								users/grfn/bbbg/src/bbbg/util/display.clj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								users/grfn/bbbg/src/bbbg/util/display.clj
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
(ns bbbg.util.display
 | 
			
		||||
  (:require
 | 
			
		||||
   [bbbg.util.time :as t])
 | 
			
		||||
  (:import
 | 
			
		||||
   [java.time.format DateTimeFormatter FormatStyle]))
 | 
			
		||||
 | 
			
		||||
(defn format-date
 | 
			
		||||
  ([d] (format-date d FormatStyle/MEDIUM))
 | 
			
		||||
  ([d ^FormatStyle format-style]
 | 
			
		||||
   (let [formatter (DateTimeFormatter/ofLocalizedDate format-style)]
 | 
			
		||||
     (.format (t/->LocalDate d) formatter))))
 | 
			
		||||
 | 
			
		||||
(comment
 | 
			
		||||
  (format-date #inst "2021-12-19T05:00:00.000-00:00")
 | 
			
		||||
  (format-date #inst "2021-12-19T05:00:00.000-00:00"
 | 
			
		||||
               FormatStyle/SHORT)
 | 
			
		||||
  )
 | 
			
		||||
							
								
								
									
										149
									
								
								users/grfn/bbbg/src/bbbg/util/time.clj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								users/grfn/bbbg/src/bbbg/util/time.clj
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,149 @@
 | 
			
		|||
(ns bbbg.util.time
 | 
			
		||||
  "Utilities for dealing with date/time"
 | 
			
		||||
  (:require [clojure.spec.alpha :as s]
 | 
			
		||||
            [clojure.test.check.generators :as gen]
 | 
			
		||||
            [java-time :as jt])
 | 
			
		||||
  (:import [java.time
 | 
			
		||||
            LocalDateTime LocalTime OffsetDateTime ZoneId ZoneOffset
 | 
			
		||||
            LocalDate Year]
 | 
			
		||||
           [java.time.format DateTimeFormatter DateTimeParseException]
 | 
			
		||||
           java.util.Calendar
 | 
			
		||||
           org.apache.commons.lang3.time.DurationFormatUtils))
 | 
			
		||||
 | 
			
		||||
(set! *warn-on-reflection* true)
 | 
			
		||||
 | 
			
		||||
(defprotocol ToOffsetDateTime
 | 
			
		||||
  (->OffsetDateTime [this]
 | 
			
		||||
    "Coerces its argument to a `java.time.OffsetDateTime`"))
 | 
			
		||||
 | 
			
		||||
(extend-protocol ToOffsetDateTime
 | 
			
		||||
  OffsetDateTime
 | 
			
		||||
  (->OffsetDateTime [odt] odt)
 | 
			
		||||
 | 
			
		||||
  java.util.Date
 | 
			
		||||
  (->OffsetDateTime [d]
 | 
			
		||||
    (-> d
 | 
			
		||||
        .toInstant
 | 
			
		||||
        (OffsetDateTime/ofInstant (ZoneId/of "UTC")))))
 | 
			
		||||
 | 
			
		||||
(defprotocol ToLocalTime (->LocalTime [this]))
 | 
			
		||||
(extend-protocol ToLocalTime
 | 
			
		||||
  LocalTime
 | 
			
		||||
  (->LocalTime [lt] lt)
 | 
			
		||||
 | 
			
		||||
  java.sql.Time
 | 
			
		||||
  (->LocalTime [t]
 | 
			
		||||
    (let [^Calendar cal (doto (Calendar/getInstance)
 | 
			
		||||
                          (.setTime t))]
 | 
			
		||||
      (LocalTime/of
 | 
			
		||||
       (.get cal Calendar/HOUR_OF_DAY)
 | 
			
		||||
       (.get cal Calendar/MINUTE)
 | 
			
		||||
       (.get cal Calendar/SECOND))))
 | 
			
		||||
 | 
			
		||||
  java.util.Date
 | 
			
		||||
  (->LocalTime [d]
 | 
			
		||||
    (-> d .toInstant (LocalTime/ofInstant (ZoneId/of "UTC")))))
 | 
			
		||||
 | 
			
		||||
(defn local-time? [x] (satisfies? ToLocalTime x))
 | 
			
		||||
(s/def ::local-time
 | 
			
		||||
  (s/with-gen local-time?
 | 
			
		||||
    #(gen/let [hour (gen/choose 0 23)
 | 
			
		||||
               minute (gen/choose 0 59)
 | 
			
		||||
               second (gen/choose 0 59)
 | 
			
		||||
               nanos gen/nat]
 | 
			
		||||
       (LocalTime/of hour minute second nanos))))
 | 
			
		||||
 | 
			
		||||
(defprotocol ToLocalDate (->LocalDate [this]))
 | 
			
		||||
(extend-protocol ToLocalDate
 | 
			
		||||
  LocalDate
 | 
			
		||||
  (->LocalDate [ld] ld)
 | 
			
		||||
 | 
			
		||||
  java.util.Date
 | 
			
		||||
  (->LocalDate [d]
 | 
			
		||||
    (-> d .toInstant (LocalDate/ofInstant (ZoneId/of "UTC")))))
 | 
			
		||||
 | 
			
		||||
(defn local-date? [x] (satisfies? ToLocalDate x))
 | 
			
		||||
(s/def ::local-date
 | 
			
		||||
  (s/with-gen local-date?
 | 
			
		||||
    #(gen/let [year (gen/choose Year/MIN_VALUE Year/MAX_VALUE)
 | 
			
		||||
               day (gen/choose 1 (if (.isLeap (Year/of year))
 | 
			
		||||
                                   366
 | 
			
		||||
                                   365))]
 | 
			
		||||
       (LocalDate/ofYearDay year day))))
 | 
			
		||||
 | 
			
		||||
(extend-protocol Inst
 | 
			
		||||
  OffsetDateTime
 | 
			
		||||
  (inst-ms* [zdt]
 | 
			
		||||
    (inst-ms* (.toInstant zdt)))
 | 
			
		||||
 | 
			
		||||
  LocalDateTime
 | 
			
		||||
  (inst-ms* [^LocalDateTime ldt]
 | 
			
		||||
    (inst-ms* (.toInstant ldt ZoneOffset/UTC))))
 | 
			
		||||
 | 
			
		||||
(let [formatter DateTimeFormatter/ISO_OFFSET_DATE_TIME]
 | 
			
		||||
  (defn ^OffsetDateTime parse-iso-8601
 | 
			
		||||
    "Parse s as an iso-8601 datetime, returning nil if invalid"
 | 
			
		||||
    [^String s]
 | 
			
		||||
    (try
 | 
			
		||||
      (OffsetDateTime/parse s formatter)
 | 
			
		||||
      (catch DateTimeParseException _ nil)))
 | 
			
		||||
 | 
			
		||||
  (defn format-iso-8601
 | 
			
		||||
    "Format dt, which can be an OffsetDateTime or java.util.Date, as iso-8601"
 | 
			
		||||
    [dt]
 | 
			
		||||
    (some->> dt ->OffsetDateTime (.format formatter))))
 | 
			
		||||
 | 
			
		||||
(let [formatter DateTimeFormatter/ISO_TIME]
 | 
			
		||||
  (defn parse-iso-8601-time
 | 
			
		||||
    "Parse s as an iso-8601 timestamp, returning nil if invalid"
 | 
			
		||||
    [^String s]
 | 
			
		||||
    (try
 | 
			
		||||
      (LocalTime/parse s formatter)
 | 
			
		||||
      (catch DateTimeParseException _ nil)))
 | 
			
		||||
 | 
			
		||||
  (defn format-iso-8601-time
 | 
			
		||||
    "Format lt, which can be a LocalTime or java.sql.Time, as an iso-8601
 | 
			
		||||
    formatted timestamp without a date."
 | 
			
		||||
    [lt]
 | 
			
		||||
    (some->> lt ->LocalTime (.format formatter))))
 | 
			
		||||
 | 
			
		||||
(defmethod print-dup LocalTime [t w]
 | 
			
		||||
  (binding [*out* w]
 | 
			
		||||
    (print "#local-time ")
 | 
			
		||||
    (print (str "\"" (format-iso-8601-time t) "\""))))
 | 
			
		||||
 | 
			
		||||
(defmethod print-method LocalTime [t w]
 | 
			
		||||
  (print-dup t w))
 | 
			
		||||
 | 
			
		||||
(let [formatter DateTimeFormatter/ISO_LOCAL_DATE]
 | 
			
		||||
  (defn parse-iso-8601-date
 | 
			
		||||
    "Parse s as an iso-8601 date, returning nil if invalid"
 | 
			
		||||
    [^String s]
 | 
			
		||||
    (try
 | 
			
		||||
      (LocalDate/parse s formatter)
 | 
			
		||||
      (catch DateTimeParseException _ nil)))
 | 
			
		||||
 | 
			
		||||
  (defn format-iso-8601-date
 | 
			
		||||
    "Format lt, which can be a LocalDate, as an iso-8601 formatted date without
 | 
			
		||||
    a timestamp."
 | 
			
		||||
    [lt]
 | 
			
		||||
    (some->> lt ->LocalDate (.format formatter))))
 | 
			
		||||
 | 
			
		||||
(defmethod print-dup LocalDate [t w]
 | 
			
		||||
  (binding [*out* w]
 | 
			
		||||
    (print "#local-date ")
 | 
			
		||||
    (print (str "\"" (format-iso-8601-date t) "\""))))
 | 
			
		||||
 | 
			
		||||
(defmethod print-method LocalDate [t w]
 | 
			
		||||
  (print-dup t w))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
(defn ^String human-format-duration
 | 
			
		||||
  "Human-format the given duration"
 | 
			
		||||
  [^java.time.Duration dur]
 | 
			
		||||
  (DurationFormatUtils/formatDurationWords (Math/abs (.toMillis dur)) true true))
 | 
			
		||||
 | 
			
		||||
(comment
 | 
			
		||||
  (human-format-duration (jt/hours 5))
 | 
			
		||||
  (human-format-duration (jt/plus (jt/hours 5) (jt/minutes 7)))
 | 
			
		||||
  )
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
(ns bbbg.web
 | 
			
		||||
  (:require
 | 
			
		||||
   [bbbg.discord.auth :as discord.auth :refer [wrap-discord-auth]]
 | 
			
		||||
   [bbbg.handlers.attendee-checks :as attendee-checks]
 | 
			
		||||
   [bbbg.handlers.attendees :as attendees]
 | 
			
		||||
   [bbbg.handlers.events :as events]
 | 
			
		||||
   [bbbg.handlers.home :as home]
 | 
			
		||||
| 
						 | 
				
			
			@ -74,6 +75,7 @@
 | 
			
		|||
         (content-type "text/javascript")))
 | 
			
		||||
 | 
			
		||||
   (attendees/attendees-routes env)
 | 
			
		||||
   (attendee-checks/attendee-checks-routes env)
 | 
			
		||||
   (signup-form/signup-form-routes env)
 | 
			
		||||
   (events/events-routes env)
 | 
			
		||||
   (home/home-routes env)))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue