add cuddle requests with colored tables and accept/decline system
This commit is contained in:
parent
4082fd0a36
commit
ae5a0ac0ae
11 changed files with 230 additions and 47 deletions
11
README.md
11
README.md
|
@ -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
|
|
@ -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>
|
||||||
|
|
12
resources/html/parts/colored-status.html
Normal file
12
resources/html/parts/colored-status.html
Normal 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>
|
|
@ -0,0 +1 @@
|
||||||
|
DROP TYPE STATUS;
|
|
@ -0,0 +1,2 @@
|
||||||
|
CREATE TYPE STATUS as ENUM
|
||||||
|
('open', 'accepted', 'declined');
|
|
@ -0,0 +1 @@
|
||||||
|
DROP TABLE relation_requests;
|
|
@ -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));
|
|
@ -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
|
|
@ -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))))
|
|
@ -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]]
|
||||||
|
|
|
@ -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"))))))
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue