aboutsummaryrefslogtreecommitdiff
path: root/service
diff options
context:
space:
mode:
authorr <r@freesoftwareextremist.com>2019-12-13 18:08:26 +0000
committerr <r@freesoftwareextremist.com>2019-12-13 18:26:24 +0000
commit5e4da01c3ae3ae2e870faba9085d9d9213c01c29 (patch)
tree39d6f1e76b901549f194ddbac3c6cb82e0abd019 /service
downloadbloat-5e4da01c3ae3ae2e870faba9085d9d9213c01c29.tar.gz
bloat-5e4da01c3ae3ae2e870faba9085d9d9213c01c29.zip
Initial commit
Diffstat (limited to 'service')
-rw-r--r--service/auth.go151
-rw-r--r--service/logging.go117
-rw-r--r--service/service.go285
-rw-r--r--service/transport.go165
4 files changed, 718 insertions, 0 deletions
diff --git a/service/auth.go b/service/auth.go
new file mode 100644
index 0000000..cb442a7
--- /dev/null
+++ b/service/auth.go
@@ -0,0 +1,151 @@
+package service
+
+import (
+ "context"
+ "errors"
+ "io"
+ "mastodon"
+ "web/model"
+)
+
+var (
+ ErrInvalidSession = errors.New("invalid session")
+)
+
+type authService struct {
+ sessionRepo model.SessionRepository
+ appRepo model.AppRepository
+ Service
+}
+
+func NewAuthService(sessionRepo model.SessionRepository, appRepo model.AppRepository, s Service) Service {
+ return &authService{sessionRepo, appRepo, s}
+}
+
+func getSessionID(ctx context.Context) (sessionID string, err error) {
+ sessionID, ok := ctx.Value("session_id").(string)
+ if !ok || len(sessionID) < 1 {
+ return "", ErrInvalidSession
+ }
+ return sessionID, nil
+}
+
+func (s *authService) getClient(ctx context.Context) (c *mastodon.Client, err error) {
+ sessionID, err := getSessionID(ctx)
+ if err != nil {
+ return nil, ErrInvalidSession
+ }
+ session, err := s.sessionRepo.Get(sessionID)
+ if err != nil {
+ return nil, ErrInvalidSession
+ }
+ client, err := s.appRepo.Get(session.InstanceURL)
+ if err != nil {
+ return
+ }
+ c = mastodon.NewClient(&mastodon.Config{
+ Server: session.InstanceURL,
+ ClientID: client.ClientID,
+ ClientSecret: client.ClientSecret,
+ AccessToken: session.AccessToken,
+ })
+ return c, nil
+}
+
+func (s *authService) GetAuthUrl(ctx context.Context, instance string) (
+ redirectUrl string, sessionID string, err error) {
+ return s.Service.GetAuthUrl(ctx, instance)
+}
+
+func (s *authService) GetUserToken(ctx context.Context, sessionID string, c *mastodon.Client,
+ code string) (token string, err error) {
+ sessionID, err = getSessionID(ctx)
+ if err != nil {
+ return
+ }
+ c, err = s.getClient(ctx)
+ if err != nil {
+ return
+ }
+
+ token, err = s.Service.GetUserToken(ctx, sessionID, c, code)
+ if err != nil {
+ return
+ }
+
+ err = s.sessionRepo.Update(sessionID, token)
+ if err != nil {
+ return
+ }
+
+ return
+}
+
+func (s *authService) ServeHomePage(ctx context.Context, client io.Writer) (err error) {
+ return s.Service.ServeHomePage(ctx, client)
+}
+
+func (s *authService) ServeErrorPage(ctx context.Context, client io.Writer, err error) {
+ s.Service.ServeErrorPage(ctx, client, err)
+}
+
+func (s *authService) ServeSigninPage(ctx context.Context, client io.Writer) (err error) {
+ return s.Service.ServeSigninPage(ctx, client)
+}
+
+func (s *authService) ServeTimelinePage(ctx context.Context, client io.Writer,
+ c *mastodon.Client, maxID string, sinceID string, minID string) (err error) {
+ c, err = s.getClient(ctx)
+ if err != nil {
+ return
+ }
+ return s.Service.ServeTimelinePage(ctx, client, c, maxID, sinceID, minID)
+}
+
+func (s *authService) ServeThreadPage(ctx context.Context, client io.Writer, c *mastodon.Client, id string, reply bool) (err error) {
+ c, err = s.getClient(ctx)
+ if err != nil {
+ return
+ }
+ return s.Service.ServeThreadPage(ctx, client, c, id, reply)
+}
+
+func (s *authService) Like(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {
+ c, err = s.getClient(ctx)
+ if err != nil {
+ return
+ }
+ return s.Service.Like(ctx, client, c, id)
+}
+
+func (s *authService) UnLike(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {
+ c, err = s.getClient(ctx)
+ if err != nil {
+ return
+ }
+ return s.Service.UnLike(ctx, client, c, id)
+}
+
+func (s *authService) Retweet(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {
+ c, err = s.getClient(ctx)
+ if err != nil {
+ return
+ }
+ return s.Service.Retweet(ctx, client, c, id)
+}
+
+func (s *authService) UnRetweet(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {
+ c, err = s.getClient(ctx)
+ if err != nil {
+ return
+ }
+ return s.Service.UnRetweet(ctx, client, c, id)
+}
+
+func (s *authService) PostTweet(ctx context.Context, client io.Writer, c *mastodon.Client, content string, replyToID string) (err error) {
+ c, err = s.getClient(ctx)
+ if err != nil {
+ return
+ }
+ return s.Service.PostTweet(ctx, client, c, content, replyToID)
+}
diff --git a/service/logging.go b/service/logging.go
new file mode 100644
index 0000000..b11599e
--- /dev/null
+++ b/service/logging.go
@@ -0,0 +1,117 @@
+package service
+
+import (
+ "context"
+ "io"
+ "log"
+ "mastodon"
+ "time"
+)
+
+type loggingService struct {
+ logger *log.Logger
+ Service
+}
+
+func NewLoggingService(logger *log.Logger, s Service) Service {
+ return &loggingService{logger, s}
+}
+
+func (s *loggingService) GetAuthUrl(ctx context.Context, instance string) (
+ redirectUrl string, sessionID string, err error) {
+ defer func(begin time.Time) {
+ s.logger.Printf("method=%v, instance=%v, took=%v, err=%v\n",
+ "GetAuthUrl", instance, time.Since(begin), err)
+ }(time.Now())
+ return s.Service.GetAuthUrl(ctx, instance)
+}
+
+func (s *loggingService) GetUserToken(ctx context.Context, sessionID string, c *mastodon.Client,
+ code string) (token string, err error) {
+ defer func(begin time.Time) {
+ s.logger.Printf("method=%v, session_id=%v, code=%v, took=%v, err=%v\n",
+ "GetUserToken", sessionID, code, time.Since(begin), err)
+ }(time.Now())
+ return s.Service.GetUserToken(ctx, sessionID, c, code)
+}
+
+func (s *loggingService) ServeHomePage(ctx context.Context, client io.Writer) (err error) {
+ defer func(begin time.Time) {
+ s.logger.Printf("method=%v, took=%v, err=%v\n",
+ "ServeHomePage", time.Since(begin), err)
+ }(time.Now())
+ return s.Service.ServeHomePage(ctx, client)
+}
+
+func (s *loggingService) ServeErrorPage(ctx context.Context, client io.Writer, err error) {
+ defer func(begin time.Time) {
+ s.logger.Printf("method=%v, err=%v, took=%v\n",
+ "ServeErrorPage", err, time.Since(begin))
+ }(time.Now())
+ s.Service.ServeErrorPage(ctx, client, err)
+}
+
+func (s *loggingService) ServeSigninPage(ctx context.Context, client io.Writer) (err error) {
+ defer func(begin time.Time) {
+ s.logger.Printf("method=%v, took=%v, err=%v\n",
+ "ServeSigninPage", time.Since(begin), err)
+ }(time.Now())
+ return s.Service.ServeSigninPage(ctx, client)
+}
+
+func (s *loggingService) ServeTimelinePage(ctx context.Context, client io.Writer,
+ c *mastodon.Client, maxID string, sinceID string, minID string) (err error) {
+ defer func(begin time.Time) {
+ s.logger.Printf("method=%v, max_id=%v, since_id=%v, min_id=%v, took=%v, err=%v\n",
+ "ServeTimelinePage", maxID, sinceID, minID, time.Since(begin), err)
+ }(time.Now())
+ return s.Service.ServeTimelinePage(ctx, client, c, maxID, sinceID, minID)
+}
+
+func (s *loggingService) ServeThreadPage(ctx context.Context, client io.Writer, c *mastodon.Client, id string, reply bool) (err error) {
+ defer func(begin time.Time) {
+ s.logger.Printf("method=%v, id=%v, reply=%v, took=%v, err=%v\n",
+ "ServeThreadPage", id, reply, time.Since(begin), err)
+ }(time.Now())
+ return s.Service.ServeThreadPage(ctx, client, c, id, reply)
+}
+
+func (s *loggingService) Like(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {
+ defer func(begin time.Time) {
+ s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n",
+ "Like", id, time.Since(begin), err)
+ }(time.Now())
+ return s.Service.Like(ctx, client, c, id)
+}
+
+func (s *loggingService) UnLike(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {
+ defer func(begin time.Time) {
+ s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n",
+ "UnLike", id, time.Since(begin), err)
+ }(time.Now())
+ return s.Service.UnLike(ctx, client, c, id)
+}
+
+func (s *loggingService) Retweet(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {
+ defer func(begin time.Time) {
+ s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n",
+ "Retweet", id, time.Since(begin), err)
+ }(time.Now())
+ return s.Service.Retweet(ctx, client, c, id)
+}
+
+func (s *loggingService) UnRetweet(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {
+ defer func(begin time.Time) {
+ s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n",
+ "UnRetweet", id, time.Since(begin), err)
+ }(time.Now())
+ return s.Service.UnRetweet(ctx, client, c, id)
+}
+
+func (s *loggingService) PostTweet(ctx context.Context, client io.Writer, c *mastodon.Client, content string, replyToID string) (err error) {
+ defer func(begin time.Time) {
+ s.logger.Printf("method=%v, content=%v, reply_to_id=%v, took=%v, err=%v\n",
+ "PostTweet", content, replyToID, time.Since(begin), err)
+ }(time.Now())
+ return s.Service.PostTweet(ctx, client, c, content, replyToID)
+}
diff --git a/service/service.go b/service/service.go
new file mode 100644
index 0000000..7088a9b
--- /dev/null
+++ b/service/service.go
@@ -0,0 +1,285 @@
+package service
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "path"
+ "strings"
+
+ "mastodon"
+ "web/model"
+ "web/renderer"
+ "web/util"
+)
+
+var (
+ ErrInvalidArgument = errors.New("invalid argument")
+ ErrInvalidToken = errors.New("invalid token")
+ ErrInvalidClient = errors.New("invalid client")
+)
+
+type Service interface {
+ ServeHomePage(ctx context.Context, client io.Writer) (err error)
+ GetAuthUrl(ctx context.Context, instance string) (url string, sessionID string, err error)
+ GetUserToken(ctx context.Context, sessionID string, c *mastodon.Client, token string) (accessToken string, err error)
+ ServeErrorPage(ctx context.Context, client io.Writer, err error)
+ ServeSigninPage(ctx context.Context, client io.Writer) (err error)
+ ServeTimelinePage(ctx context.Context, client io.Writer, c *mastodon.Client, maxID string, sinceID string, minID string) (err error)
+ ServeThreadPage(ctx context.Context, client io.Writer, c *mastodon.Client, id string, reply bool) (err error)
+ Like(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error)
+ UnLike(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error)
+ Retweet(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error)
+ UnRetweet(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error)
+ PostTweet(ctx context.Context, client io.Writer, c *mastodon.Client, content string, replyToID string) (err error)
+}
+
+type service struct {
+ clientName string
+ clientScope string
+ clientWebsite string
+ renderer renderer.Renderer
+ sessionRepo model.SessionRepository
+ appRepo model.AppRepository
+}
+
+func NewService(clientName string, clientScope string, clientWebsite string,
+ renderer renderer.Renderer, sessionRepo model.SessionRepository,
+ appRepo model.AppRepository) Service {
+ return &service{
+ clientName: clientName,
+ clientScope: clientScope,
+ clientWebsite: clientWebsite,
+ renderer: renderer,
+ sessionRepo: sessionRepo,
+ appRepo: appRepo,
+ }
+}
+
+func (svc *service) GetAuthUrl(ctx context.Context, instance string) (
+ redirectUrl string, sessionID string, err error) {
+ if !strings.HasPrefix(instance, "https://") {
+ instance = "https://" + instance
+ }
+
+ sessionID = util.NewSessionId()
+ err = svc.sessionRepo.Add(model.Session{
+ ID: sessionID,
+ InstanceURL: instance,
+ })
+ if err != nil {
+ return
+ }
+
+ app, err := svc.appRepo.Get(instance)
+ if err != nil {
+ if err != model.ErrAppNotFound {
+ return
+ }
+
+ var mastoApp *mastodon.Application
+ mastoApp, err = mastodon.RegisterApp(ctx, &mastodon.AppConfig{
+ Server: instance,
+ ClientName: svc.clientName,
+ Scopes: svc.clientScope,
+ Website: svc.clientWebsite,
+ RedirectURIs: svc.clientWebsite + "/oauth_callback",
+ })
+ if err != nil {
+ return
+ }
+
+ app = model.App{
+ InstanceURL: instance,
+ ClientID: mastoApp.ClientID,
+ ClientSecret: mastoApp.ClientSecret,
+ }
+
+ err = svc.appRepo.Add(app)
+ if err != nil {
+ return
+ }
+ }
+
+ u, err := url.Parse(path.Join(instance, "/oauth/authorize"))
+ if err != nil {
+ return
+ }
+
+ q := make(url.Values)
+ q.Set("scope", "read write follow")
+ q.Set("client_id", app.ClientID)
+ q.Set("response_type", "code")
+ q.Set("redirect_uri", svc.clientWebsite+"/oauth_callback")
+ u.RawQuery = q.Encode()
+
+ redirectUrl = u.String()
+
+ return
+}
+
+func (svc *service) GetUserToken(ctx context.Context, sessionID string, c *mastodon.Client,
+ code string) (token string, err error) {
+ if len(code) < 1 {
+ err = ErrInvalidArgument
+ return
+ }
+
+ session, err := svc.sessionRepo.Get(sessionID)
+ if err != nil {
+ return
+ }
+
+ app, err := svc.appRepo.Get(session.InstanceURL)
+ if err != nil {
+ return
+ }
+
+ data := &bytes.Buffer{}
+ err = json.NewEncoder(data).Encode(map[string]string{
+ "client_id": app.ClientID,
+ "client_secret": app.ClientSecret,
+ "grant_type": "authorization_code",
+ "code": code,
+ "redirect_uri": svc.clientWebsite + "/oauth_callback",
+ })
+ if err != nil {
+ return
+ }
+
+ resp, err := http.Post(app.InstanceURL+"/oauth/token", "application/json", data)
+ if err != nil {
+ return
+ }
+ defer resp.Body.Close()
+
+ var res struct {
+ AccessToken string `json:"access_token"`
+ }
+
+ err = json.NewDecoder(resp.Body).Decode(&res)
+ if err != nil {
+ return
+ }
+ /*
+ err = c.AuthenticateToken(ctx, code, svc.clientWebsite+"/oauth_callback")
+ if err != nil {
+ return
+ }
+ err = svc.sessionRepo.Update(sessionID, c.GetAccessToken(ctx))
+ */
+
+ return res.AccessToken, nil
+}
+
+func (svc *service) ServeHomePage(ctx context.Context, client io.Writer) (err error) {
+ err = svc.renderer.RenderHomePage(ctx, client)
+ if err != nil {
+ return
+ }
+
+ return
+}
+
+func (svc *service) ServeErrorPage(ctx context.Context, client io.Writer, err error) {
+ svc.renderer.RenderErrorPage(ctx, client, err)
+}
+
+func (svc *service) ServeSigninPage(ctx context.Context, client io.Writer) (err error) {
+ err = svc.renderer.RenderSigninPage(ctx, client)
+ if err != nil {
+ return
+ }
+
+ return
+}
+
+func (svc *service) ServeTimelinePage(ctx context.Context, client io.Writer,
+ c *mastodon.Client, maxID string, sinceID string, minID string) (err error) {
+
+ var hasNext, hasPrev bool
+ var nextLink, prevLink string
+
+ var pg = mastodon.Pagination{
+ MaxID: maxID,
+ SinceID: sinceID,
+ MinID: minID,
+ Limit: 20,
+ }
+
+ statuses, err := c.GetTimelineHome(ctx, &pg)
+ if err != nil {
+ return err
+ }
+
+ if len(pg.MaxID) > 0 {
+ hasNext = true
+ nextLink = fmt.Sprintf("/timeline?max_id=%s", pg.MaxID)
+ }
+ if len(pg.SinceID) > 0 {
+ hasPrev = true
+ prevLink = fmt.Sprintf("/timeline?since_id=%s", pg.SinceID)
+ }
+
+ data := renderer.NewTimelinePageTemplateData(statuses, hasNext, nextLink, hasPrev, prevLink)
+ err = svc.renderer.RenderTimelinePage(ctx, client, data)
+ if err != nil {
+ return
+ }
+
+ return
+}
+
+func (svc *service) ServeThreadPage(ctx context.Context, client io.Writer, c *mastodon.Client, id string, reply bool) (err error) {
+ status, err := c.GetStatus(ctx, id)
+ if err != nil {
+ return
+ }
+
+ context, err := c.GetStatusContext(ctx, id)
+ if err != nil {
+ return
+ }
+
+ data := renderer.NewThreadPageTemplateData(status, context, reply, id)
+ err = svc.renderer.RenderThreadPage(ctx, client, data)
+ if err != nil {
+ return
+ }
+
+ return
+}
+
+func (svc *service) Like(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {
+ _, err = c.Favourite(ctx, id)
+ return
+}
+
+func (svc *service) UnLike(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {
+ _, err = c.Unfavourite(ctx, id)
+ return
+}
+
+func (svc *service) Retweet(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {
+ _, err = c.Reblog(ctx, id)
+ return
+}
+
+func (svc *service) UnRetweet(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {
+ _, err = c.Unreblog(ctx, id)
+ return
+}
+
+func (svc *service) PostTweet(ctx context.Context, client io.Writer, c *mastodon.Client, content string, replyToID string) (err error) {
+ tweet := &mastodon.Toot{
+ Status: content,
+ InReplyToID: replyToID,
+ }
+ _, err = c.PostStatus(ctx, tweet)
+ return
+}
diff --git a/service/transport.go b/service/transport.go
new file mode 100644
index 0000000..f4f5ed7
--- /dev/null
+++ b/service/transport.go
@@ -0,0 +1,165 @@
+package service
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "path"
+
+ "github.com/gorilla/mux"
+)
+
+var (
+ ctx = context.Background()
+ cookieAge = "31536000"
+)
+
+func getContextWithSession(ctx context.Context, req *http.Request) context.Context {
+ sessionID, err := req.Cookie("session_id")
+ if err != nil {
+ return ctx
+ }
+ return context.WithValue(ctx, "session_id", sessionID.Value)
+}
+
+func NewHandler(s Service, staticDir string) http.Handler {
+ r := mux.NewRouter()
+
+ r.PathPrefix("/static").Handler(http.StripPrefix("/static",
+ http.FileServer(http.Dir(path.Join(".", staticDir)))))
+
+ r.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
+ err := s.ServeHomePage(ctx, w)
+ if err != nil {
+ s.ServeErrorPage(ctx, w, err)
+ return
+ }
+ }).Methods(http.MethodGet)
+
+ r.HandleFunc("/signin", func(w http.ResponseWriter, req *http.Request) {
+ err := s.ServeSigninPage(ctx, w)
+ if err != nil {
+ s.ServeErrorPage(ctx, w, err)
+ return
+ }
+ }).Methods(http.MethodGet)
+
+ r.HandleFunc("/signin", func(w http.ResponseWriter, req *http.Request) {
+ instance := req.FormValue("instance")
+ url, sessionId, err := s.GetAuthUrl(ctx, instance)
+ if err != nil {
+ s.ServeErrorPage(ctx, w, err)
+ return
+ }
+
+ w.Header().Add("Set-Cookie", fmt.Sprintf("session_id=%s;max-age=%s", sessionId, cookieAge))
+ w.Header().Add("Location", url)
+ w.WriteHeader(http.StatusSeeOther)
+ }).Methods(http.MethodPost)
+
+ r.HandleFunc("/oauth_callback", func(w http.ResponseWriter, req *http.Request) {
+ ctx := getContextWithSession(context.Background(), req)
+ token := req.URL.Query().Get("code")
+ _, err := s.GetUserToken(ctx, "", nil, token)
+ if err != nil {
+ s.ServeErrorPage(ctx, w, err)
+ return
+ }
+
+ w.Header().Add("Location", "/timeline")
+ w.WriteHeader(http.StatusSeeOther)
+ }).Methods(http.MethodGet)
+
+ r.HandleFunc("/timeline", func(w http.ResponseWriter, req *http.Request) {
+ ctx := getContextWithSession(context.Background(), req)
+
+ maxID := req.URL.Query().Get("max_id")
+ sinceID := req.URL.Query().Get("since_id")
+ minID := req.URL.Query().Get("min_id")
+
+ err := s.ServeTimelinePage(ctx, w, nil, maxID, sinceID, minID)
+ if err != nil {
+ s.ServeErrorPage(ctx, w, err)
+ return
+ }
+ }).Methods(http.MethodGet)
+
+ r.HandleFunc("/thread/{id}", func(w http.ResponseWriter, req *http.Request) {
+ ctx := getContextWithSession(context.Background(), req)
+ id, _ := mux.Vars(req)["id"]
+ reply := req.URL.Query().Get("reply")
+ err := s.ServeThreadPage(ctx, w, nil, id, len(reply) > 1)
+ if err != nil {
+ s.ServeErrorPage(ctx, w, err)
+ return
+ }
+ }).Methods(http.MethodGet)
+
+ r.HandleFunc("/like/{id}", func(w http.ResponseWriter, req *http.Request) {
+ ctx := getContextWithSession(context.Background(), req)
+ id, _ := mux.Vars(req)["id"]
+ err := s.Like(ctx, w, nil, id)
+ if err != nil {
+ s.ServeErrorPage(ctx, w, err)
+ return
+ }
+
+ w.Header().Add("Location", req.Header.Get("Referer"))
+ w.WriteHeader(http.StatusSeeOther)
+ }).Methods(http.MethodGet)
+
+ r.HandleFunc("/unlike/{id}", func(w http.ResponseWriter, req *http.Request) {
+ ctx := getContextWithSession(context.Background(), req)
+ id, _ := mux.Vars(req)["id"]
+ err := s.UnLike(ctx, w, nil, id)
+ if err != nil {
+ s.ServeErrorPage(ctx, w, err)
+ return
+ }
+
+ w.Header().Add("Location", req.Header.Get("Referer"))
+ w.WriteHeader(http.StatusSeeOther)
+ }).Methods(http.MethodGet)
+
+ r.HandleFunc("/retweet/{id}", func(w http.ResponseWriter, req *http.Request) {
+ ctx := getContextWithSession(context.Background(), req)
+ id, _ := mux.Vars(req)["id"]
+ err := s.Retweet(ctx, w, nil, id)
+ if err != nil {
+ s.ServeErrorPage(ctx, w, err)
+ return
+ }
+
+ w.Header().Add("Location", req.Header.Get("Referer"))
+ w.WriteHeader(http.StatusSeeOther)
+ }).Methods(http.MethodGet)
+
+ r.HandleFunc("/unretweet/{id}", func(w http.ResponseWriter, req *http.Request) {
+ ctx := getContextWithSession(context.Background(), req)
+ id, _ := mux.Vars(req)["id"]
+ err := s.UnRetweet(ctx, w, nil, id)
+ if err != nil {
+ s.ServeErrorPage(ctx, w, err)
+ return
+ }
+
+ w.Header().Add("Location", req.Header.Get("Referer"))
+ w.WriteHeader(http.StatusSeeOther)
+ }).Methods(http.MethodGet)
+
+ r.HandleFunc("/post", func(w http.ResponseWriter, req *http.Request) {
+ ctx := getContextWithSession(context.Background(), req)
+ content := req.FormValue("content")
+ replyToID := req.FormValue("reply_to_id")
+ err := s.PostTweet(ctx, w, nil, content, replyToID)
+ if err != nil {
+ s.ServeErrorPage(ctx, w, err)
+ return
+ }
+
+ w.Header().Add("Location", req.Header.Get("Referer"))
+ w.WriteHeader(http.StatusSeeOther)
+ }).Methods(http.MethodPost)
+
+ return r
+}