fbab2e55d254583cc08256bb4df26b65e426e722
[bloat] / service / transport.go
1 package service
2
3 import (
4         "context"
5         "encoding/json"
6         "io"
7         "mime/multipart"
8         "net/http"
9         "path"
10         "strconv"
11         "time"
12
13         "bloat/model"
14
15         "github.com/gorilla/mux"
16 )
17
18 func newClient(w io.Writer) *model.Client {
19         return &model.Client{
20                 Writer: w,
21         }
22 }
23
24 func newCtxWithSesion(req *http.Request) context.Context {
25         ctx := context.Background()
26         sessionID, err := req.Cookie("session_id")
27         if err != nil {
28                 return ctx
29         }
30         return context.WithValue(ctx, "session_id", sessionID.Value)
31 }
32
33 func newCtxWithSesionCSRF(req *http.Request, csrfToken string) context.Context {
34         ctx := newCtxWithSesion(req)
35         return context.WithValue(ctx, "csrf_token", csrfToken)
36 }
37
38 func getMultipartFormValue(mf *multipart.Form, key string) (val string) {
39         vals, ok := mf.Value[key]
40         if !ok {
41                 return ""
42         }
43         if len(vals) < 1 {
44                 return ""
45         }
46         return vals[0]
47 }
48
49 func serveJson(w io.Writer, data interface{}) (err error) {
50         var d = make(map[string]interface{})
51         d["data"] = data
52         return json.NewEncoder(w).Encode(d)
53 }
54
55 func NewHandler(s Service, staticDir string) http.Handler {
56         r := mux.NewRouter()
57
58         rootPage := func(w http.ResponseWriter, req *http.Request) {
59                 sessionID, _ := req.Cookie("session_id")
60
61                 location := "/signin"
62                 if sessionID != nil && len(sessionID.Value) > 0 {
63                         location = "/timeline/home"
64                 }
65
66                 w.Header().Add("Location", location)
67                 w.WriteHeader(http.StatusFound)
68         }
69
70         signinPage := func(w http.ResponseWriter, req *http.Request) {
71                 c := newClient(w)
72                 ctx := context.Background()
73                 err := s.ServeSigninPage(ctx, c)
74                 if err != nil {
75                         s.ServeErrorPage(ctx, c, err)
76                         return
77                 }
78         }
79
80         timelinePage := func(w http.ResponseWriter, req *http.Request) {
81                 c := newClient(w)
82                 ctx := newCtxWithSesion(req)
83                 tType, _ := mux.Vars(req)["type"]
84                 maxID := req.URL.Query().Get("max_id")
85                 minID := req.URL.Query().Get("min_id")
86
87                 err := s.ServeTimelinePage(ctx, c, tType, maxID, minID)
88                 if err != nil {
89                         s.ServeErrorPage(ctx, c, err)
90                         return
91                 }
92         }
93
94         timelineOldPage := func(w http.ResponseWriter, req *http.Request) {
95                 w.Header().Add("Location", "/timeline/home")
96                 w.WriteHeader(http.StatusFound)
97         }
98
99         threadPage := func(w http.ResponseWriter, req *http.Request) {
100                 c := newClient(w)
101                 ctx := newCtxWithSesion(req)
102                 id, _ := mux.Vars(req)["id"]
103                 reply := req.URL.Query().Get("reply")
104
105                 err := s.ServeThreadPage(ctx, c, id, len(reply) > 1)
106                 if err != nil {
107                         s.ServeErrorPage(ctx, c, err)
108                         return
109                 }
110         }
111
112         likedByPage := func(w http.ResponseWriter, req *http.Request) {
113                 c := newClient(w)
114                 ctx := newCtxWithSesion(req)
115                 id, _ := mux.Vars(req)["id"]
116
117                 err := s.ServeLikedByPage(ctx, c, id)
118                 if err != nil {
119                         s.ServeErrorPage(ctx, c, err)
120                         return
121                 }
122         }
123
124         retweetedByPage := func(w http.ResponseWriter, req *http.Request) {
125                 c := newClient(w)
126                 ctx := newCtxWithSesion(req)
127                 id, _ := mux.Vars(req)["id"]
128
129                 err := s.ServeRetweetedByPage(ctx, c, id)
130                 if err != nil {
131                         s.ServeErrorPage(ctx, c, err)
132                         return
133                 }
134         }
135
136         followingPage := func(w http.ResponseWriter, req *http.Request) {
137                 c := newClient(w)
138                 ctx := newCtxWithSesion(req)
139                 id, _ := mux.Vars(req)["id"]
140                 maxID := req.URL.Query().Get("max_id")
141                 minID := req.URL.Query().Get("min_id")
142
143                 err := s.ServeFollowingPage(ctx, c, id, maxID, minID)
144                 if err != nil {
145                         s.ServeErrorPage(ctx, c, err)
146                         return
147                 }
148         }
149
150         followersPage := func(w http.ResponseWriter, req *http.Request) {
151                 c := newClient(w)
152                 ctx := newCtxWithSesion(req)
153                 id, _ := mux.Vars(req)["id"]
154                 maxID := req.URL.Query().Get("max_id")
155                 minID := req.URL.Query().Get("min_id")
156
157                 err := s.ServeFollowersPage(ctx, c, id, maxID, minID)
158                 if err != nil {
159                         s.ServeErrorPage(ctx, c, err)
160                         return
161                 }
162         }
163
164         notificationsPage := func(w http.ResponseWriter, req *http.Request) {
165                 c := newClient(w)
166                 ctx := newCtxWithSesion(req)
167                 maxID := req.URL.Query().Get("max_id")
168                 minID := req.URL.Query().Get("min_id")
169
170                 err := s.ServeNotificationPage(ctx, c, maxID, minID)
171                 if err != nil {
172                         s.ServeErrorPage(ctx, c, err)
173                         return
174                 }
175         }
176
177         userPage := func(w http.ResponseWriter, req *http.Request) {
178                 c := newClient(w)
179                 ctx := newCtxWithSesion(req)
180                 id, _ := mux.Vars(req)["id"]
181                 maxID := req.URL.Query().Get("max_id")
182                 minID := req.URL.Query().Get("min_id")
183
184                 err := s.ServeUserPage(ctx, c, id, maxID, minID)
185                 if err != nil {
186                         s.ServeErrorPage(ctx, c, err)
187                         return
188                 }
189         }
190
191         aboutPage := func(w http.ResponseWriter, req *http.Request) {
192                 c := newClient(w)
193                 ctx := newCtxWithSesion(req)
194
195                 err := s.ServeAboutPage(ctx, c)
196                 if err != nil {
197                         s.ServeErrorPage(ctx, c, err)
198                         return
199                 }
200         }
201
202         emojisPage := func(w http.ResponseWriter, req *http.Request) {
203                 c := newClient(w)
204                 ctx := newCtxWithSesion(req)
205
206                 err := s.ServeEmojiPage(ctx, c)
207                 if err != nil {
208                         s.ServeErrorPage(ctx, c, err)
209                         return
210                 }
211         }
212
213         searchPage := func(w http.ResponseWriter, req *http.Request) {
214                 c := newClient(w)
215                 ctx := newCtxWithSesion(req)
216                 q := req.URL.Query().Get("q")
217                 qType := req.URL.Query().Get("type")
218                 offsetStr := req.URL.Query().Get("offset")
219
220                 var offset int
221                 var err error
222                 if len(offsetStr) > 1 {
223                         offset, err = strconv.Atoi(offsetStr)
224                         if err != nil {
225                                 s.ServeErrorPage(ctx, c, err)
226                                 return
227                         }
228                 }
229
230                 err = s.ServeSearchPage(ctx, c, q, qType, offset)
231                 if err != nil {
232                         s.ServeErrorPage(ctx, c, err)
233                         return
234                 }
235         }
236
237         settingsPage := func(w http.ResponseWriter, req *http.Request) {
238                 c := newClient(w)
239                 ctx := newCtxWithSesion(req)
240
241                 err := s.ServeSettingsPage(ctx, c)
242                 if err != nil {
243                         s.ServeErrorPage(ctx, c, err)
244                         return
245                 }
246         }
247
248         signin := func(w http.ResponseWriter, req *http.Request) {
249                 c := newClient(w)
250                 ctx := context.Background()
251                 instance := req.FormValue("instance")
252
253                 url, sessionID, err := s.NewSession(ctx, instance)
254                 if err != nil {
255                         s.ServeErrorPage(ctx, c, err)
256                         return
257                 }
258
259                 http.SetCookie(w, &http.Cookie{
260                         Name:    "session_id",
261                         Value:   sessionID,
262                         Expires: time.Now().Add(365 * 24 * time.Hour),
263                 })
264
265                 w.Header().Add("Location", url)
266                 w.WriteHeader(http.StatusFound)
267         }
268
269         oauthCallback := func(w http.ResponseWriter, req *http.Request) {
270                 c := newClient(w)
271                 ctx := newCtxWithSesion(req)
272                 token := req.URL.Query().Get("code")
273
274                 _, err := s.Signin(ctx, c, "", token)
275                 if err != nil {
276                         s.ServeErrorPage(ctx, c, err)
277                         return
278                 }
279
280                 w.Header().Add("Location", "/timeline/home")
281                 w.WriteHeader(http.StatusFound)
282         }
283
284         post := func(w http.ResponseWriter, req *http.Request) {
285                 c := newClient(w)
286                 err := req.ParseMultipartForm(4 << 20)
287                 if err != nil {
288                         s.ServeErrorPage(context.Background(), c, err)
289                         return
290                 }
291
292                 ctx := newCtxWithSesionCSRF(req,
293                         getMultipartFormValue(req.MultipartForm, "csrf_token"))
294                 content := getMultipartFormValue(req.MultipartForm, "content")
295                 replyToID := getMultipartFormValue(req.MultipartForm, "reply_to_id")
296                 format := getMultipartFormValue(req.MultipartForm, "format")
297                 visibility := getMultipartFormValue(req.MultipartForm, "visibility")
298                 isNSFW := "on" == getMultipartFormValue(req.MultipartForm, "is_nsfw")
299                 files := req.MultipartForm.File["attachments"]
300
301                 id, err := s.Post(ctx, c, content, replyToID, format, visibility, isNSFW, files)
302                 if err != nil {
303                         s.ServeErrorPage(ctx, c, err)
304                         return
305                 }
306
307                 location := "/timeline/home" + "#status-" + id
308                 if len(replyToID) > 0 {
309                         location = "/thread/" + replyToID + "#status-" + id
310                 }
311                 w.Header().Add("Location", location)
312                 w.WriteHeader(http.StatusFound)
313         }
314
315         like := func(w http.ResponseWriter, req *http.Request) {
316                 c := newClient(w)
317                 ctx := newCtxWithSesionCSRF(req, req.FormValue("csrf_token"))
318                 id, _ := mux.Vars(req)["id"]
319                 retweetedByID := req.FormValue("retweeted_by_id")
320
321                 _, err := s.Like(ctx, c, id)
322                 if err != nil {
323                         s.ServeErrorPage(ctx, c, err)
324                         return
325                 }
326
327                 rID := id
328                 if len(retweetedByID) > 0 {
329                         rID = retweetedByID
330                 }
331                 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+rID)
332                 w.WriteHeader(http.StatusFound)
333         }
334
335         unlike := func(w http.ResponseWriter, req *http.Request) {
336                 c := newClient(w)
337                 ctx := newCtxWithSesionCSRF(req, req.FormValue("csrf_token"))
338                 id, _ := mux.Vars(req)["id"]
339                 retweetedByID := req.FormValue("retweeted_by_id")
340
341                 _, err := s.UnLike(ctx, c, id)
342                 if err != nil {
343                         s.ServeErrorPage(ctx, c, err)
344                         return
345                 }
346
347                 rID := id
348                 if len(retweetedByID) > 0 {
349                         rID = retweetedByID
350                 }
351                 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+rID)
352                 w.WriteHeader(http.StatusFound)
353         }
354
355         retweet := func(w http.ResponseWriter, req *http.Request) {
356                 c := newClient(w)
357                 ctx := newCtxWithSesionCSRF(req, req.FormValue("csrf_token"))
358                 id, _ := mux.Vars(req)["id"]
359                 retweetedByID := req.FormValue("retweeted_by_id")
360
361                 _, err := s.Retweet(ctx, c, id)
362                 if err != nil {
363                         s.ServeErrorPage(ctx, c, err)
364                         return
365                 }
366
367                 rID := id
368                 if len(retweetedByID) > 0 {
369                         rID = retweetedByID
370                 }
371                 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+rID)
372                 w.WriteHeader(http.StatusFound)
373         }
374
375         unretweet := func(w http.ResponseWriter, req *http.Request) {
376                 c := newClient(w)
377                 ctx := newCtxWithSesionCSRF(req, req.FormValue("csrf_token"))
378                 id, _ := mux.Vars(req)["id"]
379                 retweetedByID := req.FormValue("retweeted_by_id")
380
381                 _, err := s.UnRetweet(ctx, c, id)
382                 if err != nil {
383                         s.ServeErrorPage(ctx, c, err)
384                         return
385                 }
386
387                 rID := id
388                 if len(retweetedByID) > 0 {
389                         rID = retweetedByID
390                 }
391
392                 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+rID)
393                 w.WriteHeader(http.StatusFound)
394         }
395
396         follow := func(w http.ResponseWriter, req *http.Request) {
397                 c := newClient(w)
398                 ctx := newCtxWithSesionCSRF(req, req.FormValue("csrf_token"))
399                 id, _ := mux.Vars(req)["id"]
400
401                 err := s.Follow(ctx, c, id)
402                 if err != nil {
403                         s.ServeErrorPage(ctx, c, err)
404                         return
405                 }
406
407                 w.Header().Add("Location", req.Header.Get("Referer"))
408                 w.WriteHeader(http.StatusFound)
409         }
410
411         unfollow := func(w http.ResponseWriter, req *http.Request) {
412                 c := newClient(w)
413                 ctx := newCtxWithSesionCSRF(req, req.FormValue("csrf_token"))
414                 id, _ := mux.Vars(req)["id"]
415
416                 err := s.UnFollow(ctx, c, id)
417                 if err != nil {
418                         s.ServeErrorPage(ctx, c, err)
419                         return
420                 }
421
422                 w.Header().Add("Location", req.Header.Get("Referer"))
423                 w.WriteHeader(http.StatusFound)
424         }
425
426         settings := func(w http.ResponseWriter, req *http.Request) {
427                 c := newClient(w)
428                 ctx := newCtxWithSesionCSRF(req, req.FormValue("csrf_token"))
429                 visibility := req.FormValue("visibility")
430                 copyScope := req.FormValue("copy_scope") == "true"
431                 threadInNewTab := req.FormValue("thread_in_new_tab") == "true"
432                 maskNSFW := req.FormValue("mask_nsfw") == "true"
433                 fluorideMode := req.FormValue("fluoride_mode") == "true"
434                 darkMode := req.FormValue("dark_mode") == "true"
435
436                 settings := &model.Settings{
437                         DefaultVisibility: visibility,
438                         CopyScope:         copyScope,
439                         ThreadInNewTab:    threadInNewTab,
440                         MaskNSFW:          maskNSFW,
441                         FluorideMode:      fluorideMode,
442                         DarkMode:          darkMode,
443                 }
444
445                 err := s.SaveSettings(ctx, c, settings)
446                 if err != nil {
447                         s.ServeErrorPage(ctx, c, err)
448                         return
449                 }
450
451                 w.Header().Add("Location", req.Header.Get("Referer"))
452                 w.WriteHeader(http.StatusFound)
453         }
454
455         signout := func(w http.ResponseWriter, req *http.Request) {
456                 // TODO remove session from database
457                 http.SetCookie(w, &http.Cookie{
458                         Name:    "session_id",
459                         Value:   "",
460                         Expires: time.Now(),
461                 })
462                 w.Header().Add("Location", "/")
463                 w.WriteHeader(http.StatusFound)
464         }
465
466         fLike := func(w http.ResponseWriter, req *http.Request) {
467                 c := newClient(w)
468                 ctx := newCtxWithSesionCSRF(req, req.FormValue("csrf_token"))
469                 id, _ := mux.Vars(req)["id"]
470
471                 count, err := s.Like(ctx, c, id)
472                 if err != nil {
473                         s.ServeErrorPage(ctx, c, err)
474                         return
475                 }
476
477                 err = serveJson(w, count)
478                 if err != nil {
479                         s.ServeErrorPage(ctx, c, err)
480                         return
481                 }
482         }
483
484         fUnlike := func(w http.ResponseWriter, req *http.Request) {
485                 c := newClient(w)
486                 ctx := newCtxWithSesionCSRF(req, req.FormValue("csrf_token"))
487                 id, _ := mux.Vars(req)["id"]
488                 count, err := s.UnLike(ctx, c, id)
489                 if err != nil {
490                         s.ServeErrorPage(ctx, c, err)
491                         return
492                 }
493
494                 err = serveJson(w, count)
495                 if err != nil {
496                         s.ServeErrorPage(ctx, c, err)
497                         return
498                 }
499         }
500
501         fRetweet := func(w http.ResponseWriter, req *http.Request) {
502                 c := newClient(w)
503                 ctx := newCtxWithSesionCSRF(req, req.FormValue("csrf_token"))
504                 id, _ := mux.Vars(req)["id"]
505
506                 count, err := s.Retweet(ctx, c, id)
507                 if err != nil {
508                         s.ServeErrorPage(ctx, c, err)
509                         return
510                 }
511
512                 err = serveJson(w, count)
513                 if err != nil {
514                         s.ServeErrorPage(ctx, c, err)
515                         return
516                 }
517         }
518
519         fUnretweet := func(w http.ResponseWriter, req *http.Request) {
520                 c := newClient(w)
521                 ctx := newCtxWithSesionCSRF(req, req.FormValue("csrf_token"))
522                 id, _ := mux.Vars(req)["id"]
523
524                 count, err := s.UnRetweet(ctx, c, id)
525                 if err != nil {
526                         s.ServeErrorPage(ctx, c, err)
527                         return
528                 }
529
530                 err = serveJson(w, count)
531                 if err != nil {
532                         s.ServeErrorPage(ctx, c, err)
533                         return
534                 }
535         }
536
537         r.HandleFunc("/", rootPage).Methods(http.MethodGet)
538         r.HandleFunc("/signin", signinPage).Methods(http.MethodGet)
539         r.HandleFunc("/timeline/{type}", timelinePage).Methods(http.MethodGet)
540         r.HandleFunc("/timeline", timelineOldPage).Methods(http.MethodGet)
541         r.HandleFunc("/thread/{id}", threadPage).Methods(http.MethodGet)
542         r.HandleFunc("/likedby/{id}", likedByPage).Methods(http.MethodGet)
543         r.HandleFunc("/retweetedby/{id}", retweetedByPage).Methods(http.MethodGet)
544         r.HandleFunc("/following/{id}", followingPage).Methods(http.MethodGet)
545         r.HandleFunc("/followers/{id}", followersPage).Methods(http.MethodGet)
546         r.HandleFunc("/notifications", notificationsPage).Methods(http.MethodGet)
547         r.HandleFunc("/user/{id}", userPage).Methods(http.MethodGet)
548         r.HandleFunc("/about", aboutPage).Methods(http.MethodGet)
549         r.HandleFunc("/emojis", emojisPage).Methods(http.MethodGet)
550         r.HandleFunc("/search", searchPage).Methods(http.MethodGet)
551         r.HandleFunc("/settings", settingsPage).Methods(http.MethodGet)
552         r.HandleFunc("/signin", signin).Methods(http.MethodPost)
553         r.HandleFunc("/oauth_callback", oauthCallback).Methods(http.MethodGet)
554         r.HandleFunc("/post", post).Methods(http.MethodPost)
555         r.HandleFunc("/like/{id}", like).Methods(http.MethodPost)
556         r.HandleFunc("/unlike/{id}", unlike).Methods(http.MethodPost)
557         r.HandleFunc("/retweet/{id}", retweet).Methods(http.MethodPost)
558         r.HandleFunc("/unretweet/{id}", unretweet).Methods(http.MethodPost)
559         r.HandleFunc("/follow/{id}", follow).Methods(http.MethodPost)
560         r.HandleFunc("/unfollow/{id}", unfollow).Methods(http.MethodPost)
561         r.HandleFunc("/settings", settings).Methods(http.MethodPost)
562         r.HandleFunc("/signout", signout).Methods(http.MethodGet)
563         r.HandleFunc("/fluoride/like/{id}", fLike).Methods(http.MethodPost)
564         r.HandleFunc("/fluoride/unlike/{id}", fUnlike).Methods(http.MethodPost)
565         r.HandleFunc("/fluoride/retweet/{id}", fRetweet).Methods(http.MethodPost)
566         r.HandleFunc("/fluoride/unretweet/{id}", fUnretweet).Methods(http.MethodPost)
567         r.PathPrefix("/static").Handler(http.StripPrefix("/static",
568                 http.FileServer(http.Dir(path.Join(".", staticDir)))))
569
570         return r
571 }