Add settings page
[bloat] / service / transport.go
1 package service
2
3 import (
4         "context"
5         "fmt"
6         "mime/multipart"
7         "net/http"
8         "path"
9         "strconv"
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                 w.Header().Add("Set-Cookie", fmt.Sprintf("session_id=%s;max-age=%s", sessionId, cookieAge))
55                 w.Header().Add("Location", url)
56                 w.WriteHeader(http.StatusFound)
57         }).Methods(http.MethodPost)
58
59         r.HandleFunc("/oauth_callback", func(w http.ResponseWriter, req *http.Request) {
60                 ctx := getContextWithSession(context.Background(), req)
61                 token := req.URL.Query().Get("code")
62                 _, err := s.GetUserToken(ctx, "", nil, token)
63                 if err != nil {
64                         s.ServeErrorPage(ctx, w, err)
65                         return
66                 }
67
68                 w.Header().Add("Location", "/timeline/home")
69                 w.WriteHeader(http.StatusFound)
70         }).Methods(http.MethodGet)
71
72         r.HandleFunc("/timeline", func(w http.ResponseWriter, req *http.Request) {
73                 w.Header().Add("Location", "/timeline/home")
74                 w.WriteHeader(http.StatusFound)
75         }).Methods(http.MethodGet)
76
77         r.HandleFunc("/timeline/{type}", func(w http.ResponseWriter, req *http.Request) {
78                 ctx := getContextWithSession(context.Background(), req)
79
80                 timelineType, _ := mux.Vars(req)["type"]
81                 maxID := req.URL.Query().Get("max_id")
82                 sinceID := req.URL.Query().Get("since_id")
83                 minID := req.URL.Query().Get("min_id")
84
85                 err := s.ServeTimelinePage(ctx, w, nil, timelineType, maxID, sinceID, minID)
86                 if err != nil {
87                         s.ServeErrorPage(ctx, w, err)
88                         return
89                 }
90         }).Methods(http.MethodGet)
91
92         r.HandleFunc("/thread/{id}", func(w http.ResponseWriter, req *http.Request) {
93                 ctx := getContextWithSession(context.Background(), req)
94                 id, _ := mux.Vars(req)["id"]
95                 reply := req.URL.Query().Get("reply")
96                 err := s.ServeThreadPage(ctx, w, nil, id, len(reply) > 1)
97                 if err != nil {
98                         s.ServeErrorPage(ctx, w, err)
99                         return
100                 }
101         }).Methods(http.MethodGet)
102
103         r.HandleFunc("/likedby/{id}", func(w http.ResponseWriter, req *http.Request) {
104                 ctx := getContextWithSession(context.Background(), req)
105                 id, _ := mux.Vars(req)["id"]
106
107                 err := s.ServeLikedByPage(ctx, w, nil, id)
108                 if err != nil {
109                         s.ServeErrorPage(ctx, w, err)
110                         return
111                 }
112         }).Methods(http.MethodGet)
113
114         r.HandleFunc("/retweetedby/{id}", func(w http.ResponseWriter, req *http.Request) {
115                 ctx := getContextWithSession(context.Background(), req)
116                 id, _ := mux.Vars(req)["id"]
117
118                 err := s.ServeRetweetedByPage(ctx, w, nil, id)
119                 if err != nil {
120                         s.ServeErrorPage(ctx, w, err)
121                         return
122                 }
123         }).Methods(http.MethodGet)
124
125         r.HandleFunc("/like/{id}", func(w http.ResponseWriter, req *http.Request) {
126                 ctx := getContextWithSession(context.Background(), req)
127                 id, _ := mux.Vars(req)["id"]
128                 err := s.Like(ctx, w, nil, id)
129                 if err != nil {
130                         s.ServeErrorPage(ctx, w, err)
131                         return
132                 }
133
134                 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+id)
135                 w.WriteHeader(http.StatusFound)
136         }).Methods(http.MethodGet)
137
138         r.HandleFunc("/unlike/{id}", func(w http.ResponseWriter, req *http.Request) {
139                 ctx := getContextWithSession(context.Background(), req)
140                 id, _ := mux.Vars(req)["id"]
141                 err := s.UnLike(ctx, w, nil, id)
142                 if err != nil {
143                         s.ServeErrorPage(ctx, w, err)
144                         return
145                 }
146
147                 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+id)
148                 w.WriteHeader(http.StatusFound)
149         }).Methods(http.MethodGet)
150
151         r.HandleFunc("/retweet/{id}", func(w http.ResponseWriter, req *http.Request) {
152                 ctx := getContextWithSession(context.Background(), req)
153                 id, _ := mux.Vars(req)["id"]
154                 err := s.Retweet(ctx, w, nil, id)
155                 if err != nil {
156                         s.ServeErrorPage(ctx, w, err)
157                         return
158                 }
159
160                 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+id)
161                 w.WriteHeader(http.StatusFound)
162         }).Methods(http.MethodGet)
163
164         r.HandleFunc("/unretweet/{id}", func(w http.ResponseWriter, req *http.Request) {
165                 ctx := getContextWithSession(context.Background(), req)
166                 id, _ := mux.Vars(req)["id"]
167                 err := s.UnRetweet(ctx, w, nil, id)
168                 if err != nil {
169                         s.ServeErrorPage(ctx, w, err)
170                         return
171                 }
172
173                 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+id)
174                 w.WriteHeader(http.StatusFound)
175         }).Methods(http.MethodGet)
176
177         r.HandleFunc("/post", func(w http.ResponseWriter, req *http.Request) {
178                 ctx := getContextWithSession(context.Background(), req)
179
180                 err := req.ParseMultipartForm(4 << 20)
181                 if err != nil {
182                         s.ServeErrorPage(ctx, w, err)
183                         return
184                 }
185
186                 content := getMultipartFormValue(req.MultipartForm, "content")
187                 replyToID := getMultipartFormValue(req.MultipartForm, "reply_to_id")
188                 format := getMultipartFormValue(req.MultipartForm, "format")
189                 visibility := getMultipartFormValue(req.MultipartForm, "visibility")
190                 isNSFW := "on" == getMultipartFormValue(req.MultipartForm, "is_nsfw")
191
192                 files := req.MultipartForm.File["attachments"]
193
194                 id, err := s.PostTweet(ctx, w, nil, content, replyToID, format, visibility, isNSFW, files)
195                 if err != nil {
196                         s.ServeErrorPage(ctx, w, err)
197                         return
198                 }
199
200                 location := "/timeline/home" + "#status-" + id
201                 if len(replyToID) > 0 {
202                         location = "/thread/" + replyToID + "#status-" + id
203                 }
204                 w.Header().Add("Location", location)
205                 w.WriteHeader(http.StatusFound)
206         }).Methods(http.MethodPost)
207
208         r.HandleFunc("/notifications", func(w http.ResponseWriter, req *http.Request) {
209                 ctx := getContextWithSession(context.Background(), req)
210
211                 maxID := req.URL.Query().Get("max_id")
212                 minID := req.URL.Query().Get("min_id")
213
214                 err := s.ServeNotificationPage(ctx, w, nil, maxID, minID)
215                 if err != nil {
216                         s.ServeErrorPage(ctx, w, err)
217                         return
218                 }
219         }).Methods(http.MethodGet)
220
221         r.HandleFunc("/user/{id}", func(w http.ResponseWriter, req *http.Request) {
222                 ctx := getContextWithSession(context.Background(), req)
223
224                 id, _ := mux.Vars(req)["id"]
225                 maxID := req.URL.Query().Get("max_id")
226                 minID := req.URL.Query().Get("min_id")
227
228                 err := s.ServeUserPage(ctx, w, nil, id, maxID, minID)
229                 if err != nil {
230                         s.ServeErrorPage(ctx, w, err)
231                         return
232                 }
233         }).Methods(http.MethodGet)
234
235         r.HandleFunc("/follow/{id}", func(w http.ResponseWriter, req *http.Request) {
236                 ctx := getContextWithSession(context.Background(), req)
237
238                 id, _ := mux.Vars(req)["id"]
239
240                 err := s.Follow(ctx, w, nil, id)
241                 if err != nil {
242                         s.ServeErrorPage(ctx, w, err)
243                         return
244                 }
245
246                 w.Header().Add("Location", req.Header.Get("Referer"))
247                 w.WriteHeader(http.StatusFound)
248         }).Methods(http.MethodPost)
249
250         r.HandleFunc("/unfollow/{id}", func(w http.ResponseWriter, req *http.Request) {
251                 ctx := getContextWithSession(context.Background(), req)
252
253                 id, _ := mux.Vars(req)["id"]
254
255                 err := s.UnFollow(ctx, w, nil, id)
256                 if err != nil {
257                         s.ServeErrorPage(ctx, w, err)
258                         return
259                 }
260
261                 w.Header().Add("Location", req.Header.Get("Referer"))
262                 w.WriteHeader(http.StatusFound)
263         }).Methods(http.MethodPost)
264
265         r.HandleFunc("/about", func(w http.ResponseWriter, req *http.Request) {
266                 ctx := getContextWithSession(context.Background(), req)
267
268                 err := s.ServeAboutPage(ctx, w, nil)
269                 if err != nil {
270                         s.ServeErrorPage(ctx, w, err)
271                         return
272                 }
273         }).Methods(http.MethodGet)
274
275         r.HandleFunc("/emojis", func(w http.ResponseWriter, req *http.Request) {
276                 ctx := getContextWithSession(context.Background(), req)
277
278                 err := s.ServeEmojiPage(ctx, w, nil)
279                 if err != nil {
280                         s.ServeErrorPage(ctx, w, err)
281                         return
282                 }
283         }).Methods(http.MethodGet)
284
285         r.HandleFunc("/search", func(w http.ResponseWriter, req *http.Request) {
286                 ctx := getContextWithSession(context.Background(), req)
287
288                 q := req.URL.Query().Get("q")
289                 qType := req.URL.Query().Get("type")
290                 offsetStr := req.URL.Query().Get("offset")
291
292                 var offset int
293                 var err error
294                 if len(offsetStr) > 1 {
295                         offset, err = strconv.Atoi(offsetStr)
296                         if err != nil {
297                                 s.ServeErrorPage(ctx, w, err)
298                                 return
299                         }
300                 }
301
302                 err = s.ServeSearchPage(ctx, w, nil, q, qType, offset)
303                 if err != nil {
304                         s.ServeErrorPage(ctx, w, err)
305                         return
306                 }
307         }).Methods(http.MethodGet)
308
309         r.HandleFunc("/settings", func(w http.ResponseWriter, req *http.Request) {
310                 ctx := getContextWithSession(context.Background(), req)
311
312                 err := s.ServeSettingsPage(ctx, w, nil)
313                 if err != nil {
314                         s.ServeErrorPage(ctx, w, err)
315                         return
316                 }
317         }).Methods(http.MethodGet)
318
319         r.HandleFunc("/settings", func(w http.ResponseWriter, req *http.Request) {
320                 ctx := getContextWithSession(context.Background(), req)
321
322                 visibility := req.FormValue("visibility")
323                 copyScope := req.FormValue("copy_scope") == "true"
324                 settings := &model.Settings{
325                         DefaultVisibility: visibility,
326                         CopyScope: copyScope,
327                 }
328
329                 err := s.SaveSettings(ctx, w, nil, settings)
330                 if err != nil {
331                         s.ServeErrorPage(ctx, w, err)
332                         return
333                 }
334
335                 w.Header().Add("Location", req.Header.Get("Referer"))
336                 w.WriteHeader(http.StatusFound)
337         }).Methods(http.MethodPost)
338
339         r.HandleFunc("/signout", func(w http.ResponseWriter, req *http.Request) {
340                 // TODO remove session from database
341                 w.Header().Add("Set-Cookie", fmt.Sprintf("session_id=;max-age=0"))
342                 w.Header().Add("Location", "/")
343                 w.WriteHeader(http.StatusFound)
344         }).Methods(http.MethodGet)
345
346         return r
347 }
348
349 func getContextWithSession(ctx context.Context, req *http.Request) context.Context {
350         sessionID, err := req.Cookie("session_id")
351         if err != nil {
352                 return ctx
353         }
354         return context.WithValue(ctx, "session_id", sessionID.Value)
355 }
356
357 func getMultipartFormValue(mf *multipart.Form, key string) (val string) {
358         vals, ok := mf.Value[key]
359         if !ok {
360                 return ""
361         }
362         if len(vals) < 1 {
363                 return ""
364         }
365         return vals[0]
366 }