add cuddle requests with colored tables and accept/decline system

This commit is contained in:
flynn 2019-01-15 05:07:35 +01:00
parent 4082fd0a36
commit ae5a0ac0ae
11 changed files with 230 additions and 47 deletions

View file

@ -24,6 +24,17 @@ To start the ui live rendering, run:
lein figwheel lein figwheel
## Development
### Database
* ENUM TYPE
Because of the lack of typing in clojure and the forced typing of the jdbc driver
we need to manually manage conversion of enum types to clojure keywords.
When adding an enum to the database, make sure to add it to the '+schema-enums+' set [src/clj/cat/db/core.clj]
## License ## License
Copyright © 2019 FIXME Copyright © 2019 FIXME

View file

@ -101,8 +101,8 @@
{% if user %} {% if user %}
<section class="section"> <section class="section">
<div class="columns"> <div class="columns">
<div class="column"> <div class="column is-narrow">
<h3 class="title is-3">Your cuddles</h3> <h3 class="subtitle is-3">Your cuddles</h3>
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
@ -121,11 +121,9 @@
</table> </table>
</div> </div>
<div class="column"> <div class="column">
<h3 class="title is-3">Request cuddles!</h3>
<form action="/request_relation" method="post"> <form action="/request_relation" method="post">
{% csrf-field %} {% csrf-field %}
<div class="field"> <div class="field has-addons">
<label for="to_id">Person to</label>
<div class="control"> <div class="control">
<div class="select"> <div class="select">
<select name="to_id" id="to_id"> <select name="to_id" id="to_id">
@ -135,25 +133,71 @@
</select> </select>
</div> </div>
</div> </div>
</div>
<div class="field">
<div class="control"> <div class="control">
<input class="button is-link" type="submit" value="Submit"> <input class="button is-link" type="submit" value="Request cuddle!">
</div> </div>
</div> </div>
</form> </form>
<table class="table"> <div class="columns">
<thead> <div class="column">
<tr> <table class="table">
<th>Outgoing requests</th> <thead>
</tr> <tr>
</thead> <th>Outgoing requests</th>
{% for rel_req in user-relation-requests %} <th>Status</th>
<tr> </tr>
<td>{{rel_req.name}}</td> </thead>
</tr> {% for rr in rel-requests-out %}
{% endfor %} <tr>
</table> <td>{{rr.to_name}}
</td>
<td>
{% include "parts/colored-status.html" %}
</td>
</tr>
{% endfor %}
</table>
</div>
<div class="column">
<table class="table">
<thead>
<tr>
<th>Incoming requests</th>
<th colspan="2">Status</th>
</tr>
</thead>
{% for rr in rel-requests-in %}
<tr>
<td>
<span>{{rr.from_name}}</span>
</td>
<td {% ifunequal rr.status "open" %}colspan='2'{% endifunequal %}>
{% include "parts/colored-status.html" %}
</td>
{% ifequal rr.status "open" %}
<td>
<form action="/relation_request/{{rr.rr_id}}/status" method="post">
{% csrf-field %}
<div class="field has-addons is-right">
<div class="control">
<button type="submit" name="accept"
class="button is-success is-small is-rounded is-outlined">Accept
</button>
</div>
<div class="control">
<button type="submit" name="decline"
class="button is-danger is-small is-rounded is-outlined">Decline
</button>
</div>
</div>
</form>
</td>
{% endifequal %}
</tr>
{% endfor %}
</table>
</div>
</div>
</div> </div>
</div> </div>
</section> </section>
@ -182,7 +226,7 @@
</div> </div>
<div class="field"> <div class="field">
<div class="control"> <div class="control">
<input class="button is-link" type="submit" value="Submit"> <input class="button is-link" type="submit" value="Add!">
</div> </div>
</div> </div>
</form> </form>
@ -222,8 +266,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="field"> <div class="field has-addons">
<label for="to">Person to</label>
<div class="control"> <div class="control">
<div class="select"> <div class="select">
<select name="to_id" id="to"> <select name="to_id" id="to">
@ -233,10 +276,8 @@
</select> </select>
</div> </div>
</div> </div>
</div>
<div class="field">
<div class="control"> <div class="control">
<input class="button is-link" type="submit" value="Submit"> <input class="button is-link" type="submit" value="Add!">
</div> </div>
</div> </div>
</form> </form>

View file

@ -0,0 +1,12 @@
<span class='
{% ifequal rr.status "open" %}
has-text-info
{% else %}
{% ifequal rr.status "accepted" %}
has-text-success
{% else %}
has-text-danger
{% endifequal %}
{% endifequal %}'>
{{rr.status}}
</span>

View file

@ -0,0 +1 @@
DROP TYPE STATUS;

View file

@ -0,0 +1,2 @@
CREATE TYPE STATUS as ENUM
('open', 'accepted', 'declined');

View file

@ -0,0 +1 @@
DROP TABLE relation_requests;

View file

@ -0,0 +1,7 @@
CREATE TABLE relation_requests
(id SERIAL PRIMARY KEY,
from_id INTEGER NOT NULL,
to_id INTEGER NOT NULL,
status STATUS NOT NULL,
FOREIGN KEY (from_id) REFERENCES users (id),
FOREIGN KEY (to_id) REFERENCES users (id));

View file

@ -36,4 +36,38 @@ VALUES (:from_id, :to_id)
-- :doc retrieves all relations -- :doc retrieves all relations
SELECT * FROM relations SELECT * FROM relations
JOIN users u_from on relations.from_id = u_from.id JOIN users u_from on relations.from_id = u_from.id
JOIN users u_to on relations.to_id = u_to.id JOIN users u_to on relations.to_id = u_to.id
/*
-------------------------------
RELATION REQUESTS
*/
-- :name create-relation-request! :!
-- :doc adds a request for a relation from a user to another user
INSERT INTO relation_requests
(from_id, to_id, status)
VALUES (:from_id, :to_id, :status)
-- :name update-relation-request-status! :! :n
-- :doc updates an existing relation record
UPDATE relation_requests
SET status = :status
WHERE id = :id
-- :name get-relation-request :? :1
-- :doc retrieves one relation request on id
SELECT * FROM relation_requests
WHERE id = :id
-- :name get-relation-requests-from-user :? :*
-- :doc retrieves all relations requests that a user made
SELECT rr.id as rr_id, rr.status, u_to.name as to_name FROM relation_requests as rr
JOIN users u_to on rr.to_id = u_to.id
WHERE from_id = :from_id
-- :name get-relation-requests-to-user :? :*
-- :doc retrieves all relations requests send to a user
SELECT rr.id as rr_id, rr.status, u_from.name as from_name FROM relation_requests as rr
JOIN users u_from on rr.from_id = u_from.id
WHERE to_id = :to_id

View file

