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
|
||||
|
||||
## 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
|
|
@ -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>
|
||||
|
|
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));
|
|
@ -37,3 +37,37 @@ VALUES (:from_id, :to_id)
|
|||
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
|
||||
|
||||
/*
|
||||
-------------------------------
|
||||
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]
|
||||
[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))))
|
|
@ -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]]
|
||||
|
|
|
@ -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"))))))
|
||||
|
||||
|
|
Loading…
Reference in a new issue