Initial commit
commit
86681ec999
@ -0,0 +1,3 @@
|
||||
((nil . ((cider-ns-refresh-before-fn . "integrant.repl/halt")
|
||||
(cider-ns-refresh-after-fn . "integrant.repl/init")
|
||||
(cider-clojure-cli-global-options . "-A:dev:cider-nrepl"))))
|
@ -0,0 +1,3 @@
|
||||
/db/data/dev
|
||||
.nrepl-port
|
||||
.cpcache
|
@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Run once as part of Docker build script to initialize
|
||||
# the database with encryption for user passwords.
|
||||
|
||||
POSTGRES_USER=${POSTGRES_USER:-"dev"}
|
||||
POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-"dev"}
|
||||
|
||||
psql -v ON_ERROR_STOP=1 \
|
||||
--username "$POSTGRES_USER" \
|
||||
--dbname "$POSTGRES_DB" <<-EOSQL
|
||||
SET password_encryption = "scram-sha-256";
|
||||
ALTER USER "$POSTGRES_USER" WITH ENCRYPTED PASSWORD '$POSTGRES_PASSWORD';
|
||||
EOSQL
|
@ -0,0 +1,6 @@
|
||||
FROM postgres
|
||||
|
||||
# See the section on "Initialization scripts" at
|
||||
# https://hub.docker.com/_/postgres/
|
||||
|
||||
COPY 0001_init.sh /docker-entrypoint-initdb.d/
|
@ -0,0 +1,48 @@
|
||||
#+TITLE: Database setup
|
||||
|
||||
This directory contains necessities for initializing and running a database
|
||||
locally inside a docker container.
|
||||
|
||||
We want our develpment environment to use the same tech stack that production
|
||||
uses. So rather than suppord a development configuration that uses SQLite and a
|
||||
production configuration that uses PostgreSQL, just use PostgreSQL for both.
|
||||
With Docker, it's easy.
|
||||
|
||||
|
||||
Building requires the following environment variables.
|
||||
|
||||
- `POSTGRES_DB`
|
||||
- `POSTGRES_USER`
|
||||
- `POSTGRES_PASSWORD`
|
||||
|
||||
Running requires the following environment variables.
|
||||
|
||||
- `PGDATA`
|
||||
- `POSTGRES_USER`
|
||||
- `POSTGRES_PASSWORD`
|
||||
|
||||
|
||||
#+BEGIN_SRC sh
|
||||
docker build -t db .
|
||||
#+END_SRC
|
||||
|
||||
#+BEGIN_SRC sh :tangle run.sh :tangle-mode (identity #o755)
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PGDATA=${PGDATA:-"$(pwd)/data/dev"}
|
||||
POSTGRES_USER=${POSTGRES_USER:-"dev"}
|
||||
POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-"dev"}
|
||||
|
||||
docker run \
|
||||
--name db \
|
||||
-e POSTGRES_PASSWORD=$POSTGRES_PASSWORD \
|
||||
-e POSTGRES_USER=$POSTGRES_USER \
|
||||
-v $PGDATA:/var/lib/postgresql/data \
|
||||
-p 5432:5432 \
|
||||
db
|
||||
#+END_SRC
|
||||
|
||||
#+BEGIN_SRC sh
|
||||
docker start db
|
||||
#+END_SRC
|
@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PGDATA=${PGDATA:-"$(pwd)/data/dev"}
|
||||
POSTGRES_USER=${POSTGRES_USER:-"dev"}
|
||||
POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-"dev"}
|
||||
|
||||
docker run \
|
||||
--name darklimericks-db \
|
||||
-e POSTGRES_PASSWORD=$POSTGRES_PASSWORD \
|
||||
-e POSTGRES_USER=$POSTGRES_USER \
|
||||
-v $PGDATA:/var/lib/postgresql/data \
|
||||
-p 5432:5432 \
|
||||
darklimericks-db
|
@ -0,0 +1,27 @@
|
||||
{:deps
|
||||
{org.clojure/tools.namespace {:mvn/version "1.0.0"}
|
||||
org.clojure/tools.deps.alpha
|
||||
{:git/url "https://github.com/clojure/tools.deps.alpha.git"
|
||||
:sha "d492e97259c013ba401c5238842cd3445839d020"}
|
||||
hiccup {:mvn/version "1.0.5"}
|
||||
com.taoensso/timbre {:mvn/version "5.1.0"}
|
||||
http-kit {:mvn/version "2.5.0"}
|
||||
integrant {:mvn/version "0.8.0"}
|
||||
integrant/repl {:mvn/version "0.3.2"}
|
||||
seancorfield/next.jdbc {:mvn/version "1.1.610"}
|
||||
org.xerial/sqlite-jdbc {:mvn/version "3.32.3.2"}
|
||||
honeysql {:mvn/version "1.0.444"}
|
||||
metosin/muuntaja {:mvn/version "0.6.7"}
|
||||
metosin/reitit {:mvn/version "0.5.10"}
|
||||
metosin/reitit-http {:mvn/version "0.5.10"}
|
||||
metosin/reitit-interceptors {:mvn/version "0.5.10"}
|
||||
com.layerware/hugsql {:mvn/version "0.5.1"}
|
||||
org.postgresql/postgresql {:mvn/version "42.2.18"}
|
||||
migratus {:mvn/version "1.3.2"}
|
||||
ring/ring-core {:mvn/version "1.8.2"}
|
||||
environ {:mvn/version "1.2.0"}
|
||||
prhyme {:local/root "/home/eihli/code/prhyme/prhyme.jar"}}
|
||||
:paths ["src" "resources"]
|
||||
:aliases {:dev {:extra-paths ["dev"]
|
||||
:extra-deps {hawk {:mvn/version "0.2.11"}
|
||||
ring/ring-devel {:mvn/version "1.8.2"}}}}}
|
@ -0,0 +1,28 @@
|
||||
(ns db
|
||||
(:require [next.jdbc.sql :as jdbc.sql]
|
||||
[next.jdbc :as jdbc]
|
||||
[honeysql.core :as honey.sql]
|
||||
[integrant.repl.state :refer [system]]
|
||||
[com.darklimericks.db.artists :as artists]
|
||||
[com.darklimericks.db.albums :as albums]
|
||||
[com.darklimericks.server.limericks :as server.limericks]
|
||||
[com.darklimericks.db.limericks :as limericks]))
|
||||
|
||||
(def conn (:database.sql/connection system))
|
||||
|
||||
(comment
|
||||
(->> (honey.sql/build :select :* :from :artist)
|
||||
(honey.sql/format)
|
||||
(jdbc.sql/query conn))
|
||||
(artists/insert-artist conn "Optimus Prhyme")
|
||||
(server.limericks/get-artist-and-album-for-new-limerick conn)
|
||||
(artists/artist conn 1)
|
||||
(albums/artist-albums-sql 1)
|
||||
(albums/artist-albums conn 1)
|
||||
(albums/insert-album conn "Limericks Illicit" 1)
|
||||
(albums/insert-album-sql "Limericks Illicit" 1)
|
||||
(artists/artists-by-letter conn "O")
|
||||
(limericks/album-limericks-sql 1)
|
||||
(limericks/album-limericks conn 1)
|
||||
(albums/artist-most-resent-album conn 1)
|
||||
)
|
@ -0,0 +1,13 @@
|
||||
(ns migrations
|
||||
(:require [migratus.core :as migratus]
|
||||
[integrant.repl.state :as state]))
|
||||
|
||||
(def config {:store :database
|
||||
:migration-dir "migrations/"
|
||||
:init-script "init.sql"
|
||||
:db {:connection-uri (-> state/config :database.sql/connection :jdbcUrl)}})
|
||||
|
||||
(comment
|
||||
state/config
|
||||
(:database.sql/connection state/system)
|
||||
(migratus/init config))
|
@ -0,0 +1,89 @@
|
||||
(ns user
|
||||
(:require [clojure.tools.deps.alpha.repl :refer [add-lib]]
|
||||
[clojure.tools.namespace.repl :refer [set-refresh-dirs]]
|
||||
[integrant.repl :as repl]
|
||||
[integrant.repl.state :as state]
|
||||
[migratus.core :as migratus]
|
||||
[com.darklimericks.server.handlers :as handlers]
|
||||
[integrant.core :as ig]
|
||||
[hawk.core :as hawk]
|
||||
[clojure.java.io :as io]
|
||||
[com.darklimericks.server.limericks :as limericks]
|
||||
[com.darklimericks.server.util :as util]
|
||||
[com.owoga.prhyme.limerick :as limerick]
|
||||
[com.darklimericks.server.system]
|
||||
[reitit.core :as reitit]))
|
||||
|
||||
(comment
|
||||
(-> state/system
|
||||
:app/router
|
||||
(reitit/routes))
|
||||
)
|
||||
|
||||
(set-refresh-dirs "src" "dev" "resources")
|
||||
|
||||
(defn add-project-dep
|
||||
([lib-name lib-version]
|
||||
(let [dep-name (symbol lib-name)
|
||||
dep-version (name lib-version)]
|
||||
(add-lib dep-name {:mvn/version dep-version}))))
|
||||
|
||||
(defn- clojure-file? [_ {:keys [file]}]
|
||||
(re-matches #"[^.].*(\.clj|\.edn)$" (.getName file)))
|
||||
|
||||
(defn- auto-reset-handler [ctx event]
|
||||
(binding [*ns* *ns*]
|
||||
(integrant.repl/reset)
|
||||
ctx))
|
||||
|
||||
(defn auto-reset []
|
||||
(hawk/watch! [{:paths ["src/" "resources/" "dev/"]
|
||||
:filter clojure-file?
|
||||
:handler auto-reset-handler}]))
|
||||
|
||||
(defn init []
|
||||
(repl/halt)
|
||||
(-> "server/config.edn"
|
||||
io/resource
|
||||
slurp
|
||||
ig/read-string
|
||||
ig/prep
|
||||
constantly
|
||||
repl/set-prep!)
|
||||
(repl/prep)
|
||||
(repl/init))
|
||||
|
||||
(defn reset []
|
||||
(repl/halt))
|
||||
|
||||
(comment
|
||||
(init)
|
||||
(auto-reset)
|
||||
(limericks/get-artist-and-album-for-new-limerick (-> state/system :database.sql/connection))
|
||||
(let [handler (handlers/limerick-generation-post-handler
|
||||
(-> state/system :database.sql/connection)
|
||||
(-> state/system :app/cache))]
|
||||
(handler {:params {:scheme "A9 A9 B5 B5 A9" #_'((A 9) (A 9) (B 5) (B 5) (A 9))}}))
|
||||
|
||||
(reitit/match-by-path
|
||||
(-> state/system :app/router)
|
||||
"/limericks/1/1")
|
||||
|
||||
(let [router (-> state/system :app/router)]
|
||||
(util/route-name->path {::reitit/router router}
|
||||
:com.darklimericks.server.system/artist))
|
||||
(let [router (-> state/system :app/router)]
|
||||
(util/route-name->path
|
||||
{::reitit/router router}
|
||||
:com.darklimericks.server.system/artist
|
||||
{:artist-id 1}))
|
||||
|
||||
(let [router (-> state/system :app/router)]
|
||||
(->> :com.darklimericks.server.system/artist
|
||||
(#(reitit/match-by-name (::reitit/router {::reitit/router router})
|
||||
%
|
||||
{:artist-id 1}))
|
||||
(reitit/match->path)))
|
||||
|
||||
|
||||
)
|
@ -0,0 +1,3 @@
|
||||
DROP TABLE artist;
|
||||
DROP TABLE album;
|
||||
DROP TABLE limerick;
|
@ -0,0 +1,17 @@
|
||||
CREATE TABLE artist (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(64)
|
||||
);
|
||||
|
||||
CREATE TABLE album (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(64),
|
||||
artist_id INTEGER NOT NULL REFERENCES artist
|
||||
);
|
||||
|
||||
CREATE TABLE limerick (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(64),
|
||||
text VARCHAR(1024),
|
||||
album_id INTEGER NOT NULL REFERENCES album
|
||||
);
|
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
@ -0,0 +1,25 @@
|
||||
<!doctype html>
|
||||
<html class="no-js" lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<title>DarkLimericks</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
||||
<!-- Place favicon.ico in the root directory -->
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<!--[if lt IE 8]>
|
||||
<p class="browserupgrade">
|
||||
You are using an <strong>outdated</strong> browser. Please
|
||||
<a href="http://browsehappy.com/">upgrade your browser</a> to improve
|
||||
your experience.
|
||||
</p>
|
||||
<![endif]-->
|
||||
|
||||
Hello World!
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1 @@
|
||||
/home/eihli/src/tachyons/css/tachyons.css
|
@ -0,0 +1 @@
|
||||
/home/eihli/src/tachyons/css/tachyons.min.css
|
@ -0,0 +1 @@
|
||||
hi
|
@ -0,0 +1,6 @@
|
||||
{:app/logging {:min-level :debug}
|
||||
:app/server {:port 8000 :handler #ig/ref :app/handler}
|
||||
:app/cache {}
|
||||
:database.sql/connection {}
|
||||
:app/router {:db #ig/ref :database.sql/connection :cache #ig/ref :app/cache}
|
||||
:app/handler {:router #ig/ref :app/router}}
|
@ -0,0 +1,43 @@
|
||||
(ns com.darklimericks.db.albums
|
||||
(:require [next.jdbc :as jdbc]
|
||||
[next.jdbc.sql :as jdbc.sql]
|
||||
[honeysql.core :as honey.sql]
|
||||
[honeysql.helpers :as honey.helpers]))
|
||||
|
||||
(defn insert-album-sql [name artist-id]
|
||||
(let [[s & params] (-> (honey.helpers/insert-into :album)
|
||||
(honey.helpers/columns :name :artist-id)
|
||||
(honey.helpers/values
|
||||
[[name artist-id]])
|
||||
honey.sql/format)
|
||||
s (format "%s RETURNING id" s)]
|
||||
(concat [s] params)))
|
||||
|
||||
(defn insert-album [db name artist-id]
|
||||
(jdbc/execute! db (insert-album-sql name artist-id)))
|
||||
|
||||
(defn artist-albums-sql [artist-id]
|
||||
(honey.sql/format
|
||||
(honey.sql/build
|
||||
:select :*
|
||||
:from :album
|
||||
:where [:= :artist_id artist-id]
|
||||
:order-by [[:album.id :desc]])))
|
||||
|
||||
(defn artist-albums [db artist-id]
|
||||
(jdbc/execute! db (artist-albums-sql artist-id)))
|
||||
|
||||
(defn artist-most-recent-album-sql [artist-id]
|
||||
(honey.sql/format
|
||||
(honey.sql/build
|
||||
:select :*
|
||||
:from :album
|
||||
:join [:artist [:= :album.artist_id :artist.id]]
|
||||
:order-by [[:album.id :desc]]
|
||||
:where [:= :artist.id artist-id])))
|
||||
|
||||
(defn artist-most-resent-album [db artist-id]
|
||||
(jdbc/execute! db (artist-most-recent-album-sql artist-id)))
|
||||
|
||||
(defn album [db id]
|
||||
(jdbc.sql/get-by-id db :album id))
|
@ -0,0 +1,42 @@
|
||||
(ns com.darklimericks.db.artists
|
||||
(:require [next.jdbc :as jdbc]
|
||||
[honeysql.core :as honey.sql]
|
||||
[honeysql.helpers :as honey.helpers]))
|
||||
|
||||
(defn insert-artist-sql [name]
|
||||
(let [[s & params] (-> (honey.helpers/insert-into :artist)
|
||||
(honey.helpers/columns :name)
|
||||
(honey.helpers/values
|
||||
[[name]])
|
||||
honey.sql/format)
|
||||
s (format "%s RETURNING id" s)]
|
||||
(concat [s] params)))
|
||||
|
||||
(defn insert-artist [db name]
|
||||
(jdbc/execute! db (insert-artist-sql name)))
|
||||
|
||||
(defn most-recent-artist-sql []
|
||||
(-> {:select [:*]
|
||||
:from [:artist]
|
||||
:order-by [[:id :desc]]
|
||||
:limit 1}
|
||||
(honey.sql/format)))
|
||||
|
||||
(defn most-recent-artist [db]
|
||||
(jdbc/execute-one! db (most-recent-artist-sql)))
|
||||
|
||||
(defn artists-by-letter [db letter]
|
||||
(jdbc/execute!
|
||||
db
|
||||
(honey.sql/format
|
||||
{:select [:*]
|
||||
:from [:artist]
|
||||
:where [:like :name (str letter "%")]})))
|
||||
|
||||
(defn artist [db id]
|
||||
(jdbc/execute-one!
|
||||
db
|
||||
(honey.sql/format
|
||||
{:select [:*]
|
||||
:from [:artist]
|
||||
:where [:= :id id]})))
|
@ -0,0 +1,42 @@
|
||||
(ns com.darklimericks.db.limericks
|
||||
(:require [next.jdbc :as jdbc]
|
||||
[honeysql.core :as honey.sql]
|
||||
[honeysql.helpers :as honey.helpers]))
|
||||
|
||||
(defn insert-limerick-sql [name text album-id]
|
||||
(-> (honey.helpers/insert-into :limerick)
|
||||
(honey.helpers/columns :name :text :album-id)
|
||||
(honey.helpers/values
|
||||
[[name text album-id]])
|
||||
honey.sql/format))
|
||||
|
||||
(defn insert-limerick [db name text album-id]
|
||||
(jdbc/execute! db (insert-limerick-sql name text album-id)))
|
||||
|
||||
(defn limerick-sql [limerick-id]
|
||||
(honey.sql/format
|
||||
(honey.sql/build
|
||||
:select :*
|
||||
:from :limerick
|
||||
:where [:= :limerick_id limerick-id])))
|
||||
|
||||
(defn limerick [db limerick-id]
|
||||
(jdbc/execute! db (limerick-sql limerick-id)))
|
||||
|
||||
(defn limericks [db]
|
||||
(jdbc/execute!
|
||||
db
|
||||
(honey.sql/format
|
||||
(honey.sql/build
|
||||
:select :*
|
||||
:from :limerick))))
|
||||
|
||||
(defn album-limericks-sql [album-id]
|
||||
(honey.sql/format
|
||||
(honey.sql/build
|
||||
:select :*
|
||||
:from :limerick
|
||||
:where [:= :album_id album-id])))
|
||||
|
||||
(defn album-limericks [db album-id]
|
||||
(jdbc/execute! db (album-limericks-sql album-id)))
|
@ -0,0 +1 @@
|
||||
(ns com.darklimericks.db.tasks)
|
@ -0,0 +1,16 @@
|
||||
(ns com.darklimericks.linguistics.core
|
||||
(:require [com.owoga.prhyme.data.dictionary :as dict]
|
||||
[clojure.string :as string]))
|
||||
|
||||
(defn gen-artist []
|
||||
(->> [(rand-nth (seq dict/adverbs))
|
||||
(rand-nth (seq dict/nouns))]
|
||||
(map string/capitalize)
|
||||
(string/join " ")))
|
||||
|
||||
(defn gen-album []
|
||||
(->> [(rand-nth (seq dict/adjectives))
|
||||
(rand-nth (seq dict/nouns))]
|
||||
(map string/capitalize)
|
||||
(string/join " ")))
|
||||
|
@ -0,0 +1,26 @@
|
||||
(ns com.darklimericks.server.core
|
||||
(:gen-class)
|
||||
(:require [integrant.core :as ig]
|
||||
[com.darklimericks.server.system :as system]
|
||||
[reitit.coercion]
|
||||
[reitit.coercion.spec]
|
||||
[taoensso.timbre :as timbre]
|
||||
[clojure.java.io :as io]))
|
||||
|
||||
|
||||
(def a (atom {}))
|
||||
|
||||
(defn -main []
|
||||
(try
|
||||
(let [system (->> "server/config.edn"
|
||||
io/resource
|
||||
slurp
|
||||
ig/read-string
|
||||
ig/prep
|
||||
ig/init)]
|
||||
(timbre/info "Running with config: server/config.edn" )
|
||||
system)
|
||||
(catch Throwable e
|
||||
(.printStackTrace e)
|
||||
(System/exit 1))))
|
||||
|
@ -0,0 +1,6 @@
|
||||
(ns com.darklimericks.server.db
|
||||
(:require [clojure.tools.namespace.repl :as c.t.n.r]))
|
||||
|
||||
(c.t.n.r/disable-unload!)
|
||||
|
||||
(defonce db (atom {}))
|
@ -0,0 +1,55 @@
|
||||
(ns com.darklimericks.server.example
|
||||
(:require [integrant.core :as ig]
|
||||
[clojure.tools.namespace.repl :refer [set-refresh-dirs]]
|
||||
[integrant.repl :as repl]
|
||||
[org.httpkit.server :as kit]
|
||||
[reitit.http :as http]
|
||||
[hiccup.core :as hiccup]
|
||||
[hiccup.page :as page]
|
||||
[taoensso.timbre :as timbre]
|
||||
[reitit.interceptor.sieppari :as sieppari]))
|
||||
|
||||
(defn home []
|
||||
(page/html5
|
||||
[:head
|
||||
[:meta {:charset "utf-8"}]
|
||||
[:meta {:name "viewport" :content "width=device-width, initial-scale=1.0"}]]
|
||||
[:title "Hello World"]
|
||||
[:body "Goodbye, world!"]))
|
||||
|
||||
(defn home-handler [request]
|
||||
{:status 200
|
||||
:headers {"Content-Type" "text/html; charset=utf-8"}
|
||||
:body (hiccup/html (home))})
|
||||
|
||||
(def routes
|
||||
[["/" {:name ::home
|
||||
:get {:handler home-handler}}]])
|
||||
|
||||
(def config
|
||||
{:app/handler {:router (ig/ref :app/router)}
|
||||
:app/router {:routes routes}
|
||||
:app/server {:port 8000 :handler (ig/ref :app/handler)}})
|
||||
|
||||
(defmethod ig/init-key :app/router [_ {:keys [routes]}]
|
||||
(http/router routes))
|
||||
|
||||
(defmethod ig/init-key :app/handler [_ {:keys [router]}]
|
||||
(http/ring-handler router {:executor sieppari/executor}))
|
||||
|
||||
(defmethod ig/init-key :app/server [_ opts]
|
||||
(timbre/info "Starting server with " opts)
|
||||
(kit/run-server (:handler opts) (dissoc opts :handler)))
|
||||
|
||||
(defmethod ig/halt-key! :app/server [_ server]
|
||||
(timbre/info "Stopping server")
|
||||
(server))
|
||||
|
||||
(comment
|
||||
(set-refresh-dirs "src" "dev")
|
||||
(repl/set-prep! (constantly config))
|
||||
(repl/prep)
|
||||
(repl/go)
|
||||
(repl/reset)
|
||||
(repl/halt)
|
||||
)
|
@ -0,0 +1,197 @@
|
||||
(ns com.darklimericks.server.handlers
|
||||
(:require [taoensso.timbre :as timbre]
|
||||
[hiccup.core :as hiccup]
|
||||
[reitit.ring :as ring]
|
||||
[reitit.core :as reitit]
|
||||
[clojure.string :as string]
|
||||
[clojure.core.async :as async]
|
||||
[com.darklimericks.server.util :as util]
|
||||
[com.darklimericks.db.albums :as db.albums]
|
||||
[com.darklimericks.db.limericks :as db.limericks]
|
||||
[com.darklimericks.db.artists :as db.artists]
|
||||
[com.darklimericks.server.views :as views]
|
||||
[com.darklimericks.server.limericks :as limericks]))
|
||||
|
||||
(defn home-handler [request]
|
||||
(timbre/info "home-handler")
|
||||
{:status 200
|
||||
:headers {"Content-Type" "text/html; charset=utf-8"}
|
||||
:body (hiccup/html (views/home request))})
|
||||
|
||||
(def resource-handler (ring/create-resource-handler {:allow-symlinks? true}))
|
||||
|
||||
(defn limerick-generation-post-handler
|
||||
[db cache]
|
||||
(fn [{{:keys [scheme]} :params :as request}]
|
||||
(let [tasks (:tasks @cache)]
|
||||
(let [task-id (java.util.UUID/randomUUID)
|
||||
limerick-chan (limericks/generate-limerick cache scheme task-id)]
|
||||
(swap! cache assoc-in [:tasks task-id] {:status :pending})
|
||||
(async/go
|
||||
(let [limerick (async/<! limerick-chan)
|
||||
[artist-id album-id] (limericks/get-artist-and-album-for-new-limerick db)]
|
||||
(db.limericks/insert-limerick
|
||||
db
|
||||
(limericks/get-limerick-name limerick)
|
||||
(string/join "\n" limerick)
|
||||
album-id))))
|
||||
{:status 301
|
||||
:headers {"Content-Type" "text/html; charset=utf-8"}
|
||||
:body (views/page "Dark Limericks"
|
||||
[:h1 "Creating your limerick..."]
|
||||
[:div "Submission processing..."]
|
||||
[:h1 "Current limericks"]
|
||||
(views/limerick-tasks tasks))})))
|
||||
|
||||
(comment
|
||||
(let [c (async/thread
|
||||
(Thread/sleep 1000)
|
||||
200)]
|
||||
(async/go
|
||||
(let [val (async/<! c)]
|
||||
(println (+ 20 val)))))
|
||||
|
||||
)
|
||||
(defn limericks-get-handler [db cache]
|
||||
(fn [request]
|
||||
(let [artist-id (get-in request [:parameters :path :artist-id])
|
||||
artist (db.artists/artist db artist-id)
|
||||
album-id (get-in request [:parameters :path :album-id])
|
||||
album (db.albums/album db album-id)
|
||||
limericks (db.limericks/album-limericks db album-id)]
|
||||
{:status 200
|
||||
:headers {"Content-Type" "text/html; charset=utf-8"}
|
||||
:body (views/page
|
||||
"Dark Limericks"
|
||||
[:div.flex.items-center.flex-column
|
||||
[:div.f3.pt4.light-yellow
|
||||
(->> artist
|
||||
:artist/name
|
||||
(format "%s LYRICS")
|
||||
(string/upper-case))]
|
||||
[:div.f4.pv3
|
||||
[:strong
|
||||
(->> album
|
||||
:album/name
|
||||
(format "album: %s"))]]
|
||||
(map-indexed
|
||||
(fn [i limerick]
|
||||
(let [i (inc i)
|
||||
limerick-url
|
||||
(format
|
||||
"%s#%s"
|
||||
(util/route-name->path
|
||||
request
|
||||
:com.darklimericks.server.system/album
|
||||
{:artist-id (:artist/id artist)
|
||||
:album-id (:album/id album)})
|
||||
i)]
|
||||
[:a.db.light-yellow
|
||||
{:href limerick-url}
|
||||
(format
|
||||
"%s. %s"
|
||||
i
|
||||
(:limerick/name limerick))]))
|
||||
limericks)
|
||||
(map-indexed
|
||||
(fn [i limerick]
|
||||
(views/limerick i limerick))
|
||||
limericks)
|
||||
[:div.f6.light-yellow.pt4.w-60
|
||||
(string/join
|
||||
" / "
|
||||
["Thanks be to Optimus Prhyme"
|
||||
"by blood honor these words sublime"
|
||||
"sacred heuristics"
|
||||
"glorious limericks"
|
||||
"singularity we climb"])]
|
||||
[:div.f6.washed-yellow.pt3.w-60
|
||||
(string/join
|
||||
" / "
|
||||
["Submissions, comments, corrections"
|
||||
"adorations, exultations, rejections"
|
||||
"all receieved freely"
|
||||
"@owoga.com, eihli"
|
||||
"by Optimus Prhyme's directions"])]])})))
|
||||
|
||||
(defn limerick-generation-get-handler [db cache]
|
||||
(fn [request]
|
||||
(let [tasks (:tasks @cache)]
|
||||
{:status 200
|
||||
:headers {"Content-Type" "text/html; charset=utf-8"}
|
||||
:body (views/page
|
||||
"Dark Limericks"
|
||||
(views/limerick-tasks tasks))})))
|
||||
|
||||
(defn artists-by-letter [db]
|
||||
(fn [{{:keys [letter]} :path-params :as req}]
|
||||
(let [artists (db.artists/artists-by-letter db (string/upper-case letter))]
|
||||
{:status 200
|
||||
:headers {"Content-Type" "text/html; charset=utf-8"}
|
||||
:body (views/page
|
||||
"Dark Limericks"
|
||||
(when artists
|
||||
(let [[right-hand-side-artists
|
||||
left-hand-side-artists]
|
||||
((juxt #(take (quot (inc (count artists)) 2) %)
|
||||
#(drop (quot (inc (count artists)) 2) %))
|
||||
artists)]
|
||||
[:div.bg-near-black.br4.flex
|
||||
[:div.fl.w-50.pa2
|
||||
(for [artist right-hand-side-artists]
|
||||
[:a.washed-yellow
|
||||
{:href (util/route-name->path
|
||||
req
|
||||
:com.darklimericks.server.system/artist
|
||||
{:artist-id (:artist/id artist)})}
|
||||
(:artist/name artist)])]
|
||||
[:div.fl.w-50.pa2
|
||||
(for [artist left-hand-side-artists]
|
||||
[:a.washed-yellow
|
||||
{:href "#"}
|
||||
(:artist/name artist)])]])))})))
|
||||
|
||||
(comment
|
||||
(let [artists ["a"]]
|
||||
(partition 1 1 nil artists))
|
||||
((juxt #(take 1 %) #(drop 1 %)) [1 2 3 4])
|
||||
(into [] (take 1) [1 2 3])
|
||||
)
|
||||
|
||||
(defn artist-get-handler [db]
|
||||
(fn [request]
|
||||
(let [artist-id (get-in request [:parameters :path :artist-id])
|
||||
artist (db.artists/artist db artist-id)
|
||||
albums (db.albums/artist-albums db artist-id)
|
||||
limericks (into {} (map
|
||||
#(vector
|
||||
(:album/id %)
|
||||
(db.limericks/album-limericks db (:album/id %)))
|
||||
albums))]
|
||||
{:status 200
|
||||
:headers {"Content-Type" "text/html; charset=utf-8"}
|
||||
:body (views/page
|
||||
"Dark Limericks"
|
||||
[:div
|
||||
[:div.f3.pt3.light-yellow
|
||||
(->> artist
|
||||
:artist/name
|
||||
(format "%s Lyrics")
|
||||
(string/upper-case))]
|
||||
(for [album albums]
|
||||
[:div
|
||||
[:div.f4.pt3.light-yellow.pb3
|
||||
(->> album
|
||||
:album/name
|
||||
(format "album: \"%s\""))]
|
||||
(let [album-url (util/route-name->path
|
||||
request
|
||||
:com.darklimericks.server.system/album
|
||||
{:artist-id (:artist/id artist)
|
||||
:album-id (:album/id album)})]
|
||||
(map-indexed
|
||||
(fn [index limerick]
|
||||
[:a.f5.washed-yellow.db
|
||||
{:href (format "%s#%s" album-url (inc index))}
|
||||
(:limerick/name limerick)])
|
||||
(get limericks (:album/id album))))])])})))
|
@ -0,0 +1,40 @@
|
||||
(ns com.darklimericks.server.interceptors
|
||||
(:require [reitit.http.interceptors.parameters :refer [parameters-interceptor]]
|
||||
[muuntaja.interceptor :as muuntaja-interceptor]
|
||||
[reitit.interceptor.sieppari :as sieppari]
|
||||
[ring.middleware.keyword-params :refer [keyword-params-request]]
|
||||
[taoensso.timbre :as timbre]
|
||||
[reitit.core :as reitit]
|
||||
[reitit.coercion :as coercion]
|
||||
[reitit.impl :as impl]))
|
||||
|
||||
|
||||
(def format-interceptor (muuntaja-interceptor/format-interceptor))
|
||||
|
||||
(def keywordize-params-interceptor
|
||||
{:enter
|
||||
(fn [{:keys [request] :as ctx}]
|
||||
(update ctx :request keyword-params-request))})
|
||||
|
||||
(def logging-interceptor
|
||||
{:enter (fn [{:keys [request] :as ctx}]
|
||||
(timbre/info
|
||||
(str "Entering " (dissoc request ::reitit/match)))
|
||||
ctx)
|
||||
:exit (fn [{:keys [response] :as ctx}]
|
||||
(timbre/info
|
||||
(str "Exiting " (dissoc response ::reitit/match)))
|
||||
ctx)})
|
||||
|
||||
(def coerce-request-interceptor
|
||||
{:enter
|
||||
(fn [{:keys [request] :as ctx}]
|
||||
(let [{{{:keys [coercion parameters]} :data :as opts} :reitit.core/match} request]
|
||||
(cond
|
||||
(not coercion) ctx
|
||||
(not parameters) ctx
|
||||
:else
|
||||
(if-let [coercers (coercion/request-coercers coercion parameters opts)]
|
||||
(let [coerced (coercion/coerce-request coercers request)]
|
||||
(assoc-in ctx [:request :parameters] coerced))
|
||||
ctx))))})
|
@ -0,0 +1,104 @@
|
||||
(ns com.darklimericks.server.limericks
|
||||
(:require [clojure.string :as string]
|
||||
[clojure.core.async :as async]
|
||||
[reitit.core :as reitit]
|
||||
[com.darklimericks.db.artists :as artists]
|
||||
[com.darklimericks.db.albums :as albums]
|
||||
[com.darklimericks.db.limericks :as limericks]
|
||||
[com.darklimericks.linguistics.core :as linguistics]
|
||||
[com.owoga.prhyme.limerick :as limerick]
|
||||
[com.owoga.prhyme.data.dictionary :as dict]
|
||||
[com.owoga.prhyme.data.darklyrics :refer [darklyrics-markov-2]]))
|
||||
|
||||
(defn parse-scheme-element [[tokens ctx]]
|
||||
(cond
|
||||
(or (= 5 (count tokens))
|
||||
(= 3 (count tokens)))
|
||||
(do (assert (re-matches #"\w+\d" (first tokens))
|
||||
"Expected a letter followed by an integer.")
|
||||
(let [text (re-find #"[a-zA-Z]+" (first tokens))
|
||||
count (Integer/parseInt (re-find #"\d+" (first tokens)))]
|
||||
[(rest tokens) (conj ctx [text count])]))
|
||||
|
||||
(or (= 4 (count tokens))
|
||||
(= 1 (count tokens)))
|
||||
(do (assert (= (first tokens) (apply str (first ctx))) (format "Expected %s" (first ctx)))
|
||||
[(rest tokens) ctx])
|
||||
|
||||
(= 2 (count tokens))
|
||||
(do (assert (= (first tokens) (apply str (second ctx))) (format "Expected %s" (second ctx)))
|
||||
[(rest tokens) ctx])
|
||||
|
||||
:else
|
||||
(throw (ex-info "Invalid scheme" {:scheme tokens}))))
|
||||
|
||||
(comment
|
||||
(parse-scheme-element [["A9" "A9" "B5" "B5" "A9"] []]))
|
||||
|
||||
(defn parse-scheme [scheme-string]
|
||||
(-> (string/split scheme-string #"\s+")
|
||||
(#(vector % []))
|
||||
(parse-scheme-element)
|
||||
(parse-scheme-element)
|
||||
(parse-scheme-element)
|
||||
(parse-scheme-element)
|
||||
(parse-scheme-element)
|
||||
((fn [[tokens [a b]]]
|
||||
[a a b b a]))))
|
||||
|
||||
(comment
|
||||
(re-find #"\d+" "alpha 234 aset 34a")
|
||||
(re-find #"[a-zA-Z]+" "apla352 spac")
|
||||
(parse-scheme "a9 a9 b6 b6 a9")
|
||||
(require '[integrant.repl.state :as state])
|
||||
(reitit/match-by-path (:app/router state/system) "/limerick-generation-task")
|
||||
((:app/handler state/system) {:request-method :post
|
||||
:uri "/limerick-generation-task"
|
||||
:body-params {:scheme 23}})
|
||||
)
|
||||
|
||||
(defn get-artist-and-album-for-new-limerick [db]
|
||||
(let [artist (artists/most-recent-artist db)
|
||||
albums (albums/artist-albums db (:artist/id artist))
|
||||
limericks (limericks/album-limericks db (:album/id (first albums)))]
|
||||
(cond
|
||||
(< (count limericks) 10)
|
||||
[(:artist/id artist) (:album/id (first albums))]
|
||||
|
||||
(< (count albums) 5)
|
||||
(let [album-name (linguistics/gen-album)
|
||||
{album-id :album/id} (albums/insert-album db album-name (:id artist))]
|
||||
[(:id artist) album-id])
|
||||
|
||||
:else
|
||||
(let [artist-name (linguistics/gen-artist)
|
||||
{artist-id :artist/id} (artists/insert-artist db artist-name)
|
||||
album-name (linguistics/gen-album)
|
||||
{album-id :album/id} (albums/insert-album db album-name artist-id)]
|
||||
[artist-id album-id]))))
|
||||
|
||||
(defn get-limerick-name [lines]
|
||||
(->> lines
|
||||
(string/join " ")
|
||||
(#(string/split % #"\s+"))
|
||||
shuffle
|
||||
(take 2)
|
||||
(map string/capitalize)
|
||||
(string/join " ")))
|
||||
|
||||
(defn generate-limerick [db scheme task-id]
|
||||
(let [scheme (parse-scheme scheme)]
|
||||
(async/thread
|
||||
(try
|
||||
(let [rhyme-id (java.util.UUID/randomUUID)
|
||||
rhyme (limerick/rhyme-from-scheme
|
||||
dict/prhyme-dict
|
||||
darklyrics-markov-2
|
||||
scheme)]
|
||||
(swap! db update-in [:tasks task-id] assoc :status :finished)
|
||||
(swap! db assoc-in [:limericks rhyme-id] {:text rhyme :task-id task-id})
|
||||
rhyme)
|
||||
(catch Throwable e
|
||||
(println "Exception" e)
|
||||
(swap! db update-in [:tasks task-id] assoc :status :failed)
|
||||
(swap! db assoc-in [:tasks task-id] {:text (.getMessage e)}))))))
|
@ -0,0 +1,84 @@
|
||||
(ns com.darklimericks.server.system
|
||||
(:require [integrant.core :as ig]
|
||||
[clojure.java.io :as io]
|
||||
[taoensso.timbre :as timbre]
|
||||
[org.httpkit.server :as kit]
|
||||
[next.jdbc :as jdbc]
|
||||
[environ.core :refer [env]]
|
||||
[reitit.http :as http]
|
||||
[reitit.ring :as ring]
|
||||
[reitit.coercion.spec]
|
||||
[reitit.ring.coercion :as coercion]
|
||||
[reitit.interceptor.sieppari :as sieppari]
|
||||
[reitit.http.interceptors.parameters :refer [parameters-interceptor]]
|
||||
[com.darklimericks.server.handlers :as handlers]
|
||||
[com.darklimericks.server.interceptors :as interceptors]
|
||||
[com.darklimericks.server.db :as db]))
|
||||
|
||||
(defmethod ig/prep-key :database.sql/connection [_ _]
|
||||
{:jdbcUrl (str "jdbc:postgresql://localhost:5432/?user="
|
||||
(or (env :postgres-user)
|
||||
"dev")
|
||||
"&password="
|
||||
(or (env :postgres-password)
|
||||
"dev"))})
|
||||
|
||||
(defmethod ig/init-key :database.sql/connection [_ db-spec]
|
||||
(jdbc/get-datasource db-spec))
|
||||
|
||||
(defmethod ig/init-key :app/logging [_ config]
|
||||
(timbre/merge-config! config))
|
||||
|
||||
(defmethod ig/halt-key! :app/logging [_ _])
|
||||
|
||||
(defmethod ig/init-key :app/cache [_ _]
|
||||
(timbre/debug "Initializing cache.")
|
||||
db/db)
|
||||
|
||||
(defmethod ig/halt-key! :app/db [_ db]
|
||||
#_(reset! db {}))
|
||||
|
||||
(defmethod ig/init-key :app/router [_ {:keys [db cache]}]
|
||||
(let [routes [["/" {:name ::home
|
||||
:get {:handler handlers/home-handler}}]
|
||||
["/{letter}.html"
|
||||
{:name ::artists-by-letter
|
||||
:handler (handlers/artists-by-letter db)}]
|
||||
["/limerick-generation-task"
|
||||
{:name ::limerick-generation-task
|
||||
:post {:handler (handlers/limerick-generation-post-handler db cache)}
|
||||
:get {:handler (handlers/limerick-generation-get-handler db cache)}}]
|
||||
["/limericks"
|
||||
["/:artist-id/:album-id"
|
||||
{:name ::album
|
||||
:coercion reitit.coercion.spec/coercion
|
||||
:parameters {:path {:artist-id int?
|
||||
:album-id int?}}
|
||||
:get {:handler (handlers/limericks-get-handler db cache)}}]
|
||||
["/:artist-id"
|
||||
{:name ::artist
|
||||
:coercion reitit.coercion.spec/coercion
|
||||
:parameters {:path {:artist-id int?}}
|
||||
:get {:handler (handlers/artist-get-handler db)}}]]
|
||||
["/assets/*" handlers/resource-handler]]]
|
||||
(http/router
|
||||
routes
|
||||
{:data {:interceptors [interceptors/coerce-request-interceptor
|
||||
interceptors/logging-interceptor
|
||||
interceptors/format-interceptor
|
||||
(parameters-interceptor)
|
||||
interceptors/keywordize-params-interceptor]}})))
|
||||
|
||||
(defmethod ig/init-key :app/handler [_ {:keys [router]}]
|
||||
(http/ring-handler
|
||||
router
|
||||
(ring/create-default-handler)
|
||||
{:executor sieppari/executor}))
|
||||
|
||||
(defmethod ig/init-key :app/server [_ {:keys [handler] :as opts}]
|
||||
(timbre/info (format "Starting server on port %d" (:port opts)))
|
||||
(kit/run-server handler (dissoc opts :handler)))
|
||||
|
||||
(defmethod ig/halt-key! :app/server [_ server]
|
||||
(timbre/info "Stopping server.")
|
||||
(server))
|
@ -0,0 +1,10 @@
|
||||
(ns com.darklimericks.server.util
|
||||
(:require [reitit.core :as reitit]))
|
||||
|
||||
(defn route-name->path
|
||||
([request name]
|
||||
(route-name->path request name {}))
|
||||
([request name params]
|
||||
(->> name
|
||||
(#(reitit/match-by-name (::reitit/router request) % params))
|
||||
reitit/match->path)))
|
@ -0,0 +1,89 @@
|
||||
(ns com.darklimericks.server.views
|
||||
(:require [hiccup.form :as form]
|
||||
[hiccup.page :as page]
|
||||
[clojure.string :as string]
|
||||
[com.darklimericks.server.util :as util]))
|
||||
|
||||
(defn page [title & body]
|
||||
(page/html5
|
||||
[:head
|
||||
[:meta {:charset "utf-8"}]
|
||||
[:meta {:name "viewport" :content "width=device-width, initial-scale=1.0"}]
|
||||
(page/include-css "/assets/tachyons.css")
|
||||
[:title title]
|
||||
[:link {:rel "shortcut icon" :href "/assets/favicon.ico"}]]
|
||||
[:body.tc.washed-yellow.bg-near-black.avenir
|
||||
[:h1
|
||||
[:a.link.dim.washed-yellow {:href "/"} "DarkLimericks.com"]]
|
||||
[:div.w-50-ns.w-90.center.bg-dark-gray.pa2
|
||||
[:div.f6.lh-copy.flex.justify-between
|
||||
[:a.washed-yellow {:href "#"} "SUBMIT LIMERICKS"]
|
||||
[:span.dark-yellow "METAL LIMERICKS - CURRENTLY 0 ALBUMS FROM 0+ BANDS"]
|
||||
[:a.washed-yellow {:href "#"} "LINKS"]]
|
||||
[:div.flex.items-stretch.w-100.bg-near-black
|
||||
(let [letters (map (comp str char) (range 97 123))]
|
||||
(for [letter letters]
|
||||
[:a.washed-yellow.bg-mid-gray.w-100.pv2.flex-grow-1
|
||||
{:href (format "/%s.html" letter)
|
||||
:style "margin: 1px;"}
|
||||
(string/upper-case letter)]))]
|
||||
[:div.flex.items-center.justify-center.pv2
|
||||
[:span.f6.ph2 "Search the darkness for limericks heartless"]
|
||||
[:form.ph2
|
||||
{:method "GET" :action "#"}
|
||||
[:input.bg-white
|
||||
{:type "text"
|
||||
:name "search"
|
||||
:id "search"
|
||||
:value ""}]]]
|
||||
[:div.bg-near-black.br4.pa2
|
||||
body]
|
||||
|
||||
[:div.flex.items-center.justify-center.pv2
|
||||
[:span.f6.ph2 "Search the darkness for limericks heartless"]
|
||||
[:form.ph2
|
||||
{:method "GET" :action "#"}
|
||||
[:input.bg-white
|
||||
{:type "text"
|
||||
:name "search"
|
||||
:id "search"
|
||||
:value ""}]]]
|
||||
|
||||
[:div.flex.items-stretch.w-100.bg-near-black
|
||||
(let [letters (map (comp str char) (range 97 123))]
|
||||
(for [letter letters]
|
||||
[:a.washed-yellow.bg-mid-gray.w-100.pv2.flex-grow-1
|
||||
{:href (format "/%s.html" letter)
|
||||
:style "margin: 1px;"}
|
||||
(string/upper-case letter)]))]
|
||||
|
||||
[:div.f6.lh-copy.flex.justify-between
|
||||
[:a.washed-yellow {:href "#"} "SUBMIT LIMERICKS"]
|
||||
[:span.dark-yellow "METAL LIMERICKS - CURRENTLY 0 ALBUMS FROM 0+ BANDS"]
|
||||
[:a.washed-yellow {:href "#"} "LINKS"]]]]))
|
||||
|
||||
(defn home [request]
|
||||
(page "Dark Limericks"
|
||||
(form/form-to
|
||||
[:post (util/route-name->path request :com.darklimericks.server.system/limerick-generation-task)]
|
||||
(form/text-field "scheme")
|
||||
(form/submit-button "Generate dark limerick"))))
|
||||
|
||||
(defn limerick-tasks [tasks]
|
||||
[:ul
|
||||
(for [[task-id task] tasks]
|
||||
[:li (format
|
||||
"%s - %s"
|
||||
task-id
|
||||
(if (:rhyme task)
|
||||
(string/join " / " (:rhyme task))
|
||||
(:status task)))])])
|
||||
|
||||
(defn limerick [i lim]
|
||||
(let [lines (string/split (:limerick/text lim) #"\n")
|
||||
name (:limerick/name lim)]
|
||||
[:div.tc
|
||||
[:a {:name (inc i)}
|
||||
[:h3.f3.washed-yellow (format "%s. %s" (inc i) name)]]
|
||||
(for [line lines]
|
||||
[:div line])]))
|
Loading…
Reference in New Issue