@ -6,7 +6,8 @@
[conman.core :as conman] [conman.core :as conman]
[java-time :as jt] [java-time :as jt]
[cat.config :refer [env]] [cat.config :refer [env]]
[mount.core :refer [defstate]]) [mount.core :refer [defstate]]
[clojure.string :as s])
(:import org.postgresql.util.PGobject (:import org.postgresql.util.PGobject
java.sql.Array java.sql.Array
clojure.lang.IPersistentMap clojure.lang.IPersistentMap
@ -83,3 +84,40 @@
IPersistentVector IPersistentVector
(sql-value [value] (to-pg-json value))) (sql-value [value] (to-pg-json value)))
; postgres enum type <--> clojure namespaces keywords
; https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-big.png
; Handle Inserting of keywords as enums into the db
; Usage: (insert! pg-db :files {:name "my-file.txt", :status :processing-status/pending})
(defn kw->pgenum
"Convert clj keyword to equivalent PGobject"
[kw]
(let [type (-> (namespace kw)
(s/replace "-" "_"))
value (name kw)]
(doto (PGobject.)
(.setType type)
(.setValue value))))
(extend-type clojure.lang.Keyword
jdbc/ISQLValue
(sql-value [kw]
(kw->pgenum kw)))
; Handle extracting keywords from the db enum type
; Usage:
; (query (:db user/system) ["SELECT * FROM files"])
; => ({:status :processing-status/pending, :name "my-file.txt"})
(def +schema-enums+
"A set of all PostgreSQL enums in schema.sql. Used to convert
enum-values back into Clojure keywords."
#{"status"})
(extend-type String
jdbc/IResultSetReadColumn
(result-set-read-column [val rsmeta idx]
(let [type (.getColumnTypeName rsmeta idx)]
(if (contains? +schema-enums+ type)
(keyword (s/replace type "_" "-") val)
val))))

View file

@ -4,13 +4,18 @@
[markdown.core :refer [md-to-html-string]] [markdown.core :refer [md-to-html-string]]
[ring.util.http-response :refer [content-type ok]] [ring.util.http-response :refer [content-type ok]]
[ring.util.anti-forgery :refer [anti-forgery-field]] [ring.util.anti-forgery :refer [anti-forgery-field]]
[ring.middleware.anti-forgery :refer [*anti-forgery-token*]])) [ring.middleware.anti-forgery :refer [*anti-forgery-token*]]
[clojure.string :as s]))
(parser/set-resource-path! (clojure.java.io/resource "html")) (parser/set-resource-path! (clojure.java.io/resource "html"))
(parser/add-tag! :csrf-field (fn [_ _] (anti-forgery-field))) (parser/add-tag! :csrf-field (fn [_ _] (anti-forgery-field)))
(filters/add-filter! :markdown (fn [content] [:safe (md-to-html-string content)])) (filters/add-filter! :markdown (fn [content] [:safe (md-to-html-string content)]))
(filters/add-filter! :print (fn [x]
(println "Selmer var: " x)
x))
(defn render (defn render
"renders the HTML template located relative to resources/html" "renders the HTML template located relative to resources/html"
[template & [params]] [template & [params]]

View file

@ -4,7 +4,9 @@
[compojure.core :refer [defroutes GET POST]] [compojure.core :refer [defroutes GET POST]]
[ring.util.http-response :as response] [ring.util.http-response :as response]
[struct.core :as st] [struct.core :as st]
[clojure.tools.logging :as log])) [clojure.tools.logging :as log]
[cat.layout :refer [error-page]]
[clojure.string :as s]))
(def user-schema (def user-schema
[[:name st/required st/string] [[:name st/required st/string]
@ -28,6 +30,15 @@
(defn get-users [] (defn get-users []
(db/get-users)) (db/get-users))
(defn response-wrong-parameters []
(error-page {:status 400
:title "Wrong request parameters"
:message "Please contact your system administrator to fix this issue"}))
(defn map-status-to-value
[relation-requests]
(map (fn [rr] (cond-> rr (:status rr) (assoc :status (name (:status rr))))) relation-requests))
(defroutes home-routes (defroutes home-routes
(GET "/" req (GET "/" req
(let [users (get-users) (let [users (get-users)
@ -36,20 +47,25 @@
user-relations (when user user-relations (when user
(seq (filter (fn [rel] (seq (filter (fn [rel]
(or (or
(= (:name rel) (:username user)) (= (:name rel) (:name user))
(= (:name_2 rel) (:username user)))) (= (:name_2 rel) (:name user))))
relations))) relations)))
other_users (when user other_users (when user
(seq (filter (fn [usr] (not (= (:id usr) (:id user)))) (seq (filter (fn [usr] (not (= (:id usr) (:id user))))
users)))] users)))
rel-requests-out (seq (map-status-to-value (db/get-relation-requests-from-user {:from_id (:id user)})))
rel-requests-in (seq (map-status-to-value (db/get-relation-requests-to-user {:to_id (:id user)})))]
(log/info (str "Session: " (:session req))) (log/info (str "Session: " (:session req)))
(log/info (str "Relation requests: \n OUTGOING: " rel-requests-out "\n INCOMING: " rel-requests-in))
;(log/info (str "User relations: " user-relations)) ;(log/info (str "User relations: " user-relations))
;(log/info (str "Other Users: " other_users)) ;(log/info (str "Other Users: " other_users))
(home-page {:relations relations (home-page {:relations relations
:users users :users users
:user user :user user
:user-relations user-relations :user-relations user-relations
:other_users other_users}))) :other_users other_users
:rel-requests-out rel-requests-out
:rel-requests-in rel-requests-in})))
;(GET "/docs" [] ;(GET "/docs" []
; (-> (response/ok (-> "docs/docs.md" io/resource slurp)) ; (-> (response/ok (-> "docs/docs.md" io/resource slurp))
; (response/header "Content-Type" "text/plain; charset=utf-8"))) ; (response/header "Content-Type" "text/plain; charset=utf-8")))
@ -79,26 +95,41 @@
(assoc :group (rand-int 5))))))] (assoc :group (rand-int 5))))))]
(response/ok {:nodes nodes-indexed (response/ok {:nodes nodes-indexed
:links rels-indexed}))) :links rels-indexed})))
; TODO make next 2 user protected
(POST "/relation_request/:id/status" [id & body]
(let [rr_id_map {:id id}
success (cond
(contains? body :accept) (do
(let [rr (db/get-relation-request rr_id_map)]
(db/create-relation! {:from_id (:from_id rr) :to_id (:to_id rr)}))
(db/update-relation-request-status! (assoc rr_id_map :status :status/accepted)))
(contains? body :decline) (db/update-relation-request-status! (assoc rr_id_map :status :status/declined))
:else false)]
(if success
(response/found "/")
(response-wrong-parameters))))
; STATUS ENUM: (open, accepted, rejected)
(POST "/request_relation" req (POST "/request_relation" req
(let [data (:params req) [err result] (st/validate data request_relation-schema)] (let [data (:params req) [err result] (st/validate data request_relation-schema)]
(log/info "Post to " (:uri req) "\n with data " result) (log/info "Post to " (:uri req) "\n with data " result)
(if (nil? err) (if (nil? err)
(do (do
() (db/create-relation-request! {:from_id (get-in req [:session :user :id])
(response/no-content) :to_id (:to_id result)
;TODO add a request to the db :status :status/open})
) (response/found "/"))
(do (do
(response/bad-request "Incorrect input"))))) (response/bad-request "Incorrect input")))))
; TODO make bottom 2 protected ; TODO make bottom 2 admin protected
(POST "/relations" req (POST "/relations" req
(let [data (:params req) [err result] (st/validate data relation-schema)] (let [data (:params req) [err result] (st/validate data relation-schema)]
(log/info "Post to " (:uri req)) (log/info "Post to " (:uri req))
(if (nil? err) (if (nil? err)
(do (do
(db/create-relation! result) (db/create-relation! result)
(response/no-content)) (response/found "/"))
(do (do
(response/bad-request "Incorrect input"))))) (response/bad-request "Incorrect input")))))
(POST "/users" req (POST "/users" req
@ -107,8 +138,8 @@
(println data) (println data)
(if (st/valid? data user-schema) (if (st/valid? data user-schema)
(do (do
(db/create-user! data) (db/create-user! (assoc data :zeusid nil))
(response/no-content)) (response/found "/"))
(do (do
(response/bad-request "Incorrect input")))))) (response/bad-request "Incorrect input"))))))