aboutsummaryrefslogtreecommitdiff
path: root/src/dionysus/web
diff options
context:
space:
mode:
Diffstat (limited to 'src/dionysus/web')
-rw-r--r--src/dionysus/web/admin.clj45
-rw-r--r--src/dionysus/web/admin/home.clj32
-rw-r--r--src/dionysus/web/admin/home/blacklist.clj106
-rw-r--r--src/dionysus/web/admin/home/server_settings.clj25
-rw-r--r--src/dionysus/web/enduser.clj35
-rw-r--r--src/dionysus/web/enduser/home.clj53
-rw-r--r--src/dionysus/web/enduser/home/search.clj67
-rw-r--r--src/dionysus/web/utils.clj22
8 files changed, 385 insertions, 0 deletions
diff --git a/src/dionysus/web/admin.clj b/src/dionysus/web/admin.clj
new file mode 100644
index 0000000..20c7409
--- /dev/null
+++ b/src/dionysus/web/admin.clj
@@ -0,0 +1,45 @@
+(ns dionysus.web.admin
+ (:require [reitit.ring :as rring]
+ [org.httpkit.server :as http-server]
+ [ring.middleware.oauth2 :as rmoauth2]
+ [dotenv :as env]
+ [ring.middleware.params :as rmparams]
+ [ring.middleware.session :as rmsessions]
+
+ [dionysus.web.admin.home :as dwahome]
+ [dionysus.web.admin.home.blacklist :as dwahblacklist]
+ [dionysus.web.admin.home.server-settings :as dwahserver-settings]))
+
+(def ^:private routes
+ [["/" {:get {:handler dwahome/handle}}]
+ ["/blacklist"
+ ["/" {:get {:handler dwahblacklist/handle}}]
+ ["/add" {:post {:handler dwahblacklist/handle-add}}]
+ ["/change/:index" {:post {:handler dwahblacklist/handle-change}}]
+ ["/delete/:index" {:delete {:handler dwahblacklist/handle-delete}}]]
+ ["/server-settings" {:post {:handler dwahserver-settings/handle}}]
+ ["/assets/*" (rring/create-resource-handler)]])
+
+(def ^:private handler
+ (-> routes
+ rring/router
+ (rring/ring-handler (rring/redirect-trailing-slash-handler))
+ (rmoauth2/wrap-oauth2 {:spotify {:authorize-uri "https://accounts.spotify.com/authorize"
+ :access-token-uri "https://accounts.spotify.com/api/token"
+ :client-id (env/env "SPOTIFY_CLIENT_ID")
+ :client-secret (env/env "SPOTIFY_CLIENT_SECRET")
+ :scopes ["user-read-private"
+ "user-modify-playback-state"
+ "user-read-currently-playing"]
+ :launch-uri "/auth"
+ :redirect-uri "/callback"
+ :landing-uri "/"}})
+ rmsessions/wrap-session
+ rmparams/wrap-params))
+
+(def ^:private stop-fn (atom nil))
+
+(defn start-server! []
+ (when (fn? @stop-fn) (@stop-fn))
+ (reset! stop-fn (http-server/run-server handler {:port (or (env/env "ADMIN_PORT") 8081)})))
+#_(start-server!)
diff --git a/src/dionysus/web/admin/home.clj b/src/dionysus/web/admin/home.clj
new file mode 100644
index 0000000..a06a4ff
--- /dev/null
+++ b/src/dionysus/web/admin/home.clj
@@ -0,0 +1,32 @@
+(ns dionysus.web.admin.home
+ (:require [dionysus.spotify :as dspotify]
+ [dionysus.web.utils :as dwutils]
+ [ring.util.response :as ruresp]
+ [dionysus.web.admin.home.blacklist :as dwahblacklist]
+ [dionysus.web.admin.home.server-settings :as dwahserver-settings]))
+
+(defn- render [_req]
+ (let [title (str @dwutils/title " - ADMIN")]
+ (dwutils/render-page title
+ [:div {:class "text"}
+ [:h1 title]
+ (dwahserver-settings/render-server-settings)
+ [:h2 "Blacklist"]
+ (dwahblacklist/render-blacklist-table nil)]
+ [:script {:src "/assets/htmx.js"}])))
+
+(defn handle [req]
+ (let [access-token (get-in req [:oauth2/access-tokens :spotify])
+ page-resp (delay (-> req
+ render
+ ruresp/response))]
+ (cond
+ (and (nil? access-token) (nil? @dspotify/token))
+ (ruresp/redirect "/auth")
+
+ (nil? @dspotify/token)
+ (do (reset! dspotify/token access-token)
+ @page-resp)
+
+ :else
+ @page-resp)))
diff --git a/src/dionysus/web/admin/home/blacklist.clj b/src/dionysus/web/admin/home/blacklist.clj
new file mode 100644
index 0000000..81ac39d
--- /dev/null
+++ b/src/dionysus/web/admin/home/blacklist.clj
@@ -0,0 +1,106 @@
+(ns dionysus.web.admin.home.blacklist
+ (:require [clojure.string :as cstr]
+ [dionysus.blacklist :as dblacklist]
+ [dionysus.spotify :as dspotify]
+ [dionysus.web.utils :as dwutils]
+ [ring.util.response :as ruresp]))
+
+(defn- blacklist-table-item [index edited-item]
+ (let [item (nth @dblacklist/blacklist index)
+ editor? (= (str index) edited-item)]
+ [:tr
+ [:td (when-not editor? (cstr/upper-case (:type item)))]
+ [:td (if editor?
+ [:input {:type :text :name "link" :value (:url item)}]
+ [:a {:href (:url item) :target "_blank"}
+ (condp = (:type item)
+ "track"
+ (-> (:id item)
+ dspotify/get-track!
+ :name)
+
+ "artist"
+ (-> (:id item)
+ dspotify/get-artist!
+ :name)
+
+ nil)])]
+ (if editor?
+ [:td
+ [:img {:src "/assets/icons/check.svg"
+ :class "icon"
+ :hx-trigger "click"
+ :hx-post (str "/blacklist/change/" index)
+ :hx-target "#blacklist"
+ :hx-swap "outerHTML"
+ :hx-include "previous input"}]
+ [:img {:src "/assets/icons/trash.svg"
+ :class "icon"
+ :hx-trigger "click"
+ :hx-delete (str "/blacklist/delete/" index)
+ :hx-target "#blacklist"
+ :hx-swap "outerHTML"}]]
+ [:td
+ [:img {:src "/assets/icons/edit.svg"
+ :class "icon"
+ :hx-trigger "click"
+ :hx-get (str "/blacklist?editing=" index)
+ :hx-target "#blacklist"
+ :hx-swap "outerHTML"}]])]))
+
+(defn render-blacklist-table [edited-item]
+ [:table {:id "blacklist"}
+ [:thead
+ [:tr
+ [:th "Typ"]
+ [:th "Link"]
+ [:th "Aktionen"]]]
+ [:tbody
+ (for [index (-> @dblacklist/blacklist
+ .length
+ range)]
+ (blacklist-table-item index edited-item))
+ [:tr
+ [:td]
+ [:td [:input {:type :text
+ :placeholder "Link to track or artist"
+ :name "link"}]]
+ [:td [:img {:src "/assets/icons/plus.svg"
+ :class "icon"
+ :hx-trigger "click"
+ :hx-post "/blacklist/add"
+ :hx-target "#blacklist"
+ :hx-swap "outerHTML"
+ :hx-include "previous input"}]]]]])
+
+(defn handle [req]
+ (-> (get-in req [:query-params "editing"])
+ render-blacklist-table
+ dwutils/render-html
+ ruresp/response))
+
+(defn handle-add [req]
+ (-> req
+ (get-in [:form-params "link"])
+ dblacklist/add)
+ (handle {}))
+
+(defn- valid-index? [s]
+ (and (string? s) (string? (re-matches #"[0-9][0-9]*" s))))
+
+(defn handle-change [req]
+ (let [index (get-in req [:path-params :index])
+ link (get-in req [:form-params "link"])]
+ (if (and (valid-index? index) (string? link))
+ (do (-> index
+ Integer/parseInt
+ (dblacklist/change link))
+ (handle {}))
+ (ruresp/bad-request "Bad request."))))
+
+(defn handle-delete [req]
+ (let [index (get-in req [:path-params :index])]
+ (if (valid-index? index)
+ (do (dblacklist/delete (Integer/parseInt index))
+ (handle {}))
+ (ruresp/bad-request "Bad request."))))
diff --git a/src/dionysus/web/admin/home/server_settings.clj b/src/dionysus/web/admin/home/server_settings.clj
new file mode 100644
index 0000000..e4a8d3f
--- /dev/null
+++ b/src/dionysus/web/admin/home/server_settings.clj
@@ -0,0 +1,25 @@
+(ns dionysus.web.admin.home.server-settings
+ (:require [ring.util.response :as ruresp]
+ [dionysus.web.utils :as dwutils]
+ [dionysus.web.enduser :as dwenduser]))
+
+(defn render-server-settings []
+ [:div {:id "server-settings"}
+ [:input {:type :checkbox
+ :name "enduser-ui"
+ :style {:display :inline-block}
+ :checked (dwenduser/server-runs?)
+ :hx-trigger "input"
+ :hx-post "/server-settings"
+ :hx-swap "outerHTML"
+ :hx-target "#server-settings"}]
+ [:p {:style {:display :inline-block
+ :margin-right "1ch"}} "Gäste UI"]])
+
+(defn handle [req]
+ (if (= (get-in req [:form-params "enduser-ui"]) "on")
+ (dwenduser/start-server!)
+ (dwenduser/stop-server!))
+ (-> (render-server-settings)
+ dwutils/render-html
+ ruresp/response))
diff --git a/src/dionysus/web/enduser.clj b/src/dionysus/web/enduser.clj
new file mode 100644
index 0000000..90b661b
--- /dev/null
+++ b/src/dionysus/web/enduser.clj
@@ -0,0 +1,35 @@
+(ns dionysus.web.enduser
+ (:require [dotenv :as env]
+ [org.httpkit.server :as http-server]
+ [reitit.ring :as rring]
+
+ [dionysus.web.enduser.home :as dwehome]
+ [dionysus.web.enduser.home.search :as dwehsearch]
+ [ring.middleware.params :as rmparams]))
+
+(def ^:private routes
+ [["/" {:get {:handler dwehome/handle}}]
+ ["/search"
+ ["/" {:get {:handler dwehsearch/handle-search}}]
+ ["/add/:uri/" {:post {:handler dwehsearch/handle-search-add}}]]
+ ["/current-track/" {:get {:handler dwehome/handle-current-track}}]
+ ["/assets/*" (rring/create-resource-handler)]])
+
+(def ^:private handler
+ (-> routes
+ rring/router
+ (rring/ring-handler (rring/redirect-trailing-slash-handler))
+ rmparams/wrap-params))
+
+(def ^:private stop-fn (atom nil))
+(defn server-runs? [] (fn? @stop-fn))
+
+(defn start-server! []
+ (when (server-runs?) (@stop-fn))
+ (reset! stop-fn (http-server/run-server handler {:port (or (env/env "ENDUSER_PORT") 8080)})))
+#_(start-server!)
+
+(defn stop-server! []
+ (when (server-runs?)
+ (@stop-fn)
+ (reset! stop-fn nil)))
diff --git a/src/dionysus/web/enduser/home.clj b/src/dionysus/web/enduser/home.clj
new file mode 100644
index 0000000..69d8ece
--- /dev/null
+++ b/src/dionysus/web/enduser/home.clj
@@ -0,0 +1,53 @@
+(ns dionysus.web.enduser.home
+ (:require [dionysus.web.utils :as dwutils]
+ [ring.util.response :as ruresp]
+ [dionysus.spotify :as dspotify]
+ [clojure.string :as cstr]
+ [dionysus.web.enduser.home.search :as dwehsearch]))
+
+(defn- render-current-track [_req]
+ (let [track (:item (dspotify/get-current-track!))]
+ [:div {:id "current-track"
+ :class "card"
+ :hx-swap "outerHTML"
+ :hx-get "/current-track/"
+ :hx-trigger "every 10s"}
+ [:img {:class "album-image"
+ :src (get-in track [:album :images 0 :url])
+ :width "130em"}]
+ [:div
+ [:b (:name track)]
+ [:p (str "Von " (cstr/join ", " (map :name (:artists track))))]
+ [:a {:href (get-in track [:external_urls :spotify])
+ :target "_blank"} "Auf Spotify anschauen"]]]))
+
+(defn handle-current-track [req]
+ (-> req
+ render-current-track
+ dwutils/render-html
+ ruresp/response))
+
+(defn- render [_req]
+ (dwutils/render-page (str @dwutils/title " - Home")
+ [:div {:class "text"}
+ [:h1 @dwutils/title]
+ [:p "Füge hier Lieder zur Warteschlange hinzu!"]]
+ [:input {:id "search-bar"
+ :type "text"
+ :placeholder "Suche nach einem Lied:"
+ :name "query"
+ :hx-get "/search/"
+ :hx-swap "innerHTML"
+ :hx-target "#search-results"
+ :hx-trigger "change"}]
+ [:div {:id "search-results"}
+ (dwehsearch/render-search nil)]
+ [:h2 {:class "text"}
+ "Aktuelles Lied"]
+ (render-current-track nil)
+ [:script {:src "/assets/htmx.js"}]))
+
+(defn handle [req]
+ (-> req
+ render
+ ruresp/response))
diff --git a/src/dionysus/web/enduser/home/search.clj b/src/dionysus/web/enduser/home/search.clj
new file mode 100644
index 0000000..cea539c
--- /dev/null
+++ b/src/dionysus/web/enduser/home/search.clj
@@ -0,0 +1,67 @@
+(ns dionysus.web.enduser.home.search
+ (:require [clojure.string :as cstr]
+ [dionysus.blacklist :as dblacklist]
+ [dionysus.spotify :as dspotify]
+ [dionysus.web.utils :as dwutils]
+ [ring.util.response :as ruresp]))
+
+(defn- search-item [track]
+ [:div {:class ["card" "search-item"]}
+ [:img {:class "album-image"
+ :src (get-in track [:album :images 0 :url])
+ :width "130em"}]
+ [:div
+ [:b (:name track)]
+ [:p (str "Von " (cstr/join ", " (map :name (:artists track))))]]
+ [:img {:class "track-action-icon"
+ :src "/assets/icons/plus.svg"
+ :hx-post (str "/search/add/" (:uri track) "/")
+ :hx-trigger "click"
+ :hx-swap "outerHTML"
+ :hx-target "this"
+ :height "60em"
+ :alt "plus icon"
+ :title "Lied zur Warteschlange hinzufügen"}]])
+
+(defn render-search [req]
+ (let [query (get-in req [:query-params "query"])
+ tracks (-> (dspotify/search! query)
+ (get-in [:tracks :items])
+ delay)]
+ (cond
+ (cstr/blank? query)
+ [:i "Nutze die Suchleiste oben, um nach einem Lied zu suchen oder füge den Link zu dem Lied ein."]
+
+ (nil? @tracks)
+ [:b "An error occurred."]
+
+ :else
+ (for [track @tracks]
+ (search-item track)))))
+
+(defn handle-search [req]
+ (-> req
+ render-search
+ dwutils/render-html
+ ruresp/response))
+
+(defn handle-search-add [req]
+ (let [uri (get-in req [:path-params :uri])
+ on-blacklist? (-> uri
+ (cstr/split #":")
+ last
+ dblacklist/on-blacklist?)]
+ (when on-blacklist? (dspotify/add-item-to-queue! uri))
+ (-> [:img {:class "track-action-icon"
+ :src (if on-blacklist?
+ "/assets/icons/warning.svg"
+ "/assets/icons/check.svg")
+ :alt (if on-blacklist?
+ "warn dreieck icon"
+ "haken icon")
+ :title (if on-blacklist?
+ "Lied oder Künstler ist auf der Blacklist"
+ "Lied wurde zur Warteschlange hinzugefügt")
+ :height "60em"}]
+ dwutils/render-html
+ ruresp/response)))
diff --git a/src/dionysus/web/utils.clj b/src/dionysus/web/utils.clj
new file mode 100644
index 0000000..771bd6d
--- /dev/null
+++ b/src/dionysus/web/utils.clj
@@ -0,0 +1,22 @@
+(ns dionysus.web.utils
+ (:require [hiccup2.core :as html]
+ [dotenv :as env]))
+
+(def title (delay (or (env/env "TITLE") "Dionysus")))
+
+(defn render-html [src]
+ (-> src
+ html/html
+ str))
+
+(defn render-page [title & src]
+ (-> [:html {:lang "de"}
+ [:head
+ [:meta {:charset "UTF-8"}]
+ [:meta {:name "viewport" :content "width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"}]
+ [:link {:rel "stylesheet" :href "/assets/style.css"}]
+ [:title title]]
+ (-> src
+ (conj :body)
+ vec)]
+ render-html))