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
## 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
Copyright © 2019 FIXME

View file

@ -101,8 +101,8 @@
{% if user %}
<section class="section">
<div class="columns">
<div class="column">
<h3 class="title is-3">Your cuddles</h3>
<div class="column is-narrow">
<h3 class="subtitle is-3">Your cuddles</h3>
<table class="table">
<thead>
<tr>
@ -121,11 +121,9 @@
</table>
</div>
<div class="column">
<h3 class="title is-3">Request cuddles!</h3>
<form action="/request_relation" method="post">
{% csrf-field %}
<div class="field">
<label for="to_id">Person to</label>
<div class="field has-addons">
<div class="control">
<div class="select">
<select name="to_id" id="to_id">
@ -135,25 +133,71 @@
</select>
</div>
</div>
</div>
<div class="field">
<div class="control">
<input class="button is-link" type="submit" value="Submit">
<input class="button is-link" type="submit" value="Request cuddle!">
</div>
</div>
</form>
<table class="table">
<thead>
<tr>
<th>Outgoing requests</th>
</tr>
</thead>
{% for rel_req in user-relation-requests %}
<tr>
<td>{{rel_req.name}}</td>
</tr>
{% endfor %}
</table>
<div class="columns">
<div class="column">
<table class="table">
<thead>
<tr>
<th>Outgoing requests</th>
<th>Status</th>
</tr>
</thead>
{% for rr in rel-requests-out %}
<tr>
<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>
</section>
@ -182,7 +226,7 @@
</div>
<div class="field">
<div class="control">
<input class="button is-link" type="submit" value="Submit">
<input class="button is-link" type="submit" value="Add!">
</div>
</div>
</form>
@ -222,8 +266,7 @@
</div>
</div>
</div>
<div class="field">
<label for="to">Person to</label>
<div class="field has-addons">
<div class="control">
<div class="select">
<select name="to_id" id="to">
@ -233,10 +276,8 @@
</select>
</div>
</div>
</div>
<div class="field">
<div class="control">
<input class="button is-link" type="submit" value="Submit">
<input class="button is-link" type="submit" value="Add!">
</div>
</div>
</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
SELECT * FROM relations
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]
[java-time :as jt]
[cat.config :refer [env]]
[mount.core :refer [defstate]])
[mount.core :refer [defstate]]
[clojure.string :as s])
(:import org.postgresql.util.PGobject
java.sql.Array
clojure.lang.IPersistentMap
@ -83,3 +84,40 @@
IPersistentVector
(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]]
[ring.util.http-response :refer [content-type ok]]
[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)))
(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
"renders the HTML template located relative to resources/html"
[template & [params]]

View file

@ -4,7 +4,9 @@
[compojure.core :refer [defroutes GET POST]]
[ring.util.http-response :as response]
[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
[[:name st/required st/string]
@ -28,6 +30,15 @@
(defn 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
(GET "/" req
(let [users (get-users)
@ -36,20 +47,25 @@
user-relations (when user
(seq (filter (fn [rel]
(or
(= (:name rel) (:username user))
(= (:name_2 rel) (:username user))))
(= (:name rel) (:name user))
(= (:name_2 rel) (:name user))))
relations)))
other_users (when 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 "Relation requests: \n OUTGOING: " rel-requests-out "\n INCOMING: " rel-requests-in))
;(log/info (str "User relations: " user-relations))
;(log/info (str "Other Users: " other_users))
(home-page {:relations relations
:users users
:user user
:user-relations user-relations
:other_users other_users})))
(home-page {:relations relations
:users users
:user user
:user-relations user-relations
:other_users other_users
:rel-requests-out rel-requests-out
:rel-requests-in rel-requests-in})))
;(GET "/docs" []
; (-> (response/ok (-> "docs/docs.md" io/resource slurp))
; (response/header "Content-Type" "text/plain; charset=utf-8")))
@ -79,26 +95,41 @@
(assoc :group (rand-int 5))))))]
(response/ok {:nodes nodes-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
(let [data (:params req) [err result] (st/validate data request_relation-schema)]
(log/info "Post to " (:uri req) "\n with data " result)
(if (nil? err)
(do
()
(response/no-content)
;TODO add a request to the db
)
(db/create-relation-request! {:from_id (get-in req [:session :user :id])
:to_id (:to_id result)
:status :status/open})
(response/found "/"))
(do
(response/bad-request "Incorrect input")))))
; TODO make bottom 2 protected
; TODO make bottom 2 admin protected
(POST "/relations" req
(let [data (:params req) [err result] (st/validate data relation-schema)]
(log/info "Post to " (:uri req))
(if (nil? err)
(do
(db/create-relation! result)
(response/no-content))
(response/found "/"))
(do
(response/bad-request "Incorrect input")))))
(POST "/users" req
@ -107,8 +138,8 @@
(println data)
(if (st/valid? data user-schema)
(do
(db/create-user! data)
(response/no-content))
(db/create-user! (assoc data :zeusid nil))
(response/found "/"))
(do
(response/bad-request "Incorrect input"))))))