13 "github.com/gorilla/mux"
17 ctx = context.Background()
18 cookieAge = "31536000"
21 func NewHandler(s Service, staticDir string) http.Handler {
24 r.PathPrefix("/static").Handler(http.StripPrefix("/static",
25 http.FileServer(http.Dir(path.Join(".", staticDir)))))
27 r.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
30 sessionID, _ := req.Cookie("session_id")
31 if sessionID != nil && len(sessionID.Value) > 0 {
32 location = "/timeline/home"
35 w.Header().Add("Location", location)
36 w.WriteHeader(http.StatusFound)
37 }).Methods(http.MethodGet)
39 r.HandleFunc("/signin", func(w http.ResponseWriter, req *http.Request) {
40 err := s.ServeSigninPage(ctx, w)
42 s.ServeErrorPage(ctx, w, err)
45 }).Methods(http.MethodGet)
47 r.HandleFunc("/signin", func(w http.ResponseWriter, req *http.Request) {
48 instance := req.FormValue("instance")
49 url, sessionID, err := s.GetAuthUrl(ctx, instance)
51 s.ServeErrorPage(ctx, w, err)
55 http.SetCookie(w, &http.Cookie{
58 Expires: time.Now().Add(365 * 24 * time.Hour),
61 w.Header().Add("Location", url)
62 w.WriteHeader(http.StatusFound)
63 }).Methods(http.MethodPost)
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)
70 s.ServeErrorPage(ctx, w, err)
74 w.Header().Add("Location", "/timeline/home")
75 w.WriteHeader(http.StatusFound)
76 }).Methods(http.MethodGet)
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)
83 r.HandleFunc("/timeline/{type}", func(w http.ResponseWriter, req *http.Request) {
84 ctx := getContextWithSession(context.Background(), req)
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")
91 err := s.ServeTimelinePage(ctx, w, nil, timelineType, maxID, sinceID, minID)
93 s.ServeErrorPage(ctx, w, err)
96 }).Methods(http.MethodGet)
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)
104 s.ServeErrorPage(ctx, w, err)
107 }).Methods(http.MethodGet)
109 r.HandleFunc("/likedby/{id}", func(w http.ResponseWriter, req *http.Request) {
110 ctx := getContextWithSession(context.Background(), req)
111 id, _ := mux.Vars(req)["id"]
113 err := s.ServeLikedByPage(ctx, w, nil, id)
115 s.ServeErrorPage(ctx, w, err)
118 }).Methods(http.MethodGet)
120 r.HandleFunc("/retweetedby/{id}", func(w http.ResponseWriter, req *http.Request) {
121 ctx := getContextWithSession(context.Background(), req)
122 id, _ := mux.Vars(req)["id"]
124 err := s.ServeRetweetedByPage(ctx, w, nil, id)
126 s.ServeErrorPage(ctx, w, err)
129 }).Methods(http.MethodGet)
131 r.HandleFunc("/following/{id}", func(w http.ResponseWriter, req *http.Request) {
132 ctx := getContextWithSession(context.Background(), req)
134 id, _ := mux.Vars(req)["id"]
135 maxID := req.URL.Query().Get("max_id")
136 minID := req.URL.Query().Get("min_id")
138 err := s.ServeFollowingPage(ctx, w, nil, id, maxID, minID)
140 s.ServeErrorPage(ctx, w, err)
143 }).Methods(http.MethodGet)
145 r.HandleFunc("/followers/{id}", func(w http.ResponseWriter, req *http.Request) {
146 ctx := getContextWithSession(context.Background(), req)
148 id, _ := mux.Vars(req)["id"]
149 maxID := req.URL.Query().Get("max_id")
150 minID := req.URL.Query().Get("min_id")
152 err := s.ServeFollowersPage(ctx, w, nil, id, maxID, minID)
154 s.ServeErrorPage(ctx, w, err)
157 }).Methods(http.MethodGet)
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)
164 s.ServeErrorPage(ctx, w, err)
168 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+id)
169 w.WriteHeader(http.StatusFound)
170 }).Methods(http.MethodPost)
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)
177 s.ServeErrorPage(ctx, w, err)
181 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+id)
182 w.WriteHeader(http.StatusFound)
183 }).Methods(http.MethodPost)
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)
190 s.ServeErrorPage(ctx, w, err)
194 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+id)
195 w.WriteHeader(http.StatusFound)
196 }).Methods(http.MethodPost)
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)
203 s.ServeErrorPage(ctx, w, err)
207 w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+id)
208 w.WriteHeader(http.StatusFound)
209 }).Methods(http.MethodPost)
211 r.HandleFunc("/post", func(w http.ResponseWriter, req *http.Request) {
212 ctx := getContextWithSession(context.Background(), req)
214 err := req.ParseMultipartForm(4 << 20)
216 s.ServeErrorPage(ctx, w, err)
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")
226 files := req.MultipartForm.File["attachments"]
228 id, err := s.PostTweet(ctx, w, nil, content, replyToID, format, visibility, isNSFW, files)
230 s.ServeErrorPage(ctx, w, err)
234 location := "/timeline/home" + "#status-" + id
235 if len(replyToID) > 0 {
236 location = "/thread/" + replyToID + "#status-" + id
238 w.Header().Add("Location", location)
239 w.WriteHeader(http.StatusFound)
240 }).Methods(http.MethodPost)
242 r.HandleFunc("/notifications", func(w http.ResponseWriter, req *http.Request) {
243 ctx := getContextWithSession(context.Background(), req)
245 maxID := req.URL.Query().Get("max_id")
246 minID := req.URL.Query().Get("min_id")
248 err := s.ServeNotificationPage(ctx, w, nil, maxID, minID)
250 s.ServeErrorPage(ctx, w, err)
253 }).Methods(http.MethodGet)
255 r.HandleFunc("/user/{id}", func(w http.ResponseWriter, req *http.Request) {
256 ctx := getContextWithSession(context.Background(), req)
258 id, _ := mux.Vars(req)["id"]
259 maxID := req.URL.Query().Get("max_id")
260 minID := req.URL.Query().Get("min_id")
262 err := s.ServeUserPage(ctx, w, nil, id, maxID, minID)
264 s.ServeErrorPage(ctx, w, err)
267 }).Methods(http.MethodGet)
269 r.HandleFunc("/follow/{id}", func(w http.ResponseWriter, req *http.Request) {
270 ctx := getContextWithSession(context.Background(), req)
272 id, _ := mux.Vars(req)["id"]
274 err := s.Follow(ctx, w, nil, id)
276 s.ServeErrorPage(ctx, w, err)
280 w.Header().Add("Location", req.Header.Get("Referer"))
281 w.WriteHeader(http.StatusFound)
282 }).Methods(http.MethodPost)
284 r.HandleFunc("/unfollow/{id}", func(w http.ResponseWriter, req *http.Request) {
285 ctx := getContextWithSession(context.Background(), req)
287 id, _ := mux.Vars(req)["id"]
289 err := s.UnFollow(ctx, w, nil, id)
291 s.ServeErrorPage(ctx, w, err)
295 w.Header().Add("Location", req.Header.Get("Referer"))
296 w.WriteHeader(http.StatusFound)
297 }).Methods(http.MethodPost)
299 r.HandleFunc("/about", func(w http.ResponseWriter, req *http.Request) {
300 ctx := getContextWithSession(context.Background(), req)
302 err := s.ServeAboutPage(ctx, w, nil)
304 s.ServeErrorPage(ctx, w, err)
307 }).Methods(http.MethodGet)
309 r.HandleFunc("/emojis", func(w http.ResponseWriter, req *http.Request) {
310 ctx := getContextWithSession(context.Background(), req)
312 err := s.ServeEmojiPage(ctx, w, nil)
314 s.ServeErrorPage(ctx, w, err)
317 }).Methods(http.MethodGet)
319 r.HandleFunc("/search", func(w http.ResponseWriter, req *http.Request) {
320 ctx := getContextWithSession(context.Background(), req)
322 q := req.URL.Query().Get("q")
323 qType := req.URL.Query().Get("type")
324 offsetStr := req.URL.Query().Get("offset")
328 if len(offsetStr) > 1 {
329 offset, err = strconv.Atoi(offsetStr)
331 s.ServeErrorPage(ctx, w, err)
336 err = s.ServeSearchPage(ctx, w, nil, q, qType, offset)
338 s.ServeErrorPage(ctx, w, err)
341 }).Methods(http.MethodGet)
343 r.HandleFunc("/settings", func(w http.ResponseWriter, req *http.Request) {
344 ctx := getContextWithSession(context.Background(), req)
346 err := s.ServeSettingsPage(ctx, w, nil)
348 s.ServeErrorPage(ctx, w, err)
351 }).Methods(http.MethodGet)
353 r.HandleFunc("/settings", func(w http.ResponseWriter, req *http.Request) {
354 ctx := getContextWithSession(context.Background(), req)
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,
367 err := s.SaveSettings(ctx, w, nil, settings)
369 s.ServeErrorPage(ctx, w, err)
373 w.Header().Add("Location", req.Header.Get("Referer"))
374 w.WriteHeader(http.StatusFound)
375 }).Methods(http.MethodPost)
377 r.HandleFunc("/signout", func(w http.ResponseWriter, req *http.Request) {
378 // TODO remove session from database
379 http.SetCookie(w, &http.Cookie{
384 w.Header().Add("Location", "/")
385 w.WriteHeader(http.StatusFound)
386 }).Methods(http.MethodGet)
391 func getContextWithSession(ctx context.Context, req *http.Request) context.Context {
392 sessionID, err := req.Cookie("session_id")
396 return context.WithValue(ctx, "session_id", sessionID.Value)
399 func getMultipartFormValue(mf *multipart.Form, key string) (val string) {
400 vals, ok := mf.Value[key]