about summary refs log tree commit diff
path: root/users/grfn
diff options
context:
space:
mode:
authorGriffin Smith <grfn@gws.fyi>2021-12-14T02·28-0500
committerGriffin Smith <grfn@gws.fyi>2021-12-14T02·45-0500
commitc3cb7b0df82479016c252ef45a302f566bd569f6 (patch)
tree2f60fac21680a379950c7d34c2e12304e10cc115 /users/grfn
parent479e9ea279a157d81964a9b8cc97423b484921e6 (diff)
feat(grfn/bbbg): Init r/3233
This will eventually become a signup sheet + no-show tracker for my
local board game meetup group

Change-Id: Id8d1d80d95d1e2fda5041275cff2fecfd6fa43f1
Diffstat (limited to 'users/grfn')
-rw-r--r--users/grfn/bbbg/.clj-kondo/config.edn1
-rw-r--r--users/grfn/bbbg/.envrc1
-rw-r--r--users/grfn/bbbg/.gitignore9
-rw-r--r--users/grfn/bbbg/Makefile2
-rw-r--r--users/grfn/bbbg/arion-compose.nix15
-rw-r--r--users/grfn/bbbg/arion-pkgs.nix2
-rw-r--r--users/grfn/bbbg/default.nix78
-rw-r--r--users/grfn/bbbg/deps.edn63
-rw-r--r--users/grfn/bbbg/deps.nix1255
-rw-r--r--users/grfn/bbbg/env/dev/bbbg-signup/env.clj3
-rw-r--r--users/grfn/bbbg/env/dev/logback.xml15
-rw-r--r--users/grfn/bbbg/env/prod/bbbg-signup/env.clj3
-rw-r--r--users/grfn/bbbg/env/prod/logback.xml31
-rw-r--r--users/grfn/bbbg/env/test/bbbg-signup/env.clj3
-rw-r--r--users/grfn/bbbg/env/test/logback.xml11
-rw-r--r--users/grfn/bbbg/pom.xml42
-rw-r--r--users/grfn/bbbg/resources/main.js49
-rw-r--r--users/grfn/bbbg/resources/migrations/20211212165646-init-schema.down.sql14
-rw-r--r--users/grfn/bbbg/resources/migrations/20211212165646-init-schema.up.sql31
-rw-r--r--users/grfn/bbbg/shell.nix20
-rw-r--r--users/grfn/bbbg/src/bbbg/attendee.clj4
-rw-r--r--users/grfn/bbbg/src/bbbg/core.clj58
-rw-r--r--users/grfn/bbbg/src/bbbg/db.clj357
-rw-r--r--users/grfn/bbbg/src/bbbg/db/attendee.clj29
-rw-r--r--users/grfn/bbbg/src/bbbg/db/event.clj50
-rw-r--r--users/grfn/bbbg/src/bbbg/event.clj4
-rw-r--r--users/grfn/bbbg/src/bbbg/event_attendee.clj4
-rw-r--r--users/grfn/bbbg/src/bbbg/handlers/attendees.clj40
-rw-r--r--users/grfn/bbbg/src/bbbg/handlers/core.clj34
-rw-r--r--users/grfn/bbbg/src/bbbg/handlers/events.clj44
-rw-r--r--users/grfn/bbbg/src/bbbg/handlers/home.clj17
-rw-r--r--users/grfn/bbbg/src/bbbg/handlers/signup_form.clj57
-rw-r--r--users/grfn/bbbg/src/bbbg/styles.clj9
-rw-r--r--users/grfn/bbbg/src/bbbg/util/core.clj117
-rw-r--r--users/grfn/bbbg/src/bbbg/web.clj77
35 files changed, 2549 insertions, 0 deletions
diff --git a/users/grfn/bbbg/.clj-kondo/config.edn b/users/grfn/bbbg/.clj-kondo/config.edn
new file mode 100644
index 0000000000..8faddb77ec
--- /dev/null
+++ b/users/grfn/bbbg/.clj-kondo/config.edn
@@ -0,0 +1 @@
+{:lint-as {garden.def/defstyles clojure.core/def}}
diff --git a/users/grfn/bbbg/.envrc b/users/grfn/bbbg/.envrc
new file mode 100644
index 0000000000..051d09d292
--- /dev/null
+++ b/users/grfn/bbbg/.envrc
@@ -0,0 +1 @@
+eval "$(lorri direnv)"
diff --git a/users/grfn/bbbg/.gitignore b/users/grfn/bbbg/.gitignore
new file mode 100644
index 0000000000..99dbfc4436
--- /dev/null
+++ b/users/grfn/bbbg/.gitignore
@@ -0,0 +1,9 @@
+/target
+/classes
+*.jar
+*.class
+/.nrepl-port
+/.cpcache
+/.clojure
+/result
+/.clj-kondo/.cache
diff --git a/users/grfn/bbbg/Makefile b/users/grfn/bbbg/Makefile
new file mode 100644
index 0000000000..fc45477984
--- /dev/null
+++ b/users/grfn/bbbg/Makefile
@@ -0,0 +1,2 @@
+deps.nix: deps.edn
+	clj2nix ./deps.edn ./deps.nix '-A:uberjar' '-A:clj-test'
diff --git a/users/grfn/bbbg/arion-compose.nix b/users/grfn/bbbg/arion-compose.nix
new file mode 100644
index 0000000000..c8a6dd156d
--- /dev/null
+++ b/users/grfn/bbbg/arion-compose.nix
@@ -0,0 +1,15 @@
+{ ... }:
+
+{
+  services = {
+    postgres.service = {
+      image = "postgres:12";
+      environment = {
+        POSTGRES_DB = "bbbg";
+        POSTGRES_USER = "bbbg";
+        POSTGRES_PASSWORD = "password";
+      };
+      ports = [ "5432:5432" ];
+    };
+  };
+}
diff --git a/users/grfn/bbbg/arion-pkgs.nix b/users/grfn/bbbg/arion-pkgs.nix
new file mode 100644
index 0000000000..66c016c283
--- /dev/null
+++ b/users/grfn/bbbg/arion-pkgs.nix
@@ -0,0 +1,2 @@
+let depot = import ../../.. {};
+in depot.third_party.nixpkgs
diff --git a/users/grfn/bbbg/default.nix b/users/grfn/bbbg/default.nix
new file mode 100644
index 0000000000..90f112bf28
--- /dev/null
+++ b/users/grfn/bbbg/default.nix
@@ -0,0 +1,78 @@
+{ depot, pkgs, ... }:
+
+with pkgs.lib;
+
+let
+  inherit (depot.third_party) gitignoreSource;
+
+  deps = import ./deps.nix {
+    inherit (pkgs) fetchMavenArtifact fetchgit lib;
+  };
+in rec {
+  meta.targets = [
+    "db-util"
+    "server"
+  ];
+
+  depsPaths = deps.makePaths {};
+
+  resources = builtins.filterSource (_: type: type != "symlink") ./resources;
+
+  classpath.dev = concatStringsSep ":" (
+    (map gitignoreSource [./src ./test ./env/dev]) ++ [resources] ++ depsPaths
+  );
+
+  classpath.test = concatStringsSep ":" (
+    (map gitignoreSource [./src ./test ./env/test]) ++ [resources] ++ depsPaths
+  );
+
+  classpath.prod = concatStringsSep ":" (
+    (map gitignoreSource [./src ./env/prod]) ++ [resources] ++ depsPaths
+  );
+
+  testClojure = pkgs.writeShellScript "test-clojure" ''
+    export HOME=$(pwd)
+    ${pkgs.clojure}/bin/clojure -Scp ${depsPaths}
+  '';
+
+  mkJar = name: opts:
+    with pkgs;
+    assert (hasSuffix ".jar" name);
+    stdenv.mkDerivation rec {
+      inherit name;
+      dontUnpack = true;
+      buildPhase = ''
+        export HOME=$(pwd)
+        cp ${./pom.xml} pom.xml
+        cp ${./deps.edn} deps.edn
+        ${clojure}/bin/clojure \
+          -Scp ${classpath.prod} \
+          -A:uberjar \
+          ${name} \
+          -C ${opts}
+      '';
+
+      doCheck = true;
+
+      checkPhase = ''
+        echo "checking for existence of ${name}"
+        [ -f ${name} ]
+      '';
+
+      installPhase = ''
+        cp ${name} $out
+      '';
+    };
+
+  db-util-jar = mkJar "bbbg-db-util.jar" "-m bbbg.db";
+
+  db-util = pkgs.writeShellScriptBin "bbbg-db-util" ''
+    exec ${pkgs.openjdk17_headless}/bin/java -jar ${db-util-jar} "$@"
+  '';
+
+  server-jar = mkJar "bbbg-server.jar" "-m bbbg.core";
+
+  server = pkgs.writeShellScriptBin "bbbg-server" ''
+    exec ${pkgs.openjdk17_headless}/bin/java -jar ${server-jar} "$@"
+  '';
+}
diff --git a/users/grfn/bbbg/deps.edn b/users/grfn/bbbg/deps.edn
new file mode 100644
index 0000000000..1028771457
--- /dev/null
+++ b/users/grfn/bbbg/deps.edn
@@ -0,0 +1,63 @@
+{:deps
+ {org.clojure/clojure {:mvn/version "1.11.0-alpha3"}
+
+  ;; DB
+  com.github.seancorfield/next.jdbc {:mvn/version "1.2.753"}
+  com.impossibl.pgjdbc-ng/pgjdbc-ng {:mvn/version "0.8.4"}
+  com.zaxxer/HikariCP {:mvn/version "5.0.0"}
+  migratus/migratus {:mvn/version "1.3.5"}
+  com.github.seancorfield/honeysql {:mvn/version "2.1.833"}
+  nilenso/honeysql-postgres {:mvn/version "0.4.112"}
+
+  ;; HTTP
+  http-kit/http-kit {:mvn/version "2.5.3"}
+  ring/ring {:mvn/version "1.9.4"}
+  compojure/compojure {:mvn/version "1.6.2"}
+  javax.servlet/servlet-api {:mvn/version "2.5"}
+
+  ;; Web
+  hiccup/hiccup {:mvn/version "1.0.5"}
+  garden/garden {:mvn/version "1.3.10"}
+
+  ;; Utils
+  com.stuartsierra/component {:mvn/version "1.0.0"}
+
+  ;; Logging + Observability
+  ch.qos.logback/logback-classic {:mvn/version "1.2.3"
+                                  :exclusions [org.slf4j/slf4j-api]}
+  org.slf4j/jul-to-slf4j {:mvn/version "1.7.30"}
+  org.slf4j/jcl-over-slf4j {:mvn/version "1.7.30"}
+  org.slf4j/log4j-over-slf4j {:mvn/version "1.7.30"}
+  cambium/cambium.core {:mvn/version "0.9.3"}
+  cambium/cambium.codec-cheshire {:mvn/version "0.9.3"}
+  cambium/cambium.logback.core {:mvn/version "0.4.3"}
+  cambium/cambium.logback.json {:mvn/version "0.4.3"}
+  clj-commons/iapetos {:mvn/version "0.1.12"}
+
+  ;; Utilities
+  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"}
+
+  ;; 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"}}
+
+ :paths
+ ["src"
+  "test"
+  "resources"
+  "target/classes"]
+ :aliases
+ {:dev {:extra-paths ["env/dev"]
+        :jvm-opts ["-XX:-OmitStackTraceInFastThrow"]}
+  :clj-test {:extra-paths ["test" "env/test"]
+             :extra-deps {io.github.cognitect-labs/test-runner
+                          {:git/url "https://github.com/cognitect-labs/test-runner"
+                           :sha "cc75980b43011773162b485f46f939dc5fba91e4"}}
+             :main-opts ["-m" "cognitect.test-runner"
+                         "-d" "test"]}
+  :uberjar {:extra-deps {seancorfield/depstar {:mvn/version "1.0.94"}}
+            :extra-paths ["env/prod"]
+            :main-opts ["-m" "hf.depstar.uberjar"]}}}
diff --git a/users/grfn/bbbg/deps.nix b/users/grfn/bbbg/deps.nix
new file mode 100644
index 0000000000..e1c17f5609
--- /dev/null
+++ b/users/grfn/bbbg/deps.nix
@@ -0,0 +1,1255 @@
+# generated by clj2nix-1.1.0-rc
+{ fetchMavenArtifact, fetchgit, lib }:
+
+let repos = [
+        "https://repo1.maven.org/maven2/"
+        "https://repo.clojars.org/" ];
+
+  in rec {
+      makePaths = {extraClasspaths ? []}:
+        if (builtins.typeOf extraClasspaths != "list")
+        then builtins.throw "extraClasspaths must be of type 'list'!"
+        else (lib.concatMap (dep:
+          builtins.map (path:
+            if builtins.isString path then
+              path
+            else if builtins.hasAttr "jar" path then
+              path.jar
+            else if builtins.hasAttr "outPath" path then
+              path.outPath
+            else
+              path
+            )
+          dep.paths)
+        packages) ++ extraClasspaths;
+      makeClasspaths = {extraClasspaths ? []}:
+       if (builtins.typeOf extraClasspaths != "list")
+       then builtins.throw "extraClasspaths must be of type 'list'!"
+       else builtins.concatStringsSep ":" (makePaths {inherit extraClasspaths;});
+      packageSources = builtins.map (dep: dep.src) packages;
+      packages = [
+  rec {
+    name = "cambium.logback.json/cambium";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "cambium.logback.json";
+      groupId = "cambium";
+      sha512 = "a19e990652749f02a258da3ca30fee126e324a2c6969667fcdbfd7e0b313997d729efb83ccf54a500e7af7476b4bbe0e511b37540fb6f7bc9f117d2db8c4fe99";
+      version = "0.4.3";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "clojure/org.clojure";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "clojure";
+      groupId = "org.clojure";
+      sha512 = "a242514f623a17601b360886563c4a4fe09335e4e16522ac42bbcacda073ae77651cfed446daae7fe74061bb7dff5adc454769c0edc0ded350136c3c707e75b9";
+      version = "1.11.0-alpha3";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "commons-codec/commons-codec";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "commons-codec";
+      groupId = "commons-codec";
+      sha512 = "da30a716770795fce390e4dd340a8b728f220c6572383ffef55bd5839655d5611fcc06128b2144f6cdcb36f53072a12ec80b04afee787665e7ad0b6e888a6787";
+      version = "1.15";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "HikariCP/com.zaxxer";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "HikariCP";
+      groupId = "com.zaxxer";
+      sha512 = "a41b6d8b1c4656e633459824f10320965976eeead01bd5cb24911040073181730e61feb797aef89d9e01c922e89cb58654f364df0a6b1bf62ab3e6f9cc367d77";
+      version = "5.0.0";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "ring-devel/ring";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "ring-devel";
+      groupId = "ring";
+      sha512 = "79a1ec9f9d03aa4fa0426353970b13468ee65ce314b51ab7a2682212a196a9b5c985eacdee5dbc6ff2f1b536a4e06d0e85e9dd7cc9a49958735c9c4e6d427fd5";
+      version = "1.9.4";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "simpleclient/io.prometheus";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "simpleclient";
+      groupId = "io.prometheus";
+      sha512 = "60af1cefff04e7036467eae54f5930d5677e4ab066f8ed38a391b54df17733acfefac45e19ee53cef289347bddce5fc69a2766f4e580d21a22cfd9e2348e2723";
+      version = "0.12.0";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "tools.logging/org.clojure";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "tools.logging";
+      groupId = "org.clojure";
+      sha512 = "d846b3032f5350af7e8847e94dea223ef8a644d353e45d6ec44c2ffa1f0df96b90cfe7afc0610cd0a13b52527ff2fe71003fbd5cbc2dd60320a1be5979b62e80";
+      version = "1.1.0";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "core.specs.alpha/org.clojure";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "core.specs.alpha";
+      groupId = "org.clojure";
+      sha512 = "f521f95b362a47bb35f7c85528c34537f905fb3dd24f2284201e445635a0df701b35d8419d53c6507cc78d3717c1f83cda35ea4c82abd8943cd2ab3de3fcad70";
+      version = "0.2.62";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "netty-common/io.netty";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "netty-common";
+      groupId = "io.netty";
+      sha512 = "1c0baeff9f2cc687612aca4d4e4ca671eae99ac34bd91f22b1c550e5cc6ab98984bef059923248004ad92013a79503a59a89a1779ae2499116bc35f6ef7db634";
+      version = "4.1.42.Final";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "jackson-databind/com.fasterxml.jackson.core";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "jackson-databind";
+      groupId = "com.fasterxml.jackson.core";
+      sha512 = "9f771e78af669b1e1683d6c5903bbf4790aaa88b6b420c2018437da318c3fa4220cd7fa726f3e42a1b8075def1fdbd3744937c15f3bcedfca3050199247363e8";
+      version = "2.12.4";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "expound/expound";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "expound";
+      groupId = "expound";
+      sha512 = "ca0a57cfd215cff6be36d1f83461ec2d0559c0eae172c8a8bd6e1676d49933d3c30a71192889bd75d813581707d5eda0ec05de03326396bc0cedebf2d71811e5";
+      version = "0.8.10";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "spec.alpha/org.clojure";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "spec.alpha";
+      groupId = "org.clojure";
+      sha512 = "0bc22505d8aadb8eda3c46af88ef2f572494b553a68333e4d9787714d5ad383c2a21c56e1c81ce47aa2e07d7d9b268236eb57edc5adf6394250f30ce2fad087d";
+      version = "0.3.214";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "tools.cli/org.clojure";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "tools.cli";
+      groupId = "org.clojure";
+      sha512 = "1d88aa03eb6a664bf2c0ce22c45e7296d54d716e29b11904115be80ea1661623cf3e81fc222d164047058239010eb678af92ffedc7c3006475cceb59f3b21265";
+      version = "1.0.206";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "compojure/compojure";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "compojure";
+      groupId = "compojure";
+      sha512 = "1f4ba1354bd95772963a4ef0e129dde59d16f4f9fac0f89f2505a1d5de3b4527e45073219c0478e0b3285da46793e7c145ec5a55a9dae2fca6b77dc8d67b4db6";
+      version = "1.6.2";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "commons-fileupload/commons-fileupload";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "commons-fileupload";
+      groupId = "commons-fileupload";
+      sha512 = "a8780b7dd7ab68f9e1df38e77a5207c45ff50ec53d8b1476570d069edc8f59e52fb1d0fc534d7e513ac5a01b385ba73c320794c82369a72bd6d817a3b3b21f39";
+      version = "1.4";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "jetty-http/org.eclipse.jetty";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "jetty-http";
+      groupId = "org.eclipse.jetty";
+      sha512 = "60422ff3ef311f1d9d7340c2accdf611d40e738a39e9128967175ede4990439f4725995988849957742d488f749dd2e0740f74dc5bd9b3364e32fbaa66689308";
+      version = "9.4.42.v20210604";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "jetty-util/org.eclipse.jetty";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "jetty-util";
+      groupId = "org.eclipse.jetty";
+      sha512 = "d69084e2cfe0c3af1dc7ee2745d563549a4068b6e8aed5cd2b9f31167168fb64d418c4134a6dfb811b627ec0051d7ff71e0a02e4e775d18a53543d0871c44730";
+      version = "9.4.42.v20210604";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "janino/org.codehaus.janino";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "janino";
+      groupId = "org.codehaus.janino";
+      sha512 = "e0bbae3511a2b9cc134afff4fa6c38b6cf0fc1761f233e842df78ba9e25b26f6932e8ee590c7567c0ee86b91472c6b95c49547c034cda98d862e2f00916cd98a";
+      version = "3.0.12";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "jcl-over-slf4j/org.slf4j";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "jcl-over-slf4j";
+      groupId = "org.slf4j";
+      sha512 = "aed539305114718bd76da54f29a391362eb331108d9c033956bf68df29c62a44874c73ae39d19a25342625b6092326d39b4d8720aed6c62234a234e0855b00b5";
+      version = "1.7.30";
+      
+    };
+    paths = [ src ];
+  }
+
+  (rec {
+    name = "io.github.cognitect-labs/test-runner";
+    src = fetchgit {
+      name = "test-runner";
+      url = "https://github.com/cognitect-labs/test-runner";
+      rev = "cc75980b43011773162b485f46f939dc5fba91e4";
+      sha256 = "1661ddmmqva1yiz9p09i5l32lfpi0a99h56022zgvz03nca2ksbg";
+    };
+    paths = map (path: src + path) [
+      "/src"
+    ];
+  })
+
+  rec {
+    name = "cambium.logback.core/cambium";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "cambium.logback.core";
+      groupId = "cambium";
+      sha512 = "b7b5eda0a455e36e3ba2b1cb978f0c87a3db10f7ff504437f22ca92af26ac762b1e5943b8d748e1a623036bfdad8da70e718f7a4c9e5198c31a4d27954444a19";
+      version = "0.4.3";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "logback-jackson/ch.qos.logback.contrib";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "logback-jackson";
+      groupId = "ch.qos.logback.contrib";
+      sha512 = "d9a3d4cb6cf4eda6fc18e2d374007d27c6ddba98e989a8d8a01b49859b280450113f685df6e16c5fbe0472bc9e26308bc7e8b7e0aedab9404cf0b492d7511685";
+      version = "0.1.5";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "simpleclient_tracer_otel/io.prometheus";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "simpleclient_tracer_otel";
+      groupId = "io.prometheus";
+      sha512 = "bce192e6162cb3ada7dd6c2d10456e78bce71c170faa09bad2896272fa1bd4a036288d707f3d47747991d8946c74fe21c565713fb15c7052305eb753c94dd939";
+      version = "0.12.0";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "netty-codec/io.netty";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "netty-codec";
+      groupId = "io.netty";
+      sha512 = "74e04cf1a78f0066fc96b43f87562f231def49300665b679208314c2df96889835ba71f2dcd90c43fe88bf51583aaa6c868bde90cab8419e0f51702c0cadfc23";
+      version = "4.1.42.Final";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "tools.macro/org.clojure";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "tools.macro";
+      groupId = "org.clojure";
+      sha512 = "65ce5e29379620ac458274c53cd9926e4b764fcaebb1a2b3bc8aef86bbe10c79e654b028bc4328905d2495a680fa90f5002cf5c47885f6449fad43a04a594b26";
+      version = "0.1.5";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "jackson-dataformat-cbor/com.fasterxml.jackson.dataformat";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "jackson-dataformat-cbor";
+      groupId = "com.fasterxml.jackson.dataformat";
+      sha512 = "ea5d049eac1b94666479c5e36de14d8fa4b7f24cb92f0f310d2ec2b4de66ef9023161060e67228ef2d7420a002ef861db12a29cad0864638c21612da49686f4f";
+      version = "2.12.4";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "depstar/seancorfield";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "depstar";
+      groupId = "seancorfield";
+      sha512 = "0f4458b39b8b1949755bc2fe64b239673a9efa3a0140998464bbbcab216ec847344c1b8920611f7c9ca07261850f3a08144ae221cc2c41813a080189e32f9c10";
+      version = "1.0.94";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "logback-core/ch.qos.logback";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "logback-core";
+      groupId = "ch.qos.logback";
+      sha512 = "bd1a7512647fe61b90cfd18bedf2a33f3f16f334f8f8ce947cdd353c0b0b7a7cce203070f0d2183f6583e0f2b2fe6e0b12eb93bd5b2dc29076e7b466447f6dc5";
+      version = "1.2.3";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "honeysql/honeysql";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "honeysql";
+      groupId = "honeysql";
+      sha512 = "74d1d93c968b33686848e3bf8934f3b5f002c2b69b1b55a3a3b172c952e9991324e6e95e3a0ce2fecf1de0d3a036f4dff7286df689f0733f253909464e0269f6";
+      version = "1.0.461";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "netty-buffer/io.netty";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "netty-buffer";
+      groupId = "io.netty";
+      sha512 = "931d0933886888c6729617bd6ebae105683dad6d8ad17b8b70d02df0f406660b003533c370b83fb0aac2d0edb4b182adc7fbb1a38d7e325e6c150ba723c17605";
+      version = "4.1.42.Final";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "ring-jetty-adapter/ring";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "ring-jetty-adapter";
+      groupId = "ring";
+      sha512 = "93075903ad73a8b73cb77ee9f53ed33594f40a5dafe8129089adb4cfa333e37468764203c00244568f02abf0c0eee9f5d9a9f96c420919027cf2746a41ec38e3";
+      version = "1.9.4";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "simpleclient_tracer_common/io.prometheus";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "simpleclient_tracer_common";
+      groupId = "io.prometheus";
+      sha512 = "6f717af63340efd84c5467ae752be7e66f586f0e8b57adb5b7a8ef99b223203ed829aad6797f6ef1811d6d861b00a621a1288c9271ec2ba77018d6d9eb9e7987";
+      version = "0.12.0";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "component/com.stuartsierra";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "component";
+      groupId = "com.stuartsierra";
+      sha512 = "108b02f51165ad07c2cf5232fbd954d052880c2456e6fb6db3342bda6851c76b73bf9145f03fb0df2b5782fe39f368b2868780c1e8e2dfa2ab2c68dd97f34ab7";
+      version = "1.0.0";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "netty-handler/io.netty";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "netty-handler";
+      groupId = "io.netty";
+      sha512 = "73c446fef8fb2cc08bc35ce11f37c88a51d9f0b15bc2bb0cda9d51964ac868bcb211eb23c267ba6facf221790bf29633edda04150b37e254edd9439338b951f7";
+      version = "4.1.42.Final";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "yuicompressor/com.yahoo.platform.yui";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "yuicompressor";
+      groupId = "com.yahoo.platform.yui";
+      sha512 = "ba2588bd50eaa3005b1919daad9f9c86a33351ceb9b7b5f0a9a498a548cc523e99f9345917a64303f8e23925feea226386d3eac01f640f788d1be4c7cf0315e0";
+      version = "2.4.8";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "commons-io/commons-io";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "commons-io";
+      groupId = "commons-io";
+      sha512 = "6af22dffaaecd1553147e788b5cf50368582318f396e456fe9ff33f5175836713a5d700e51720465c932c2b1987daa83027358005812d6a95d5755432de3a79d";
+      version = "2.10.0";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "tools.namespace/org.clojure";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "tools.namespace";
+      groupId = "org.clojure";
+      sha512 = "2cdb9c5d9bc4fd01dae182e9ad4b91eeaa2487003a977e7d8d5e66f562a9544b59f558710eccf421ea63cbbfa953ac8944fe9b9a76049fb82a47eb2bdcb3a4d7";
+      version = "1.1.1";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "honeysql/com.github.seancorfield";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "honeysql";
+      groupId = "com.github.seancorfield";
+      sha512 = "0c611a1d14d18226b7796bd3e5c774a58d03af0329b83dd4ca4324f8a3ffc6e8dbab81dd4eadb4c76dbce2c88f29dd7722590ad8b865d57655e5cb0d0122c480";
+      version = "2.1.833";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "jackson-core/com.fasterxml.jackson.core";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "jackson-core";
+      groupId = "com.fasterxml.jackson.core";
+      sha512 = "428e0ebb16dd4c74ab0adf712058fd0dc0cd788f6e6f90c60c627da6577b345fac60a30694e111f1cd4e3e8bf79a1f1b820d30ada114984b26c28e299e326eaa";
+      version = "2.12.4";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "jul-to-slf4j/org.slf4j";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "jul-to-slf4j";
+      groupId = "org.slf4j";
+      sha512 = "82d77af8c4db84d6b35b8138dc73e6d8c5ebbc6907f77295627d8402a5c63aa3fea05bfc0f8982b3a2120615d8edc4fcb947468c1de3dd33cf943690737e652d";
+      version = "1.7.30";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "migratus/migratus";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "migratus";
+      groupId = "migratus";
+      sha512 = "ee5ce8601930d063e0d9d90fc8e165b78fc1587bfd7e0fc9922735bc2f9fc27f8cf8bf10d49d6fd57b899ac4b250145bd653915ed770424416e026ba37d1b604";
+      version = "1.3.5";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "instaparse/instaparse";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "instaparse";
+      groupId = "instaparse";
+      sha512 = "ec2fcf4a09319a8fa9489b08fd9c9a5fe6e63155dde74d096f947fabc4f68d3d1bf68faf21e175e80eaee785f563a1903d30c550f93fb13a16a240609e3dfa2e";
+      version = "1.4.8";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "honeysql-postgres/nilenso";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "honeysql-postgres";
+      groupId = "nilenso";
+      sha512 = "d4accd3b8819cf715ecdb29496cf5a6a5ad3871fd579e55c7148d4e05774cb896c681b0c6f84df88aa9cd8e6ef9bfd65788ede9a49ba365ad0e32ee350091879";
+      version = "0.4.112";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "clj-tuple/clj-tuple";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "clj-tuple";
+      groupId = "clj-tuple";
+      sha512 = "dd626944d0aba679a21b164ed0c77ea84449359361496cba810f83b9fdeab751e5889963888098ce4bf8afa112dbda0a46ed60348a9c01ad36a2e255deb7ab6d";
+      version = "0.2.2";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "jackson-annotations/com.fasterxml.jackson.core";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "jackson-annotations";
+      groupId = "com.fasterxml.jackson.core";
+      sha512 = "6fdad6c5bb71a97331a662fe26265aacab6869f3307a710697d5c2f256fd48935764bfb0b3505a2cbb1605daf0b7350abdf84a1b1cf2bb1e91d9184565243c8e";
+      version = "2.12.4";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "hiccup/hiccup";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "hiccup";
+      groupId = "hiccup";
+      sha512 = "034f15be46c35029f41869c912f82cb2929fbbb0524ea64bd98dcdb9cf09875b28c75e926fa5fff53942b0f9e543e85a73a2d03c3f2112eecae30fcef8b148f4";
+      version = "1.0.5";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "java.classpath/org.clojure";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "java.classpath";
+      groupId = "org.clojure";
+      sha512 = "90cd8edeaea02bd908d8cfb0cf5b1cf901aeb38ea3f4971c4b813d33210438aae6fff8e724a8272d2ea9441d373e7d936fa5870e309c1e9721299f662dbbdb9a";
+      version = "1.0.0";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "simpleclient_pushgateway/io.prometheus";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "simpleclient_pushgateway";
+      groupId = "io.prometheus";
+      sha512 = "31c8878929f516ba7030cc9ec4ac4cbcb09955a9fdae23c6904bc481e40e70e1b3e05619c49b646119077ef6f57c430cc7944f6bafdbca24c9efa8145474fcf7";
+      version = "0.12.0";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "ns-tracker/ns-tracker";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "ns-tracker";
+      groupId = "ns-tracker";
+      sha512 = "cfb6c2c9f899b43d1284acdc572b34b977936c4df734b38137dfea045421b74d529509cde23695f1dc5ee06d046c2f6b61a2cd98058da1c7220c21dd0361964f";
+      version = "0.4.0";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "clout/clout";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "clout";
+      groupId = "clout";
+      sha512 = "99d6e1a8c5726ca4e5d12b280a39e6d1182d734922600f27d588d3d65fbc830c5e03f9e0421ff25c819deee4d1f389fd3906222716ace1eb17ce70ef9c5e8f4b";
+      version = "2.2.1";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "clojure.java-time/clojure.java-time";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "clojure.java-time";
+      groupId = "clojure.java-time";
+      sha512 = "62d8a286ec3393594e7f84eba22dbb02c1305a80a18b2574058ae963d3f3e829ff960c8b66e89069e6c071a11f869203134c6c4cdec6f8e516c9b314796c8108";
+      version = "0.3.3";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "simpleclient_tracer_otel_agent/io.prometheus";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "simpleclient_tracer_otel_agent";
+      groupId = "io.prometheus";
+      sha512 = "97694210d9a5b48a7cb9dda2a187432c4813edb3051edfa5832a0a471e0b2d5988dab92b70c292e78f59b169345deb5c1c706361fd726f3dc2480766dedfdcec";
+      version = "0.12.0";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "next.jdbc/com.github.seancorfield";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "next.jdbc";
+      groupId = "com.github.seancorfield";
+      sha512 = "1bcaa7092d4a082851b3d0d3daed171d8eb76f9b1f485de91d93cf32c617cf9869ab16a134e932f7813782cbc6c1585b15d2f2ae2fa24633a59bebf72a14d60e";
+      version = "1.2.753";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "java.jdbc/org.clojure";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "java.jdbc";
+      groupId = "org.clojure";
+      sha512 = "6162b7774dca58b62a94bc5a04ba845e4c7065c9c589cc3bb802becfec0baf0989a338c1bf9a5db7c3128873702840d5f2451628f3aac977245975d65a683b7d";
+      version = "0.7.11";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "netty-transport/io.netty";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "netty-transport";
+      groupId = "io.netty";
+      sha512 = "21c12294fc82e7032ce25b2f25769a93f75777021fd6e30cc5c450d41cb3ad5c1ea79c02291c9a8b6dd30c40a8388a00bac95983495df01fb1a539138b3b46db";
+      version = "4.1.42.Final";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "crypto-random/crypto-random";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "crypto-random";
+      groupId = "crypto-random";
+      sha512 = "3520df744f250dbe061d1a5d7a05b7143f3a67a4c3f9ad87b8044ee68a36a702a0bcb3a203e35d380899dd01c28e01988b0a7af914b942ccbe0c35c9bdb22e11";
+      version = "1.2.1";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "netty-transport-native-unix-common/io.netty";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "netty-transport-native-unix-common";
+      groupId = "io.netty";
+      sha512 = "62843f4fade55ea84cc831ef4f0cba75b068d15ccf2bccf7abf8813b41d75b593d5d761f35852420f8440e7b41bef631af4394df0b17666007c9ce7476a10bd9";
+      version = "4.1.42.Final";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "ring-codec/ring";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "ring-codec";
+      groupId = "ring";
+      sha512 = "38b9775a794831b8afd8d66991a75aa5910cd50952c9035866bf9cc01353810aedafbc3f35d8f9e56981ebf9e5c37c00b968759ed087d2855348b3f46d8d0487";
+      version = "1.1.3";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "spy/com.impossibl.pgjdbc-ng";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "spy";
+      groupId = "com.impossibl.pgjdbc-ng";
+      sha512 = "68efbf7a80acdaaefebc8e9693d1a1246cdd422472791f663422b4a5f7c6fa953a3030250e7874f97621ffd3bc844a05bdbbede67f852a0c02cca4658cc8a044";
+      version = "0.8.4";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "logback-json-core/ch.qos.logback.contrib";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "logback-json-core";
+      groupId = "ch.qos.logback.contrib";
+      sha512 = "2a826036f21997e2979fda83ae3e33cf62f3b2b2df15a7b11d1fd8a52163b09f0f2f8d72f5fdcea0ec1289b3d27727ed5e6b0bcdf4c5d741f4bac07b7b6139e8";
+      version = "0.1.5";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "crypto-equality/crypto-equality";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "crypto-equality";
+      groupId = "crypto-equality";
+      sha512 = "54cf3bd28f633665962bf6b41f5ccbf2634d0db210a739e10a7b12f635e13c7ef532efe1d5d8c0120bb46478bbd08000b179f4c2dd52123242dab79fea97d6a6";
+      version = "1.0.0";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "cheshire/cheshire";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "cheshire";
+      groupId = "cheshire";
+      sha512 = "855e9c42a8d1c64f4db5cda45e31e914eb5ed99a715e8d7a5759a9c4ab6c69a82353635ca7b0837880c6cf9b41b11184ae11e09cbf2c07aa13db32c539e5dfd4";
+      version = "5.10.1";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "tigris/tigris";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "tigris";
+      groupId = "tigris";
+      sha512 = "fdff4ef5e7175a973aaef98de4f37dee8e125fc711c495382e280aaf3e11341fe8925d52567ca60f3f1795511ade11bc23461c88959632dfae3cf50374d02bf6";
+      version = "0.1.2";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "config/yogthos";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "config";
+      groupId = "yogthos";
+      sha512 = "af0a73e168d24864a97e4698b993ee21a86cecd5c020975257af4f5e6fb2d3741a78eb3072b235a2f601558dd5d165bfc40839c0c154ff50aee6cf38eb0bf7c8";
+      version = "1.1.8";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "jetty-io/org.eclipse.jetty";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "jetty-io";
+      groupId = "org.eclipse.jetty";
+      sha512 = "a8c5f73089daa0c8b27f836acddf40bcbf07bbb2571a4d73653be8aac3fb339022f546326722f216bad78a68886934d24db9bec54235124592dd29dbeab69051";
+      version = "9.4.42.v20210604";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "logback-json-classic/ch.qos.logback.contrib";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "logback-json-classic";
+      groupId = "ch.qos.logback.contrib";
+      sha512 = "d30bf70217d316914d83d46cc15783f656354084087d59cbc0620a746f10b4a42e56d33b3e50a8b3596a64ec8314730bf5ff9a3f7dc3417bdd0582665be009ec";
+      version = "0.1.5";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "tools.reader/org.clojure";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "tools.reader";
+      groupId = "org.clojure";
+      sha512 = "3481259c7a1eac719db2921e60173686726a0c2b65879d51a64d516a37f6120db8ffbb74b8bd273404285d7b25143ab5c7ced37e7c0eaf4ab1e44586ccd3c651";
+      version = "1.3.6";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "simpleclient_common/io.prometheus";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "simpleclient_common";
+      groupId = "io.prometheus";
+      sha512 = "dedd003638eb3651c112e2d697ac94eb4e3b3e32c94fa41bb1efe2c889a347cdc7bd13256e05423f3370592d4fd65faf8db57f0387ab75814d7fa77b14cbbadf";
+      version = "0.12.0";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "commons-compiler/org.codehaus.janino";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "commons-compiler";
+      groupId = "org.codehaus.janino";
+      sha512 = "51fc5542e05d9a310413677855456a3e08f318210d54e24aa209826ed8f4e524a691bcabccab085d091634827fa656b110ab75ee444808e1a5a9b670cd4ff138";
+      version = "3.0.12";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "servlet-api/javax.servlet";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "servlet-api";
+      groupId = "javax.servlet";
+      sha512 = "363ba5590436ab82067b7a2e14b481aeb2b12ca4048d7a1519a2e549b2d3c09ddf718ac64dc2be6c2fc24c51fdc9c8160261329403113369588ce27d87771db6";
+      version = "2.5";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "iapetos/clj-commons";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "iapetos";
+      groupId = "clj-commons";
+      sha512 = "d17f36c0cf0ec78db5e893e5c033f8562b31650bda6f5ee582e68f84a07a3631d04d6f69e4e18b1ca64e732c180fa669dfb69a78849e13f601cd563a7a8aab94";
+      version = "0.1.12";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "javax.servlet-api/javax.servlet";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "javax.servlet-api";
+      groupId = "javax.servlet";
+      sha512 = "32f7e3565c6cdf3d9a562f8fd597fe5059af0cf6b05b772a144a74bbc95927ac275eb38374538ec1c72adcce4c8e1e2c9f774a7b545db56b8085af0065e4a1e5";
+      version = "3.1.0";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "netty-resolver/io.netty";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "netty-resolver";
+      groupId = "io.netty";
+      sha512 = "2ec515e4191b10caa78d34efffba4fbe4bf6c08badff26cd619c591eb514abba1447258181fcf61c59cd912c5d7df65bca99f092235e679ccbe04575bc41584d";
+      version = "4.1.42.Final";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "netty-transport-native-epoll/io.netty";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "netty-transport-native-epoll";
+      groupId = "io.netty";
+      sha512 = "cdcf04b0ba953f98b09737b100394cfc2b755ae334c5897703508ce5ecc6a0c94792040bc21e2af59509a7b61f838a92b64949468c7c6f72fffe5c7574d52a25";
+      version = "4.1.42.Final";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "clj-stacktrace/clj-stacktrace";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "clj-stacktrace";
+      groupId = "clj-stacktrace";
+      sha512 = "993f8a544203801fc074eefacee8e553e426422b3492d47b857d87ac73cde72c91e29f629382b9eae8cf9600bc2c4c29d2e7169e509c46302ab973c86e73af0c";
+      version = "0.2.8";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "cambium.codec-cheshire/cambium";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "cambium.codec-cheshire";
+      groupId = "cambium";
+      sha512 = "33732ebdbc257b7679f705a20d4fbe41860763f67b0d338e9c8eb23839016464cf2fe7c312317ad9b1b47b3fc604fe5039e30172ee73df09faead9aa7ec6dff6";
+      version = "0.9.3";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "slf4j-api/org.slf4j";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "slf4j-api";
+      groupId = "org.slf4j";
+      sha512 = "db7440b47e87215f9cc88c9521bce7a0f2ffa81b07e5f53c5f8afa90a0ab61168755ee6d136245461b57a01f0a857fe0d509c60baa7838c4eb6be1f885438049";
+      version = "2.0.0-alpha1";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "ring-servlet/ring";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "ring-servlet";
+      groupId = "ring";
+      sha512 = "3d8e6ec224e13d54810a945c0b6c0d2d863736a48d8c4bfc8fadb96b6b0fa9baa638644d0d92d8a53650b188e6e75d391731b08b26eb0f551e90a7504e7f4267";
+      version = "1.9.4";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "logback-classic/ch.qos.logback";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "logback-classic";
+      groupId = "ch.qos.logback";
+      sha512 = "9ad5df9055e74c1db67e10422774e740903477c821591702d2709a4c1f73e3fc3fa6b1a871b6985901817bc2bdeba916849035dc2bbf518f308637b0586e36f1";
+      version = "1.2.3";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "dependency/com.stuartsierra";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "dependency";
+      groupId = "com.stuartsierra";
+      sha512 = "d32fbc4813bd16f2ed8c82e2915e1fb564e88422159bd3580a85c8cd969d1bbbe315bdc13d29c2f0eaceeeafcf649ee712c8df4532464d560aaeae4ae5953866";
+      version = "1.0.0";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "camel-snake-kebab/camel-snake-kebab";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "camel-snake-kebab";
+      groupId = "camel-snake-kebab";
+      sha512 = "589d34b500560b7113760a16bfb6f0ccd8f162a1ce8c9bc829495432159ba9c95aebf6bc43aa126237a0525806a205a05f9910122074902b659e7fd151d176b1";
+      version = "0.4.2";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "ring/ring";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "ring";
+      groupId = "ring";
+      sha512 = "93c48fb670736b91fb41d8076e1e9c4f53c67693d15e75290da319e7d7881b829a24180029b3a0fa051473c6c77ac3c97b519254ebf2b2c9538b185e79b69162";
+      version = "1.9.4";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "netty-transport-native-kqueue/io.netty";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "netty-transport-native-kqueue";
+      groupId = "io.netty";
+      sha512 = "054ec02b78cbfe5cee6e96e3d8aeff35b2283f5f08e88456c9946c36c86f2a5b35074d333ad87a3f002a15e5816e3d51fbceb4134a91930dae83fee1ce80e5f4";
+      version = "4.1.42.Final";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "java.data/org.clojure";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "java.data";
+      groupId = "org.clojure";
+      sha512 = "7fea02e1620e44602b806174d3500051a1ce45d8464fe4600dc19dce06e24bafa071cdbb0e4ae858e8dc01eda77f3e01269c1b1e21b42709b432f38828b39f2d";
+      version = "1.0.92";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "jetty-server/org.eclipse.jetty";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "jetty-server";
+      groupId = "org.eclipse.jetty";
+      sha512 = "b347f8a6e5b84e0f460037027e238a61edec710ade768c95e7be13dcea498abe43d5e622ee69ac7494138d1a8fcf92e07b7deab569c554831c57baad71c53b9b";
+      version = "9.4.42.v20210604";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "log4j-over-slf4j/org.slf4j";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "log4j-over-slf4j";
+      groupId = "org.slf4j";
+      sha512 = "c70a080409a2741dd1977318ce78b61f86fc8e02a2120e934e91cc3b8b0f9bb67e0bac3d3753a0e7a195ff1866bead77e3fc51e1ede89bb537a6869ec58a5724";
+      version = "1.7.30";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "ring-core/ring";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "ring-core";
+      groupId = "ring";
+      sha512 = "38d7214a3fc1b80ab55999036638dd1971272e01bec4cb8e0ee0a4aa83f51b8c41ba8a5850b0660227f067d2f9c6d75c0c0737725ea02762bbf8d192dc72febe";
+      version = "1.9.4";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "cambium.core/cambium";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "cambium.core";
+      groupId = "cambium";
+      sha512 = "c355fcd2855f9639155ea8ecf310e42bbe3cac4e9f1b305590284f25ffda25bd2fb76908b39bb947293d7af0fd3beba8b76b55d79c115c1222a5c0944c2ba47a";
+      version = "0.9.3";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "medley/medley";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "medley";
+      groupId = "medley";
+      sha512 = "749ef43b5ea2cae7dc96db871cdd15c7b3c9cfbd96828c20ab08e67d39a5e938357d15994d8d413bc68678285d6c666f2a7296fbf305706d03b3007254e3c55c";
+      version = "1.3.0";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "garden/garden";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "garden";
+      groupId = "garden";
+      sha512 = "2cc29f071b68bf451835f76de351ac2efb930b5df9ca7237fdca439d3c4d797d7fa207a147886efe1738ab1c50b76c1e366bf9ffcd6f286b0b211260aedd0b25";
+      version = "1.3.10";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "jackson-dataformat-smile/com.fasterxml.jackson.dataformat";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "jackson-dataformat-smile";
+      groupId = "com.fasterxml.jackson.dataformat";
+      sha512 = "69676964a2b09516b8ffd0d847b6f9a9b843424185453731b548c25e7e9ce30e808c56d66923f9183e2b5c1ba007421b146a6806e768b8e6b07470d60227f1dd";
+      version = "2.12.4";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "jaxb-api/javax.xml.bind";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "jaxb-api";
+      groupId = "javax.xml.bind";
+      sha512 = "0c5bfc2c9f655bf5e6d596e0c196dcb9344d6dc78bf774207c8f8b6be59f69addf2b3121e81491983eff648dfbd55002b9878132de190825dad3ef3a1265b367";
+      version = "2.3.0";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "pgjdbc-ng/com.impossibl.pgjdbc-ng";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "pgjdbc-ng";
+      groupId = "com.impossibl.pgjdbc-ng";
+      sha512 = "d19ddde1c700972531df24f4ea9326772f74210e5489ee340d81c6f2c6963788d0f4c86aeb869450ef599c741f91154079168e12c26c32f3e926abedc6b9c2a2";
+      version = "0.8.4";
+      
+    };
+    paths = [ src ];
+  }
+
+  rec {
+    name = "http-kit/http-kit";
+    src = fetchMavenArtifact {
+      inherit repos;
+      artifactId = "http-kit";
+      groupId = "http-kit";
+      sha512 = "4186a2429984745e18730aa8fd545f1fc1812083819ebf77aecfc04e0d31585358a5e25a308c7f21d81359418bbc72390c281f5ed91ae116cf1af79860ba22c3";
+      version = "2.5.3";
+      
+    };
+    paths = [ src ];
+  }
+
+  ];
+  }
+  
\ No newline at end of file
diff --git a/users/grfn/bbbg/env/dev/bbbg-signup/env.clj b/users/grfn/bbbg/env/dev/bbbg-signup/env.clj
new file mode 100644
index 0000000000..c30e328ffa
--- /dev/null
+++ b/users/grfn/bbbg/env/dev/bbbg-signup/env.clj
@@ -0,0 +1,3 @@
+(ns bbbg.env)
+
+(def environment :env/dev)
diff --git a/users/grfn/bbbg/env/dev/logback.xml b/users/grfn/bbbg/env/dev/logback.xml
new file mode 100644
index 0000000000..7aa21978bb
--- /dev/null
+++ b/users/grfn/bbbg/env/dev/logback.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+    <encoder>
+      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg { %mdc }%n</pattern>
+    </encoder>
+  </appender>
+
+  <root level="INFO">
+    <appender-ref ref="STDOUT" />
+  </root>
+
+  <logger name="user" level="ALL" />
+  <logger name="ci.windtunnel" level="ALL" />
+</configuration>
diff --git a/users/grfn/bbbg/env/prod/bbbg-signup/env.clj b/users/grfn/bbbg/env/prod/bbbg-signup/env.clj
new file mode 100644
index 0000000000..46e8cd67e3
--- /dev/null
+++ b/users/grfn/bbbg/env/prod/bbbg-signup/env.clj
@@ -0,0 +1,3 @@
+(ns bbbg.env)
+
+(def environment :env/prod)
diff --git a/users/grfn/bbbg/env/prod/logback.xml b/users/grfn/bbbg/env/prod/logback.xml
new file mode 100644
index 0000000000..b81118ed6b
--- /dev/null
+++ b/users/grfn/bbbg/env/prod/logback.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+  <!-- Silence Logback's own status messages about config parsing -->
+  <statusListener class="ch.qos.logback.core.status.NopStatusListener" />
+
+  <!-- Console output -->
+  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+    <!-- Only log level INFO and above -->
+    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+      <level>INFO</level>
+    </filter>
+    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
+      <layout class="cambium.logback.json.FlatJsonLayout">
+        <jsonFormatter class="ch.qos.logback.contrib.jackson.JacksonJsonFormatter">
+          <prettyPrint>false</prettyPrint>
+        </jsonFormatter>
+        <!-- <context>api</context> -->
+        <timestampFormat>yyyy-MM-dd'T'HH:mm:ss.SSS'Z'</timestampFormat>
+        <timestampFormatTimezoneId>UTC</timestampFormatTimezoneId>
+        <appendLineSeparator>true</appendLineSeparator>
+      </layout>
+    </encoder>
+  </appender>
+
+
+  <root level="INFO">
+    <appender-ref ref="STDOUT" />
+  </root>
+
+  <logger name="user" level="ALL" />
+</configuration>
diff --git a/users/grfn/bbbg/env/test/bbbg-signup/env.clj b/users/grfn/bbbg/env/test/bbbg-signup/env.clj
new file mode 100644
index 0000000000..352147a6d0
--- /dev/null
+++ b/users/grfn/bbbg/env/test/bbbg-signup/env.clj
@@ -0,0 +1,3 @@
+(ns bbbg.env)
+
+(def environment :env/test)
diff --git a/users/grfn/bbbg/env/test/logback.xml b/users/grfn/bbbg/env/test/logback.xml
new file mode 100644
index 0000000000..8554f3d0ed
--- /dev/null
+++ b/users/grfn/bbbg/env/test/logback.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+      <pattern>%msg%n</pattern>
+    </encoder>
+  </appender>
+  <root level="OFF">
+    <appender-ref ref="CONSOLE"/>
+  </root>
+</configuration>
diff --git a/users/grfn/bbbg/pom.xml b/users/grfn/bbbg/pom.xml
new file mode 100644
index 0000000000..012c0985f1
--- /dev/null
+++ b/users/grfn/bbbg/pom.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>fyi.gws</groupId>
+  <artifactId>bbbg</artifactId>
+  <version>0.1.0-SNAPSHOT</version>
+  <name>fyi.gws/bbbg</name>
+  <description>webhook listener for per-branch deploys</description>
+  <url>https://bbbg.gws.fyi</url>
+  <developers>
+    <developer>
+      <name>Griffin Smith</name>
+    </developer>
+  </developers>
+  <dependencies>
+    <dependency>
+      <groupId>org.clojure</groupId>
+      <artifactId>clojure</artifactId>
+      <version>1.11.0-alpha3</version>
+    </dependency>
+  </dependencies>
+  <build>
+    <sourceDirectory>src</sourceDirectory>
+  </build>
+  <repositories>
+    <repository>
+      <id>clojars</id>
+      <url>https://repo.clojars.org/</url>
+    </repository>
+    <repository>
+      <id>sonatype</id>
+      <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
+    </repository>
+  </repositories>
+  <distributionManagement>
+    <repository>
+      <id>clojars</id>
+      <name>Clojars repository</name>
+      <url>https://clojars.org/repo</url>
+    </repository>
+  </distributionManagement>
+</project>
diff --git a/users/grfn/bbbg/resources/main.js b/users/grfn/bbbg/resources/main.js
new file mode 100644
index 0000000000..d4752f1141
--- /dev/null
+++ b/users/grfn/bbbg/resources/main.js
@@ -0,0 +1,49 @@
+window.onload = () => {
+  console.log("loaded");
+  const input = document.getElementById("name-autocomplete");
+  if (input != null) {
+    const eventID = document.getElementById("event-id").value;
+
+    const autocomplete = new autoComplete({
+      selector: "#name-autocomplete",
+      placeHolder: "Enter your name",
+      data: {
+        src: async (query) => {
+          const resp = await fetch(
+            `/attendees.json?q=${query}&event_id=${eventID}&attended=false`
+          );
+          console.log("got resp");
+          const { results } = await resp.json();
+          return results;
+        },
+        keys: ["bbbg.attendee/meetup-name"],
+      },
+      resultItem: {
+        highlight: {
+          render: true,
+        },
+      },
+    });
+
+    input.addEventListener("selection", function (event) {
+      const attendee = event.detail.selection.value;
+      event.target.value = attendee["bbbg.attendee/meetup-name"];
+
+      const attendeeID = attendee["bbbg.attendee/id"];
+      document.getElementById("attendee-id").value = attendeeID;
+      document.getElementById("signup-form").removeAttribute("disabled");
+      document
+        .getElementById("signup-form")
+        .querySelector('input[type="submit"]')
+        .removeAttribute("disabled");
+    });
+  }
+
+  document.querySelectorAll("form").forEach((form) => {
+    form.onsubmit = (e) => {
+      if (e.target.attributes.disabled) {
+        e.preventDefault();
+      }
+    };
+  });
+};
diff --git a/users/grfn/bbbg/resources/migrations/20211212165646-init-schema.down.sql b/users/grfn/bbbg/resources/migrations/20211212165646-init-schema.down.sql
new file mode 100644
index 0000000000..69b818a4f4
--- /dev/null
+++ b/users/grfn/bbbg/resources/migrations/20211212165646-init-schema.down.sql
@@ -0,0 +1,14 @@
+drop table "public"."user";
+
+-- ;;
+
+drop table "public"."event_attendee";
+
+
+-- ;;
+
+drop table "public"."event";
+
+-- ;;
+
+drop table "public"."attendee";
diff --git a/users/grfn/bbbg/resources/migrations/20211212165646-init-schema.up.sql b/users/grfn/bbbg/resources/migrations/20211212165646-init-schema.up.sql
new file mode 100644
index 0000000000..8c3276e1e6
--- /dev/null
+++ b/users/grfn/bbbg/resources/migrations/20211212165646-init-schema.up.sql
@@ -0,0 +1,31 @@
+CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
+-- ;;
+CREATE TABLE "attendee" (
+    "id" UUID PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
+    "meetup_name" TEXT NOT NULL,
+    "discord_name" TEXT,
+    "meetup_user_id" TEXT,
+    "organizer_notes" TEXT NOT NULL DEFAULT '',
+    "created_at" TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now()
+);
+-- ;;
+CREATE TABLE "event" (
+    "id" UUID PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
+    "date" DATE NOT NULL,
+    "created_at" TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now()
+);
+-- ;;
+CREATE TABLE "event_attendee" (
+    "event_id" UUID NOT NULL REFERENCES "event" ("id"),
+    "attendee_id" UUID NOT NULL REFERENCES "attendee" ("id"),
+    "rsvpd_attending" BOOL,
+    "attended" BOOL,
+    "created_at" TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now(),
+    PRIMARY KEY ("event_id", "attendee_id")
+);
+-- ;;
+CREATE TABLE "user" (
+    "id" UUID PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
+    "discord_user_id" TEXT NOT NULL,
+    "created_at" TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now()
+);
diff --git a/users/grfn/bbbg/shell.nix b/users/grfn/bbbg/shell.nix
new file mode 100644
index 0000000000..195562519e
--- /dev/null
+++ b/users/grfn/bbbg/shell.nix
@@ -0,0 +1,20 @@
+let
+ depot = import ../../.. {};
+in
+with depot.third_party.nixpkgs;
+
+mkShell {
+  buildInputs = [
+    arion
+    depot.third_party.clj2nix
+    clojure
+    openjdk11_headless
+    postgresql_12
+    nix-prefetch-git
+  ];
+
+  PGHOST = "localhost";
+  PGUSER = "bbbg";
+  PGDATABASE = "bbbg";
+  PGPASSWORD = "password";
+}
diff --git a/users/grfn/bbbg/src/bbbg/attendee.clj b/users/grfn/bbbg/src/bbbg/attendee.clj
new file mode 100644
index 0000000000..fabedb0a91
--- /dev/null
+++ b/users/grfn/bbbg/src/bbbg/attendee.clj
@@ -0,0 +1,4 @@
+(ns bbbg.attendee
+  (:require [clojure.spec.alpha :as s]))
+
+(s/def ::id uuid?)
diff --git a/users/grfn/bbbg/src/bbbg/core.clj b/users/grfn/bbbg/src/bbbg/core.clj
new file mode 100644
index 0000000000..70c7da50d5
--- /dev/null
+++ b/users/grfn/bbbg/src/bbbg/core.clj
@@ -0,0 +1,58 @@
+(ns bbbg.core
+  (:gen-class)
+  (:require
+   [bbbg.db :as db]
+   [bbbg.web :as web]
+   [clojure.spec.alpha :as s]
+   [clojure.spec.test.alpha :as stest]
+   [com.stuartsierra.component :as component]
+   [expound.alpha :as exp]))
+
+(s/def ::config
+  (s/merge
+   ::db/config
+   ::web/config))
+
+(defn make-system [config]
+  (component/system-map
+   :db (db/make-database config)
+   :web (web/make-server config)))
+
+(defn env->config []
+  (s/assert
+   ::config
+   (merge
+    (db/env->config)
+    (web/env->config))))
+
+(defn dev-config []
+  (s/assert
+   ::config
+   (merge
+    (db/dev-config)
+    (web/dev-config))))
+
+(defonce system nil)
+
+(defn init-dev []
+  (s/check-asserts true)
+  (set! s/*explain-out* exp/printer)
+  (stest/instrument))
+
+(defn run-dev []
+  (init-dev)
+  (alter-var-root
+   #'system
+   (fn [sys]
+     (when sys
+       (component/start sys))
+     (component/start (make-system (dev-config))))))
+
+(defn -main [& _args]
+  (alter-var-root
+   #'system
+   (constantly (component/start (make-system (env->config))))))
+
+(comment
+  (run-dev)
+  )
diff --git a/users/grfn/bbbg/src/bbbg/db.clj b/users/grfn/bbbg/src/bbbg/db.clj
new file mode 100644
index 0000000000..03c86d6fb9
--- /dev/null
+++ b/users/grfn/bbbg/src/bbbg/db.clj
@@ -0,0 +1,357 @@
+(ns bbbg.db
+  (:gen-class)
+  (:refer-clojure :exclude [get list])
+  (:require [camel-snake-kebab.core :as csk :refer [->kebab-case ->snake_case]]
+            [bbbg.util.core :as u]
+            [clojure.set :as set]
+            [clojure.spec.alpha :as s]
+            [clojure.string :as str]
+            [com.stuartsierra.component :as component]
+            [config.core :refer [env]]
+            [honeysql.format :as hformat]
+            [migratus.core :as migratus]
+            [next.jdbc :as jdbc]
+            [next.jdbc.connection :as jdbc.conn]
+            next.jdbc.date-time
+            [next.jdbc.optional :as jdbc.opt]
+            [next.jdbc.result-set :as rs]
+            [next.jdbc.sql :as sql])
+  (:import [com.impossibl.postgres.jdbc PGSQLSimpleException]
+           com.zaxxer.hikari.HikariDataSource
+           [java.sql Connection ResultSet Types]
+           javax.sql.DataSource))
+
+(s/def ::host string?)
+(s/def ::database string?)
+(s/def ::user string?)
+(s/def ::password string?)
+
+(s/def ::config
+  (s/keys :opt [::host
+                ::database
+                ::user
+                ::password]))
+
+(s/fdef make-database
+  :args
+  (s/cat :config (s/keys :opt [::config])))
+
+(s/fdef env->config :ret ::config)
+
+(s/def ::db any?)
+
+;;;
+
+(def default-config
+  (s/assert
+   ::config
+   {::host "localhost"
+    ::database "bbbg"
+    ::user "bbbg"
+    ::password "password"}))
+
+(defn dev-config [] default-config)
+
+(defn env->config []
+  (->>
+   {::host (:pghost env)
+    ::database (:pgdatabase env)
+    ::user (:pguser env)
+    ::password (:pgpassword env)}
+   u/remove-nils
+   (s/assert ::config)))
+
+(defn ->db-spec [config]
+  (-> default-config
+      (merge config)
+      (set/rename-keys
+       {::host :host
+        ::database :dbname
+        ::user :username
+        ::password :password})
+      (assoc :dbtype "pgsql")))
+
+(defn connection
+  "Make a one-off connection from the given `::config` map, or the environment
+  if not provided"
+  ([] (connection (env->config)))
+  ([config]
+   (-> config
+       ->db-spec
+       (set/rename-keys {:username :user})
+       jdbc/get-datasource
+       jdbc/get-connection)))
+
+(defrecord Database [config]
+  component/Lifecycle
+  (start [this]
+    (assoc this :pool (jdbc.conn/->pool HikariDataSource (->db-spec config))))
+  (stop [this]
+    (some-> this :pool .close)
+    (dissoc this :pool))
+
+  clojure.lang.IFn
+  (invoke [this] (:pool this)))
+
+(defn make-database [config]
+  (map->Database {:config config}))
+
+;;;
+;;; Migrations
+;;;
+
+(defn migratus-config
+  [db]
+  {:store :database
+   :migration-dir "migrations/"
+   :migration-table-name "__migrations__"
+   :db
+   (let [db (if (ifn? db) (db) db)]
+     (cond
+       (.isInstance Connection db)
+       {:connection db}
+       (.isInstance DataSource db)
+       {:datasource db}
+       :else (throw
+              (ex-info "migratus-config called with value of unrecognized type"
+                       {:value db}))))})
+
+(defn generate-migration
+  ([db name] (generate-migration db name :sql))
+  ([db name type] (migratus/create (migratus-config db) name type)))
+
+(defn migrate!
+  [db] (migratus/migrate (migratus-config db)))
+
+(defn rollback!
+  [db] (migratus/rollback (migratus-config db)))
+
+;;;
+;;; Database interaction
+;;;
+
+(defn ->key-ns [tn]
+  (let [tn (name tn)
+        tn (if (str/starts-with? tn "public.")
+             (second (str/split tn #"\." 2))
+             tn)]
+    (str "bbbg." (->kebab-case tn))))
+
+(defn ->table-name [kns]
+  (let [kns (name kns)]
+    (->snake_case
+     (if (str/starts-with? kns "public.")
+       kns
+       (str "public." (last (str/split kns #"\.")))))))
+
+(defn ->column
+  ([col] (->column nil col))
+  ([table col]
+   (let [col-table (some-> col namespace ->table-name)
+         snake-col (-> col name ->snake_case (str/replace #"\?$" ""))]
+     (if (or (not (namespace col))
+             (not table)
+             (= (->table-name table) col-table))
+       snake-col
+       ;; different table, assume fk
+       (str
+        (str/replace-first col-table "public." "")
+        "_"
+        snake-col)))))
+
+(defn ->value [v]
+  (if (keyword? v)
+    (-> v name csk/->snake_case_string)
+    v))
+
+(defn process-key-map [table key-map]
+  (into {}
+        (map (fn [[k v]] [(->column table k)
+                          (->value v)]))
+        key-map))
+
+(defn fkize [col]
+  (if (str/ends-with? col "-id")
+    (let [table (str/join "-" (butlast (str/split (name col) #"-")))]
+      (keyword (->key-ns table) "id"))
+    col))
+
+(def ^:private enum-members-cache (atom {}))
+(defn- enum-members
+  "Returns a set of enum members as strings for the enum with the given name"
+  [db name]
+  (if-let [e (find @enum-members-cache name)]
+    (val e)
+    (let [r (try
+              (-> (jdbc/execute-one!
+                   (db)
+                   [(format "select enum_range(null::%s) as members" name)])
+                  :members
+                  .getArray
+                  set)
+              (catch PGSQLSimpleException _
+                nil))]
+      (swap! enum-members-cache assoc name r)
+      r)))
+
+(def ^{:private true
+       :dynamic true}
+  *meta-db*
+  "Database connection to use to query metadata"
+  nil)
+
+(extend-protocol rs/ReadableColumn
+  String
+  (read-column-by-label [x _] x)
+  (read-column-by-index [x rsmeta idx]
+    (if-not *meta-db*
+      x
+      (let [typ (.getColumnTypeName rsmeta idx)]
+        ;; TODO: Is there a better way to figure out if a type is an enum?
+        (if (enum-members *meta-db* typ)
+          (keyword (csk/->kebab-case-string typ)
+                   (csk/->kebab-case-string x))
+          x)))))
+
+(comment
+  (->key-ns :public.user)
+  (->key-ns :public.api-token)
+  (->key-ns :api-token)
+  (->table-name :api-token)
+  (->table-name :public.user)
+  (->table-name :bbbg.user)
+  )
+
+(defn as-fq-maps [^ResultSet rs _opts]
+  (let [qualify #(when (seq %) (str "bbbg." (->kebab-case %)))
+        rsmeta (.getMetaData rs)
+        cols (mapv
+              (fn [^Integer i]
+                (let [ty (.getColumnType rsmeta i)
+                      lab (.getColumnLabel rsmeta i)
+                      n (str (->kebab-case lab)
+                             (when (= ty Types/BOOLEAN) "?"))]
+                  (fkize
+                   (if-let [q (some-> rsmeta (.getTableName i) qualify not-empty)]
+                     (keyword q n)
+                     (keyword n)))))
+              (range 1 (inc (.getColumnCount rsmeta))))]
+    (jdbc.opt/->MapResultSetOptionalBuilder rs rsmeta cols)))
+
+(def jdbc-opts
+  {:builder-fn as-fq-maps
+   :column-fn ->snake_case
+   :table-fn ->snake_case})
+
+(defmethod hformat/fn-handler "count-distinct" [_ field]
+  (str "count(distinct " (hformat/to-sql field) ")"))
+
+(defn fetch
+  "Fetch a single row from the db matching the given `sql-map` or query"
+  [db sql-map & [opts]]
+  (s/assert
+   (s/nilable (s/keys))
+   (binding [*meta-db* db]
+     (jdbc/execute-one!
+      (db)
+      (if (map? sql-map)
+        (hformat/format sql-map)
+        sql-map)
+      (merge jdbc-opts opts)))))
+
+(defn get
+  "Retrieve a single record from the given table by ID"
+  [db table id & [opts]]
+  (when id
+    (fetch
+     db
+     {:select [:*]
+      :from [table]
+      :where [:= :id id]}
+     opts)))
+
+(defn list
+  "Returns a list of rows from the db matching the given sql-map, table or
+  query"
+  [db sql-map-or-table & [opts]]
+  (s/assert
+   (s/coll-of (s/keys))
+   (binding [*meta-db* db]
+     (jdbc/execute!
+      (db)
+      (cond
+        (map? sql-map-or-table)
+        (hformat/format sql-map-or-table)
+        (keyword? sql-map-or-table)
+        (hformat/format {:select [:*] :from [sql-map-or-table]})
+        :else
+        sql-map-or-table)
+      (merge jdbc-opts opts)))))
+
+(defn exists?
+  "Returns true if the given sql query-map would return any results"
+  [db sql-map]
+  (binding [*meta-db* db]
+    (pos?
+     (:count
+      (fetch db {:select [[:%count.* :count]], :from [[sql-map :sq]]})))))
+
+(defn execute!
+  "Given a database and a honeysql query map, perform an operation on the
+  database and discard the results"
+  [db sql-map & [opts]]
+  (jdbc/execute!
+   (db)
+   (hformat/format sql-map)
+   (merge jdbc-opts opts)))
+
+(defn insert!
+  "Given a database, a table name, and a data hash map, inserts the
+  data as a single row in the database and attempts to return a map of generated
+  keys."
+  [db table key-map & [opts]]
+  (binding [*meta-db* db]
+    (sql/insert!
+     (db)
+     table
+     (process-key-map table key-map)
+     (merge jdbc-opts opts))))
+
+(defn update!
+  "Given a database, a table name, a hash map of columns and values
+  to set, and a honeysql predicate, perform an update on the table.
+  Will "
+  [db table key-map where-params & [opts]]
+  (binding [*meta-db* db]
+    (execute! db
+              {:update table
+               :set (u/map-keys keyword (process-key-map table key-map))
+               :where where-params
+               :returning [:id]}
+              opts)))
+
+(defn delete!
+  "Delete all rows from the given table matching the given where clause"
+  [db table where-clause]
+  (binding [*meta-db* db]
+    (sql/delete! (db) table (hformat/format-predicate where-clause))))
+
+(defmacro with-transaction [[sym db opts] & body]
+  `(jdbc/with-transaction
+     [tx# (~db) ~opts]
+     (let [~sym (constantly tx#)]
+       ~@body)))
+
+(defn -main [& args]
+  (let [db (component/start (make-database {::config (env->config)}))]
+    (case (first args)
+      "migrate" (migrate! db)
+      "rollback" (rollback! db))))
+
+(comment
+  (def db (:db bbbg.core/system))
+  (generate-migration db "init-schema")
+  (migrate! db)
+
+
+  )
diff --git a/users/grfn/bbbg/src/bbbg/db/attendee.clj b/users/grfn/bbbg/src/bbbg/db/attendee.clj
new file mode 100644
index 0000000000..7584b1cceb
--- /dev/null
+++ b/users/grfn/bbbg/src/bbbg/db/attendee.clj
@@ -0,0 +1,29 @@
+(ns bbbg.db.attendee
+  (:require
+   [bbbg.db :as db]
+   honeysql-postgres.helpers
+   [honeysql.helpers :refer [merge-join merge-where]]))
+
+(defn search
+  ([query]
+   (cond->
+       {:select [:attendee.*]
+        :from [:attendee]}
+     query
+     (assoc
+      :where [:or
+              [:ilike :meetup_name (str "%" query "%")]
+              [:ilike :discord_name (str "%" query "%")]])))
+  ([db query]
+   (db/list db (search query))))
+
+(defn for-event
+  ([query event-id]
+   (-> query
+       (merge-join :event_attendee [:= :attendee.id :event_attendee.attendee_id])
+       (merge-where [:= :event_attendee.event_id event-id]))))
+
+(comment
+  (def db (:db bbbg.core/system))
+  (search db "gri")
+  )
diff --git a/users/grfn/bbbg/src/bbbg/db/event.clj b/users/grfn/bbbg/src/bbbg/db/event.clj
new file mode 100644
index 0000000000..a2aa30fd0d
--- /dev/null
+++ b/users/grfn/bbbg/src/bbbg/db/event.clj
@@ -0,0 +1,50 @@
+(ns bbbg.db.event
+  (:require
+   [bbbg.attendee :as attendee]
+   [bbbg.db :as db]
+   [bbbg.event :as event]
+   [honeysql.helpers :refer [merge-group-by merge-join merge-select]]
+   [java-time :refer [local-date]]))
+
+(defn create! [db event]
+  (db/insert! db :event (select-keys event [::event/date])))
+
+(defn attended!
+  [db params]
+  (db/execute!
+   db
+   {:insert-into :event-attendee
+    :values [{:event_id (::event/id params)
+              :attendee_id (::attendee/id params)
+              :attended true}]
+    :upsert {:on-conflict [:event-id :attendee-id]
+             :do-update-set! {:attended true}}}))
+
+(defn on-day
+  ([day] {:select [:event.*]
+          :from [:event]
+          :where [:= :date (str day)]})
+  ([db day]
+   (db/list db (on-day day))))
+
+(defn today
+  ([] (on-day (local-date)))
+  ([db] (db/list db (today))))
+
+(defn with-attendee-counts
+  [query]
+  (-> query
+      (merge-join :event_attendee [:= :event.id :event_attendee.event-id])
+      (merge-select :%count.event_attendee.attendee_id)
+      (merge-group-by :event.id :event_attendee.event-id)))
+
+(comment
+  (def db (:db bbbg.core/system))
+  (db/list db (-> (today) (with-attendee-counts)))
+
+  (honeysql.format/format
+   (honeysql-postgres.helpers/upsert {:insert-into :foo
+                                      :values {:bar 1}}
+                                     (-> (honeysql-postgres.helpers/on-conflict :did)
+                                         (honeysql-postgres.helpers/do-update-set! [:did true]))))
+  )
diff --git a/users/grfn/bbbg/src/bbbg/event.clj b/users/grfn/bbbg/src/bbbg/event.clj
new file mode 100644
index 0000000000..aa0578f354
--- /dev/null
+++ b/users/grfn/bbbg/src/bbbg/event.clj
@@ -0,0 +1,4 @@
+(ns bbbg.event
+  (:require [clojure.spec.alpha :as s]))
+
+(s/def ::id uuid?)
diff --git a/users/grfn/bbbg/src/bbbg/event_attendee.clj b/users/grfn/bbbg/src/bbbg/event_attendee.clj
new file mode 100644
index 0000000000..af37bf01c0
--- /dev/null
+++ b/users/grfn/bbbg/src/bbbg/event_attendee.clj
@@ -0,0 +1,4 @@
+(ns bbbg.event-attendee
+  (:require [clojure.spec.alpha :as s]))
+
+(s/def ::attended? boolean?)
diff --git a/users/grfn/bbbg/src/bbbg/handlers/attendees.clj b/users/grfn/bbbg/src/bbbg/handlers/attendees.clj
new file mode 100644
index 0000000000..00a8a59080
--- /dev/null
+++ b/users/grfn/bbbg/src/bbbg/handlers/attendees.clj
@@ -0,0 +1,40 @@
+(ns bbbg.handlers.attendees
+  (:require
+   [bbbg.attendee :as attendee]
+   [bbbg.db :as db]
+   [bbbg.db.attendee :as db.attendee]
+   [bbbg.db.event :as db.event]
+   [bbbg.event :as event]
+   [cheshire.core :as json]
+   [compojure.core :refer [GET POST routes]]
+   [honeysql.helpers :refer [merge-where]]
+   [ring.util.response :refer [content-type redirect response]]))
+
+(defn attendees-routes [{:keys [db]}]
+  (routes
+   (GET "/attendees.json" [q event_id attended]
+     (let [results
+           (db/list
+            db
+            (cond->
+                (if q
+                  (db.attendee/search q)
+                  {:select [:attendee.*] :from [:attendee]})
+              event_id (db.attendee/for-event event_id)
+              (some? attended) (merge-where [:= :attended (case attended
+                                                            "true" true
+                                                            "false" false)])))]
+       (-> {:results results}
+           json/generate-string
+           response
+           (content-type "application/json"))))
+
+   (POST "/event_attendees" [event_id attendee_id]
+     (if (and (db/exists? db {:select [:id] :from [:event] :where [:= :id event_id]})
+              (db/exists? db {:select [:id] :from [:attendee] :where [:= :id attendee_id]}))
+       (do
+         (db.event/attended! db {::event/id event_id
+                                 ::attendee/id attendee_id})
+         (-> (redirect (str "/signup-forms/" event_id))
+             (assoc :flash "Thank you for signing in! Enjoy the event.")))
+       (response "Something went wrong")))))
diff --git a/users/grfn/bbbg/src/bbbg/handlers/core.clj b/users/grfn/bbbg/src/bbbg/handlers/core.clj
new file mode 100644
index 0000000000..34ab74553f
--- /dev/null
+++ b/users/grfn/bbbg/src/bbbg/handlers/core.clj
@@ -0,0 +1,34 @@
+(ns bbbg.handlers.core
+  (:require
+   [hiccup.core :refer [html]]
+   [ring.util.response :refer [content-type response]]))
+
+(defn render-page [opts & body]
+  (let [[{:keys [title]} body]
+        (if (map? opts)
+          [opts body]
+          [{} (into [opts] body)])]
+    (html
+     [:html {:lang "en"}
+      [:head
+       [:meta {:charset "UTF-8"}]
+       [:title (if title
+                 (str title " - BBBG")
+                 "BBBG")]
+       [:link {:rel "stylesheet"
+               :type "text/css"
+               :href "/main.css"}]]
+      [:body
+       (into [:div.content] body)
+       [:script {:src "https://cdnjs.cloudflare.com/ajax/libs/tarekraafat-autocomplete.js/10.2.6/autoComplete.js"}]
+       [:script {:src "/main.js"}]]])))
+
+(defn page-response [& render-page-args]
+  (-> (apply render-page render-page-args)
+      response
+      (content-type "text/html")))
+
+(comment
+  (render-page
+   [:h1 "hi"])
+  )
diff --git a/users/grfn/bbbg/src/bbbg/handlers/events.clj b/users/grfn/bbbg/src/bbbg/handlers/events.clj
new file mode 100644
index 0000000000..f42b7bea2c
--- /dev/null
+++ b/users/grfn/bbbg/src/bbbg/handlers/events.clj
@@ -0,0 +1,44 @@
+(ns bbbg.handlers.events
+  (:require
+   [bbbg.db :as db]
+   [bbbg.db.event :as db.event]
+   [bbbg.event :as event]
+   [bbbg.handlers.core :refer [page-response]]
+   [compojure.core :refer [context GET POST]]
+   [ring.util.response :refer [redirect]]))
+
+(defn events-index [events]
+  [:ul.events-list
+   (for [event events]
+     [:li (::event/date event)])])
+
+(defn event-form
+  ([] (event-form {}))
+  ([event]
+   [:form {:method "POST" :action "/events"}
+    [:div.form-group
+     [:label "Date"
+      [:input {:type "date"
+               :id "date"
+               :name "date"
+               :value (str (::event/date event))}]]]
+    [:div.form-group
+     [:input {:type "submit"
+              :value "Create Event"}]]]))
+
+(defn events-routes [{:keys [db]}]
+  (context "/events" []
+    (GET "/" []
+      (let [events (db/list db :event)]
+        (events-index events)))
+
+    (GET "/new" [date]
+      (page-response
+       {:title "New Event"}
+       (event-form {::event/date date})))
+
+    (POST "/" [date]
+      (let [event (db.event/create! db {::event/date date})]
+        (-> (str "/signup-forms/" (::event/id event))
+            redirect
+            (assoc-in [:flash :message] "Event Created"))))))
diff --git a/users/grfn/bbbg/src/bbbg/handlers/home.clj b/users/grfn/bbbg/src/bbbg/handlers/home.clj
new file mode 100644
index 0000000000..d5ba72878a
--- /dev/null
+++ b/users/grfn/bbbg/src/bbbg/handlers/home.clj
@@ -0,0 +1,17 @@
+(ns bbbg.handlers.home
+  (:require
+   [bbbg.handlers.core :refer [page-response]]
+   [compojure.core :refer [GET routes]]))
+
+(defn- home-page []
+  [:nav.home-nav
+   [:ul
+    [:li [:a {:href "/signup-forms"}
+          "Event Signup Form"]]
+    [:li [:a {:href "/login"}
+          "Sign In"]]]])
+
+(defn home-routes [_env]
+  (routes
+   (GET "/" []
+     (page-response (home-page)))))
diff --git a/users/grfn/bbbg/src/bbbg/handlers/signup_form.clj b/users/grfn/bbbg/src/bbbg/handlers/signup_form.clj
new file mode 100644
index 0000000000..8c4958f103
--- /dev/null
+++ b/users/grfn/bbbg/src/bbbg/handlers/signup_form.clj
@@ -0,0 +1,57 @@
+(ns bbbg.handlers.signup-form
+  (:require
+   [bbbg.db :as db]
+   [bbbg.db.event :as db.event]
+   [bbbg.event :as event]
+   [bbbg.handlers.core :refer [page-response]]
+   [compojure.core :refer [GET context]]
+   [java-time :refer [local-date]]
+   [ring.util.response :refer [redirect]]))
+
+(defn no-events-page []
+  [:div.no-events
+   [:p
+    "There are no events for today"]
+   [:p
+    [:a {:href (str "/events/new?date=" (str (local-date)))} "Create Event"]
+    [:a {:href "/events"} "All Events"]]])
+
+(defn signup-page [event]
+  [:div.signup-page
+   [:form#signup-form
+    {:method "POST"
+     :action "/event_attendees"
+     :disabled "disabled"}
+    [:input#event-id {:type "hidden" :name "event_id" :value (::event/id event)}]
+    [:input#attendee-id {:type "hidden" :name "attendee_id"}]
+    [:label "Name"
+     [:input#name-autocomplete
+      {:type "search"
+       :name "name"
+       :spellcheck "false"
+       :autocorrect "off"
+       :autocomplete "off"
+       :autocapitalize "off"
+       :maxlength "2048"}]]
+    [:input {:type "submit"
+             :value "Sign In"
+             :disabled "disabled"}]]])
+
+(defn event-not-found []
+  [:div.event-not-found
+   [:p "Event not found"]
+   [:p [:a {:href (str "/events/new")} "Create a new event"]]])
+
+;;;
+
+(defn signup-form-routes [{:keys [db]}]
+  (context "/signup-forms" []
+   (GET "/" []
+     (if-let [event (db/fetch db (db.event/today))]
+       (redirect (str "/signup-forms/" (::event/id event)))
+       (page-response (no-events-page))))
+
+   (GET "/:event-id" [event-id]
+     (if-let [event (db/get db :event event-id)]
+       (page-response (signup-page event))
+       (event-not-found)))))
diff --git a/users/grfn/bbbg/src/bbbg/styles.clj b/users/grfn/bbbg/src/bbbg/styles.clj
new file mode 100644
index 0000000000..07ed87ba1a
--- /dev/null
+++ b/users/grfn/bbbg/src/bbbg/styles.clj
@@ -0,0 +1,9 @@
+(ns bbbg.styles
+  (:require [garden.def :refer [defstyles]]
+            [garden.compiler :refer [compile-css]]))
+
+(defstyles styles
+  )
+
+(def stylesheet
+  (compile-css styles))
diff --git a/users/grfn/bbbg/src/bbbg/util/core.clj b/users/grfn/bbbg/src/bbbg/util/core.clj
new file mode 100644
index 0000000000..7f2a8516bf
--- /dev/null
+++ b/users/grfn/bbbg/src/bbbg/util/core.clj
@@ -0,0 +1,117 @@
+(ns bbbg.util.core
+  (:import java.util.UUID))
+
+(defn remove-nils
+  "Remove all keys with nil values from m"
+  [m]
+  (let [!m (transient m)]
+    (doseq [[k v] m]
+      (when (nil? v)
+        (dissoc! !m k)))
+    (persistent! !m)))
+
+
+(defn alongside
+  "Apply a pair of functions to the first and second element of a two element
+  vector, respectively. The two argument form partially applies, such that:
+
+  ((alongside f g) xy) ≡ (alongside f g xy)
+
+  This is equivalent to (***) in haskell's Control.Arrow"
+  ([f g] (partial alongside f g))
+  ([f g [x y]] [(f x) (g y)]))
+
+(defn map-kv
+  "Map a pair of functions over the keys and values of a map, respectively.
+  Preserves metadata on the incoming map.
+  The two argument form returns a transducer that yields map-entries.
+
+  (partial map-kv identity identity) ≡ identity"
+  ([kf vf]
+   (map (fn [[k v]]
+          ;; important to return a map-entry here so that callers down the road
+          ;; can use `key` or `val`
+          (first {(kf k) (vf v)}))))
+  ([kf vf m]
+   (into (empty m) (map-kv kf vf) m)))
+
+(defn filter-kv
+  "Returns a map containing the elements of m for which (f k v) returns logical
+  true. The one-argument form returns a transducer that yields map entries"
+  ([f] (filter (partial apply f)))
+  ([f m]
+   (into (empty m) (filter-kv f) m)))
+
+(defn map-keys
+  "Map f over the keys of m. Preserves metadata on the incoming map. The
+  one-argument form returns a transducer that yields map-entries."
+  ([f] (map-kv f identity))
+  ([f m] (map-kv f identity m)))
+
+(defn map-vals
+  "Map f over the values of m. Preserves metadata on the incoming map. The
+  one-argument form returns a transducer that yields map-entries."
+  ([f] (map-kv identity f))
+  ([f m] (map-kv identity f m)))
+
+(defn map-keys-recursive [f x]
+  (cond
+    (map? x) (map-kv f (partial map-keys-recursive f) x)
+    (sequential? x) (map (partial map-keys-recursive f) x)
+    :else x))
+
+(defn denamespace [x]
+  (if (keyword? x)
+    (keyword (name x))
+    (map-keys-recursive denamespace x)))
+
+(defn reverse-merge
+  "Like `clojure.core/merge`, except duplicate keys from maps earlier in the
+  argument list take precedence
+
+    => (merge {:x 1} {:x 2})
+    {:x 2}
+
+    => (sut/reverse-merge {:x 1} {:x 2})
+    {:x 1}"
+  [& ms]
+  (apply merge (reverse ms)))
+
+(defn invert-map
+  "Invert the keys and vals of m. Behavior with duplicate vals is undefined.
+
+  => (sut/invert-map {:x 1 :y 2})
+  {1 :x 2 :y}"
+  [m]
+  (into {} (map (comp vec reverse)) m))
+
+(defn ->uuid
+  "Converts x to uuid, returning nil if x is nil or empty"
+  [x]
+  (cond
+    (not x) nil
+    (uuid? x) x
+    (and (string? x) (seq x))
+    (UUID/fromString x)))
+
+(defn key-by
+  "Create a map from a seq obtaining keys via f
+
+    => (sut/key-by :x [{:x 1} {:x 2 :y 3}])
+    {1 {:x 1}, 2 {:x 2 :y 3}}"
+  [f l]
+  (into {} (map (juxt f identity)) l))
+
+(defn distinct-by
+  "Like clojure.core/distinct, but can take a function f by which
+  distinctiveness is calculated"
+  [distinction-fn coll]
+  (let [step (fn step [xs seen]
+               (lazy-seq
+                ((fn [[f :as xs] seen]
+                   (when-let [s (seq xs)]
+                     (if (contains? seen (distinction-fn f))
+                       (recur (rest s) seen)
+                       (cons f (step (rest s) (conj seen (distinction-fn f)))))))
+                 xs seen)))]
+    (step coll #{})))
diff --git a/users/grfn/bbbg/src/bbbg/web.clj b/users/grfn/bbbg/src/bbbg/web.clj
new file mode 100644
index 0000000000..4e0566bcc3
--- /dev/null
+++ b/users/grfn/bbbg/src/bbbg/web.clj
@@ -0,0 +1,77 @@
+(ns bbbg.web
+  (:require
+   [bbbg.handlers.attendees :as attendees]
+   [bbbg.handlers.events :as events]
+   [bbbg.handlers.home :as home]
+   [bbbg.handlers.signup-form :as signup-form]
+   [bbbg.styles :refer [stylesheet]]
+   [clojure.spec.alpha :as s]
+   [com.stuartsierra.component :as component]
+   [compojure.core :refer [GET routes]]
+   [config.core :refer [env]]
+   [org.httpkit.server :as http-kit]
+   [ring.middleware.flash :refer [wrap-flash]]
+   [ring.middleware.keyword-params :refer [wrap-keyword-params]]
+   [ring.middleware.params :refer [wrap-params]]
+   [ring.util.response :refer [content-type response resource-response]]))
+
+(s/def ::port pos-int?)
+
+(s/def ::config
+  (s/keys :req [::port]))
+
+(s/fdef make-server
+  :args (s/cat :config ::config))
+
+(defn env->config []
+  (s/assert
+   ::config
+   {::port (:port env 8888)}))
+
+(defn dev-config []
+  (s/assert ::config {::port 8888}))
+
+;;;
+
+(defn app-routes [env]
+  (routes
+   (GET "/main.css" []
+     (-> (response stylesheet)
+         (content-type "text/css")))
+   (GET "/main.js" []
+     (-> (resource-response "main.js")
+         (content-type "text/javascript")))
+
+   (attendees/attendees-routes env)
+   (signup-form/signup-form-routes env)
+   (events/events-routes env)
+   (home/home-routes env)))
+
+(defn middleware [app]
+  (-> app
+      wrap-keyword-params
+      wrap-params
+      wrap-flash))
+
+(defn handler [this]
+  (middleware
+   (app-routes this)))
+
+(defrecord WebServer [port db]
+  component/Lifecycle
+  (start [this]
+    (assoc this
+           ::shutdown-fn
+           (http-kit/run-server
+            (fn [r] ((handler this) r))
+            {:port port})))
+  (stop [this]
+    (if-let [shutdown-fn (::shutdown-fn this)]
+      (do (shutdown-fn :timeout 100)
+          (dissoc this ::shutdown-fn))
+      this)))
+
+(defn make-server [{::keys [port]}]
+  (component/using
+   (map->WebServer {:port port})
+   [:db]))