Use post method for like and retweet
[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                 err := s.Like(ctx, w, nil, id)
163                 if err != nil {
164                         s.ServeErrorPage(ctx, w, err)
165                         return
166                 }
167
168                 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+id)
169                 w.WriteHeader(http.StatusFound)
170         }).Methods(http.MethodPost)
171
172         r.HandleFunc("/unlike/{id}", func(w http.ResponseWriter, req *http.Request) {
173                 ctx := getContextWithSession(context.Background(), req)
174                 id, _ := mux.Vars(req)["id"]
175                 err := s.UnLike(ctx, w, nil, id)
176                 if err != nil {
177                         s.ServeErrorPage(ctx, w, err)
178                         return
179                 }
180
181                 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+id)
182                 w.WriteHeader(http.StatusFound)
183         }).Methods(http.MethodPost)
184
185         r.HandleFunc("/retweet/{id}", func(w http.ResponseWriter, req *http.Request) {
186                 ctx := getContextWithSession(context.Background(), req)
187                 id, _ := mux.Vars(req)["id"]
188                 err := s.Retweet(ctx, w, nil, id)
189                 if err != nil {
190                         s.ServeErrorPage(ctx, w, err)
191                         return
192                 }
193
194                 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+id)
195                 w.WriteHeader(http.StatusFound)
196         }).Methods(http.MethodPost)
197
198         r.HandleFunc("/unretweet/{id}", func(w http.ResponseWriter, req *http.Request) {
199                 ctx := getContextWithSession(context.Background(), req)
200                 id, _ := mux.Vars(req)["id"]
201                 err := s.UnRetweet(ctx, w, nil, id)
202                 if err != nil {
203                         s.ServeErrorPage(ctx, w, err)
204                         return
205                 }
206
207                 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+id)
208                 w.WriteHeader(http.StatusFound)
209         }).Methods(http.MethodPost)
210
211         r.HandleFunc("/post", func(w http.ResponseWriter, req *http.Request) {
212                 ctx := getContextWithSession(context.Background(), req)
213
214                 err := req.ParseMultipartForm(4 << 20)
215                 if err != nil {
216                         s.ServeErrorPage(ctx, w, err)
217                         return
218                 }
219
220                 content := getMultipartFormValue(req.MultipartForm, "content")
221                 replyToID := getMultipartFormValue(req.MultipartForm, "reply_to_id")
222                 format := getMultipartFormValue(req.MultipartForm, "format")
223                 visibility := getMultipartFormValue(req.MultipartForm, "visibility")
224                 isNSFW := "on" == getMultipartFormValue(req.MultipartForm, "is_nsfw")
225
226                 files := req.MultipartForm.File["attachments"]
227
228                 id, err := s.PostTweet(ctx, w, nil, content, replyToID, format, visibility, isNSFW, files)
229                 if err != nil {
230                         s.ServeErrorPage(ctx, w, err)
231                         return
232                 }
233
234                 location := "/timeline/home" + "#status-" + id
235                 if len(replyToID) > 0 {
236                         location = "/thread/" + replyToID + "#status-" + id
237                 }
238                 w.Header().Add("Location", location)
239                 w.WriteHeader(http.StatusFound)
240         }).Methods(http.MethodPost)
241
242         r.HandleFunc("/notifications", func(w http.ResponseWriter, req *http.Request) {
243                 ctx := getContextWithSession(context.Background(), req)
244
245                 maxID := req.URL.Query().Get("max_id")
246                 minID := req.URL.Query().Get("min_id")
247
248                 err := s.ServeNotificationPage(ctx, w, nil, maxID, minID)
249                 if err != nil {
250                         s.ServeErrorPage(ctx, w, err)
251                         return
252                 }
253         }).Methods(http.MethodGet)
254
255         r.HandleFunc("/user/{id}", func(w http.ResponseWriter, req *http.Request) {
256                 ctx := getContextWithSession(context.Background(), req)
257
258                 id, _ := mux.Vars(req)["id"]
259                 maxID := req.URL.Query().Get("max_id")
260                 minID := req.URL.Query().Get("min_id")
261
262                 err := s.ServeUserPage(ctx, w, nil, id, maxID, minID)
263                 if err != nil {
264                         s.ServeErrorPage(ctx, w, err)
265                         return
266                 }
267         }).Methods(http.MethodGet)
268
269         r.HandleFunc("/follow/{id}", func(w http.ResponseWriter, req *http.Request) {
270                 ctx := getContextWithSession(context.Background(), req)
271
272                 id, _ := mux.Vars(req)["id"]
273
274                 err := s.Follow(ctx, w, nil, id)
275                 if err != nil {
276                         s.ServeErrorPage(ctx, w, err)
277                         return
278                 }
279
280                 w.Header().Add("Location", req.Header.Get("Referer"))
281                 w.WriteHeader(http.StatusFound)
282         }).Methods(http.MethodPost)
283
284         r.HandleFunc("/unfollow/{id}", func(w http.ResponseWriter, req *http.Request) {
285                 ctx := getContextWithSession(context.Background(), req)
286
287                 id, _ := mux.Vars(req)["id"]
288
289                 err := s.UnFollow(ctx, w, nil, id)
290                 if err != nil {
291                         s.ServeErrorPage(ctx, w, err)
292                         return
293                 }
294
295                 w.Header().Add("Location", req.Header.Get("Referer"))
296                 w.WriteHeader(http.StatusFound)
297         }).Methods(http.MethodPost)
298
299         r.HandleFunc("/about", func(w http.ResponseWriter, req *http.Request) {
300                 ctx := getContextWithSession(context.Background(), req)
301
302                 err := s.ServeAboutPage(ctx, w, nil)
303                 if err != nil {
304                         s.ServeErrorPage(ctx, w, err)
305                         return
306                 }
307         }).Methods(http.MethodGet)
308
309         r.HandleFunc("/emojis", func(w http.ResponseWriter, req *http.Request) {
310                 ctx := getContextWithSession(context.Background(), req)
311
312                 err := s.ServeEmojiPage(ctx, w, nil)
313                 if err != nil {
314                         s.ServeErrorPage(ctx, w, err)
315                         return
316                 }
317         }).Methods(http.MethodGet)
318
319         r.HandleFunc("/search", func(w http.ResponseWriter, req *http.Request) {
320                 ctx := getContextWithSession(context.Background(), req)
321
322                 q := req.URL.Query().Get("q")
323                 qType := req.URL.Query().Get("type")
324                 offsetStr := req.URL.Query().Get("offset")
325
326                 var offset int
327                 var err error
328                 if len(offsetStr) > 1 {
329                         offset, err = strconv.Atoi(offsetStr)
330                         if err != nil {
331                                 s.ServeErrorPage(ctx, w, err)
332                                 return
333                         }
334                 }
335
336                 err = s.ServeSearchPage(ctx, w, nil, q, qType, offset)
337                 if err != nil {
338                         s.ServeErrorPage(ctx, w, err)
339                         return
340                 }
341         }).Methods(http.MethodGet)
342
343         r.HandleFunc("/settings", func(w http.ResponseWriter, req *http.Request) {
344                 ctx := getContextWithSession(context.Background(), req)
345
346                 err := s.ServeSettingsPage(ctx, w, nil)
347                 if err != nil {
348                         s.ServeErrorPage(ctx, w, err)
349                         return
350                 }
351         }).Methods(http.MethodGet)
352
353         r.HandleFunc("/settings", func(w http.ResponseWriter, req *http.Request) {
354                 ctx := getContextWithSession(context.Background(), req)
355
356                 visibility := req.FormValue("visibility")
357                 copyScope := req.FormValue("copy_scope") == "true"
358                 threadInNewTab := req.FormValue("thread_in_new_tab") == "true"
359                 maskNSFW := req.FormValue("mask_nsfw") == "true"
360                 settings := &model.Settings{
361                         DefaultVisibility: visibility,
362                         CopyScope:         copyScope,
363                         ThreadInNewTab:    threadInNewTab,
364                         MaskNSFW:          maskNSFW,
365                 }
366
367                 err := s.SaveSettings(ctx, w, nil, settings)
368                 if err != nil {
369                         s.ServeErrorPage(ctx, w, err)
370                         return
371                 }
372
373                 w.Header().Add("Location", req.Header.Get("Referer"))
374                 w.WriteHeader(http.StatusFound)
375         }).Methods(http.MethodPost)
376
377         r.HandleFunc("/signout", func(w http.ResponseWriter, req *http.Request) {
378                 // TODO remove session from database
379                 http.SetCookie(w, &http.Cookie{
380                         Name:    "session_id",
381                         Value:   "",
382                         Expires: time.Now(),
383                 })
384                 w.Header().Add("Location", "/")
385                 w.WriteHeader(http.StatusFound)
386         }).Methods(http.MethodGet)
387
388         return r
389 }
390
391 func getContextWithSession(ctx context.Context, req *http.Request) context.Context {
392         sessionID, err := req.Cookie("session_id")
393         if err != nil {
394                 return ctx
395         }
396         return context.WithValue(ctx, "session_id", sessionID.Value)
397 }
398
399 func getMultipartFormValue(mf *multipart.Form, key string) (val string) {
400         vals, ok := mf.Value[key]
401         if !ok {
402                 return ""
403         }
404         if len(vals) < 1 {
405                 return ""
406         }
407         return vals[0]
408 }