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