aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--assets_src/css/list.css4
-rw-r--r--assets_src/js/lib/ajax.js3
-rw-r--r--assets_src/js/lib/dom.js16
-rw-r--r--assets_src/js/like.js75
-rw-r--r--modules/likes/likes.go120
-rw-r--r--views/includes/like.html4
-rw-r--r--views/pages/list.html2
7 files changed, 219 insertions, 5 deletions
diff --git a/assets_src/css/list.css b/assets_src/css/list.css
index 51b2514..a3e6fcf 100644
--- a/assets_src/css/list.css
+++ b/assets_src/css/list.css
@@ -2,6 +2,10 @@
2 color: rgb(166, 166, 166); 2 color: rgb(166, 166, 166);
3} 3}
4 4
5.liked {
6 background-color: green;
7}
8
5.list-entry { 9.list-entry {
6 height: 80px; 10 height: 80px;
7 padding: 8px; 11 padding: 8px;
diff --git a/assets_src/js/lib/ajax.js b/assets_src/js/lib/ajax.js
index b189a92..71e3764 100644
--- a/assets_src/js/lib/ajax.js
+++ b/assets_src/js/lib/ajax.js
@@ -61,3 +61,6 @@ export async function post(url, body, options) {
61export async function del(url, body, options) { 61export async function del(url, body, options) {
62 return request('DELETE', url, body, options); 62 return request('DELETE', url, body, options);
63} 63}
64export async function put(url, body, options) {
65 return request('PUT', url, body, options);
66}
diff --git a/assets_src/js/lib/dom.js b/assets_src/js/lib/dom.js
index 3c2f4d9..82f5dd2 100644
--- a/assets_src/js/lib/dom.js
+++ b/assets_src/js/lib/dom.js
@@ -27,6 +27,22 @@ export function withClass(className) {
27 return arr; 27 return arr;
28} 28}
29 29
30export function firstChild(el, fn) {
31 if (!el) {
32 return null;
33 }
34
35 for (const child of el.childNodes) {
36 if (child.nodeName === '#text') {
37 continue;
38 }
39 if (fn(child)) {
40 return child;
41 }
42 }
43 return null;
44}
45
30export function next(el, fn) { 46export function next(el, fn) {
31 if (!el) { 47 if (!el) {
32 return null; 48 return null;
diff --git a/assets_src/js/like.js b/assets_src/js/like.js
index 03b15a1..feae345 100644
--- a/assets_src/js/like.js
+++ b/assets_src/js/like.js
@@ -11,12 +11,81 @@ async function updateLikeCount(el) {
11 11
12 const count = await ajax.get(`/api/likes/count?id=${contentId}&type=${type}`, {}); 12 const count = await ajax.get(`/api/likes/count?id=${contentId}&type=${type}`, {});
13 13
14 el.textContent = count; 14 dom.firstChild(el, e => e.classList.contains('like-count')).textContent = count;
15}
16
17async function isLikedByCurrentUser(el) {
18 const type = parseInt(el.getAttribute('content-type'), 10);
19 const contentId = parseInt(el.getAttribute('content-id'), 10);
20 const userId = parseInt(el.getAttribute('update-with'), 10);
21
22 if (isNaN(type) || isNaN(contentId) || isNaN(userId)) {
23 return;
24 }
25
26 const b = await ajax.get(`/api/likes/liked_by?id=${contentId}&type=${type}&user=${userId}`, {});
27
28 console.log(b);
29 return b === 'true';
30}
31
32async function updateLikeByCurrentUser(el, to) {
33 const type = parseInt(el.getAttribute('content-type'), 10);
34 const contentId = parseInt(el.getAttribute('content-id'), 10);
35 const userId = parseInt(el.getAttribute('update-with'), 10);
36
37 if (isNaN(type) || isNaN(contentId) || isNaN(userId)) {
38 return;
39 }
40
41 updateLikeClass(el, to);
42
43 if (to) {
44 await ajax.put('/api/likes', `id=${contentId}&type=${type}&user=${userId}`, {
45 headers: {
46 'Content-Type': 'application/x-www-form-urlencoded',
47 }
48 });
49 } else {
50 await ajax.del('/api/likes', `id=${contentId}&type=${type}&user=${userId}`, {
51 headers: {
52 'Content-Type': 'application/x-www-form-urlencoded',
53 }
54 });
55 }
56 const cont = dom.firstChild(el, e => e.classList.contains('like-count'));
57
58 cont.textContent = parseInt(cont.textContent, 10) + (to ? 1 : -1);
59}
60
61function updateLikeClass(el, to) {
62 el.classList.toggle('liked', to);
15} 63}
16 64
17dom.ready(() => { 65dom.ready(() => {
18 dom.withClass('like-count') 66 dom.withClass('like-div')
19 .forEach(el => { 67 .forEach(async el => {
20 updateLikeCount(el); 68 updateLikeCount(el);
69
70 const userId = parseInt(el.getAttribute('update-with'), 10);
71
72 let liked = await isLikedByCurrentUser(el);
73 updateLikeClass(el, liked);
74 if (!isNaN(userId)) {
75 el.addEventListener('click', async () => {
76 liked = !liked;
77 await updateLikeByCurrentUser(el, liked);
78 });
79 const cap = dom.firstChild(el, e => e.classList.contains('like-caption'));
80 if (cap) {
81 el.addEventListener('mouseover', () => {
82 cap.textContent = `${liked ? 'nicht mehr ' : ''}geil finden`;
83 });
84 el.addEventListener('mouseout', () => cap.textContent = 'Finden das geil');
85 }
86 } else {
87 el.classList.add('disabled');
88 }
89
21 }); 90 });
22}); 91});
diff --git a/modules/likes/likes.go b/modules/likes/likes.go
index 949cba2..4d69d7b 100644
--- a/modules/likes/likes.go
+++ b/modules/likes/likes.go
@@ -2,11 +2,14 @@ package likes
2 2
3import ( 3import (
4 "fmt" 4 "fmt"
5 "io/ioutil"
5 "log" 6 "log"
6 "net/http" 7 "net/http"
8 "net/url"
7 "strconv" 9 "strconv"
8 10
9 "fagott.pw/charakterin" 11 "fagott.pw/charakterin"
12 "fagott.pw/grilist/cache"
10 "fagott.pw/grilist/grilist" 13 "fagott.pw/grilist/grilist"
11 "fagott.pw/grilist/modules/lists" 14 "fagott.pw/grilist/modules/lists"
12 15
@@ -15,6 +18,7 @@ import (
15 18
16type Module struct { 19type Module struct {
17 g *grilist.Grilist 20 g *grilist.Grilist
21 c *cache.Cache
18 lists *lists.Module 22 lists *lists.Module
19} 23}
20 24
@@ -33,7 +37,11 @@ func (m *Module) Init(g *grilist.Grilist) {
33 log.Fatal("tags: lists module not found") 37 log.Fatal("tags: lists module not found")
34 } 38 }
35 m.lists = gm.(*lists.Module) 39 m.lists = gm.(*lists.Module)
40 m.c = cache.New()
36 m.g.Router.GET("/api/likes/count", m.getLikeCount) 41 m.g.Router.GET("/api/likes/count", m.getLikeCount)
42 m.g.Router.GET("/api/likes/liked_by", m.isLikedBy)
43 m.g.Router.PUT("/api/likes", m.addLike)
44 m.g.Router.DELETE("/api/likes", m.removeLike)
37} 45}
38 46
39func (m *Module) Interface() interface{} { 47func (m *Module) Interface() interface{} {
@@ -68,3 +76,115 @@ func (m *Module) getLikeCount(w http.ResponseWriter, r *http.Request, p httprout
68 w.WriteHeader(200) 76 w.WriteHeader(200)
69 w.Write([]byte(fmt.Sprintf("%d", res))) 77 w.Write([]byte(fmt.Sprintf("%d", res)))
70} 78}
79func (m *Module) isLikedBy(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
80 params := r.URL.Query()
81
82 contentId, err := strconv.Atoi(params.Get("id"))
83 if err != nil {
84 http.Error(w, "invalid content id", http.StatusBadRequest)
85 return
86 }
87 contentType, err := strconv.Atoi(params.Get("type"))
88 if err != nil {
89 http.Error(w, "invalid content type", http.StatusBadRequest)
90 return
91 }
92 userId, err := strconv.Atoi(params.Get("user"))
93 if err != nil {
94 http.Error(w, "invalid user id", http.StatusBadRequest)
95 return
96 }
97
98 var res int
99 if err := m.g.DB.QueryRow(`SELECT COUNT(*) FROM grilist.likes WHERE content = $1 AND type = $2 AND "user" = $3`, contentId, contentType, userId).Scan(&res); err != nil {
100 http.Error(w, "pq error", http.StatusInternalServerError)
101 log.Printf("error getting like by user: %s", err)
102 return
103 }
104
105 w.WriteHeader(200)
106 w.Write([]byte(fmt.Sprintf("%t", res > 0)))
107}
108func (m *Module) addLike(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
109 params, err := readBody(r)
110 if err != nil {
111 log.Println(err)
112 http.Error(w, "invalid body", http.StatusBadRequest)
113 return
114 }
115
116 contentId, err := strconv.Atoi(params.Get("id"))
117 if err != nil {
118 log.Println(err)
119 http.Error(w, "invalid content id", http.StatusBadRequest)
120 return
121 }
122 contentType, err := strconv.Atoi(params.Get("type"))
123 if err != nil {
124 log.Println(err)
125 http.Error(w, "invalid content type", http.StatusBadRequest)
126 return
127 }
128 userId, err := strconv.Atoi(params.Get("user"))
129 if err != nil {
130 http.Error(w, "invalid user id", http.StatusBadRequest)
131 return
132 }
133
134 _, err = m.g.DB.Exec(`INSERT INTO grilist.likes(content, "user", type) SELECT $1, $2, $3 WHERE NOT EXISTS (SELECT * FROM grilist.likes WHERE content = $1 AND "user" = $2 AND type = $3)`, contentId, userId, contentType)
135 if err != nil {
136 http.Error(w, "pq error", http.StatusInternalServerError)
137 log.Printf("error add like: %s", err)
138 return
139 }
140
141 w.WriteHeader(200)
142}
143
144func (m *Module) removeLike(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
145 params, err := readBody(r)
146 if err != nil {
147 http.Error(w, "invalid body", http.StatusBadRequest)
148 return
149 }
150
151 contentId, err := strconv.Atoi(params.Get("id"))
152 if err != nil {
153 http.Error(w, "invalid content id", http.StatusBadRequest)
154 return
155 }
156 contentType, err := strconv.Atoi(params.Get("type"))
157 if err != nil {
158 http.Error(w, "invalid content type", http.StatusBadRequest)
159 return
160 }
161 userId, err := strconv.Atoi(params.Get("user"))
162 if err != nil {
163 http.Error(w, "invalid user id", http.StatusBadRequest)
164 return
165 }
166
167 _, err = m.g.DB.Exec(`DELETE FROM grilist.likes WHERE content = $1 AND "user" = $2 AND type = $3`, contentId, userId, contentType)
168 if err != nil {
169 http.Error(w, "pq error", http.StatusInternalServerError)
170 log.Printf("error add like: %s", err)
171 return
172 }
173
174 w.WriteHeader(200)
175}
176
177func readBody(r *http.Request) (url.Values, error) {
178 defer r.Body.Close()
179 data, err := ioutil.ReadAll(r.Body)
180 if err != nil {
181 return nil, err
182 }
183
184 values, err := url.ParseQuery(string(data))
185 if err != nil {
186 return nil, err
187 }
188
189 return values, nil
190}
diff --git a/views/includes/like.html b/views/includes/like.html
index 226aaff..bfb9576 100644
--- a/views/includes/like.html
+++ b/views/includes/like.html
@@ -1,3 +1,5 @@
1{{ define "like" }} 1{{ define "like" }}
2<span class="like-count" content-type={{ .ContentType }} content-id={{ .ContentID }}></span> Likes 2<div class="like-div btn purple lighten-4" {{ if .User }}update-with="{{ .User.ID }}"{{ end }} content-type={{ .ContentType }} content-id={{ .ContentID }}>
3 <span class="like-count"></span> <span class="like-caption">Finden das geil</span>
4</div>
3{{ end }} 5{{ end }}
diff --git a/views/pages/list.html b/views/pages/list.html
index f884a63..b1ec236 100644
--- a/views/pages/list.html
+++ b/views/pages/list.html
@@ -25,7 +25,7 @@
25 </div> 25 </div>
26 </div>{{ end }} 26 </div>{{ end }}
27 <blockquote>{{ $list.Description }}</blockquote><br /> 27 <blockquote>{{ $list.Description }}</blockquote><br />
28 {{ template "like" (map "ContentType" 0 "ContentID" $list.ID) }} 28 {{ template "like" (map "ContentType" 0 "ContentID" $list.ID "User" $user) }}
29 <div class="row"> 29 <div class="row">
30 <div class="col s12 {{ if ($user) and eq $user.ID $list.Owner.ID }}l8{{ end }}"> 30 <div class="col s12 {{ if ($user) and eq $user.ID $list.Owner.ID }}l8{{ end }}">
31 <ul id="gril-list" class="gril-list"> 31 <ul id="gril-list" class="gril-list">