diff options
Diffstat (limited to 'service/transport.go')
-rw-r--r-- | service/transport.go | 327 |
1 files changed, 159 insertions, 168 deletions
diff --git a/service/transport.go b/service/transport.go index 9841650..ed083b6 100644 --- a/service/transport.go +++ b/service/transport.go @@ -1,8 +1,8 @@ package service import ( + "context" "encoding/json" - "errors" "log" "net/http" "strconv" @@ -10,44 +10,34 @@ import ( "bloat/mastodon" "bloat/model" + "bloat/renderer" "github.com/gorilla/mux" ) -var ( - errInvalidSession = errors.New("invalid session") - errInvalidCSRFToken = errors.New("invalid csrf token") -) - const ( sessionExp = 365 * 24 * time.Hour ) -type respType int - const ( - HTML respType = iota + HTML int = iota JSON ) -type authType int - const ( - NOAUTH authType = iota + NOAUTH int = iota SESSION CSRF ) type client struct { *mastodon.Client - http.ResponseWriter - Req *http.Request - CSRFToken string - Session model.Session -} - -func (c *client) url() string { - return c.Req.URL.RequestURI() + w http.ResponseWriter + r *http.Request + s model.Session + csrf string + ctx context.Context + rctx *renderer.Context } func setSessionCookie(w http.ResponseWriter, sid string, exp time.Duration) { @@ -59,7 +49,7 @@ func setSessionCookie(w http.ResponseWriter, sid string, exp time.Duration) { } func writeJson(c *client, data interface{}) error { - return json.NewEncoder(c).Encode(map[string]interface{}{ + return json.NewEncoder(c.w).Encode(map[string]interface{}{ "data": data, }) } @@ -74,60 +64,44 @@ func serveJsonError(w http.ResponseWriter, err error) { } func redirect(c *client, url string) { - c.Header().Add("Location", url) - c.WriteHeader(http.StatusFound) + c.w.Header().Add("Location", url) + c.w.WriteHeader(http.StatusFound) } func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { r := mux.NewRouter() - writeError := func(c *client, err error, t respType) { + writeError := func(c *client, err error, t int, retry bool) { switch t { case HTML: - c.WriteHeader(http.StatusInternalServerError) - s.ErrorPage(c, err) + c.w.WriteHeader(http.StatusInternalServerError) + s.ErrorPage(c, err, retry) case JSON: - c.WriteHeader(http.StatusInternalServerError) - json.NewEncoder(c).Encode(map[string]string{ + c.w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(c.w).Encode(map[string]string{ "error": err.Error(), }) } } - authenticate := func(c *client, t authType) error { - if t >= SESSION { - cookie, err := c.Req.Cookie("session_id") - if err != nil || len(cookie.Value) < 1 { - return errInvalidSession - } - c.Session, err = s.sessionRepo.Get(cookie.Value) - if err != nil { - return errInvalidSession - } - app, err := s.appRepo.Get(c.Session.InstanceDomain) - if err != nil { - return err - } - c.Client = mastodon.NewClient(&mastodon.Config{ - Server: app.InstanceURL, - ClientID: app.ClientID, - ClientSecret: app.ClientSecret, - AccessToken: c.Session.AccessToken, - }) - } - if t >= CSRF { - c.CSRFToken = c.Req.FormValue("csrf_token") - if len(c.CSRFToken) < 1 || c.CSRFToken != c.Session.CSRFToken { - return errInvalidCSRFToken - } + authenticate := func(c *client, t int) error { + var sid string + if cookie, _ := c.r.Cookie("session_id"); cookie != nil { + sid = cookie.Value } - return nil + csrf := c.r.FormValue("csrf_token") + ref := c.r.URL.RequestURI() + return s.authenticate(c, sid, csrf, ref, t) } - handle := func(f func(c *client) error, at authType, rt respType) http.HandlerFunc { + handle := func(f func(c *client) error, at int, rt int) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { var err error - c := &client{Req: req, ResponseWriter: w} + c := &client{ + ctx: req.Context(), + w: w, + r: req, + } defer func(begin time.Time) { logger.Printf("path=%s, err=%v, took=%v\n", @@ -141,29 +115,24 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { case JSON: ct = "application/json" } - c.Header().Add("Content-Type", ct) + c.w.Header().Add("Content-Type", ct) err = authenticate(c, at) if err != nil { - writeError(c, err, rt) + writeError(c, err, rt, req.Method == http.MethodGet) return } err = f(c) if err != nil { - writeError(c, err, rt) + writeError(c, err, rt, req.Method == http.MethodGet) return } } } rootPage := handle(func(c *client) error { - sid, _ := c.Req.Cookie("session_id") - if sid == nil || len(sid.Value) < 0 { - redirect(c, "/signin") - return nil - } - session, err := s.sessionRepo.Get(sid.Value) + err := authenticate(c, SESSION) if err != nil { if err == errInvalidSession { redirect(c, "/signin") @@ -171,7 +140,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { } return err } - if len(session.AccessToken) < 1 { + if !c.s.IsLoggedIn() { redirect(c, "/signin") return nil } @@ -187,18 +156,18 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { if !ok { return s.SigninPage(c) } - url, sid, err := s.NewSession(instance) + url, sid, err := s.NewSession(c, instance) if err != nil { return err } - setSessionCookie(c, sid, sessionExp) + setSessionCookie(c.w, sid, sessionExp) redirect(c, url) return nil }, NOAUTH, HTML) timelinePage := handle(func(c *client) error { - tType, _ := mux.Vars(c.Req)["type"] - q := c.Req.URL.Query() + tType, _ := mux.Vars(c.r)["type"] + q := c.r.URL.Query() instance := q.Get("instance") maxID := q.Get("max_id") minID := q.Get("min_id") @@ -211,41 +180,41 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { }, SESSION, HTML) threadPage := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] - q := c.Req.URL.Query() + id, _ := mux.Vars(c.r)["id"] + q := c.r.URL.Query() reply := q.Get("reply") return s.ThreadPage(c, id, len(reply) > 1) }, SESSION, HTML) likedByPage := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] + id, _ := mux.Vars(c.r)["id"] return s.LikedByPage(c, id) }, SESSION, HTML) retweetedByPage := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] + id, _ := mux.Vars(c.r)["id"] return s.RetweetedByPage(c, id) }, SESSION, HTML) notificationsPage := handle(func(c *client) error { - q := c.Req.URL.Query() + q := c.r.URL.Query() maxID := q.Get("max_id") minID := q.Get("min_id") return s.NotificationPage(c, maxID, minID) }, SESSION, HTML) userPage := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] - pageType, _ := mux.Vars(c.Req)["type"] - q := c.Req.URL.Query() + id, _ := mux.Vars(c.r)["id"] + pageType, _ := mux.Vars(c.r)["type"] + q := c.r.URL.Query() maxID := q.Get("max_id") minID := q.Get("min_id") return s.UserPage(c, id, pageType, maxID, minID) }, SESSION, HTML) userSearchPage := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] - q := c.Req.URL.Query() + id, _ := mux.Vars(c.r)["id"] + q := c.r.URL.Query() sq := q.Get("q") offset, _ := strconv.Atoi(q.Get("offset")) return s.UserSearchPage(c, id, sq, offset) @@ -260,7 +229,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { }, SESSION, HTML) searchPage := handle(func(c *client) error { - q := c.Req.URL.Query() + q := c.r.URL.Query() sq := q.Get("q") qType := q.Get("type") offset, _ := strconv.Atoi(q.Get("offset")) @@ -271,50 +240,46 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { return s.SettingsPage(c) }, SESSION, HTML) + filtersPage := handle(func(c *client) error { + return s.FiltersPage(c) + }, SESSION, HTML) + signin := handle(func(c *client) error { - instance := c.Req.FormValue("instance") - url, sid, err := s.NewSession(instance) + instance := c.r.FormValue("instance") + url, sid, err := s.NewSession(c, instance) if err != nil { return err } - setSessionCookie(c, sid, sessionExp) + setSessionCookie(c.w, sid, sessionExp) redirect(c, url) return nil }, NOAUTH, HTML) oauthCallback := handle(func(c *client) error { - q := c.Req.URL.Query() + q := c.r.URL.Query() token := q.Get("code") - token, userID, err := s.Signin(c, token) + err := s.Signin(c, token) if err != nil { return err } - - c.Session.AccessToken = token - c.Session.UserID = userID - err = s.sessionRepo.Add(c.Session) - if err != nil { - return err - } - redirect(c, "/") return nil }, SESSION, HTML) post := handle(func(c *client) error { - content := c.Req.FormValue("content") - replyToID := c.Req.FormValue("reply_to_id") - format := c.Req.FormValue("format") - visibility := c.Req.FormValue("visibility") - isNSFW := c.Req.FormValue("is_nsfw") == "on" - files := c.Req.MultipartForm.File["attachments"] + content := c.r.FormValue("content") + replyToID := c.r.FormValue("reply_to_id") + format := c.r.FormValue("format") + visibility := c.r.FormValue("visibility") + isNSFW := c.r.FormValue("is_nsfw") == "true" + files := c.r.MultipartForm.File["attachments"] id, err := s.Post(c, content, replyToID, format, visibility, isNSFW, files) if err != nil { return err } - location := c.Req.FormValue("referrer") + location := c.r.FormValue("referrer") if len(replyToID) > 0 { location = "/thread/" + replyToID + "#status-" + id } @@ -323,8 +288,8 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { }, CSRF, HTML) like := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] - rid := c.Req.FormValue("retweeted_by_id") + id, _ := mux.Vars(c.r)["id"] + rid := c.r.FormValue("retweeted_by_id") _, err := s.Like(c, id) if err != nil { return err @@ -332,13 +297,13 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { if len(rid) > 0 { id = rid } - redirect(c, c.Req.FormValue("referrer")+"#status-"+id) + redirect(c, c.r.FormValue("referrer")+"#status-"+id) return nil }, CSRF, HTML) unlike := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] - rid := c.Req.FormValue("retweeted_by_id") + id, _ := mux.Vars(c.r)["id"] + rid := c.r.FormValue("retweeted_by_id") _, err := s.UnLike(c, id) if err != nil { return err @@ -346,13 +311,13 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { if len(rid) > 0 { id = rid } - redirect(c, c.Req.FormValue("referrer")+"#status-"+id) + redirect(c, c.r.FormValue("referrer")+"#status-"+id) return nil }, CSRF, HTML) retweet := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] - rid := c.Req.FormValue("retweeted_by_id") + id, _ := mux.Vars(c.r)["id"] + rid := c.r.FormValue("retweeted_by_id") _, err := s.Retweet(c, id) if err != nil { return err @@ -360,13 +325,13 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { if len(rid) > 0 { id = rid } - redirect(c, c.Req.FormValue("referrer")+"#status-"+id) + redirect(c, c.r.FormValue("referrer")+"#status-"+id) return nil }, CSRF, HTML) unretweet := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] - rid := c.Req.FormValue("retweeted_by_id") + id, _ := mux.Vars(c.r)["id"] + rid := c.r.FormValue("retweeted_by_id") _, err := s.UnRetweet(c, id) if err != nil { return err @@ -374,25 +339,25 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { if len(rid) > 0 { id = rid } - redirect(c, c.Req.FormValue("referrer")+"#status-"+id) + redirect(c, c.r.FormValue("referrer")+"#status-"+id) return nil }, CSRF, HTML) vote := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] - statusID := c.Req.FormValue("status_id") - choices, _ := c.Req.PostForm["choices"] + id, _ := mux.Vars(c.r)["id"] + statusID := c.r.FormValue("status_id") + choices, _ := c.r.PostForm["choices"] err := s.Vote(c, id, choices) if err != nil { return err } - redirect(c, c.Req.FormValue("referrer")+"#status-"+statusID) + redirect(c, c.r.FormValue("referrer")+"#status-"+statusID) return nil }, CSRF, HTML) follow := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] - q := c.Req.URL.Query() + id, _ := mux.Vars(c.r)["id"] + q := c.r.URL.Query() var reblogs *bool if r, ok := q["reblogs"]; ok && len(r) > 0 { reblogs = new(bool) @@ -402,111 +367,112 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { if err != nil { return err } - redirect(c, c.Req.FormValue("referrer")) + redirect(c, c.r.FormValue("referrer")) return nil }, CSRF, HTML) unfollow := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] + id, _ := mux.Vars(c.r)["id"] err := s.UnFollow(c, id) if err != nil { return err } - redirect(c, c.Req.FormValue("referrer")) + redirect(c, c.r.FormValue("referrer")) return nil }, CSRF, HTML) accept := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] + id, _ := mux.Vars(c.r)["id"] err := s.Accept(c, id) if err != nil { return err } - redirect(c, c.Req.FormValue("referrer")) + redirect(c, c.r.FormValue("referrer")) return nil }, CSRF, HTML) reject := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] + id, _ := mux.Vars(c.r)["id"] err := s.Reject(c, id) if err != nil { return err } - redirect(c, c.Req.FormValue("referrer")) + redirect(c, c.r.FormValue("referrer")) return nil }, CSRF, HTML) mute := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] + id, _ := mux.Vars(c.r)["id"] err := s.Mute(c, id) if err != nil { return err } - redirect(c, c.Req.FormValue("referrer")) + redirect(c, c.r.FormValue("referrer")) return nil }, CSRF, HTML) unMute := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] + id, _ := mux.Vars(c.r)["id"] err := s.UnMute(c, id) if err != nil { return err } - redirect(c, c.Req.FormValue("referrer")) + redirect(c, c.r.FormValue("referrer")) return nil }, CSRF, HTML) block := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] + id, _ := mux.Vars(c.r)["id"] err := s.Block(c, id) if err != nil { return err } - redirect(c, c.Req.FormValue("referrer")) + redirect(c, c.r.FormValue("referrer")) return nil }, CSRF, HTML) unBlock := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] + id, _ := mux.Vars(c.r)["id"] err := s.UnBlock(c, id) if err != nil { return err } - redirect(c, c.Req.FormValue("referrer")) + redirect(c, c.r.FormValue("referrer")) return nil }, CSRF, HTML) subscribe := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] + id, _ := mux.Vars(c.r)["id"] err := s.Subscribe(c, id) if err != nil { return err } - redirect(c, c.Req.FormValue("referrer")) + redirect(c, c.r.FormValue("referrer")) return nil }, CSRF, HTML) unSubscribe := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] + id, _ := mux.Vars(c.r)["id"] err := s.UnSubscribe(c, id) if err != nil { return err } - redirect(c, c.Req.FormValue("referrer")) + redirect(c, c.r.FormValue("referrer")) return nil }, CSRF, HTML) settings := handle(func(c *client) error { - visibility := c.Req.FormValue("visibility") - format := c.Req.FormValue("format") - copyScope := c.Req.FormValue("copy_scope") == "true" - threadInNewTab := c.Req.FormValue("thread_in_new_tab") == "true" - hideAttachments := c.Req.FormValue("hide_attachments") == "true" - maskNSFW := c.Req.FormValue("mask_nsfw") == "true" - ni, _ := strconv.Atoi(c.Req.FormValue("notification_interval")) - fluorideMode := c.Req.FormValue("fluoride_mode") == "true" - darkMode := c.Req.FormValue("dark_mode") == "true" - antiDopamineMode := c.Req.FormValue("anti_dopamine_mode") == "true" + visibility := c.r.FormValue("visibility") + format := c.r.FormValue("format") + copyScope := c.r.FormValue("copy_scope") == "true" + threadInNewTab := c.r.FormValue("thread_in_new_tab") == "true" + hideAttachments := c.r.FormValue("hide_attachments") == "true" + maskNSFW := c.r.FormValue("mask_nsfw") == "true" + ni, _ := strconv.Atoi(c.r.FormValue("notification_interval")) + fluorideMode := c.r.FormValue("fluoride_mode") == "true" + darkMode := c.r.FormValue("dark_mode") == "true" + antiDopamineMode := c.r.FormValue("anti_dopamine_mode") == "true" + css := c.r.FormValue("css") settings := &model.Settings{ DefaultVisibility: visibility, @@ -519,6 +485,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { FluorideMode: fluorideMode, DarkMode: darkMode, AntiDopamineMode: antiDopamineMode, + CSS: css, } err := s.SaveSettings(c, settings) @@ -530,49 +497,49 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { }, CSRF, HTML) muteConversation := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] + id, _ := mux.Vars(c.r)["id"] err := s.MuteConversation(c, id) if err != nil { return err } - redirect(c, c.Req.FormValue("referrer")) + redirect(c, c.r.FormValue("referrer")) return nil }, CSRF, HTML) unMuteConversation := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] + id, _ := mux.Vars(c.r)["id"] err := s.UnMuteConversation(c, id) if err != nil { return err } - redirect(c, c.Req.FormValue("referrer")) + redirect(c, c.r.FormValue("referrer")) return nil }, CSRF, HTML) delete := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] + id, _ := mux.Vars(c.r)["id"] err := s.Delete(c, id) if err != nil { return err } - redirect(c, c.Req.FormValue("referrer")) + redirect(c, c.r.FormValue("referrer")) return nil }, CSRF, HTML) readNotifications := handle(func(c *client) error { - q := c.Req.URL.Query() + q := c.r.URL.Query() maxID := q.Get("max_id") err := s.ReadNotifications(c, maxID) if err != nil { return err } - redirect(c, c.Req.FormValue("referrer")) + redirect(c, c.r.FormValue("referrer")) return nil }, CSRF, HTML) bookmark := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] - rid := c.Req.FormValue("retweeted_by_id") + id, _ := mux.Vars(c.r)["id"] + rid := c.r.FormValue("retweeted_by_id") err := s.Bookmark(c, id) if err != nil { return err @@ -580,13 +547,13 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { if len(rid) > 0 { id = rid } - redirect(c, c.Req.FormValue("referrer")+"#status-"+id) + redirect(c, c.r.FormValue("referrer")+"#status-"+id) return nil }, CSRF, HTML) unBookmark := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] - rid := c.Req.FormValue("retweeted_by_id") + id, _ := mux.Vars(c.r)["id"] + rid := c.r.FormValue("retweeted_by_id") err := s.UnBookmark(c, id) if err != nil { return err @@ -594,19 +561,40 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { if len(rid) > 0 { id = rid } - redirect(c, c.Req.FormValue("referrer")+"#status-"+id) + redirect(c, c.r.FormValue("referrer")+"#status-"+id) + return nil + }, CSRF, HTML) + + filter := handle(func(c *client) error { + phrase := c.r.FormValue("phrase") + wholeWord := c.r.FormValue("whole_word") == "true" + err := s.Filter(c, phrase, wholeWord) + if err != nil { + return err + } + redirect(c, c.r.FormValue("referrer")) + return nil + }, CSRF, HTML) + + unFilter := handle(func(c *client) error { + id, _ := mux.Vars(c.r)["id"] + err := s.UnFilter(c, id) + if err != nil { + return err + } + redirect(c, c.r.FormValue("referrer")) return nil }, CSRF, HTML) signout := handle(func(c *client) error { s.Signout(c) - setSessionCookie(c, "", 0) + setSessionCookie(c.w, "", 0) redirect(c, "/") return nil }, CSRF, HTML) fLike := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] + id, _ := mux.Vars(c.r)["id"] count, err := s.Like(c, id) if err != nil { return err @@ -615,7 +603,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { }, CSRF, JSON) fUnlike := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] + id, _ := mux.Vars(c.r)["id"] count, err := s.UnLike(c, id) if err != nil { return err @@ -624,7 +612,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { }, CSRF, JSON) fRetweet := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] + id, _ := mux.Vars(c.r)["id"] count, err := s.Retweet(c, id) if err != nil { return err @@ -633,7 +621,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { }, CSRF, JSON) fUnretweet := handle(func(c *client) error { - id, _ := mux.Vars(c.Req)["id"] + id, _ := mux.Vars(c.r)["id"] count, err := s.UnRetweet(c, id) if err != nil { return err @@ -662,6 +650,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { r.HandleFunc("/emojis", emojisPage).Methods(http.MethodGet) r.HandleFunc("/search", searchPage).Methods(http.MethodGet) r.HandleFunc("/settings", settingsPage).Methods(http.MethodGet) + r.HandleFunc("/filters", filtersPage).Methods(http.MethodGet) r.HandleFunc("/signin", signin).Methods(http.MethodPost) r.HandleFunc("/oauth_callback", oauthCallback).Methods(http.MethodGet) r.HandleFunc("/post", post).Methods(http.MethodPost) @@ -687,6 +676,8 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { r.HandleFunc("/notifications/read", readNotifications).Methods(http.MethodPost) r.HandleFunc("/bookmark/{id}", bookmark).Methods(http.MethodPost) r.HandleFunc("/unbookmark/{id}", unBookmark).Methods(http.MethodPost) + r.HandleFunc("/filter", filter).Methods(http.MethodPost) + r.HandleFunc("/unfilter/{id}", unFilter).Methods(http.MethodPost) r.HandleFunc("/signout", signout).Methods(http.MethodPost) r.HandleFunc("/fluoride/like/{id}", fLike).Methods(http.MethodPost) r.HandleFunc("/fluoride/unlike/{id}", fUnlike).Methods(http.MethodPost) |