437d32ff6f297948dded4a8118fd7e6490b74fe8
[bloat] / service / transport.go
1 package service
2
3 import (
4         "context"
5         "fmt"
6         "mime/multipart"
7         "net/http"
8         "path"
9
10         "github.com/gorilla/mux"
11 )
12
13 var (
14         ctx       = context.Background()
15         cookieAge = "31536000"
16 )
17
18 func NewHandler(s Service, staticDir string) http.Handler {
19         r := mux.NewRouter()
20
21         r.PathPrefix("/static").Handler(http.StripPrefix("/static",
22                 http.FileServer(http.Dir(path.Join(".", staticDir)))))
23
24         r.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
25                 location := "/signin"
26
27                 sessionID, _ := req.Cookie("session_id")
28                 if sessionID != nil && len(sessionID.Value) > 0 {
29                         location = "/timeline"
30                 }
31
32                 w.Header().Add("Location", location)
33                 w.WriteHeader(http.StatusFound)
34         }).Methods(http.MethodGet)
35
36         r.HandleFunc("/signin", func(w http.ResponseWriter, req *http.Request) {
37                 err := s.ServeSigninPage(ctx, w)
38                 if err != nil {
39                         s.ServeErrorPage(ctx, w, err)
40                         return
41                 }
42         }).Methods(http.MethodGet)
43
44         r.HandleFunc("/signin", func(w http.ResponseWriter, req *http.Request) {
45                 instance := req.FormValue("instance")
46                 url, sessionId, err := s.GetAuthUrl(ctx, instance)
47                 if err != nil {
48                         s.ServeErrorPage(ctx, w, err)
49                         return
50                 }
51
52                 w.Header().Add("Set-Cookie", fmt.Sprintf("session_id=%s;max-age=%s", sessionId, cookieAge))
53                 w.Header().Add("Location", url)
54                 w.WriteHeader(http.StatusFound)
55         }).Methods(http.MethodPost)
56
57         r.HandleFunc("/oauth_callback", func(w http.ResponseWriter, req *http.Request) {
58                 ctx := getContextWithSession(context.Background(), req)
59                 token := req.URL.Query().Get("code")
60                 _, err := s.GetUserToken(ctx, "", nil, token)
61                 if err != nil {
62                         s.ServeErrorPage(ctx, w, err)
63                         return
64                 }
65
66                 w.Header().Add("Location", "/timeline")
67                 w.WriteHeader(http.StatusFound)
68         }).Methods(http.MethodGet)
69
70         r.HandleFunc("/timeline", func(w http.ResponseWriter, req *http.Request) {
71                 ctx := getContextWithSession(context.Background(), req)
72
73                 maxID := req.URL.Query().Get("max_id")
74                 sinceID := req.URL.Query().Get("since_id")
75                 minID := req.URL.Query().Get("min_id")
76
77                 err := s.ServeTimelinePage(ctx, w, nil, maxID, sinceID, minID)
78                 if err != nil {
79                         s.ServeErrorPage(ctx, w, err)
80                         return
81                 }
82         }).Methods(http.MethodGet)
83
84         r.HandleFunc("/thread/{id}", func(w http.ResponseWriter, req *http.Request) {
85                 ctx := getContextWithSession(context.Background(), req)
86                 id, _ := mux.Vars(req)["id"]
87                 reply := req.URL.Query().Get("reply")
88                 err := s.ServeThreadPage(ctx, w, nil, id, len(reply) > 1)
89                 if err != nil {
90                         s.ServeErrorPage(ctx, w, err)
91                         return
92                 }
93         }).Methods(http.MethodGet)
94
95         r.HandleFunc("/like/{id}", func(w http.ResponseWriter, req *http.Request) {
96                 ctx := getContextWithSession(context.Background(), req)
97                 id, _ := mux.Vars(req)["id"]
98                 err := s.Like(ctx, w, nil, id)
99                 if err != nil {
100                         s.ServeErrorPage(ctx, w, err)
101                         return
102                 }
103
104                 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+id)
105                 w.WriteHeader(http.StatusFound)
106         }).Methods(http.MethodGet)
107
108         r.HandleFunc("/unlike/{id}", func(w http.ResponseWriter, req *http.Request) {
109                 ctx := getContextWithSession(context.Background(), req)
110                 id, _ := mux.Vars(req)["id"]
111                 err := s.UnLike(ctx, w, nil, id)
112                 if err != nil {
113                         s.ServeErrorPage(ctx, w, err)
114                         return
115                 }
116
117                 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+id)
118                 w.WriteHeader(http.StatusFound)
119         }).Methods(http.MethodGet)
120
121         r.HandleFunc("/retweet/{id}", func(w http.ResponseWriter, req *http.Request) {
122                 ctx := getContextWithSession(context.Background(), req)
123                 id, _ := mux.Vars(req)["id"]
124                 err := s.Retweet(ctx, w, nil, id)
125                 if err != nil {
126                         s.ServeErrorPage(ctx, w, err)
127                         return
128                 }
129
130                 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+id)
131                 w.WriteHeader(http.StatusFound)
132         }).Methods(http.MethodGet)
133
134         r.HandleFunc("/unretweet/{id}", func(w http.ResponseWriter, req *http.Request) {
135                 ctx := getContextWithSession(context.Background(), req)
136                 id, _ := mux.Vars(req)["id"]
137                 err := s.UnRetweet(ctx, w, nil, id)
138                 if err != nil {
139                         s.ServeErrorPage(ctx, w, err)
140                         return
141                 }
142
143                 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+id)
144                 w.WriteHeader(http.StatusFound)
145         }).Methods(http.MethodGet)
146
147         r.HandleFunc("/post", func(w http.ResponseWriter, req *http.Request) {
148                 ctx := getContextWithSession(context.Background(), req)
149
150                 err := req.ParseMultipartForm(4 << 20)
151                 if err != nil {
152                         s.ServeErrorPage(ctx, w, err)
153                         return
154                 }
155
156                 content := getMultipartFormValue(req.MultipartForm, "content")
157                 replyToID := getMultipartFormValue(req.MultipartForm, "reply_to_id")
158                 visibility := getMultipartFormValue(req.MultipartForm, "visibility")
159                 isNSFW := "on" == getMultipartFormValue(req.MultipartForm, "is_nsfw")
160
161                 files := req.MultipartForm.File["attachments"]
162
163                 id, err := s.PostTweet(ctx, w, nil, content, replyToID, visibility, isNSFW, files)
164                 if err != nil {
165                         s.ServeErrorPage(ctx, w, err)
166                         return
167                 }
168
169                 location := "/timeline" + "#status-" + id
170                 if len(replyToID) > 0 {
171                         location = "/thread/" + replyToID + "#status-" + id
172                 }
173                 w.Header().Add("Location", location)
174                 w.WriteHeader(http.StatusFound)
175         }).Methods(http.MethodPost)
176
177         r.HandleFunc("/notifications", func(w http.ResponseWriter, req *http.Request) {
178                 ctx := getContextWithSession(context.Background(), req)
179
180                 maxID := req.URL.Query().Get("max_id")
181                 minID := req.URL.Query().Get("min_id")
182
183                 err := s.ServeNotificationPage(ctx, w, nil, maxID, minID)
184                 if err != nil {
185                         s.ServeErrorPage(ctx, w, err)
186                         return
187                 }
188         }).Methods(http.MethodGet)
189
190         r.HandleFunc("/user/{id}", func(w http.ResponseWriter, req *http.Request) {
191                 ctx := getContextWithSession(context.Background(), req)
192
193                 id, _ := mux.Vars(req)["id"]
194                 maxID := req.URL.Query().Get("max_id")
195                 minID := req.URL.Query().Get("min_id")
196
197                 err := s.ServeUserPage(ctx, w, nil, id, maxID, minID)
198                 if err != nil {
199                         s.ServeErrorPage(ctx, w, err)
200                         return
201                 }
202         }).Methods(http.MethodGet)
203
204         r.HandleFunc("/follow/{id}", func(w http.ResponseWriter, req *http.Request) {
205                 ctx := getContextWithSession(context.Background(), req)
206
207                 id, _ := mux.Vars(req)["id"]
208
209                 err := s.Follow(ctx, w, nil, id)
210                 if err != nil {
211                         s.ServeErrorPage(ctx, w, err)
212                         return
213                 }
214
215                 w.Header().Add("Location", req.Header.Get("Referer"))
216                 w.WriteHeader(http.StatusFound)
217         }).Methods(http.MethodPost)
218
219         r.HandleFunc("/unfollow/{id}", func(w http.ResponseWriter, req *http.Request) {
220                 ctx := getContextWithSession(context.Background(), req)
221
222                 id, _ := mux.Vars(req)["id"]
223
224                 err := s.UnFollow(ctx, w, nil, id)
225                 if err != nil {
226                         s.ServeErrorPage(ctx, w, err)
227                         return
228                 }
229
230                 w.Header().Add("Location", req.Header.Get("Referer"))
231                 w.WriteHeader(http.StatusFound)
232         }).Methods(http.MethodPost)
233
234         r.HandleFunc("/about", func(w http.ResponseWriter, req *http.Request) {
235                 ctx := getContextWithSession(context.Background(), req)
236
237                 err := s.ServeAboutPage(ctx, w, nil)
238                 if err != nil {
239                         s.ServeErrorPage(ctx, w, err)
240                         return
241                 }
242         }).Methods(http.MethodGet)
243
244         r.HandleFunc("/emojis", func(w http.ResponseWriter, req *http.Request) {
245                 ctx := getContextWithSession(context.Background(), req)
246
247                 err := s.ServeEmojiPage(ctx, w, nil)
248                 if err != nil {
249                         s.ServeErrorPage(ctx, w, err)
250                         return
251                 }
252         }).Methods(http.MethodGet)
253
254         r.HandleFunc("/signout", func(w http.ResponseWriter, req *http.Request) {
255                 // TODO remove session from database
256                 w.Header().Add("Set-Cookie", fmt.Sprintf("session_id=;max-age=0"))
257                 w.Header().Add("Location", "/")
258                 w.WriteHeader(http.StatusFound)
259         }).Methods(http.MethodGet)
260
261         return r
262 }
263
264 func getContextWithSession(ctx context.Context, req *http.Request) context.Context {
265         sessionID, err := req.Cookie("session_id")
266         if err != nil {
267                 return ctx
268         }
269         return context.WithValue(ctx, "session_id", sessionID.Value)
270 }
271
272 func getMultipartFormValue(mf *multipart.Form, key string) (val string) {
273         vals, ok := mf.Value[key]
274         if !ok {
275                 return ""
276         }
277         if len(vals) < 1 {
278                 return ""
279         }
280         return vals[0]
281 }