041330d4824df1c9606932c4b2f3da74ab3e1dfb
[bloat] / service / transport.go
1 package service
2
3 import (
4         "context"
5         "mime/multipart"
6         "net/http"
7         "path"
8         "strconv"
9         "time"
10
11         "bloat/model"
12
13         "github.com/gorilla/mux"
14 )
15
16 var (
17         ctx       = context.Background()
18         cookieAge = "31536000"
19 )
20
21 func NewHandler(s Service, staticDir string) http.Handler {
22         r := mux.NewRouter()
23
24         r.PathPrefix("/static").Handler(http.StripPrefix("/static",
25                 http.FileServer(http.Dir(path.Join(".", staticDir)))))
26
27         r.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
28                 location := "/signin"
29
30                 sessionID, _ := req.Cookie("session_id")
31                 if sessionID != nil && len(sessionID.Value) > 0 {
32                         location = "/timeline/home"
33                 }
34
35                 w.Header().Add("Location", location)
36                 w.WriteHeader(http.StatusFound)
37         }).Methods(http.MethodGet)
38
39         r.HandleFunc("/signin", func(w http.ResponseWriter, req *http.Request) {
40                 err := s.ServeSigninPage(ctx, w)
41                 if err != nil {
42                         s.ServeErrorPage(ctx, w, err)
43                         return
44                 }
45         }).Methods(http.MethodGet)
46
47         r.HandleFunc("/signin", func(w http.ResponseWriter, req *http.Request) {
48                 instance := req.FormValue("instance")
49                 url, sessionID, err := s.GetAuthUrl(ctx, instance)
50                 if err != nil {
51                         s.ServeErrorPage(ctx, w, err)
52                         return
53                 }
54
55                 http.SetCookie(w, &http.Cookie{
56                         Name:    "session_id",
57                         Value:   sessionID,
58                         Expires: time.Now().Add(365 * 24 * time.Hour),
59                 })
60
61                 w.Header().Add("Location", url)
62                 w.WriteHeader(http.StatusFound)
63         }).Methods(http.MethodPost)
64
65         r.HandleFunc("/oauth_callback", func(w http.ResponseWriter, req *http.Request) {
66                 ctx := getContextWithSession(context.Background(), req)
67                 token := req.URL.Query().Get("code")
68                 _, err := s.GetUserToken(ctx, "", nil, token)
69                 if err != nil {
70                         s.ServeErrorPage(ctx, w, err)
71                         return
72                 }
73
74                 w.Header().Add("Location", "/timeline/home")
75                 w.WriteHeader(http.StatusFound)
76         }).Methods(http.MethodGet)
77
78         r.HandleFunc("/timeline", func(w http.ResponseWriter, req *http.Request) {
79                 w.Header().Add("Location", "/timeline/home")
80                 w.WriteHeader(http.StatusFound)
81         }).Methods(http.MethodGet)
82
83         r.HandleFunc("/timeline/{type}", func(w http.ResponseWriter, req *http.Request) {
84                 ctx := getContextWithSession(context.Background(), req)
85
86                 timelineType, _ := mux.Vars(req)["type"]
87                 maxID := req.URL.Query().Get("max_id")
88                 sinceID := req.URL.Query().Get("since_id")
89                 minID := req.URL.Query().Get("min_id")
90
91                 err := s.ServeTimelinePage(ctx, w, nil, timelineType, maxID, sinceID, minID)
92                 if err != nil {
93                         s.ServeErrorPage(ctx, w, err)
94                         return
95                 }
96         }).Methods(http.MethodGet)
97
98         r.HandleFunc("/thread/{id}", func(w http.ResponseWriter, req *http.Request) {
99                 ctx := getContextWithSession(context.Background(), req)
100                 id, _ := mux.Vars(req)["id"]
101                 reply := req.URL.Query().Get("reply")
102                 err := s.ServeThreadPage(ctx, w, nil, id, len(reply) > 1)
103                 if err != nil {
104                         s.ServeErrorPage(ctx, w, err)
105                         return
106                 }
107         }).Methods(http.MethodGet)
108
109         r.HandleFunc("/likedby/{id}", func(w http.ResponseWriter, req *http.Request) {
110                 ctx := getContextWithSession(context.Background(), req)
111                 id, _ := mux.Vars(req)["id"]
112
113                 err := s.ServeLikedByPage(ctx, w, nil, id)
114                 if err != nil {
115                         s.ServeErrorPage(ctx, w, err)
116                         return
117                 }
118         }).Methods(http.MethodGet)
119
120         r.HandleFunc("/retweetedby/{id}", func(w http.ResponseWriter, req *http.Request) {
121                 ctx := getContextWithSession(context.Background(), req)
122                 id, _ := mux.Vars(req)["id"]
123
124                 err := s.ServeRetweetedByPage(ctx, w, nil, id)
125                 if err != nil {
126                         s.ServeErrorPage(ctx, w, err)
127                         return
128                 }
129         }).Methods(http.MethodGet)
130
131         r.HandleFunc("/following/{id}", func(w http.ResponseWriter, req *http.Request) {
132                 ctx := getContextWithSession(context.Background(), req)
133
134                 id, _ := mux.Vars(req)["id"]
135                 maxID := req.URL.Query().Get("max_id")
136                 minID := req.URL.Query().Get("min_id")
137
138                 err := s.ServeFollowingPage(ctx, w, nil, id, maxID, minID)
139                 if err != nil {
140                         s.ServeErrorPage(ctx, w, err)
141                         return
142                 }
143         }).Methods(http.MethodGet)
144
145         r.HandleFunc("/followers/{id}", func(w http.ResponseWriter, req *http.Request) {
146                 ctx := getContextWithSession(context.Background(), req)
147
148                 id, _ := mux.Vars(req)["id"]
149                 maxID := req.URL.Query().Get("max_id")
150                 minID := req.URL.Query().Get("min_id")
151
152                 err := s.ServeFollowersPage(ctx, w, nil, id, maxID, minID)
153                 if err != nil {
154                         s.ServeErrorPage(ctx, w, err)
155                         return
156                 }
157         }).Methods(http.MethodGet)
158
159         r.HandleFunc("/like/{id}", func(w http.ResponseWriter, req *http.Request) {
160                 ctx := getContextWithSession(context.Background(), req)
161                 id, _ := mux.Vars(req)["id"]
162                 retweetedByID := req.FormValue("retweeted_by_id")
163
164                 _, err := s.Like(ctx, w, nil, id)
165                 if err != nil {
166                         s.ServeErrorPage(ctx, w, err)
167                         return
168                 }
169
170                 rID := id
171                 if len(retweetedByID) > 0 {
172                         rID = retweetedByID
173                 }
174                 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+rID)
175                 w.WriteHeader(http.StatusFound)
176         }).Methods(http.MethodPost)
177
178         r.HandleFunc("/unlike/{id}", func(w http.ResponseWriter, req *http.Request) {
179                 ctx := getContextWithSession(context.Background(), req)
180                 id, _ := mux.Vars(req)["id"]
181                 retweetedByID := req.FormValue("retweeted_by_id")
182
183                 _, err := s.UnLike(ctx, w, nil, id)
184                 if err != nil {
185                         s.ServeErrorPage(ctx, w, err)
186                         return
187                 }
188
189                 rID := id
190                 if len(retweetedByID) > 0 {
191                         rID = retweetedByID
192                 }
193                 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+rID)
194                 w.WriteHeader(http.StatusFound)
195         }).Methods(http.MethodPost)
196
197         r.HandleFunc("/retweet/{id}", func(w http.ResponseWriter, req *http.Request) {
198                 ctx := getContextWithSession(context.Background(), req)
199                 id, _ := mux.Vars(req)["id"]
200                 retweetedByID := req.FormValue("retweeted_by_id")
201
202                 _, err := s.Retweet(ctx, w, nil, id)
203                 if err != nil {
204                         s.ServeErrorPage(ctx, w, err)
205                         return
206                 }
207
208                 rID := id
209                 if len(retweetedByID) > 0 {
210                         rID = retweetedByID
211                 }
212                 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+rID)
213                 w.WriteHeader(http.StatusFound)
214         }).Methods(http.MethodPost)
215
216         r.HandleFunc("/unretweet/{id}", func(w http.ResponseWriter, req *http.Request) {
217                 ctx := getContextWithSession(context.Background(), req)
218                 id, _ := mux.Vars(req)["id"]
219                 retweetedByID := req.FormValue("retweeted_by_id")
220
221                 _, err := s.UnRetweet(ctx, w, nil, id)
222                 if err != nil {
223                         s.ServeErrorPage(ctx, w, err)
224                         return
225                 }
226
227                 rID := id
228                 if len(retweetedByID) > 0 {
229                         rID = retweetedByID
230                 }
231                 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+rID)
232                 w.WriteHeader(http.StatusFound)
233         }).Methods(http.MethodPost)
234
235         r.HandleFunc("/post", func(w http.ResponseWriter, req *http.Request) {
236                 ctx := getContextWithSession(context.Background(), req)
237
238                 err := req.ParseMultipartForm(4 << 20)
239                 if err != nil {
240                         s.ServeErrorPage(ctx, w, err)
241                         return
242                 }
243
244                 content := getMultipartFormValue(req.MultipartForm, "content")
245                 replyToID := getMultipartFormValue(req.MultipartForm, "reply_to_id")
246                 format := getMultipartFormValue(req.MultipartForm, "format")
247                 visibility := getMultipartFormValue(req.MultipartForm, "visibility")
248                 isNSFW := "on" == getMultipartFormValue(req.MultipartForm, "is_nsfw")
249
250                 files := req.MultipartForm.File["attachments"]
251
252                 id, err := s.PostTweet(ctx, w, nil, content, replyToID, format, visibility, isNSFW, files)
253                 if err != nil {
254                         s.ServeErrorPage(ctx, w, err)
255                         return
256                 }
257
258                 location := "/timeline/home" + "#status-" + id
259                 if len(replyToID) > 0 {
260                         location = "/thread/" + replyToID + "#status-" + id
261                 }
262                 w.Header().Add("Location", location)
263                 w.WriteHeader(http.StatusFound)
264         }).Methods(http.MethodPost)
265
266         r.HandleFunc("/notifications", func(w http.ResponseWriter, req *http.Request) {
267                 ctx := getContextWithSession(context.Background(), req)
268
269                 maxID := req.URL.Query().Get("max_id")
270                 minID := req.URL.Query().Get("min_id")
271
272                 err := s.ServeNotificationPage(ctx, w, nil, maxID, minID)
273                 if err != nil {
274                         s.ServeErrorPage(ctx, w, err)
275                         return
276                 }
277         }).Methods(http.MethodGet)
278
279         r.HandleFunc("/user/{id}", func(w http.ResponseWriter, req *http.Request) {
280                 ctx := getContextWithSession(context.Background(), req)
281
282                 id, _ := mux.Vars(req)["id"]
283                 maxID := req.URL.Query().Get("max_id")
284                 minID := req.URL.Query().Get("min_id")
285
286                 err := s.ServeUserPage(ctx, w, nil, id, maxID, minID)
287                 if err != nil {
288                         s.ServeErrorPage(ctx, w, err)
289                         return
290                 }
291         }).Methods(http.MethodGet)
292
293         r.HandleFunc("/follow/{id}", func(w http.ResponseWriter, req *http.Request) {
294                 ctx := getContextWithSession(context.Background(), req)
295
296                 id, _ := mux.Vars(req)["id"]
297
298                 err := s.Follow(ctx, w, nil, id)
299                 if err != nil {
300                         s.ServeErrorPage(ctx, w, err)
301                         return
302                 }
303
304                 w.Header().Add("Location", req.Header.Get("Referer"))
305                 w.WriteHeader(http.StatusFound)
306         }).Methods(http.MethodPost)
307
308         r.HandleFunc("/unfollow/{id}", func(w http.ResponseWriter, req *http.Request) {
309                 ctx := getContextWithSession(context.Background(), req)
310
311                 id, _ := mux.Vars(req)["id"]
312
313                 err := s.UnFollow(ctx, w, nil, id)
314                 if err != nil {
315                         s.ServeErrorPage(ctx, w, err)
316                         return
317                 }
318
319                 w.Header().Add("Location", req.Header.Get("Referer"))
320                 w.WriteHeader(http.StatusFound)
321         }).Methods(http.MethodPost)
322
323         r.HandleFunc("/about", func(w http.ResponseWriter, req *http.Request) {
324                 ctx := getContextWithSession(context.Background(), req)
325
326                 err := s.ServeAboutPage(ctx, w, nil)
327                 if err != nil {
328                         s.ServeErrorPage(ctx, w, err)
329                         return
330                 }
331         }).Methods(http.MethodGet)
332
333         r.HandleFunc("/emojis", func(w http.ResponseWriter, req *http.Request) {
334                 ctx := getContextWithSession(context.Background(), req)
335
336                 err := s.ServeEmojiPage(ctx, w, nil)
337                 if err != nil {
338                         s.ServeErrorPage(ctx, w, err)
339                         return
340                 }
341         }).Methods(http.MethodGet)
342
343         r.HandleFunc("/search", func(w http.ResponseWriter, req *http.Request) {
344                 ctx := getContextWithSession(context.Background(), req)
345
346                 q := req.URL.Query().Get("q")
347                 qType := req.URL.Query().Get("type")
348                 offsetStr := req.URL.Query().Get("offset")
349
350                 var offset int
351                 var err error
352                 if len(offsetStr) > 1 {
353                         offset, err = strconv.Atoi(offsetStr)
354                         if err != nil {
355                                 s.ServeErrorPage(ctx, w, err)
356                                 return
357                         }
358                 }
359
360                 err = s.ServeSearchPage(ctx, w, nil, q, qType, offset)
361                 if err != nil {
362                         s.ServeErrorPage(ctx, w, err)
363                         return
364                 }
365         }).Methods(http.MethodGet)
366
367         r.HandleFunc("/settings", func(w http.ResponseWriter, req *http.Request) {
368                 ctx := getContextWithSession(context.Background(), req)
369
370                 err := s.ServeSettingsPage(ctx, w, nil)
371                 if err != nil {
372                         s.ServeErrorPage(ctx, w, err)
373                         return
374                 }
375         }).Methods(http.MethodGet)
376
377         r.HandleFunc("/settings", func(w http.ResponseWriter, req *http.Request) {
378                 ctx := getContextWithSession(context.Background(), req)
379
380                 visibility := req.FormValue("visibility")
381                 copyScope := req.FormValue("copy_scope") == "true"
382                 threadInNewTab := req.FormValue("thread_in_new_tab") == "true"
383                 maskNSFW := req.FormValue("mask_nsfw") == "true"
384                 settings := &model.Settings{
385                         DefaultVisibility: visibility,
386                         CopyScope:         copyScope,
387                         ThreadInNewTab:    threadInNewTab,
388                         MaskNSFW:          maskNSFW,
389                 }
390
391                 err := s.SaveSettings(ctx, w, nil, settings)
392                 if err != nil {
393                         s.ServeErrorPage(ctx, w, err)
394                         return
395                 }
396
397                 w.Header().Add("Location", req.Header.Get("Referer"))
398                 w.WriteHeader(http.StatusFound)
399         }).Methods(http.MethodPost)
400
401         r.HandleFunc("/signout", func(w http.ResponseWriter, req *http.Request) {
402                 // TODO remove session from database
403                 http.SetCookie(w, &http.Cookie{
404                         Name:    "session_id",
405                         Value:   "",
406                         Expires: time.Now(),
407                 })
408                 w.Header().Add("Location", "/")
409                 w.WriteHeader(http.StatusFound)
410         }).Methods(http.MethodGet)
411
412         return r
413 }
414
415 func getContextWithSession(ctx context.Context, req *http.Request) context.Context {
416         sessionID, err := req.Cookie("session_id")
417         if err != nil {
418                 return ctx
419         }
420         return context.WithValue(ctx, "session_id", sessionID.Value)
421 }
422
423 func getMultipartFormValue(mf *multipart.Form, key string) (val string) {
424         vals, ok := mf.Value[key]
425         if !ok {
426                 return ""
427         }
428         if len(vals) < 1 {
429                 return ""
430         }
431         return vals[0]
432 }