Add local and twkn timelines
[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/home"
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/home")
67                 w.WriteHeader(http.StatusFound)
68         }).Methods(http.MethodGet)
69
70         r.HandleFunc("/timeline", func(w http.ResponseWriter, req *http.Request) {
71                 w.Header().Add("Location", "/timeline/home")
72                 w.WriteHeader(http.StatusFound)
73         }).Methods(http.MethodGet)
74
75         r.HandleFunc("/timeline/{type}", func(w http.ResponseWriter, req *http.Request) {
76                 ctx := getContextWithSession(context.Background(), req)
77
78                 timelineType, _ := mux.Vars(req)["type"]
79                 maxID := req.URL.Query().Get("max_id")
80                 sinceID := req.URL.Query().Get("since_id")
81                 minID := req.URL.Query().Get("min_id")
82
83                 err := s.ServeTimelinePage(ctx, w, nil, timelineType, maxID, sinceID, minID)
84                 if err != nil {
85                         s.ServeErrorPage(ctx, w, err)
86                         return
87                 }
88         }).Methods(http.MethodGet)
89
90         r.HandleFunc("/thread/{id}", func(w http.ResponseWriter, req *http.Request) {
91                 ctx := getContextWithSession(context.Background(), req)
92                 id, _ := mux.Vars(req)["id"]
93                 reply := req.URL.Query().Get("reply")
94                 err := s.ServeThreadPage(ctx, w, nil, id, len(reply) > 1)
95                 if err != nil {
96                         s.ServeErrorPage(ctx, w, err)
97                         return
98                 }
99         }).Methods(http.MethodGet)
100
101         r.HandleFunc("/like/{id}", func(w http.ResponseWriter, req *http.Request) {
102                 ctx := getContextWithSession(context.Background(), req)
103                 id, _ := mux.Vars(req)["id"]
104                 err := s.Like(ctx, w, nil, id)
105                 if err != nil {
106                         s.ServeErrorPage(ctx, w, err)
107                         return
108                 }
109
110                 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+id)
111                 w.WriteHeader(http.StatusFound)
112         }).Methods(http.MethodGet)
113
114         r.HandleFunc("/unlike/{id}", func(w http.ResponseWriter, req *http.Request) {
115                 ctx := getContextWithSession(context.Background(), req)
116                 id, _ := mux.Vars(req)["id"]
117                 err := s.UnLike(ctx, w, nil, id)
118                 if err != nil {
119                         s.ServeErrorPage(ctx, w, err)
120                         return
121                 }
122
123                 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+id)
124                 w.WriteHeader(http.StatusFound)
125         }).Methods(http.MethodGet)
126
127         r.HandleFunc("/retweet/{id}", func(w http.ResponseWriter, req *http.Request) {
128                 ctx := getContextWithSession(context.Background(), req)
129                 id, _ := mux.Vars(req)["id"]
130                 err := s.Retweet(ctx, w, nil, id)
131                 if err != nil {
132                         s.ServeErrorPage(ctx, w, err)
133                         return
134                 }
135
136                 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+id)
137                 w.WriteHeader(http.StatusFound)
138         }).Methods(http.MethodGet)
139
140         r.HandleFunc("/unretweet/{id}", func(w http.ResponseWriter, req *http.Request) {
141                 ctx := getContextWithSession(context.Background(), req)
142                 id, _ := mux.Vars(req)["id"]
143                 err := s.UnRetweet(ctx, w, nil, id)
144                 if err != nil {
145                         s.ServeErrorPage(ctx, w, err)
146                         return
147                 }
148
149                 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+id)
150                 w.WriteHeader(http.StatusFound)
151         }).Methods(http.MethodGet)
152
153         r.HandleFunc("/post", func(w http.ResponseWriter, req *http.Request) {
154                 ctx := getContextWithSession(context.Background(), req)
155
156                 err := req.ParseMultipartForm(4 << 20)
157                 if err != nil {
158                         s.ServeErrorPage(ctx, w, err)
159                         return
160                 }
161
162                 content := getMultipartFormValue(req.MultipartForm, "content")
163                 replyToID := getMultipartFormValue(req.MultipartForm, "reply_to_id")
164                 visibility := getMultipartFormValue(req.MultipartForm, "visibility")
165                 isNSFW := "on" == getMultipartFormValue(req.MultipartForm, "is_nsfw")
166
167                 files := req.MultipartForm.File["attachments"]
168
169                 id, err := s.PostTweet(ctx, w, nil, content, replyToID, visibility, isNSFW, files)
170                 if err != nil {
171                         s.ServeErrorPage(ctx, w, err)
172                         return
173                 }
174
175                 location := "/timeline/home" + "#status-" + id
176                 if len(replyToID) > 0 {
177                         location = "/thread/" + replyToID + "#status-" + id
178                 }
179                 w.Header().Add("Location", location)
180                 w.WriteHeader(http.StatusFound)
181         }).Methods(http.MethodPost)
182
183         r.HandleFunc("/notifications", func(w http.ResponseWriter, req *http.Request) {
184                 ctx := getContextWithSession(context.Background(), req)
185
186                 maxID := req.URL.Query().Get("max_id")
187                 minID := req.URL.Query().Get("min_id")
188
189                 err := s.ServeNotificationPage(ctx, w, nil, maxID, minID)
190                 if err != nil {
191                         s.ServeErrorPage(ctx, w, err)
192                         return
193                 }
194         }).Methods(http.MethodGet)
195
196         r.HandleFunc("/user/{id}", func(w http.ResponseWriter, req *http.Request) {
197                 ctx := getContextWithSession(context.Background(), req)
198
199                 id, _ := mux.Vars(req)["id"]
200                 maxID := req.URL.Query().Get("max_id")
201                 minID := req.URL.Query().Get("min_id")
202
203                 err := s.ServeUserPage(ctx, w, nil, id, maxID, minID)
204                 if err != nil {
205                         s.ServeErrorPage(ctx, w, err)
206                         return
207                 }
208         }).Methods(http.MethodGet)
209
210         r.HandleFunc("/follow/{id}", func(w http.ResponseWriter, req *http.Request) {
211                 ctx := getContextWithSession(context.Background(), req)
212
213                 id, _ := mux.Vars(req)["id"]
214
215                 err := s.Follow(ctx, w, nil, id)
216                 if err != nil {
217                         s.ServeErrorPage(ctx, w, err)
218                         return
219                 }
220
221                 w.Header().Add("Location", req.Header.Get("Referer"))
222                 w.WriteHeader(http.StatusFound)
223         }).Methods(http.MethodPost)
224
225         r.HandleFunc("/unfollow/{id}", func(w http.ResponseWriter, req *http.Request) {
226                 ctx := getContextWithSession(context.Background(), req)
227
228                 id, _ := mux.Vars(req)["id"]
229
230                 err := s.UnFollow(ctx, w, nil, id)
231                 if err != nil {
232                         s.ServeErrorPage(ctx, w, err)
233                         return
234                 }
235
236                 w.Header().Add("Location", req.Header.Get("Referer"))
237                 w.WriteHeader(http.StatusFound)
238         }).Methods(http.MethodPost)
239
240         r.HandleFunc("/about", func(w http.ResponseWriter, req *http.Request) {
241                 ctx := getContextWithSession(context.Background(), req)
242
243                 err := s.ServeAboutPage(ctx, w, nil)
244                 if err != nil {
245                         s.ServeErrorPage(ctx, w, err)
246                         return
247                 }
248         }).Methods(http.MethodGet)
249
250         r.HandleFunc("/emojis", func(w http.ResponseWriter, req *http.Request) {
251                 ctx := getContextWithSession(context.Background(), req)
252
253                 err := s.ServeEmojiPage(ctx, w, nil)
254                 if err != nil {
255                         s.ServeErrorPage(ctx, w, err)
256                         return
257                 }
258         }).Methods(http.MethodGet)
259
260         r.HandleFunc("/signout", func(w http.ResponseWriter, req *http.Request) {
261                 // TODO remove session from database
262                 w.Header().Add("Set-Cookie", fmt.Sprintf("session_id=;max-age=0"))
263                 w.Header().Add("Location", "/")
264                 w.WriteHeader(http.StatusFound)
265         }).Methods(http.MethodGet)
266
267         return r
268 }
269
270 func getContextWithSession(ctx context.Context, req *http.Request) context.Context {
271         sessionID, err := req.Cookie("session_id")
272         if err != nil {
273                 return ctx
274         }
275         return context.WithValue(ctx, "session_id", sessionID.Value)
276 }
277
278 func getMultipartFormValue(mf *multipart.Form, key string) (val string) {
279         vals, ok := mf.Value[key]
280         if !ok {
281                 return ""
282         }
283         if len(vals) < 1 {
284                 return ""
285         }
286         return vals[0]
287 }