Refactor everything
authorr <r@freesoftwareextremist.com>
Tue, 28 Jan 2020 17:51:00 +0000 (17:51 +0000)
committerr <r@freesoftwareextremist.com>
Tue, 28 Jan 2020 17:58:29 +0000 (17:58 +0000)
26 files changed:
Makefile
main.go
migrations/csrfToken/main.go
model/app.go
model/client.go
model/post.go [moved from model/postContext.go with 100% similarity]
model/session.go
renderer/model.go
renderer/renderer.go
repo/appRepo.go [moved from repository/appRepository.go with 58% similarity]
repo/sessionRepo.go [new file with mode: 0644]
repository/sessionRepository.go [deleted file]
service/auth.go
service/logging.go
service/service.go
service/transport.go
static/custom.css [deleted file]
static/style.css [moved from static/main.css with 100% similarity]
templates/followers.tmpl
templates/following.tmpl
templates/header.tmpl
templates/notification.tmpl
templates/search.tmpl
templates/timeline.tmpl
templates/user.tmpl
util/rand.go

index 8dcbd469a638a1917796a7d983425889ced2da08..780f6e8de46eff7485d446811ab455e4faaf98d1 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,3 @@
-.POSIX:
-
 GO=go
 
 all: bloat
diff --git a/main.go b/main.go
index 4f5851d32a60f4b880074916ca0171db50977e7b..003fe5d27000e6781c751a38612969c27f25c23b 100644 (file)
--- a/main.go
+++ b/main.go
@@ -12,7 +12,7 @@ import (
        "bloat/config"
        "bloat/kv"
        "bloat/renderer"
-       "bloat/repository"
+       "bloat/repo"
        "bloat/service"
        "bloat/util"
 )
@@ -67,8 +67,8 @@ func main() {
                log.Fatal(err)
        }
 
-       sessionRepo := repository.NewSessionRepository(sessionDB)
-       appRepo := repository.NewAppRepository(appDB)
+       sessionRepo := repo.NewSessionRepo(sessionDB)
+       appRepo := repo.NewAppRepo(appDB)
 
        customCSS := config.CustomCSS
        if !strings.HasPrefix(customCSS, "http://") &&
index 38ffbac1ea84048de881567c26645f37d8879bc8..3d9526d338d793b466732e906afb388d757afbad 100644 (file)
@@ -59,12 +59,12 @@ func main() {
 
        sessionRepo := repository.NewSessionRepository(sessionDB)
 
-       sessionIds, err := getKeys(sessionRepoPath)
+       sessionIDs, err := getKeys(sessionRepoPath)
        if err != nil {
                log.Fatal(err)
        }
 
-       for _, id := range sessionIds {
+       for _, id := range sessionIDs {
                s, err := sessionRepo.Get(id)
                if err != nil {
                        log.Println(id, err)
index 7abc8ec6b4f0da53817b0643787b9aed9ac27d0c..8f172c8b47b6362ad5d2dca8c74eb76362ad73be 100644 (file)
@@ -15,7 +15,7 @@ type App struct {
        ClientSecret   string `json:"client_secret"`
 }
 
-type AppRepository interface {
+type AppRepo interface {
        Add(app App) (err error)
        Get(instanceDomain string) (app App, err error)
 }
index ae7270e45e71f1581f7518abb37b383864224a61..6123b3814631af8c78defc27662fd0af91b9c44c 100644 (file)
@@ -1,8 +1,13 @@
 package model
 
-import "mastodon"
+import (
+       "io"
+
+       "mastodon"
+)
 
 type Client struct {
        *mastodon.Client
+       Writer  io.Writer
        Session Session
 }
similarity index 100%
rename from model/postContext.go
rename to model/post.go
index 6bc8a6385f786bdd1c750309d1a958237e4011b6..10fca6fb5f5fb949d714d5d1c73367864d52493f 100644 (file)
@@ -16,7 +16,7 @@ type Session struct {
        Settings       Settings `json:"settings"`
 }
 
-type SessionRepository interface {
+type SessionRepo interface {
        Add(session Session) (err error)
        Get(sessionID string) (session Session, err error)
 }
index 25fa0c65f7e977fba663ece1402130643cc3af67..8df64ab159f02323e4d3d18b926987b7269144e3 100644 (file)
@@ -47,9 +47,7 @@ type TimelineData struct {
        *CommonData
        Title       string
        Statuses    []*mastodon.Status
-       HasNext     bool
        NextLink    string
-       HasPrev     bool
        PrevLink    string
        PostContext model.PostContext
 }
@@ -64,7 +62,6 @@ type ThreadData struct {
 type NotificationData struct {
        *CommonData
        Notifications []*mastodon.Notification
-       HasNext       bool
        NextLink      string
        DarkMode      bool
 }
@@ -73,7 +70,6 @@ type UserData struct {
        *CommonData
        User     *mastodon.Account
        Statuses []*mastodon.Status
-       HasNext  bool
        NextLink string
        DarkMode bool
 }
@@ -90,28 +86,24 @@ type EmojiData struct {
 type LikedByData struct {
        *CommonData
        Users    []*mastodon.Account
-       HasNext  bool
        NextLink string
 }
 
 type RetweetedByData struct {
        *CommonData
        Users    []*mastodon.Account
-       HasNext  bool
        NextLink string
 }
 
 type FollowingData struct {
        *CommonData
        Users    []*mastodon.Account
-       HasNext  bool
        NextLink string
 }
 
 type FollowersData struct {
        *CommonData
        Users    []*mastodon.Account
-       HasNext  bool
        NextLink string
 }
 
@@ -121,7 +113,6 @@ type SearchData struct {
        Type     string
        Users    []*mastodon.Account
        Statuses []*mastodon.Status
-       HasNext  bool
        NextLink string
 }
 
index 4d2c74df5eebefbc3ded548113addc883bd53bf5..2d227c4b15299ed4d47c84dc463caa938ea5608a 100644 (file)
@@ -1,6 +1,7 @@
 package renderer
 
 import (
+       "fmt"
        "io"
        "strconv"
        "strings"
@@ -89,78 +90,100 @@ func NewRenderer(templateGlobPattern string) (r *renderer, err error) {
        }, nil
 }
 
-func (r *renderer) RenderSigninPage(ctx *Context, writer io.Writer, signinData *SigninData) (err error) {
+func (r *renderer) RenderSigninPage(ctx *Context, writer io.Writer,
+       signinData *SigninData) (err error) {
        return r.template.ExecuteTemplate(writer, "signin.tmpl", WithContext(signinData, ctx))
 }
 
-func (r *renderer) RenderErrorPage(ctx *Context, writer io.Writer, errorData *ErrorData) {
+func (r *renderer) RenderErrorPage(ctx *Context, writer io.Writer,
+       errorData *ErrorData) {
        r.template.ExecuteTemplate(writer, "error.tmpl", WithContext(errorData, ctx))
        return
 }
 
-func (r *renderer) RenderTimelinePage(ctx *Context, writer io.Writer, data *TimelineData) (err error) {
+func (r *renderer) RenderTimelinePage(ctx *Context, writer io.Writer,
+       data *TimelineData) (err error) {
        return r.template.ExecuteTemplate(writer, "timeline.tmpl", WithContext(data, ctx))
 }
 
-func (r *renderer) RenderThreadPage(ctx *Context, writer io.Writer, data *ThreadData) (err error) {
+func (r *renderer) RenderThreadPage(ctx *Context, writer io.Writer,
+       data *ThreadData) (err error) {
        return r.template.ExecuteTemplate(writer, "thread.tmpl", WithContext(data, ctx))
 }
 
-func (r *renderer) RenderNotificationPage(ctx *Context, writer io.Writer, data *NotificationData) (err error) {
+func (r *renderer) RenderNotificationPage(ctx *Context, writer io.Writer,
+       data *NotificationData) (err error) {
        return r.template.ExecuteTemplate(writer, "notification.tmpl", WithContext(data, ctx))
 }
 
-func (r *renderer) RenderUserPage(ctx *Context, writer io.Writer, data *UserData) (err error) {
+func (r *renderer) RenderUserPage(ctx *Context, writer io.Writer,
+       data *UserData) (err error) {
        return r.template.ExecuteTemplate(writer, "user.tmpl", WithContext(data, ctx))
 }
 
-func (r *renderer) RenderAboutPage(ctx *Context, writer io.Writer, data *AboutData) (err error) {
+func (r *renderer) RenderAboutPage(ctx *Context, writer io.Writer,
+       data *AboutData) (err error) {
        return r.template.ExecuteTemplate(writer, "about.tmpl", WithContext(data, ctx))
 }
 
-func (r *renderer) RenderEmojiPage(ctx *Context, writer io.Writer, data *EmojiData) (err error) {
+func (r *renderer) RenderEmojiPage(ctx *Context, writer io.Writer,
+       data *EmojiData) (err error) {
        return r.template.ExecuteTemplate(writer, "emoji.tmpl", WithContext(data, ctx))
 }
 
-func (r *renderer) RenderLikedByPage(ctx *Context, writer io.Writer, data *LikedByData) (err error) {
+func (r *renderer) RenderLikedByPage(ctx *Context, writer io.Writer,
+       data *LikedByData) (err error) {
        return r.template.ExecuteTemplate(writer, "likedby.tmpl", WithContext(data, ctx))
 }
 
-func (r *renderer) RenderRetweetedByPage(ctx *Context, writer io.Writer, data *RetweetedByData) (err error) {
+func (r *renderer) RenderRetweetedByPage(ctx *Context, writer io.Writer,
+       data *RetweetedByData) (err error) {
        return r.template.ExecuteTemplate(writer, "retweetedby.tmpl", WithContext(data, ctx))
 }
 
-func (r *renderer) RenderFollowingPage(ctx *Context, writer io.Writer, data *FollowingData) (err error) {
+func (r *renderer) RenderFollowingPage(ctx *Context, writer io.Writer,
+       data *FollowingData) (err error) {
        return r.template.ExecuteTemplate(writer, "following.tmpl", WithContext(data, ctx))
 }
 
-func (r *renderer) RenderFollowersPage(ctx *Context, writer io.Writer, data *FollowersData) (err error) {
+func (r *renderer) RenderFollowersPage(ctx *Context, writer io.Writer,
+       data *FollowersData) (err error) {
        return r.template.ExecuteTemplate(writer, "followers.tmpl", WithContext(data, ctx))
 }
 
-func (r *renderer) RenderSearchPage(ctx *Context, writer io.Writer, data *SearchData) (err error) {
+func (r *renderer) RenderSearchPage(ctx *Context, writer io.Writer,
+       data *SearchData) (err error) {
        return r.template.ExecuteTemplate(writer, "search.tmpl", WithContext(data, ctx))
 }
 
-func (r *renderer) RenderSettingsPage(ctx *Context, writer io.Writer, data *SettingsData) (err error) {
+func (r *renderer) RenderSettingsPage(ctx *Context, writer io.Writer,
+       data *SettingsData) (err error) {
        return r.template.ExecuteTemplate(writer, "settings.tmpl", WithContext(data, ctx))
 }
 
 func EmojiFilter(content string, emojis []mastodon.Emoji) string {
        var replacements []string
+       var r string
        for _, e := range emojis {
-               replacements = append(replacements, ":"+e.ShortCode+":", "<img class=\"status-emoji\" src=\""+e.URL+"\" alt=\""+e.ShortCode+"\" title=\""+e.ShortCode+"\" />")
+               r = fmt.Sprintf("<img class=\"status-emoji\" src=\"%s\" alt=\"%s\" title=\"%s\" />",
+                       e.URL, e.ShortCode, e.ShortCode)
+               replacements = append(replacements, ":"+e.ShortCode+":", r)
        }
        return strings.NewReplacer(replacements...).Replace(content)
 }
 
-func StatusContentFilter(spoiler string, content string, emojis []mastodon.Emoji, mentions []mastodon.Mention) string {
+func StatusContentFilter(spoiler string, content string,
+       emojis []mastodon.Emoji, mentions []mastodon.Mention) string {
+
+       var replacements []string
+       var r string
        if len(spoiler) > 0 {
                content = spoiler + "<br />" + content
        }
-       var replacements []string
        for _, e := range emojis {
-               replacements = append(replacements, ":"+e.ShortCode+":", "<img class=\"status-emoji\" src=\""+e.URL+"\" alt=\""+e.ShortCode+"\" title=\""+e.ShortCode+"\" />")
+               r = fmt.Sprintf("<img class=\"status-emoji\" src=\"%s\" alt=\"%s\" title=\"%s\" />",
+                       e.URL, e.ShortCode, e.ShortCode)
+               replacements = append(replacements, ":"+e.ShortCode+":", r)
        }
        for _, m := range mentions {
                replacements = append(replacements, "\""+m.URL+"\"", "\"/user/"+m.ID+"\"")
@@ -177,32 +200,26 @@ func DisplayInteractionCount(c int64) string {
 
 func TimeSince(t time.Time) string {
        dur := time.Since(t)
-
        s := dur.Seconds()
        if s < 60 {
                return strconv.Itoa(int(s)) + "s"
        }
-
        m := dur.Minutes()
        if m < 60 {
                return strconv.Itoa(int(m)) + "m"
        }
-
        h := dur.Hours()
        if h < 24 {
                return strconv.Itoa(int(h)) + "h"
        }
-
        d := h / 24
        if d < 30 {
                return strconv.Itoa(int(d)) + "d"
        }
-
        mo := d / 30
        if mo < 12 {
                return strconv.Itoa(int(mo)) + "mo"
        }
-
        y := mo / 12
        return strconv.Itoa(int(y)) + "y"
 }
similarity index 58%
rename from repository/appRepository.go
rename to repo/appRepo.go
index 9e57fb7e1b89d362b07984775212f3e8b4fbfabd..6338c4a2987685890da60cea1e2ed2a2047e9e4b 100644 (file)
@@ -1,4 +1,4 @@
-package repository
+package repo
 
 import (
        "encoding/json"
@@ -7,17 +7,17 @@ import (
        "bloat/model"
 )
 
-type appRepository struct {
+type appRepo struct {
        db *kv.Database
 }
 
-func NewAppRepository(db *kv.Database) *appRepository {
-       return &appRepository{
+func NewAppRepo(db *kv.Database) *appRepo {
+       return &appRepo{
                db: db,
        }
 }
 
-func (repo *appRepository) Add(a model.App) (err error) {
+func (repo *appRepo) Add(a model.App) (err error) {
        data, err := json.Marshal(a)
        if err != nil {
                return
@@ -26,7 +26,7 @@ func (repo *appRepository) Add(a model.App) (err error) {
        return
 }
 
-func (repo *appRepository) Get(instanceDomain string) (a model.App, err error) {
+func (repo *appRepo) Get(instanceDomain string) (a model.App, err error) {
        data, err := repo.db.Get(instanceDomain)
        if err != nil {
                err = model.ErrAppNotFound
diff --git a/repo/sessionRepo.go b/repo/sessionRepo.go
new file mode 100644 (file)
index 0000000..ce923b1
--- /dev/null
@@ -0,0 +1,42 @@
+package repo
+
+import (
+       "encoding/json"
+
+       "bloat/kv"
+       "bloat/model"
+)
+
+type sessionRepo struct {
+       db *kv.Database
+}
+
+func NewSessionRepo(db *kv.Database) *sessionRepo {
+       return &sessionRepo{
+               db: db,
+       }
+}
+
+func (repo *sessionRepo) Add(s model.Session) (err error) {
+       data, err := json.Marshal(s)
+       if err != nil {
+               return
+       }
+       err = repo.db.Set(s.ID, data)
+       return
+}
+
+func (repo *sessionRepo) Get(id string) (s model.Session, err error) {
+       data, err := repo.db.Get(id)
+       if err != nil {
+               err = model.ErrSessionNotFound
+               return
+       }
+
+       err = json.Unmarshal(data, &s)
+       if err != nil {
+               return
+       }
+
+       return
+}
diff --git a/repository/sessionRepository.go b/repository/sessionRepository.go
deleted file mode 100644 (file)
index cb19f68..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-package repository
-
-import (
-       "encoding/json"
-
-       "bloat/kv"
-       "bloat/model"
-)
-
-type sessionRepository struct {
-       db *kv.Database
-}
-
-func NewSessionRepository(db *kv.Database) *sessionRepository {
-       return &sessionRepository{
-               db: db,
-       }
-}
-
-func (repo *sessionRepository) Add(s model.Session) (err error) {
-       data, err := json.Marshal(s)
-       if err != nil {
-               return
-       }
-       err = repo.db.Set(s.ID, data)
-       return
-}
-
-func (repo *sessionRepository) Update(id string, accessToken string) (err error) {
-       data, err := repo.db.Get(id)
-       if err != nil {
-               return
-       }
-
-       var s model.Session
-       err = json.Unmarshal(data, &s)
-       if err != nil {
-               return
-       }
-
-       s.AccessToken = accessToken
-
-       data, err = json.Marshal(s)
-       if err != nil {
-               return
-       }
-
-       return repo.db.Set(id, data)
-}
-
-func (repo *sessionRepository) Get(id string) (s model.Session, err error) {
-       data, err := repo.db.Get(id)
-       if err != nil {
-               err = model.ErrSessionNotFound
-               return
-       }
-
-       err = json.Unmarshal(data, &s)
-       if err != nil {
-               return
-       }
-
-       return
-}
index 909a9a29bc6a86a236d66fa64a8054c5b4c809d6..78934fdf79e468b0b53856cdf70f538f8a6ab342 100644 (file)
@@ -3,7 +3,6 @@ package service
 import (
        "context"
        "errors"
-       "io"
        "mime/multipart"
 
        "bloat/model"
@@ -11,28 +10,28 @@ import (
 )
 
 var (
-       ErrInvalidSession   = errors.New("invalid session")
-       ErrInvalidCSRFToken = errors.New("invalid csrf token")
+       errInvalidSession   = errors.New("invalid session")
+       errInvalidCSRFToken = errors.New("invalid csrf token")
 )
 
-type authService struct {
-       sessionRepo model.SessionRepository
-       appRepo     model.AppRepository
+type as struct {
+       sessionRepo model.SessionRepo
+       appRepo     model.AppRepo
        Service
 }
 
-func NewAuthService(sessionRepo model.SessionRepository, appRepo model.AppRepository, s Service) Service {
-       return &authService{sessionRepo, appRepo, s}
+func NewAuthService(sessionRepo model.SessionRepo, appRepo model.AppRepo, s Service) Service {
+       return &as{sessionRepo, appRepo, s}
 }
 
-func (s *authService) getClient(ctx context.Context) (c *model.Client, err error) {
+func (s *as) authenticateClient(ctx context.Context, c *model.Client) (err error) {
        sessionID, ok := ctx.Value("session_id").(string)
        if !ok || len(sessionID) < 1 {
-               return nil, ErrInvalidSession
+               return errInvalidSession
        }
        session, err := s.sessionRepo.Get(sessionID)
        if err != nil {
-               return nil, ErrInvalidSession
+               return errInvalidSession
        }
        client, err := s.appRepo.Get(session.InstanceDomain)
        if err != nil {
@@ -44,152 +43,163 @@ func (s *authService) getClient(ctx context.Context) (c *model.Client, err error
                ClientSecret: client.ClientSecret,
                AccessToken:  session.AccessToken,
        })
-       c = &model.Client{Client: mc, Session: session}
-       return c, nil
+       if c == nil {
+               c = &model.Client{}
+       }
+       c.Client = mc
+       c.Session = session
+       return nil
 }
 
 func checkCSRF(ctx context.Context, c *model.Client) (err error) {
-       csrfToken, ok := ctx.Value("csrf_token").(string)
-       if !ok || csrfToken != c.Session.CSRFToken {
-               return ErrInvalidCSRFToken
+       token, ok := ctx.Value("csrf_token").(string)
+       if !ok || token != c.Session.CSRFToken {
+               return errInvalidCSRFToken
        }
        return nil
 }
 
-func (s *authService) GetAuthUrl(ctx context.Context, instance string) (
-       redirectUrl string, sessionID string, err error) {
-       return s.Service.GetAuthUrl(ctx, instance)
+func (s *as) ServeErrorPage(ctx context.Context, c *model.Client, err error) {
+       s.authenticateClient(ctx, c)
+       s.Service.ServeErrorPage(ctx, c, err)
 }
 
-func (s *authService) GetUserToken(ctx context.Context, sessionID string, c *model.Client,
-       code string) (token string, err error) {
-       c, err = s.getClient(ctx)
+func (s *as) ServeSigninPage(ctx context.Context, c *model.Client) (err error) {
+       return s.Service.ServeSigninPage(ctx, c)
+}
+
+func (s *as) ServeTimelinePage(ctx context.Context, c *model.Client, tType string,
+       maxID string, minID string) (err error) {
+       err = s.authenticateClient(ctx, c)
        if err != nil {
                return
        }
+       return s.Service.ServeTimelinePage(ctx, c, tType, maxID, minID)
+}
 
-       token, err = s.Service.GetUserToken(ctx, c.Session.ID, c, code)
+func (s *as) ServeThreadPage(ctx context.Context, c *model.Client, id string, reply bool) (err error) {
+       err = s.authenticateClient(ctx, c)
        if err != nil {
                return
        }
+       return s.Service.ServeThreadPage(ctx, c, id, reply)
+}
 
-       c.Session.AccessToken = token
-       err = s.sessionRepo.Add(c.Session)
+func (s *as) ServeLikedByPage(ctx context.Context, c *model.Client, id string) (err error) {
+       err = s.authenticateClient(ctx, c)
        if err != nil {
                return
        }
-
-       return
-}
-
-func (s *authService) ServeErrorPage(ctx context.Context, client io.Writer, c *model.Client, err error) {
-       c, _ = s.getClient(ctx)
-       s.Service.ServeErrorPage(ctx, client, c, err)
-}
-
-func (s *authService) ServeSigninPage(ctx context.Context, client io.Writer) (err error) {
-       return s.Service.ServeSigninPage(ctx, client)
+       return s.Service.ServeLikedByPage(ctx, c, id)
 }
 
-func (s *authService) ServeTimelinePage(ctx context.Context, client io.Writer,
-       c *model.Client, timelineType string, maxID string, sinceID string, minID string) (err error) {
-       c, err = s.getClient(ctx)
+func (s *as) ServeRetweetedByPage(ctx context.Context, c *model.Client, id string) (err error) {
+       err = s.authenticateClient(ctx, c)
        if err != nil {
                return
        }
-       return s.Service.ServeTimelinePage(ctx, client, c, timelineType, maxID, sinceID, minID)
+       return s.Service.ServeRetweetedByPage(ctx, c, id)
 }
 
-func (s *authService) ServeThreadPage(ctx context.Context, client io.Writer, c *model.Client, id string, reply bool) (err error) {
-       c, err = s.getClient(ctx)
+func (s *as) ServeFollowingPage(ctx context.Context, c *model.Client, id string,
+       maxID string, minID string) (err error) {
+       err = s.authenticateClient(ctx, c)
        if err != nil {
                return
        }
-       return s.Service.ServeThreadPage(ctx, client, c, id, reply)
+       return s.Service.ServeFollowingPage(ctx, c, id, maxID, minID)
 }
 
-func (s *authService) ServeNotificationPage(ctx context.Context, client io.Writer, c *model.Client, maxID string, minID string) (err error) {
-       c, err = s.getClient(ctx)
+func (s *as) ServeFollowersPage(ctx context.Context, c *model.Client, id string,
+       maxID string, minID string) (err error) {
+       err = s.authenticateClient(ctx, c)
        if err != nil {
                return
        }
-       return s.Service.ServeNotificationPage(ctx, client, c, maxID, minID)
+       return s.Service.ServeFollowersPage(ctx, c, id, maxID, minID)
 }
 
-func (s *authService) ServeUserPage(ctx context.Context, client io.Writer, c *model.Client, id string, maxID string, minID string) (err error) {
-       c, err = s.getClient(ctx)
+func (s *as) ServeNotificationPage(ctx context.Context, c *model.Client,
+       maxID string, minID string) (err error) {
+       err = s.authenticateClient(ctx, c)
        if err != nil {
                return
        }
-       return s.Service.ServeUserPage(ctx, client, c, id, maxID, minID)
+       return s.Service.ServeNotificationPage(ctx, c, maxID, minID)
 }
 
-func (s *authService) ServeAboutPage(ctx context.Context, client io.Writer, c *model.Client) (err error) {
-       c, err = s.getClient(ctx)
+func (s *as) ServeUserPage(ctx context.Context, c *model.Client, id string,
+       maxID string, minID string) (err error) {
+       err = s.authenticateClient(ctx, c)
        if err != nil {
                return
        }
-       return s.Service.ServeAboutPage(ctx, client, c)
+       return s.Service.ServeUserPage(ctx, c, id, maxID, minID)
 }
 
-func (s *authService) ServeEmojiPage(ctx context.Context, client io.Writer, c *model.Client) (err error) {
-       c, err = s.getClient(ctx)
+func (s *as) ServeAboutPage(ctx context.Context, c *model.Client) (err error) {
+       err = s.authenticateClient(ctx, c)
        if err != nil {
                return
        }
-       return s.Service.ServeEmojiPage(ctx, client, c)
+       return s.Service.ServeAboutPage(ctx, c)
 }
 
-func (s *authService) ServeLikedByPage(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) {
-       c, err = s.getClient(ctx)
+func (s *as) ServeEmojiPage(ctx context.Context, c *model.Client) (err error) {
+       err = s.authenticateClient(ctx, c)
        if err != nil {
                return
        }
-       return s.Service.ServeLikedByPage(ctx, client, c, id)
+       return s.Service.ServeEmojiPage(ctx, c)
 }
 
-func (s *authService) ServeRetweetedByPage(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) {
-       c, err = s.getClient(ctx)
+func (s *as) ServeSearchPage(ctx context.Context, c *model.Client, q string,
+       qType string, offset int) (err error) {
+       err = s.authenticateClient(ctx, c)
        if err != nil {
                return
        }
-       return s.Service.ServeRetweetedByPage(ctx, client, c, id)
+       return s.Service.ServeSearchPage(ctx, c, q, qType, offset)
 }
 
-func (s *authService) ServeFollowingPage(ctx context.Context, client io.Writer, c *model.Client, id string, maxID string, minID string) (err error) {
-       c, err = s.getClient(ctx)
+func (s *as) ServeSettingsPage(ctx context.Context, c *model.Client) (err error) {
+       err = s.authenticateClient(ctx, c)
        if err != nil {
                return
        }
-       return s.Service.ServeFollowingPage(ctx, client, c, id, maxID, minID)
+       return s.Service.ServeSettingsPage(ctx, c)
 }
 
-func (s *authService) ServeFollowersPage(ctx context.Context, client io.Writer, c *model.Client, id string, maxID string, minID string) (err error) {
-       c, err = s.getClient(ctx)
+func (s *as) NewSession(ctx context.Context, instance string) (redirectUrl string,
+       sessionID string, err error) {
+       return s.Service.NewSession(ctx, instance)
+}
+
+func (s *as) Signin(ctx context.Context, c *model.Client, sessionID string,
+       code string) (token string, err error) {
+       err = s.authenticateClient(ctx, c)
        if err != nil {
                return
        }
-       return s.Service.ServeFollowersPage(ctx, client, c, id, maxID, minID)
-}
 
-func (s *authService) ServeSearchPage(ctx context.Context, client io.Writer, c *model.Client, q string, qType string, offset int) (err error) {
-       c, err = s.getClient(ctx)
+       token, err = s.Service.Signin(ctx, c, c.Session.ID, code)
        if err != nil {
                return
        }
-       return s.Service.ServeSearchPage(ctx, client, c, q, qType, offset)
-}
 
-func (s *authService) ServeSettingsPage(ctx context.Context, client io.Writer, c *model.Client) (err error) {
-       c, err = s.getClient(ctx)
+       c.Session.AccessToken = token
+       err = s.sessionRepo.Add(c.Session)
        if err != nil {
                return
        }
-       return s.Service.ServeSettingsPage(ctx, client, c)
+
+       return
 }
 
-func (s *authService) SaveSettings(ctx context.Context, client io.Writer, c *model.Client, settings *model.Settings) (err error) {
-       c, err = s.getClient(ctx)
+func (s *as) Post(ctx context.Context, c *model.Client, content string,
+       replyToID string, format string, visibility string, isNSFW bool,
+       files []*multipart.FileHeader) (id string, err error) {
+       err = s.authenticateClient(ctx, c)
        if err != nil {
                return
        }
@@ -197,11 +207,11 @@ func (s *authService) SaveSettings(ctx context.Context, client io.Writer, c *mod
        if err != nil {
                return
        }
-       return s.Service.SaveSettings(ctx, client, c, settings)
+       return s.Service.Post(ctx, c, content, replyToID, format, visibility, isNSFW, files)
 }
 
-func (s *authService) Like(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) {
-       c, err = s.getClient(ctx)
+func (s *as) Like(ctx context.Context, c *model.Client, id string) (count int64, err error) {
+       err = s.authenticateClient(ctx, c)
        if err != nil {
                return
        }
@@ -209,11 +219,11 @@ func (s *authService) Like(ctx context.Context, client io.Writer, c *model.Clien
        if err != nil {
                return
        }
-       return s.Service.Like(ctx, client, c, id)
+       return s.Service.Like(ctx, c, id)
 }
 
-func (s *authService) UnLike(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) {
-       c, err = s.getClient(ctx)
+func (s *as) UnLike(ctx context.Context, c *model.Client, id string) (count int64, err error) {
+       err = s.authenticateClient(ctx, c)
        if err != nil {
                return
        }
@@ -221,11 +231,11 @@ func (s *authService) UnLike(ctx context.Context, client io.Writer, c *model.Cli
        if err != nil {
                return
        }
-       return s.Service.UnLike(ctx, client, c, id)
+       return s.Service.UnLike(ctx, c, id)
 }
 
-func (s *authService) Retweet(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) {
-       c, err = s.getClient(ctx)
+func (s *as) Retweet(ctx context.Context, c *model.Client, id string) (count int64, err error) {
+       err = s.authenticateClient(ctx, c)
        if err != nil {
                return
        }
@@ -233,11 +243,11 @@ func (s *authService) Retweet(ctx context.Context, client io.Writer, c *model.Cl
        if err != nil {
                return
        }
-       return s.Service.Retweet(ctx, client, c, id)
+       return s.Service.Retweet(ctx, c, id)
 }
 
-func (s *authService) UnRetweet(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) {
-       c, err = s.getClient(ctx)
+func (s *as) UnRetweet(ctx context.Context, c *model.Client, id string) (count int64, err error) {
+       err = s.authenticateClient(ctx, c)
        if err != nil {
                return
        }
@@ -245,11 +255,11 @@ func (s *authService) UnRetweet(ctx context.Context, client io.Writer, c *model.
        if err != nil {
                return
        }
-       return s.Service.UnRetweet(ctx, client, c, id)
+       return s.Service.UnRetweet(ctx, c, id)
 }
 
-func (s *authService) PostTweet(ctx context.Context, client io.Writer, c *model.Client, content string, replyToID string, format string, visibility string, isNSFW bool, files []*multipart.FileHeader) (id string, err error) {
-       c, err = s.getClient(ctx)
+func (s *as) Follow(ctx context.Context, c *model.Client, id string) (err error) {
+       err = s.authenticateClient(ctx, c)
        if err != nil {
                return
        }
@@ -257,11 +267,11 @@ func (s *authService) PostTweet(ctx context.Context, client io.Writer, c *model.
        if err != nil {
                return
        }
-       return s.Service.PostTweet(ctx, client, c, content, replyToID, format, visibility, isNSFW, files)
+       return s.Service.Follow(ctx, c, id)
 }
 
-func (s *authService) Follow(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) {
-       c, err = s.getClient(ctx)
+func (s *as) UnFollow(ctx context.Context, c *model.Client, id string) (err error) {
+       err = s.authenticateClient(ctx, c)
        if err != nil {
                return
        }
@@ -269,11 +279,11 @@ func (s *authService) Follow(ctx context.Context, client io.Writer, c *model.Cli
        if err != nil {
                return
        }
-       return s.Service.Follow(ctx, client, c, id)
+       return s.Service.UnFollow(ctx, c, id)
 }
 
-func (s *authService) UnFollow(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) {
-       c, err = s.getClient(ctx)
+func (s *as) SaveSettings(ctx context.Context, c *model.Client, settings *model.Settings) (err error) {
+       err = s.authenticateClient(ctx, c)
        if err != nil {
                return
        }
@@ -281,5 +291,5 @@ func (s *authService) UnFollow(ctx context.Context, client io.Writer, c *model.C
        if err != nil {
                return
        }
-       return s.Service.UnFollow(ctx, client, c, id)
+       return s.Service.SaveSettings(ctx, c, settings)
 }
index cafd8155a926cf9f409728e35f8f4d200130898b..e4f8985d5069766bcb4d38d27718a0d7c20e99f2 100644 (file)
@@ -2,7 +2,6 @@ package service
 
 import (
        "context"
-       "io"
        "log"
        "mime/multipart"
        "time"
@@ -10,206 +9,215 @@ import (
        "bloat/model"
 )
 
-type loggingService struct {
+type ls struct {
        logger *log.Logger
        Service
 }
 
 func NewLoggingService(logger *log.Logger, s Service) Service {
-       return &loggingService{logger, s}
+       return &ls{logger, s}
 }
 
-func (s *loggingService) GetAuthUrl(ctx context.Context, instance string) (
-       redirectUrl string, sessionID string, err error) {
+func (s *ls) ServeErrorPage(ctx context.Context, c *model.Client, 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)
+               s.logger.Printf("method=%v, err=%v, took=%v\n",
+                       "ServeErrorPage", err, time.Since(begin))
        }(time.Now())
-       return s.Service.GetAuthUrl(ctx, instance)
+       s.Service.ServeErrorPage(ctx, c, err)
 }
 
-func (s *loggingService) GetUserToken(ctx context.Context, sessionID string, c *model.Client,
-       code string) (token string, err error) {
+func (s *ls) ServeSigninPage(ctx context.Context, c *model.Client) (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)
+               s.logger.Printf("method=%v, took=%v, err=%v\n",
+                       "ServeSigninPage", time.Since(begin), err)
        }(time.Now())
-       return s.Service.GetUserToken(ctx, sessionID, c, code)
+       return s.Service.ServeSigninPage(ctx, c)
 }
 
-func (s *loggingService) ServeErrorPage(ctx context.Context, client io.Writer, c *model.Client, err error) {
+func (s *ls) ServeTimelinePage(ctx context.Context, c *model.Client, tType string,
+       maxID string, minID string) (err error) {
        defer func(begin time.Time) {
-               s.logger.Printf("method=%v, err=%v, took=%v\n",
-                       "ServeErrorPage", err, time.Since(begin))
+               s.logger.Printf("method=%v, type=%v, took=%v, err=%v\n",
+                       "ServeTimelinePage", tType, time.Since(begin), err)
        }(time.Now())
-       s.Service.ServeErrorPage(ctx, client, c, err)
+       return s.Service.ServeTimelinePage(ctx, c, tType, maxID, minID)
 }
 
-func (s *loggingService) ServeSigninPage(ctx context.Context, client io.Writer) (err error) {
+func (s *ls) ServeThreadPage(ctx context.Context, c *model.Client, id string,
+       reply bool) (err error) {
        defer func(begin time.Time) {
-               s.logger.Printf("method=%v, took=%v, err=%v\n",
-                       "ServeSigninPage", time.Since(begin), err)
+               s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n",
+                       "ServeThreadPage", id, time.Since(begin), err)
        }(time.Now())
-       return s.Service.ServeSigninPage(ctx, client)
+       return s.Service.ServeThreadPage(ctx, c, id, reply)
 }
 
-func (s *loggingService) ServeTimelinePage(ctx context.Context, client io.Writer,
-       c *model.Client, timelineType string, maxID string, sinceID string, minID string) (err error) {
+func (s *ls) ServeLikedByPage(ctx context.Context, c *model.Client, id string) (err error) {
        defer func(begin time.Time) {
-               s.logger.Printf("method=%v, timeline_type=%v, max_id=%v, since_id=%v, min_id=%v, took=%v, err=%v\n",
-                       "ServeTimelinePage", timelineType, maxID, sinceID, minID, time.Since(begin), err)
+               s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n",
+                       "ServeLikedByPage", id, time.Since(begin), err)
        }(time.Now())
-       return s.Service.ServeTimelinePage(ctx, client, c, timelineType, maxID, sinceID, minID)
+       return s.Service.ServeLikedByPage(ctx, c, id)
 }
 
-func (s *loggingService) ServeThreadPage(ctx context.Context, client io.Writer, c *model.Client, id string, reply bool) (err error) {
+func (s *ls) ServeRetweetedByPage(ctx context.Context, c *model.Client, id string) (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)
+               s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n",
+                       "ServeRetweetedByPage", id, time.Since(begin), err)
        }(time.Now())
-       return s.Service.ServeThreadPage(ctx, client, c, id, reply)
+       return s.Service.ServeRetweetedByPage(ctx, c, id)
 }
 
-func (s *loggingService) ServeNotificationPage(ctx context.Context, client io.Writer, c *model.Client, maxID string, minID string) (err error) {
+func (s *ls) ServeFollowingPage(ctx context.Context, c *model.Client, id string,
+       maxID string, minID string) (err error) {
        defer func(begin time.Time) {
-               s.logger.Printf("method=%v, max_id=%v, min_id=%v, took=%v, err=%v\n",
-                       "ServeNotificationPage", maxID, minID, time.Since(begin), err)
+               s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n",
+                       "ServeFollowingPage", id, time.Since(begin), err)
        }(time.Now())
-       return s.Service.ServeNotificationPage(ctx, client, c, maxID, minID)
+       return s.Service.ServeFollowingPage(ctx, c, id, maxID, minID)
 }
 
-func (s *loggingService) ServeUserPage(ctx context.Context, client io.Writer, c *model.Client, id string, maxID string, minID string) (err error) {
+func (s *ls) ServeFollowersPage(ctx context.Context, c *model.Client, id string,
+       maxID string, minID string) (err error) {
        defer func(begin time.Time) {
-               s.logger.Printf("method=%v, id=%v, max_id=%v, min_id=%v, took=%v, err=%v\n",
-                       "ServeUserPage", id, maxID, minID, time.Since(begin), err)
+               s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n",
+                       "ServeFollowersPage", id, time.Since(begin), err)
        }(time.Now())
-       return s.Service.ServeUserPage(ctx, client, c, id, maxID, minID)
+       return s.Service.ServeFollowersPage(ctx, c, id, maxID, minID)
 }
 
-func (s *loggingService) ServeAboutPage(ctx context.Context, client io.Writer, c *model.Client) (err error) {
+func (s *ls) ServeNotificationPage(ctx context.Context, c *model.Client,
+       maxID string, minID string) (err error) {
        defer func(begin time.Time) {
                s.logger.Printf("method=%v, took=%v, err=%v\n",
-                       "ServeAboutPage", time.Since(begin), err)
+                       "ServeNotificationPage", time.Since(begin), err)
        }(time.Now())
-       return s.Service.ServeAboutPage(ctx, client, c)
+       return s.Service.ServeNotificationPage(ctx, c, maxID, minID)
 }
 
-func (s *loggingService) ServeEmojiPage(ctx context.Context, client io.Writer, c *model.Client) (err error) {
+func (s *ls) ServeUserPage(ctx context.Context, c *model.Client, id string,
+       maxID string, minID string) (err error) {
        defer func(begin time.Time) {
-               s.logger.Printf("method=%v, took=%v, err=%v\n",
-                       "ServeEmojiPage", time.Since(begin), err)
+               s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n",
+                       "ServeUserPage", id, time.Since(begin), err)
        }(time.Now())
-       return s.Service.ServeEmojiPage(ctx, client, c)
+       return s.Service.ServeUserPage(ctx, c, id, maxID, minID)
 }
 
-func (s *loggingService) ServeLikedByPage(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) {
+func (s *ls) ServeAboutPage(ctx context.Context, c *model.Client) (err error) {
        defer func(begin time.Time) {
-               s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n",
-                       "ServeLikedByPage", id, time.Since(begin), err)
+               s.logger.Printf("method=%v, took=%v, err=%v\n",
+                       "ServeAboutPage", time.Since(begin), err)
        }(time.Now())
-       return s.Service.ServeLikedByPage(ctx, client, c, id)
+       return s.Service.ServeAboutPage(ctx, c)
 }
 
-func (s *loggingService) ServeRetweetedByPage(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) {
+func (s *ls) ServeEmojiPage(ctx context.Context, c *model.Client) (err error) {
        defer func(begin time.Time) {
-               s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n",
-                       "ServeRetweetedByPage", id, time.Since(begin), err)
+               s.logger.Printf("method=%v, took=%v, err=%v\n",
+                       "ServeEmojiPage", time.Since(begin), err)
        }(time.Now())
-       return s.Service.ServeRetweetedByPage(ctx, client, c, id)
+       return s.Service.ServeEmojiPage(ctx, c)
 }
 
-func (s *loggingService) ServeFollowingPage(ctx context.Context, client io.Writer, c *model.Client, id string, maxID string, minID string) (err error) {
+func (s *ls) ServeSearchPage(ctx context.Context, c *model.Client, q string,
+       qType string, offset int) (err error) {
        defer func(begin time.Time) {
-               s.logger.Printf("method=%v, id=%v, max_id=%v, min_id=%v, took=%v, err=%v\n",
-                       "ServeFollowingPage", id, maxID, minID, time.Since(begin), err)
+               s.logger.Printf("method=%v, took=%v, err=%v\n",
+                       "ServeSearchPage", time.Since(begin), err)
        }(time.Now())
-       return s.Service.ServeFollowingPage(ctx, client, c, id, maxID, minID)
+       return s.Service.ServeSearchPage(ctx, c, q, qType, offset)
 }
 
-func (s *loggingService) ServeFollowersPage(ctx context.Context, client io.Writer, c *model.Client, id string, maxID string, minID string) (err error) {
+func (s *ls) ServeSettingsPage(ctx context.Context, c *model.Client) (err error) {
        defer func(begin time.Time) {
-               s.logger.Printf("method=%v, id=%v, max_id=%v, min_id=%v, took=%v, err=%v\n",
-                       "ServeFollowersPage", id, maxID, minID, time.Since(begin), err)
+               s.logger.Printf("method=%v, took=%v, err=%v\n",
+                       "ServeSettingsPage", time.Since(begin), err)
        }(time.Now())
-       return s.Service.ServeFollowersPage(ctx, client, c, id, maxID, minID)
+       return s.Service.ServeSettingsPage(ctx, c)
 }
 
-func (s *loggingService) ServeSearchPage(ctx context.Context, client io.Writer, c *model.Client, q string, qType string, offset int) (err error) {
+func (s *ls) NewSession(ctx context.Context, instance string) (redirectUrl string,
+       sessionID string, err error) {
        defer func(begin time.Time) {
-               s.logger.Printf("method=%v, q=%v, type=%v, offset=%v, took=%v, err=%v\n",
-                       "ServeSearchPage", q, qType, offset, time.Since(begin), err)
+               s.logger.Printf("method=%v, instance=%v, took=%v, err=%v\n",
+                       "NewSession", instance, time.Since(begin), err)
        }(time.Now())
-       return s.Service.ServeSearchPage(ctx, client, c, q, qType, offset)
+       return s.Service.NewSession(ctx, instance)
 }
 
-func (s *loggingService) ServeSettingsPage(ctx context.Context, client io.Writer, c *model.Client) (err error) {
+func (s *ls) Signin(ctx context.Context, c *model.Client, sessionID string,
+       code string) (token string, err error) {
        defer func(begin time.Time) {
-               s.logger.Printf("method=%v, took=%v, err=%v\n",
-                       "ServeSettingsPage", time.Since(begin), err)
+               s.logger.Printf("method=%v, session_id=%v, took=%v, err=%v\n",
+                       "Signin", sessionID, time.Since(begin), err)
        }(time.Now())
-       return s.Service.ServeSettingsPage(ctx, client, c)
+       return s.Service.Signin(ctx, c, sessionID, code)
 }
 
-func (s *loggingService) SaveSettings(ctx context.Context, client io.Writer, c *model.Client, settings *model.Settings) (err error) {
+func (s *ls) Post(ctx context.Context, c *model.Client, content string,
+       replyToID string, format string, visibility string, isNSFW bool,
+       files []*multipart.FileHeader) (id string, err error) {
        defer func(begin time.Time) {
                s.logger.Printf("method=%v, took=%v, err=%v\n",
-                       "SaveSettings", time.Since(begin), err)
+                       "Post", time.Since(begin), err)
        }(time.Now())
-       return s.Service.SaveSettings(ctx, client, c, settings)
+       return s.Service.Post(ctx, c, content, replyToID, format,
+               visibility, isNSFW, files)
 }
 
-func (s *loggingService) Like(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) {
+func (s *ls) Like(ctx context.Context, c *model.Client, id string) (count int64, 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)
+       return s.Service.Like(ctx, c, id)
 }
 
-func (s *loggingService) UnLike(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) {
+func (s *ls) UnLike(ctx context.Context, c *model.Client, id string) (count int64, 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)
+       return s.Service.UnLike(ctx, c, id)
 }
 
-func (s *loggingService) Retweet(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) {
+func (s *ls) Retweet(ctx context.Context, c *model.Client, id string) (count int64, 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)
+       return s.Service.Retweet(ctx, c, id)
 }
 
-func (s *loggingService) UnRetweet(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) {
+func (s *ls) UnRetweet(ctx context.Context, c *model.Client, id string) (count int64, 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)
+       return s.Service.UnRetweet(ctx, c, id)
 }
 
-func (s *loggingService) PostTweet(ctx context.Context, client io.Writer, c *model.Client, content string, replyToID string, format string, visibility string, isNSFW bool, files []*multipart.FileHeader) (id string, err error) {
+func (s *ls) Follow(ctx context.Context, c *model.Client, id string) (err error) {
        defer func(begin time.Time) {
-               s.logger.Printf("method=%v, content=%v, reply_to_id=%v, format=%v, visibility=%v, is_nsfw=%v, took=%v, err=%v\n",
-                       "PostTweet", content, replyToID, format, visibility, isNSFW, time.Since(begin), err)
+               s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n",
+                       "Follow", id, time.Since(begin), err)
        }(time.Now())
-       return s.Service.PostTweet(ctx, client, c, content, replyToID, format, visibility, isNSFW, files)
+       return s.Service.Follow(ctx, c, id)
 }
 
-func (s *loggingService) Follow(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) {
+func (s *ls) UnFollow(ctx context.Context, c *model.Client, id string) (err error) {
        defer func(begin time.Time) {
                s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n",
-                       "Follow", id, time.Since(begin), err)
+                       "UnFollow", id, time.Since(begin), err)
        }(time.Now())
-       return s.Service.Follow(ctx, client, c, id)
+       return s.Service.UnFollow(ctx, c, id)
 }
 
-func (s *loggingService) UnFollow(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) {
+func (s *ls) SaveSettings(ctx context.Context, c *model.Client, settings *model.Settings) (err error) {
        defer func(begin time.Time) {
-               s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n",
-                       "UnFollow", id, time.Since(begin), err)
+               s.logger.Printf("method=%v, took=%v, err=%v\n",
+                       "SaveSettings", time.Since(begin), err)
        }(time.Now())
-       return s.Service.UnFollow(ctx, client, c, id)
+       return s.Service.SaveSettings(ctx, c, settings)
 }
index c9fccb4c9835b3da9f3abc3432abd3193ba5f1fc..7ad860ffd6725067df573d35fd9387daa579af54 100644 (file)
@@ -1,14 +1,10 @@
 package service
 
 import (
-       "bytes"
        "context"
-       "encoding/json"
        "errors"
        "fmt"
-       "io"
        "mime/multipart"
-       "net/http"
        "net/url"
        "strings"
 
@@ -19,37 +15,35 @@ import (
 )
 
 var (
-       ErrInvalidArgument = errors.New("invalid argument")
-       ErrInvalidToken    = errors.New("invalid token")
-       ErrInvalidClient   = errors.New("invalid client")
-       ErrInvalidTimeline = errors.New("invalid timeline")
+       errInvalidArgument = errors.New("invalid argument")
 )
 
 type Service interface {
-       GetAuthUrl(ctx context.Context, instance string) (url string, sessionID string, err error)
-       GetUserToken(ctx context.Context, sessionID string, c *model.Client, token string) (accessToken string, err error)
-       ServeErrorPage(ctx context.Context, client io.Writer, c *model.Client, err error)
-       ServeSigninPage(ctx context.Context, client io.Writer) (err error)
-       ServeTimelinePage(ctx context.Context, client io.Writer, c *model.Client, timelineType string, maxID string, sinceID string, minID string) (err error)
-       ServeThreadPage(ctx context.Context, client io.Writer, c *model.Client, id string, reply bool) (err error)
-       ServeNotificationPage(ctx context.Context, client io.Writer, c *model.Client, maxID string, minID string) (err error)
-       ServeUserPage(ctx context.Context, client io.Writer, c *model.Client, id string, maxID string, minID string) (err error)
-       ServeAboutPage(ctx context.Context, client io.Writer, c *model.Client) (err error)
-       ServeEmojiPage(ctx context.Context, client io.Writer, c *model.Client) (err error)
-       ServeLikedByPage(ctx context.Context, client io.Writer, c *model.Client, id string) (err error)
-       ServeRetweetedByPage(ctx context.Context, client io.Writer, c *model.Client, id string) (err error)
-       ServeFollowingPage(ctx context.Context, client io.Writer, c *model.Client, id string, maxID string, minID string) (err error)
-       ServeFollowersPage(ctx context.Context, client io.Writer, c *model.Client, id string, maxID string, minID string) (err error)
-       ServeSearchPage(ctx context.Context, client io.Writer, c *model.Client, q string, qType string, offset int) (err error)
-       ServeSettingsPage(ctx context.Context, client io.Writer, c *model.Client) (err error)
-       SaveSettings(ctx context.Context, client io.Writer, c *model.Client, settings *model.Settings) (err error)
-       Like(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error)
-       UnLike(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error)
-       Retweet(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error)
-       UnRetweet(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error)
-       PostTweet(ctx context.Context, client io.Writer, c *model.Client, content string, replyToID string, format string, visibility string, isNSFW bool, files []*multipart.FileHeader) (id string, err error)
-       Follow(ctx context.Context, client io.Writer, c *model.Client, id string) (err error)
-       UnFollow(ctx context.Context, client io.Writer, c *model.Client, id string) (err error)
+       ServeErrorPage(ctx context.Context, c *model.Client, err error)
+       ServeSigninPage(ctx context.Context, c *model.Client) (err error)
+       ServeTimelinePage(ctx context.Context, c *model.Client, tType string, maxID string, minID string) (err error)
+       ServeThreadPage(ctx context.Context, c *model.Client, id string, reply bool) (err error)
+       ServeLikedByPage(ctx context.Context, c *model.Client, id string) (err error)
+       ServeRetweetedByPage(ctx context.Context, c *model.Client, id string) (err error)
+       ServeFollowingPage(ctx context.Context, c *model.Client, id string, maxID string, minID string) (err error)
+       ServeFollowersPage(ctx context.Context, c *model.Client, id string, maxID string, minID string) (err error)
+       ServeNotificationPage(ctx context.Context, c *model.Client, maxID string, minID string) (err error)
+       ServeUserPage(ctx context.Context, c *model.Client, id string, maxID string, minID string) (err error)
+       ServeAboutPage(ctx context.Context, c *model.Client) (err error)
+       ServeEmojiPage(ctx context.Context, c *model.Client) (err error)
+       ServeSearchPage(ctx context.Context, c *model.Client, q string, qType string, offset int) (err error)
+       ServeSettingsPage(ctx context.Context, c *model.Client) (err error)
+       NewSession(ctx context.Context, instance string) (redirectUrl string, sessionID string, err error)
+       Signin(ctx context.Context, c *model.Client, sessionID string, code string) (token string, err error)
+       Post(ctx context.Context, c *model.Client, content string, replyToID string, format string,
+               visibility string, isNSFW bool, files []*multipart.FileHeader) (id string, err error)
+       Like(ctx context.Context, c *model.Client, id string) (count int64, err error)
+       UnLike(ctx context.Context, c *model.Client, id string) (count int64, err error)
+       Retweet(ctx context.Context, c *model.Client, id string) (count int64, err error)
+       UnRetweet(ctx context.Context, c *model.Client, id string) (count int64, err error)
+       Follow(ctx context.Context, c *model.Client, id string) (err error)
+       UnFollow(ctx context.Context, c *model.Client, id string) (err error)
+       SaveSettings(ctx context.Context, c *model.Client, settings *model.Settings) (err error)
 }
 
 type service struct {
@@ -59,13 +53,19 @@ type service struct {
        customCSS     string
        postFormats   []model.PostFormat
        renderer      renderer.Renderer
-       sessionRepo   model.SessionRepository
-       appRepo       model.AppRepository
+       sessionRepo   model.SessionRepo
+       appRepo       model.AppRepo
 }
 
-func NewService(clientName string, clientScope string, clientWebsite string,
-       customCSS string, postFormats []model.PostFormat, renderer renderer.Renderer,
-       sessionRepo model.SessionRepository, appRepo model.AppRepository) Service {
+func NewService(clientName string,
+       clientScope string,
+       clientWebsite string,
+       customCSS string,
+       postFormats []model.PostFormat,
+       renderer renderer.Renderer,
+       sessionRepo model.SessionRepo,
+       appRepo model.AppRepo,
+) Service {
        return &service{
                clientName:    clientName,
                clientScope:   clientScope,
@@ -96,137 +96,75 @@ func getRendererContext(c *model.Client) *renderer.Context {
        }
 }
 
-func (svc *service) GetAuthUrl(ctx context.Context, instance string) (
-       redirectUrl string, sessionID string, err error) {
-       var instanceURL string
-       if strings.HasPrefix(instance, "https://") {
-               instanceURL = instance
-               instance = strings.TrimPrefix(instance, "https://")
-       } else {
-               instanceURL = "https://" + instance
-       }
-
-       sessionID, err = util.NewSessionId()
-       if err != nil {
-               return
-       }
-       csrfToken, err := util.NewCSRFToken()
-       if err != nil {
-               return
-       }
-       session := model.Session{
-               ID:             sessionID,
-               InstanceDomain: instance,
-               CSRFToken:      csrfToken,
-               Settings:       *model.NewSettings(),
-       }
-       err = svc.sessionRepo.Add(session)
-       if err != nil {
+func addToReplyMap(m map[string][]mastodon.ReplyInfo, key interface{},
+       val string, number int) {
+       if key == 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:       instanceURL,
-                       ClientName:   svc.clientName,
-                       Scopes:       svc.clientScope,
-                       Website:      svc.clientWebsite,
-                       RedirectURIs: svc.clientWebsite + "/oauth_callback",
-               })
-               if err != nil {
-                       return
-               }
-
-               app = model.App{
-                       InstanceDomain: instance,
-                       InstanceURL:    instanceURL,
-                       ClientID:       mastoApp.ClientID,
-                       ClientSecret:   mastoApp.ClientSecret,
-               }
-
-               err = svc.appRepo.Add(app)
-               if err != nil {
-                       return
-               }
-       }
-
-       u, err := url.Parse("/oauth/authorize")
-       if err != nil {
+       keyStr, ok := key.(string)
+       if !ok {
                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 = instanceURL + u.String()
+       _, ok = m[keyStr]
+       if !ok {
+               m[keyStr] = []mastodon.ReplyInfo{}
+       }
 
-       return
+       m[keyStr] = append(m[keyStr], mastodon.ReplyInfo{val, number})
 }
 
-func (svc *service) GetUserToken(ctx context.Context, sessionID string, c *model.Client,
-       code string) (token string, err error) {
-       if len(code) < 1 {
-               err = ErrInvalidArgument
-               return
+func (svc *service) getCommonData(ctx context.Context, c *model.Client,
+       title string) (data *renderer.CommonData, err error) {
+
+       data = new(renderer.CommonData)
+       data.HeaderData = &renderer.HeaderData{
+               Title:             title + " - " + svc.clientName,
+               NotificationCount: 0,
+               CustomCSS:         svc.customCSS,
        }
 
-       session, err := svc.sessionRepo.Get(sessionID)
-       if err != nil {
+       if c == nil || !c.Session.IsLoggedIn() {
                return
        }
 
-       app, err := svc.appRepo.Get(session.InstanceDomain)
+       notifications, err := c.GetNotifications(ctx, nil)
        if err != nil {
-               return
+               return nil, err
        }
 
-       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
+       var notificationCount int
+       for i := range notifications {
+               if notifications[i].Pleroma != nil &&
+                       !notifications[i].Pleroma.IsSeen {
+                       notificationCount++
+               }
        }
 
-       resp, err := http.Post(app.InstanceURL+"/oauth/token", "application/json", data)
+       u, err := c.GetAccountCurrentUser(ctx)
        if err != nil {
-               return
+               return nil, err
        }
-       defer resp.Body.Close()
 
-       var res struct {
-               AccessToken string `json:"access_token"`
+       data.NavbarData = &renderer.NavbarData{
+               User:              u,
+               NotificationCount: notificationCount,
        }
 
-       err = json.NewDecoder(resp.Body).Decode(&res)
-       if err != nil {
-               return
-       }
+       data.HeaderData.NotificationCount = notificationCount
+       data.HeaderData.CSRFToken = c.Session.CSRFToken
 
-       return res.AccessToken, nil
+       return
 }
 
-func (svc *service) ServeErrorPage(ctx context.Context, client io.Writer, c *model.Client, err error) {
+func (svc *service) ServeErrorPage(ctx context.Context, c *model.Client, err error) {
        var errStr string
        if err != nil {
                errStr = err.Error()
        }
 
-       commonData, err := svc.getCommonData(ctx, client, nil, "error")
+       commonData, err := svc.getCommonData(ctx, nil, "error")
        if err != nil {
                return
        }
@@ -237,12 +175,13 @@ func (svc *service) ServeErrorPage(ctx context.Context, client io.Writer, c *mod
        }
 
        rCtx := getRendererContext(c)
-
-       svc.renderer.RenderErrorPage(rCtx, client, data)
+       svc.renderer.RenderErrorPage(rCtx, c.Writer, data)
 }
 
-func (svc *service) ServeSigninPage(ctx context.Context, client io.Writer) (err error) {
-       commonData, err := svc.getCommonData(ctx, client, nil, "signin")
+func (svc *service) ServeSigninPage(ctx context.Context, c *model.Client) (
+       err error) {
+
+       commonData, err := svc.getCommonData(ctx, nil, "signin")
        if err != nil {
                return
        }
@@ -252,26 +191,23 @@ func (svc *service) ServeSigninPage(ctx context.Context, client io.Writer) (err
        }
 
        rCtx := getRendererContext(nil)
-       return svc.renderer.RenderSigninPage(rCtx, client, data)
+       return svc.renderer.RenderSigninPage(rCtx, c.Writer, data)
 }
 
-func (svc *service) ServeTimelinePage(ctx context.Context, client io.Writer,
-       c *model.Client, timelineType string, maxID string, sinceID string, minID string) (err error) {
-
-       var hasNext, hasPrev bool
-       var nextLink, prevLink string
+func (svc *service) ServeTimelinePage(ctx context.Context, c *model.Client,
+       tType string, maxID string, minID string) (err error) {
 
+       var nextLink, prevLink, title string
+       var statuses []*mastodon.Status
        var pg = mastodon.Pagination{
                MaxID: maxID,
                MinID: minID,
                Limit: 20,
        }
 
-       var statuses []*mastodon.Status
-       var title string
-       switch timelineType {
+       switch tType {
        default:
-               return ErrInvalidTimeline
+               return errInvalidArgument
        case "home":
                statuses, err = c.GetTimelineHome(ctx, &pg)
                title = "Timeline"
@@ -293,29 +229,31 @@ func (svc *service) ServeTimelinePage(ctx context.Context, client io.Writer,
        }
 
        if len(maxID) > 0 && len(statuses) > 0 {
-               hasPrev = true
-               prevLink = fmt.Sprintf("/timeline/$s?min_id=%s", timelineType, statuses[0].ID)
+               prevLink = fmt.Sprintf("/timeline/%s?min_id=%s", tType,
+                       statuses[0].ID)
        }
+
        if len(minID) > 0 && len(pg.MinID) > 0 {
-               newStatuses, err := c.GetTimelineHome(ctx, &mastodon.Pagination{MinID: pg.MinID, Limit: 20})
+               newPg := &mastodon.Pagination{MinID: pg.MinID, Limit: 20}
+               newStatuses, err := c.GetTimelineHome(ctx, newPg)
                if err != nil {
                        return err
                }
-               newStatusesLen := len(newStatuses)
-               if newStatusesLen == 20 {
-                       hasPrev = true
-                       prevLink = fmt.Sprintf("/timeline/%s?min_id=%s", timelineType, pg.MinID)
+               newLen := len(newStatuses)
+               if newLen == 20 {
+                       prevLink = fmt.Sprintf("/timeline/%s?min_id=%s",
+                               tType, pg.MinID)
                } else {
-                       i := 20 - newStatusesLen - 1
+                       i := 20 - newLen - 1
                        if len(statuses) > i {
-                               hasPrev = true
-                               prevLink = fmt.Sprintf("/timeline/%s?min_id=%s", timelineType, statuses[i].ID)
+                               prevLink = fmt.Sprintf("/timeline/%s?min_id=%s",
+                                       tType, statuses[i].ID)
                        }
                }
        }
+
        if len(pg.MaxID) > 0 {
-               hasNext = true
-               nextLink = fmt.Sprintf("/timeline/%s?max_id=%s", timelineType, pg.MaxID)
+               nextLink = fmt.Sprintf("/timeline/%s?max_id=%s", tType, pg.MaxID)
        }
 
        postContext := model.PostContext{
@@ -323,7 +261,7 @@ func (svc *service) ServeTimelinePage(ctx context.Context, client io.Writer,
                Formats:           svc.postFormats,
        }
 
-       commonData, err := svc.getCommonData(ctx, client, c, timelineType+" timeline ")
+       commonData, err := svc.getCommonData(ctx, c, tType+" timeline ")
        if err != nil {
                return
        }
@@ -331,24 +269,21 @@ func (svc *service) ServeTimelinePage(ctx context.Context, client io.Writer,
        data := &renderer.TimelineData{
                Title:       title,
                Statuses:    statuses,
-               HasNext:     hasNext,
                NextLink:    nextLink,
-               HasPrev:     hasPrev,
                PrevLink:    prevLink,
                PostContext: postContext,
                CommonData:  commonData,
        }
+
        rCtx := getRendererContext(c)
+       return svc.renderer.RenderTimelinePage(rCtx, c.Writer, data)
+}
 
-       err = svc.renderer.RenderTimelinePage(rCtx, client, data)
-       if err != nil {
-               return
-       }
+func (svc *service) ServeThreadPage(ctx context.Context, c *model.Client,
+       id string, reply bool) (err error) {
 
-       return
-}
+       var postContext model.PostContext
 
-func (svc *service) ServeThreadPage(ctx context.Context, client io.Writer, c *model.Client, id string, reply bool) (err error) {
        status, err := c.GetStatus(ctx, id)
        if err != nil {
                return
@@ -359,19 +294,19 @@ func (svc *service) ServeThreadPage(ctx context.Context, client io.Writer, c *mo
                return
        }
 
-       var postContext model.PostContext
        if reply {
                var content string
+               var visibility string
                if u.ID != status.Account.ID {
                        content += "@" + status.Account.Acct + " "
                }
                for i := range status.Mentions {
-                       if status.Mentions[i].ID != u.ID && status.Mentions[i].ID != status.Account.ID {
+                       if status.Mentions[i].ID != u.ID &&
+                               status.Mentions[i].ID != status.Account.ID {
                                content += "@" + status.Mentions[i].Acct + " "
                        }
                }
 
-               var visibility string
                if c.Session.Settings.CopyScope {
                        s, err := c.GetStatus(ctx, id)
                        if err != nil {
@@ -400,16 +335,15 @@ func (svc *service) ServeThreadPage(ctx context.Context, client io.Writer, c *mo
        }
 
        statuses := append(append(context.Ancestors, status), context.Descendants...)
-
-       replyMap := make(map[string][]mastodon.ReplyInfo)
+       replies := make(map[string][]mastodon.ReplyInfo)
 
        for i := range statuses {
                statuses[i].ShowReplies = true
-               statuses[i].ReplyMap = replyMap
-               addToReplyMap(replyMap, statuses[i].InReplyToID, statuses[i].ID, i+1)
+               statuses[i].ReplyMap = replies
+               addToReplyMap(replies, statuses[i].InReplyToID, statuses[i].ID, i+1)
        }
 
-       commonData, err := svc.getCommonData(ctx, client, c, "post by "+status.Account.DisplayName)
+       commonData, err := svc.getCommonData(ctx, c, "post by "+status.Account.DisplayName)
        if err != nil {
                return
        }
@@ -417,224 +351,182 @@ func (svc *service) ServeThreadPage(ctx context.Context, client io.Writer, c *mo
        data := &renderer.ThreadData{
                Statuses:    statuses,
                PostContext: postContext,
-               ReplyMap:    replyMap,
+               ReplyMap:    replies,
                CommonData:  commonData,
        }
-       rCtx := getRendererContext(c)
 
-       err = svc.renderer.RenderThreadPage(rCtx, client, data)
-       if err != nil {
-               return
-       }
-
-       return
+       rCtx := getRendererContext(c)
+       return svc.renderer.RenderThreadPage(rCtx, c.Writer, data)
 }
 
-func (svc *service) ServeNotificationPage(ctx context.Context, client io.Writer, c *model.Client, maxID string, minID string) (err error) {
-       var hasNext bool
-       var nextLink string
+func (svc *service) ServeLikedByPage(ctx context.Context, c *model.Client,
+       id string) (err error) {
 
-       var pg = mastodon.Pagination{
-               MaxID: maxID,
-               MinID: minID,
-               Limit: 20,
+       likers, err := c.GetFavouritedBy(ctx, id, nil)
+       if err != nil {
+               return
        }
 
-       notifications, err := c.GetNotifications(ctx, &pg)
+       commonData, err := svc.getCommonData(ctx, c, "likes")
        if err != nil {
                return
        }
 
-       var unreadCount int
-       for i := range notifications {
-               if notifications[i].Status != nil {
-                       notifications[i].Status.CreatedAt = notifications[i].CreatedAt
-                       switch notifications[i].Type {
-                       case "reblog", "favourite":
-                               notifications[i].Status.HideAccountInfo = true
-                       }
-               }
-               if notifications[i].Pleroma != nil && !notifications[i].Pleroma.IsSeen {
-                       unreadCount++
-               }
+       data := &renderer.LikedByData{
+               CommonData: commonData,
+               Users:      likers,
        }
 
-       if unreadCount > 0 {
-               err := c.ReadNotifications(ctx, notifications[0].ID)
-               if err != nil {
-                       return err
-               }
-       }
+       rCtx := getRendererContext(c)
+       return svc.renderer.RenderLikedByPage(rCtx, c.Writer, data)
+}
 
-       if len(pg.MaxID) > 0 {
-               hasNext = true
-               nextLink = "/notifications?max_id=" + pg.MaxID
-       }
+func (svc *service) ServeRetweetedByPage(ctx context.Context, c *model.Client,
+       id string) (err error) {
 
-       commonData, err := svc.getCommonData(ctx, client, c, "notifications")
+       retweeters, err := c.GetRebloggedBy(ctx, id, nil)
        if err != nil {
                return
        }
 
-       data := &renderer.NotificationData{
-               Notifications: notifications,
-               HasNext:       hasNext,
-               NextLink:      nextLink,
-               CommonData:    commonData,
-       }
-       rCtx := getRendererContext(c)
-
-       err = svc.renderer.RenderNotificationPage(rCtx, client, data)
+       commonData, err := svc.getCommonData(ctx, c, "retweets")
        if err != nil {
                return
        }
 
-       return
+       data := &renderer.RetweetedByData{
+               CommonData: commonData,
+               Users:      retweeters,
+       }
+
+       rCtx := getRendererContext(c)
+       return svc.renderer.RenderRetweetedByPage(rCtx, c.Writer, data)
 }
 
-func (svc *service) ServeUserPage(ctx context.Context, client io.Writer, c *model.Client, id string, maxID string, minID string) (err error) {
-       user, err := c.GetAccount(ctx, id)
-       if err != nil {
-               return
-       }
+func (svc *service) ServeFollowingPage(ctx context.Context, c *model.Client,
+       id string, maxID string, minID string) (err error) {
 
-       var hasNext bool
        var nextLink string
-
        var pg = mastodon.Pagination{
                MaxID: maxID,
                MinID: minID,
                Limit: 20,
        }
 
-       statuses, err := c.GetAccountStatuses(ctx, id, &pg)
+       followings, err := c.GetAccountFollowing(ctx, id, &pg)
        if err != nil {
                return
        }
 
-       if len(pg.MaxID) > 0 {
-               hasNext = true
-               nextLink = "/user/" + id + "?max_id=" + pg.MaxID
+       if len(followings) == 20 && len(pg.MaxID) > 0 {
+               nextLink = "/following/" + id + "?max_id=" + pg.MaxID
        }
 
-       commonData, err := svc.getCommonData(ctx, client, c, user.DisplayName)
+       commonData, err := svc.getCommonData(ctx, c, "following")
        if err != nil {
                return
        }
 
-       data := &renderer.UserData{
-               User:       user,
-               Statuses:   statuses,
-               HasNext:    hasNext,
-               NextLink:   nextLink,
+       data := &renderer.FollowingData{
                CommonData: commonData,
-       }
-       rCtx := getRendererContext(c)
-
-       err = svc.renderer.RenderUserPage(rCtx, client, data)
-       if err != nil {
-               return
+               Users:      followings,
+               NextLink:   nextLink,
        }
 
-       return
+       rCtx := getRendererContext(c)
+       return svc.renderer.RenderFollowingPage(rCtx, c.Writer, data)
 }
 
-func (svc *service) ServeAboutPage(ctx context.Context, client io.Writer, c *model.Client) (err error) {
-       commonData, err := svc.getCommonData(ctx, client, c, "about")
-       if err != nil {
-               return
-       }
+func (svc *service) ServeFollowersPage(ctx context.Context, c *model.Client,
+       id string, maxID string, minID string) (err error) {
 
-       data := &renderer.AboutData{
-               CommonData: commonData,
+       var nextLink string
+       var pg = mastodon.Pagination{
+               MaxID: maxID,
+               MinID: minID,
+               Limit: 20,
        }
-       rCtx := getRendererContext(c)
 
-       err = svc.renderer.RenderAboutPage(rCtx, client, data)
+       followers, err := c.GetAccountFollowers(ctx, id, &pg)
        if err != nil {
                return
        }
 
-       return
-}
-
-func (svc *service) ServeEmojiPage(ctx context.Context, client io.Writer, c *model.Client) (err error) {
-       commonData, err := svc.getCommonData(ctx, client, c, "emojis")
-       if err != nil {
-               return
+       if len(followers) == 20 && len(pg.MaxID) > 0 {
+               nextLink = "/followers/" + id + "?max_id=" + pg.MaxID
        }
 
-       emojis, err := c.GetInstanceEmojis(ctx)
+       commonData, err := svc.getCommonData(ctx, c, "followers")
        if err != nil {
                return
        }
 
-       data := &renderer.EmojiData{
-               Emojis:     emojis,
+       data := &renderer.FollowersData{
                CommonData: commonData,
+               Users:      followers,
+               NextLink:   nextLink,
        }
        rCtx := getRendererContext(c)
-
-       err = svc.renderer.RenderEmojiPage(rCtx, client, data)
-       if err != nil {
-               return
-       }
-
-       return
+       return svc.renderer.RenderFollowersPage(rCtx, c.Writer, data)
 }
 
-func (svc *service) ServeLikedByPage(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) {
-       likers, err := c.GetFavouritedBy(ctx, id, nil)
-       if err != nil {
-               return
+func (svc *service) ServeNotificationPage(ctx context.Context, c *model.Client,
+       maxID string, minID string) (err error) {
+
+       var nextLink string
+       var unreadCount int
+       var pg = mastodon.Pagination{
+               MaxID: maxID,
+               MinID: minID,
+               Limit: 20,
        }
 
-       commonData, err := svc.getCommonData(ctx, client, c, "likes")
+       notifications, err := c.GetNotifications(ctx, &pg)
        if err != nil {
                return
        }
 
-       data := &renderer.LikedByData{
-               CommonData: commonData,
-               Users:      likers,
+       for i := range notifications {
+               if notifications[i].Status != nil {
+                       notifications[i].Status.CreatedAt = notifications[i].CreatedAt
+                       switch notifications[i].Type {
+                       case "reblog", "favourite":
+                               notifications[i].Status.HideAccountInfo = true
+                       }
+               }
+               if notifications[i].Pleroma != nil && !notifications[i].Pleroma.IsSeen {
+                       unreadCount++
+               }
        }
-       rCtx := getRendererContext(c)
 
-       err = svc.renderer.RenderLikedByPage(rCtx, client, data)
-       if err != nil {
-               return
+       if unreadCount > 0 {
+               err := c.ReadNotifications(ctx, notifications[0].ID)
+               if err != nil {
+                       return err
+               }
        }
 
-       return
-}
-
-func (svc *service) ServeRetweetedByPage(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) {
-       retweeters, err := c.GetRebloggedBy(ctx, id, nil)
-       if err != nil {
-               return
+       if len(pg.MaxID) > 0 {
+               nextLink = "/notifications?max_id=" + pg.MaxID
        }
 
-       commonData, err := svc.getCommonData(ctx, client, c, "retweets")
+       commonData, err := svc.getCommonData(ctx, c, "notifications")
        if err != nil {
                return
        }
 
-       data := &renderer.RetweetedByData{
-               CommonData: commonData,
-               Users:      retweeters,
+       data := &renderer.NotificationData{
+               Notifications: notifications,
+               NextLink:      nextLink,
+               CommonData:    commonData,
        }
        rCtx := getRendererContext(c)
-
-       err = svc.renderer.RenderRetweetedByPage(rCtx, client, data)
-       if err != nil {
-               return
-       }
-
-       return
+       return svc.renderer.RenderNotificationPage(rCtx, c.Writer, data)
 }
 
-func (svc *service) ServeFollowingPage(ctx context.Context, client io.Writer, c *model.Client, id string, maxID string, minID string) (err error) {
-       var hasNext bool
+func (svc *service) ServeUserPage(ctx context.Context, c *model.Client,
+       id string, maxID string, minID string) (err error) {
+
        var nextLink string
 
        var pg = mastodon.Pagination{
@@ -643,104 +535,91 @@ func (svc *service) ServeFollowingPage(ctx context.Context, client io.Writer, c
                Limit: 20,
        }
 
-       followings, err := c.GetAccountFollowing(ctx, id, &pg)
+       user, err := c.GetAccount(ctx, id)
        if err != nil {
                return
        }
 
-       if len(followings) == 20 && len(pg.MaxID) > 0 {
-               hasNext = true
-               nextLink = "/following/" + id + "?max_id=" + pg.MaxID
+       statuses, err := c.GetAccountStatuses(ctx, id, &pg)
+       if err != nil {
+               return
+       }
+
+       if len(pg.MaxID) > 0 {
+               nextLink = "/user/" + id + "?max_id=" + pg.MaxID
        }
 
-       commonData, err := svc.getCommonData(ctx, client, c, "following")
+       commonData, err := svc.getCommonData(ctx, c, user.DisplayName)
        if err != nil {
                return
        }
 
-       data := &renderer.FollowingData{
-               CommonData: commonData,
-               Users:      followings,
-               HasNext:    hasNext,
+       data := &renderer.UserData{
+               User:       user,
+               Statuses:   statuses,
                NextLink:   nextLink,
+               CommonData: commonData,
        }
        rCtx := getRendererContext(c)
+       return svc.renderer.RenderUserPage(rCtx, c.Writer, data)
+}
 
-       err = svc.renderer.RenderFollowingPage(rCtx, client, data)
+func (svc *service) ServeAboutPage(ctx context.Context, c *model.Client) (err error) {
+       commonData, err := svc.getCommonData(ctx, c, "about")
        if err != nil {
                return
        }
 
-       return
-}
-
-func (svc *service) ServeFollowersPage(ctx context.Context, client io.Writer, c *model.Client, id string, maxID string, minID string) (err error) {
-       var hasNext bool
-       var nextLink string
-
-       var pg = mastodon.Pagination{
-               MaxID: maxID,
-               MinID: minID,
-               Limit: 20,
+       data := &renderer.AboutData{
+               CommonData: commonData,
        }
 
-       followers, err := c.GetAccountFollowers(ctx, id, &pg)
+       rCtx := getRendererContext(c)
+       return svc.renderer.RenderAboutPage(rCtx, c.Writer, data)
+}
+
+func (svc *service) ServeEmojiPage(ctx context.Context, c *model.Client) (err error) {
+       commonData, err := svc.getCommonData(ctx, c, "emojis")
        if err != nil {
                return
        }
 
-       if len(followers) == 20 && len(pg.MaxID) > 0 {
-               hasNext = true
-               nextLink = "/followers/" + id + "?max_id=" + pg.MaxID
-       }
-
-       commonData, err := svc.getCommonData(ctx, client, c, "followers")
+       emojis, err := c.GetInstanceEmojis(ctx)
        if err != nil {
                return
        }
 
-       data := &renderer.FollowersData{
+       data := &renderer.EmojiData{
+               Emojis:     emojis,
                CommonData: commonData,
-               Users:      followers,
-               HasNext:    hasNext,
-               NextLink:   nextLink,
        }
-       rCtx := getRendererContext(c)
 
-       err = svc.renderer.RenderFollowersPage(rCtx, client, data)
-       if err != nil {
-               return
-       }
-
-       return
+       rCtx := getRendererContext(c)
+       return svc.renderer.RenderEmojiPage(rCtx, c.Writer, data)
 }
 
-func (svc *service) ServeSearchPage(ctx context.Context, client io.Writer, c *model.Client, q string, qType string, offset int) (err error) {
-       var hasNext bool
+func (svc *service) ServeSearchPage(ctx context.Context, c *model.Client,
+       q string, qType string, offset int) (err error) {
+
        var nextLink string
+       var title = "search"
 
        results, err := c.Search(ctx, q, qType, 20, true, offset)
        if err != nil {
                return
        }
 
-       switch qType {
-       case "accounts":
-               hasNext = len(results.Accounts) == 20
-       case "statuses":
-               hasNext = len(results.Statuses) == 20
-       }
-
-       if hasNext {
+       if (qType == "accounts" && len(results.Accounts) == 20) ||
+               (qType == "statuses" && len(results.Statuses) == 20) {
                offset += 20
                nextLink = fmt.Sprintf("/search?q=%s&type=%s&offset=%d", q, qType, offset)
        }
 
-       var title = "search"
        if len(q) > 0 {
                title += " \"" + q + "\""
        }
-       commonData, err := svc.getCommonData(ctx, client, c, title)
+
+       commonData, err := svc.getCommonData(ctx, c, title)
        if err != nil {
                return
        }
@@ -751,21 +630,15 @@ func (svc *service) ServeSearchPage(ctx context.Context, client io.Writer, c *mo
                Type:       qType,
                Users:      results.Accounts,
                Statuses:   results.Statuses,
-               HasNext:    hasNext,
                NextLink:   nextLink,
        }
-       rCtx := getRendererContext(c)
 
-       err = svc.renderer.RenderSearchPage(rCtx, client, data)
-       if err != nil {
-               return
-       }
-
-       return
+       rCtx := getRendererContext(c)
+       return svc.renderer.RenderSearchPage(rCtx, c.Writer, data)
 }
 
-func (svc *service) ServeSettingsPage(ctx context.Context, client io.Writer, c *model.Client) (err error) {
-       commonData, err := svc.getCommonData(ctx, client, c, "settings")
+func (svc *service) ServeSettingsPage(ctx context.Context, c *model.Client) (err error) {
+       commonData, err := svc.getCommonData(ctx, c, "settings")
        if err != nil {
                return
        }
@@ -774,122 +647,125 @@ func (svc *service) ServeSettingsPage(ctx context.Context, client io.Writer, c *
                CommonData: commonData,
                Settings:   &c.Session.Settings,
        }
+
        rCtx := getRendererContext(c)
+       return svc.renderer.RenderSettingsPage(rCtx, c.Writer, data)
+}
 
-       err = svc.renderer.RenderSettingsPage(rCtx, client, data)
+func (svc *service) NewSession(ctx context.Context, instance string) (
+       redirectUrl string, sessionID string, err error) {
+
+       var instanceURL string
+       if strings.HasPrefix(instance, "https://") {
+               instanceURL = instance
+               instance = strings.TrimPrefix(instance, "https://")
+       } else {
+               instanceURL = "https://" + instance
+       }
+
+       sessionID, err = util.NewSessionID()
        if err != nil {
                return
        }
 
-       return
-}
-
-func (svc *service) SaveSettings(ctx context.Context, client io.Writer, c *model.Client, settings *model.Settings) (err error) {
-       session, err := svc.sessionRepo.Get(c.Session.ID)
+       csrfToken, err := util.NewCSRFToken()
        if err != nil {
                return
        }
 
-       session.Settings = *settings
+       session := model.Session{
+               ID:             sessionID,
+               InstanceDomain: instance,
+               CSRFToken:      csrfToken,
+               Settings:       *model.NewSettings(),
+       }
+
        err = svc.sessionRepo.Add(session)
        if err != nil {
                return
        }
 
-       return
-}
-
-func (svc *service) getCommonData(ctx context.Context, client io.Writer, c *model.Client, title string) (data *renderer.CommonData, err error) {
-       data = new(renderer.CommonData)
-
-       data.HeaderData = &renderer.HeaderData{
-               Title:             title + " - " + svc.clientName,
-               NotificationCount: 0,
-               CustomCSS:         svc.customCSS,
-       }
+       app, err := svc.appRepo.Get(instance)
+       if err != nil {
+               if err != model.ErrAppNotFound {
+                       return
+               }
 
-       if c != nil && c.Session.IsLoggedIn() {
-               notifications, err := c.GetNotifications(ctx, nil)
+               mastoApp, err := mastodon.RegisterApp(ctx, &mastodon.AppConfig{
+                       Server:       instanceURL,
+                       ClientName:   svc.clientName,
+                       Scopes:       svc.clientScope,
+                       Website:      svc.clientWebsite,
+                       RedirectURIs: svc.clientWebsite + "/oauth_callback",
+               })
                if err != nil {
-                       return nil, err
+                       return "", "", err
                }
 
-               var notificationCount int
-               for i := range notifications {
-                       if notifications[i].Pleroma != nil && !notifications[i].Pleroma.IsSeen {
-                               notificationCount++
-                       }
+               app = model.App{
+                       InstanceDomain: instance,
+                       InstanceURL:    instanceURL,
+                       ClientID:       mastoApp.ClientID,
+                       ClientSecret:   mastoApp.ClientSecret,
                }
 
-               u, err := c.GetAccountCurrentUser(ctx)
+               err = svc.appRepo.Add(app)
                if err != nil {
-                       return nil, err
-               }
-
-               data.NavbarData = &renderer.NavbarData{
-                       User:              u,
-                       NotificationCount: notificationCount,
+                       return "", "", err
                }
-
-               data.HeaderData.NotificationCount = notificationCount
-               data.HeaderData.CSRFToken = c.Session.CSRFToken
        }
 
-       return
-}
-
-func (svc *service) Like(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) {
-       s, err := c.Favourite(ctx, id)
+       u, err := url.Parse("/oauth/authorize")
        if err != nil {
                return
        }
-       count = s.FavouritesCount
-       return
-}
 
-func (svc *service) UnLike(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) {
-       s, err := c.Unfavourite(ctx, id)
-       if err != nil {
-               return
-       }
-       count = s.FavouritesCount
+       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 = instanceURL + u.String()
+
        return
 }
 
-func (svc *service) Retweet(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) {
-       s, err := c.Reblog(ctx, id)
-       if err != nil {
+func (svc *service) Signin(ctx context.Context, c *model.Client,
+       sessionID string, code string) (token string, err error) {
+
+       if len(code) < 1 {
+               err = errInvalidArgument
                return
        }
-       if s.Reblog != nil {
-               count = s.Reblog.ReblogsCount
-       }
-       return
-}
 
-func (svc *service) UnRetweet(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) {
-       s, err := c.Unreblog(ctx, id)
+       err = c.AuthenticateToken(ctx, code, svc.clientWebsite+"/oauth_callback")
        if err != nil {
                return
        }
-       count = s.ReblogsCount
+       token = c.GetAccessToken(ctx)
+
        return
 }
 
-func (svc *service) PostTweet(ctx context.Context, client io.Writer, c *model.Client, content string, replyToID string, format string, visibility string, isNSFW bool, files []*multipart.FileHeader) (id string, err error) {
-       var mediaIds []string
+func (svc *service) Post(ctx context.Context, c *model.Client, content string,
+       replyToID string, format string, visibility string, isNSFW bool,
+       files []*multipart.FileHeader) (id string, err error) {
+
+       var mediaIDs []string
        for _, f := range files {
                a, err := c.UploadMediaFromMultipartFileHeader(ctx, f)
                if err != nil {
                        return "", err
                }
-               mediaIds = append(mediaIds, a.ID)
+               mediaIDs = append(mediaIDs, a.ID)
        }
 
        tweet := &mastodon.Toot{
                Status:      content,
                InReplyToID: replyToID,
-               MediaIDs:    mediaIds,
+               MediaIDs:    mediaIDs,
                ContentType: format,
                Visibility:  visibility,
                Sensitive:   isNSFW,
@@ -903,29 +779,66 @@ func (svc *service) PostTweet(ctx context.Context, client io.Writer, c *model.Cl
        return s.ID, nil
 }
 
-func (svc *service) Follow(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) {
-       _, err = c.AccountFollow(ctx, id)
+func (svc *service) Like(ctx context.Context, c *model.Client, id string) (
+       count int64, err error) {
+       s, err := c.Favourite(ctx, id)
+       if err != nil {
+               return
+       }
+       count = s.FavouritesCount
        return
 }
 
-func (svc *service) UnFollow(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) {
-       _, err = c.AccountUnfollow(ctx, id)
+func (svc *service) UnLike(ctx context.Context, c *model.Client, id string) (
+       count int64, err error) {
+       s, err := c.Unfavourite(ctx, id)
+       if err != nil {
+               return
+       }
+       count = s.FavouritesCount
        return
 }
 
-func addToReplyMap(m map[string][]mastodon.ReplyInfo, key interface{}, val string, number int) {
-       if key == nil {
+func (svc *service) Retweet(ctx context.Context, c *model.Client, id string) (
+       count int64, err error) {
+       s, err := c.Reblog(ctx, id)
+       if err != nil {
                return
        }
+       if s.Reblog != nil {
+               count = s.Reblog.ReblogsCount
+       }
+       return
+}
 
-       keyStr, ok := key.(string)
-       if !ok {
+func (svc *service) UnRetweet(ctx context.Context, c *model.Client, id string) (
+       count int64, err error) {
+       s, err := c.Unreblog(ctx, id)
+       if err != nil {
                return
        }
-       _, ok = m[keyStr]
-       if !ok {
-               m[keyStr] = []mastodon.ReplyInfo{}
+       count = s.ReblogsCount
+       return
+}
+
+func (svc *service) Follow(ctx context.Context, c *model.Client, id string) (err error) {
+       _, err = c.AccountFollow(ctx, id)
+       return
+}
+
+func (svc *service) UnFollow(ctx context.Context, c *model.Client, id string) (err error) {
+       _, err = c.AccountUnfollow(ctx, id)
+       return
+}
+
+func (svc *service) SaveSettings(ctx context.Context, c *model.Client,
+       settings *model.Settings) (err error) {
+
+       session, err := svc.sessionRepo.Get(c.Session.ID)
+       if err != nil {
+               return
        }
 
-       m[keyStr] = append(m[keyStr], mastodon.ReplyInfo{val, number})
+       session.Settings = *settings
+       return svc.sessionRepo.Add(session)
 }
index e878f8d5f06fd0d4405154b9c99339913aa6bc9e..fbab2e55d254583cc08256bb4df26b65e426e722 100644 (file)
@@ -15,327 +15,292 @@ import (
        "github.com/gorilla/mux"
 )
 
-var (
-       ctx       = context.Background()
-       cookieAge = "31536000"
-)
+func newClient(w io.Writer) *model.Client {
+       return &model.Client{
+               Writer: w,
+       }
+}
+
+func newCtxWithSesion(req *http.Request) context.Context {
+       ctx := context.Background()
+       sessionID, err := req.Cookie("session_id")
+       if err != nil {
+               return ctx
+       }
+       return context.WithValue(ctx, "session_id", sessionID.Value)
+}
+
+func newCtxWithSesionCSRF(req *http.Request, csrfToken string) context.Context {
+       ctx := newCtxWithSesion(req)
+       return context.WithValue(ctx, "csrf_token", csrfToken)
+}
+
+func getMultipartFormValue(mf *multipart.Form, key string) (val string) {
+       vals, ok := mf.Value[key]
+       if !ok {
+               return ""
+       }
+       if len(vals) < 1 {
+               return ""
+       }
+       return vals[0]
+}
+
+func serveJson(w io.Writer, data interface{}) (err error) {
+       var d = make(map[string]interface{})
+       d["data"] = data
+       return json.NewEncoder(w).Encode(d)
+}
 
 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)))))
+       rootPage := func(w http.ResponseWriter, req *http.Request) {
+               sessionID, _ := req.Cookie("session_id")
 
-       r.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
                location := "/signin"
-
-               sessionID, _ := req.Cookie("session_id")
                if sessionID != nil && len(sessionID.Value) > 0 {
                        location = "/timeline/home"
                }
 
                w.Header().Add("Location", location)
                w.WriteHeader(http.StatusFound)
-       }).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, nil, 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)
+       signinPage := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := context.Background()
+               err := s.ServeSigninPage(ctx, c)
                if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
+                       s.ServeErrorPage(ctx, c, err)
                        return
                }
+       }
 
-               http.SetCookie(w, &http.Cookie{
-                       Name:    "session_id",
-                       Value:   sessionID,
-                       Expires: time.Now().Add(365 * 24 * time.Hour),
-               })
-
-               w.Header().Add("Location", url)
-               w.WriteHeader(http.StatusFound)
-       }).Methods(http.MethodPost)
+       timelinePage := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := newCtxWithSesion(req)
+               tType, _ := mux.Vars(req)["type"]
+               maxID := req.URL.Query().Get("max_id")
+               minID := req.URL.Query().Get("min_id")
 
-       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)
+               err := s.ServeTimelinePage(ctx, c, tType, maxID, minID)
                if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
+                       s.ServeErrorPage(ctx, c, err)
                        return
                }
+       }
 
+       timelineOldPage := func(w http.ResponseWriter, req *http.Request) {
                w.Header().Add("Location", "/timeline/home")
                w.WriteHeader(http.StatusFound)
-       }).Methods(http.MethodGet)
-
-       r.HandleFunc("/timeline", func(w http.ResponseWriter, req *http.Request) {
-               w.Header().Add("Location", "/timeline/home")
-               w.WriteHeader(http.StatusFound)
-       }).Methods(http.MethodGet)
-
-       r.HandleFunc("/timeline/{type}", func(w http.ResponseWriter, req *http.Request) {
-               ctx := getContextWithSession(context.Background(), req)
-
-               timelineType, _ := mux.Vars(req)["type"]
-               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, timelineType, maxID, sinceID, minID)
-               if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
-                       return
-               }
-       }).Methods(http.MethodGet)
+       }
 
-       r.HandleFunc("/thread/{id}", func(w http.ResponseWriter, req *http.Request) {
-               ctx := getContextWithSession(context.Background(), req)
+       threadPage := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := newCtxWithSesion(req)
                id, _ := mux.Vars(req)["id"]
                reply := req.URL.Query().Get("reply")
-               err := s.ServeThreadPage(ctx, w, nil, id, len(reply) > 1)
+
+               err := s.ServeThreadPage(ctx, c, id, len(reply) > 1)
                if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
+                       s.ServeErrorPage(ctx, c, err)
                        return
                }
-       }).Methods(http.MethodGet)
+       }
 
-       r.HandleFunc("/likedby/{id}", func(w http.ResponseWriter, req *http.Request) {
-               ctx := getContextWithSession(context.Background(), req)
+       likedByPage := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := newCtxWithSesion(req)
                id, _ := mux.Vars(req)["id"]
 
-               err := s.ServeLikedByPage(ctx, w, nil, id)
+               err := s.ServeLikedByPage(ctx, c, id)
                if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
+                       s.ServeErrorPage(ctx, c, err)
                        return
                }
-       }).Methods(http.MethodGet)
+       }
 
-       r.HandleFunc("/retweetedby/{id}", func(w http.ResponseWriter, req *http.Request) {
-               ctx := getContextWithSession(context.Background(), req)
+       retweetedByPage := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := newCtxWithSesion(req)
                id, _ := mux.Vars(req)["id"]
 
-               err := s.ServeRetweetedByPage(ctx, w, nil, id)
+               err := s.ServeRetweetedByPage(ctx, c, id)
                if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
+                       s.ServeErrorPage(ctx, c, err)
                        return
                }
-       }).Methods(http.MethodGet)
-
-       r.HandleFunc("/following/{id}", func(w http.ResponseWriter, req *http.Request) {
-               ctx := getContextWithSession(context.Background(), req)
+       }
 
+       followingPage := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := newCtxWithSesion(req)
                id, _ := mux.Vars(req)["id"]
                maxID := req.URL.Query().Get("max_id")
                minID := req.URL.Query().Get("min_id")
 
-               err := s.ServeFollowingPage(ctx, w, nil, id, maxID, minID)
+               err := s.ServeFollowingPage(ctx, c, id, maxID, minID)
                if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
+                       s.ServeErrorPage(ctx, c, err)
                        return
                }
-       }).Methods(http.MethodGet)
-
-       r.HandleFunc("/followers/{id}", func(w http.ResponseWriter, req *http.Request) {
-               ctx := getContextWithSession(context.Background(), req)
+       }
 
+       followersPage := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := newCtxWithSesion(req)
                id, _ := mux.Vars(req)["id"]
                maxID := req.URL.Query().Get("max_id")
                minID := req.URL.Query().Get("min_id")
 
-               err := s.ServeFollowersPage(ctx, w, nil, id, maxID, minID)
+               err := s.ServeFollowersPage(ctx, c, id, maxID, minID)
                if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
+                       s.ServeErrorPage(ctx, c, err)
                        return
                }
-       }).Methods(http.MethodGet)
-
-       r.HandleFunc("/like/{id}", func(w http.ResponseWriter, req *http.Request) {
-               ctx := getContextWithSession(context.Background(), req)
-               ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
+       }
 
-               id, _ := mux.Vars(req)["id"]
-               retweetedByID := req.FormValue("retweeted_by_id")
+       notificationsPage := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := newCtxWithSesion(req)
+               maxID := req.URL.Query().Get("max_id")
+               minID := req.URL.Query().Get("min_id")
 
-               _, err := s.Like(ctx, w, nil, id)
+               err := s.ServeNotificationPage(ctx, c, maxID, minID)
                if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
+                       s.ServeErrorPage(ctx, c, err)
                        return
                }
+       }
 
-               rID := id
-               if len(retweetedByID) > 0 {
-                       rID = retweetedByID
-               }
-               w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+rID)
-               w.WriteHeader(http.StatusFound)
-       }).Methods(http.MethodPost)
-
-       r.HandleFunc("/unlike/{id}", func(w http.ResponseWriter, req *http.Request) {
-               ctx := getContextWithSession(context.Background(), req)
-               ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
-
+       userPage := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := newCtxWithSesion(req)
                id, _ := mux.Vars(req)["id"]
-               retweetedByID := req.FormValue("retweeted_by_id")
+               maxID := req.URL.Query().Get("max_id")
+               minID := req.URL.Query().Get("min_id")
 
-               _, err := s.UnLike(ctx, w, nil, id)
+               err := s.ServeUserPage(ctx, c, id, maxID, minID)
                if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
+                       s.ServeErrorPage(ctx, c, err)
                        return
                }
+       }
 
-               rID := id
-               if len(retweetedByID) > 0 {
-                       rID = retweetedByID
-               }
-               w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+rID)
-               w.WriteHeader(http.StatusFound)
-       }).Methods(http.MethodPost)
-
-       r.HandleFunc("/retweet/{id}", func(w http.ResponseWriter, req *http.Request) {
-               ctx := getContextWithSession(context.Background(), req)
-               ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
-
-               id, _ := mux.Vars(req)["id"]
-               retweetedByID := req.FormValue("retweeted_by_id")
+       aboutPage := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := newCtxWithSesion(req)
 
-               _, err := s.Retweet(ctx, w, nil, id)
+               err := s.ServeAboutPage(ctx, c)
                if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
+                       s.ServeErrorPage(ctx, c, err)
                        return
                }
+       }
 
-               rID := id
-               if len(retweetedByID) > 0 {
-                       rID = retweetedByID
-               }
-               w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+rID)
-               w.WriteHeader(http.StatusFound)
-       }).Methods(http.MethodPost)
-
-       r.HandleFunc("/unretweet/{id}", func(w http.ResponseWriter, req *http.Request) {
-               ctx := getContextWithSession(context.Background(), req)
-               ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
+       emojisPage := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := newCtxWithSesion(req)
 
-               id, _ := mux.Vars(req)["id"]
-               retweetedByID := req.FormValue("retweeted_by_id")
-
-               _, err := s.UnRetweet(ctx, w, nil, id)
+               err := s.ServeEmojiPage(ctx, c)
                if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
+                       s.ServeErrorPage(ctx, c, err)
                        return
                }
+       }
 
-               rID := id
-               if len(retweetedByID) > 0 {
-                       rID = retweetedByID
-               }
-               w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+rID)
-               w.WriteHeader(http.StatusFound)
-       }).Methods(http.MethodPost)
-
-       r.HandleFunc("/fluoride/like/{id}", func(w http.ResponseWriter, req *http.Request) {
-               ctx := getContextWithSession(context.Background(), req)
-               ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
+       searchPage := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := newCtxWithSesion(req)
+               q := req.URL.Query().Get("q")
+               qType := req.URL.Query().Get("type")
+               offsetStr := req.URL.Query().Get("offset")
 
-               id, _ := mux.Vars(req)["id"]
-               count, err := s.Like(ctx, w, nil, id)
-               if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
-                       return
+               var offset int
+               var err error
+               if len(offsetStr) > 1 {
+                       offset, err = strconv.Atoi(offsetStr)
+                       if err != nil {
+                               s.ServeErrorPage(ctx, c, err)
+                               return
+                       }
                }
 
-               err = serveJson(w, count)
+               err = s.ServeSearchPage(ctx, c, q, qType, offset)
                if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
+                       s.ServeErrorPage(ctx, c, err)
                        return
                }
-       }).Methods(http.MethodPost)
-
-       r.HandleFunc("/fluoride/unlike/{id}", func(w http.ResponseWriter, req *http.Request) {
-               ctx := getContextWithSession(context.Background(), req)
-               ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
+       }
 
-               id, _ := mux.Vars(req)["id"]
-               count, err := s.UnLike(ctx, w, nil, id)
-               if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
-                       return
-               }
+       settingsPage := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := newCtxWithSesion(req)
 
-               err = serveJson(w, count)
+               err := s.ServeSettingsPage(ctx, c)
                if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
+                       s.ServeErrorPage(ctx, c, err)
                        return
                }
-       }).Methods(http.MethodPost)
+       }
 
-       r.HandleFunc("/fluoride/retweet/{id}", func(w http.ResponseWriter, req *http.Request) {
-               ctx := getContextWithSession(context.Background(), req)
-               ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
+       signin := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := context.Background()
+               instance := req.FormValue("instance")
 
-               id, _ := mux.Vars(req)["id"]
-               count, err := s.Retweet(ctx, w, nil, id)
+               url, sessionID, err := s.NewSession(ctx, instance)
                if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
+                       s.ServeErrorPage(ctx, c, err)
                        return
                }
 
-               err = serveJson(w, count)
-               if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
-                       return
-               }
-       }).Methods(http.MethodPost)
+               http.SetCookie(w, &http.Cookie{
+                       Name:    "session_id",
+                       Value:   sessionID,
+                       Expires: time.Now().Add(365 * 24 * time.Hour),
+               })
 
-       r.HandleFunc("/fluoride/unretweet/{id}", func(w http.ResponseWriter, req *http.Request) {
-               ctx := getContextWithSession(context.Background(), req)
-               ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
+               w.Header().Add("Location", url)
+               w.WriteHeader(http.StatusFound)
+       }
 
-               id, _ := mux.Vars(req)["id"]
-               count, err := s.UnRetweet(ctx, w, nil, id)
-               if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
-                       return
-               }
+       oauthCallback := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := newCtxWithSesion(req)
+               token := req.URL.Query().Get("code")
 
-               err = serveJson(w, count)
+               _, err := s.Signin(ctx, c, "", token)
                if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
+                       s.ServeErrorPage(ctx, c, err)
                        return
                }
-       }).Methods(http.MethodPost)
 
-       r.HandleFunc("/post", func(w http.ResponseWriter, req *http.Request) {
+               w.Header().Add("Location", "/timeline/home")
+               w.WriteHeader(http.StatusFound)
+       }
+
+       post := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
                err := req.ParseMultipartForm(4 << 20)
                if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
+                       s.ServeErrorPage(context.Background(), c, err)
                        return
                }
 
-               ctx := getContextWithSession(context.Background(), req)
-               ctx = context.WithValue(ctx, "csrf_token",
+               ctx := newCtxWithSesionCSRF(req,
                        getMultipartFormValue(req.MultipartForm, "csrf_token"))
-
                content := getMultipartFormValue(req.MultipartForm, "content")
                replyToID := getMultipartFormValue(req.MultipartForm, "reply_to_id")
                format := getMultipartFormValue(req.MultipartForm, "format")
                visibility := getMultipartFormValue(req.MultipartForm, "visibility")
                isNSFW := "on" == getMultipartFormValue(req.MultipartForm, "is_nsfw")
-
                files := req.MultipartForm.File["attachments"]
 
-               id, err := s.PostTweet(ctx, w, nil, content, replyToID, format, visibility, isNSFW, files)
+               id, err := s.Post(ctx, c, content, replyToID, format, visibility, isNSFW, files)
                if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
+                       s.ServeErrorPage(ctx, c, err)
                        return
                }
 
@@ -345,131 +310,129 @@ func NewHandler(s Service, staticDir string) http.Handler {
                }
                w.Header().Add("Location", location)
                w.WriteHeader(http.StatusFound)
-       }).Methods(http.MethodPost)
-
-       r.HandleFunc("/notifications", func(w http.ResponseWriter, req *http.Request) {
-               ctx := getContextWithSession(context.Background(), req)
-
-               maxID := req.URL.Query().Get("max_id")
-               minID := req.URL.Query().Get("min_id")
-
-               err := s.ServeNotificationPage(ctx, w, nil, maxID, minID)
-               if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
-                       return
-               }
-       }).Methods(http.MethodGet)
-
-       r.HandleFunc("/user/{id}", func(w http.ResponseWriter, req *http.Request) {
-               ctx := getContextWithSession(context.Background(), req)
+       }
 
+       like := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := newCtxWithSesionCSRF(req, req.FormValue("csrf_token"))
                id, _ := mux.Vars(req)["id"]
-               maxID := req.URL.Query().Get("max_id")
-               minID := req.URL.Query().Get("min_id")
+               retweetedByID := req.FormValue("retweeted_by_id")
 
-               err := s.ServeUserPage(ctx, w, nil, id, maxID, minID)
+               _, err := s.Like(ctx, c, id)
                if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
+                       s.ServeErrorPage(ctx, c, err)
                        return
                }
-       }).Methods(http.MethodGet)
 
-       r.HandleFunc("/follow/{id}", func(w http.ResponseWriter, req *http.Request) {
-               ctx := getContextWithSession(context.Background(), req)
-               ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
+               rID := id
+               if len(retweetedByID) > 0 {
+                       rID = retweetedByID
+               }
+               w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+rID)
+               w.WriteHeader(http.StatusFound)
+       }
 
+       unlike := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := newCtxWithSesionCSRF(req, req.FormValue("csrf_token"))
                id, _ := mux.Vars(req)["id"]
+               retweetedByID := req.FormValue("retweeted_by_id")
 
-               err := s.Follow(ctx, w, nil, id)
+               _, err := s.UnLike(ctx, c, id)
                if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
+                       s.ServeErrorPage(ctx, c, err)
                        return
                }
 
-               w.Header().Add("Location", req.Header.Get("Referer"))
+               rID := id
+               if len(retweetedByID) > 0 {
+                       rID = retweetedByID
+               }
+               w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+rID)
                w.WriteHeader(http.StatusFound)
-       }).Methods(http.MethodPost)
-
-       r.HandleFunc("/unfollow/{id}", func(w http.ResponseWriter, req *http.Request) {
-               ctx := getContextWithSession(context.Background(), req)
-               ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
+       }
 
+       retweet := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := newCtxWithSesionCSRF(req, req.FormValue("csrf_token"))
                id, _ := mux.Vars(req)["id"]
+               retweetedByID := req.FormValue("retweeted_by_id")
 
-               err := s.UnFollow(ctx, w, nil, id)
+               _, err := s.Retweet(ctx, c, id)
                if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
+                       s.ServeErrorPage(ctx, c, err)
                        return
                }
 
-               w.Header().Add("Location", req.Header.Get("Referer"))
+               rID := id
+               if len(retweetedByID) > 0 {
+                       rID = retweetedByID
+               }
+               w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+rID)
                w.WriteHeader(http.StatusFound)
-       }).Methods(http.MethodPost)
+       }
 
-       r.HandleFunc("/about", func(w http.ResponseWriter, req *http.Request) {
-               ctx := getContextWithSession(context.Background(), req)
+       unretweet := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := newCtxWithSesionCSRF(req, req.FormValue("csrf_token"))
+               id, _ := mux.Vars(req)["id"]
+               retweetedByID := req.FormValue("retweeted_by_id")
 
-               err := s.ServeAboutPage(ctx, w, nil)
+               _, err := s.UnRetweet(ctx, c, id)
                if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
+                       s.ServeErrorPage(ctx, c, err)
                        return
                }
-       }).Methods(http.MethodGet)
 
-       r.HandleFunc("/emojis", func(w http.ResponseWriter, req *http.Request) {
-               ctx := getContextWithSession(context.Background(), req)
-
-               err := s.ServeEmojiPage(ctx, w, nil)
-               if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
-                       return
+               rID := id
+               if len(retweetedByID) > 0 {
+                       rID = retweetedByID
                }
-       }).Methods(http.MethodGet)
-
-       r.HandleFunc("/search", func(w http.ResponseWriter, req *http.Request) {
-               ctx := getContextWithSession(context.Background(), req)
 
-               q := req.URL.Query().Get("q")
-               qType := req.URL.Query().Get("type")
-               offsetStr := req.URL.Query().Get("offset")
+               w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+rID)
+               w.WriteHeader(http.StatusFound)
+       }
 
-               var offset int
-               var err error
-               if len(offsetStr) > 1 {
-                       offset, err = strconv.Atoi(offsetStr)
-                       if err != nil {
-                               s.ServeErrorPage(ctx, w, nil, err)
-                               return
-                       }
-               }
+       follow := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := newCtxWithSesionCSRF(req, req.FormValue("csrf_token"))
+               id, _ := mux.Vars(req)["id"]
 
-               err = s.ServeSearchPage(ctx, w, nil, q, qType, offset)
+               err := s.Follow(ctx, c, id)
                if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
+                       s.ServeErrorPage(ctx, c, err)
                        return
                }
-       }).Methods(http.MethodGet)
 
-       r.HandleFunc("/settings", func(w http.ResponseWriter, req *http.Request) {
-               ctx := getContextWithSession(context.Background(), req)
+               w.Header().Add("Location", req.Header.Get("Referer"))
+               w.WriteHeader(http.StatusFound)
+       }
+
+       unfollow := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := newCtxWithSesionCSRF(req, req.FormValue("csrf_token"))
+               id, _ := mux.Vars(req)["id"]
 
-               err := s.ServeSettingsPage(ctx, w, nil)
+               err := s.UnFollow(ctx, c, id)
                if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
+                       s.ServeErrorPage(ctx, c, err)
                        return
                }
-       }).Methods(http.MethodGet)
 
-       r.HandleFunc("/settings", func(w http.ResponseWriter, req *http.Request) {
-               ctx := getContextWithSession(context.Background(), req)
-               ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
+               w.Header().Add("Location", req.Header.Get("Referer"))
+               w.WriteHeader(http.StatusFound)
+       }
 
+       settings := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := newCtxWithSesionCSRF(req, req.FormValue("csrf_token"))
                visibility := req.FormValue("visibility")
                copyScope := req.FormValue("copy_scope") == "true"
                threadInNewTab := req.FormValue("thread_in_new_tab") == "true"
                maskNSFW := req.FormValue("mask_nsfw") == "true"
                fluorideMode := req.FormValue("fluoride_mode") == "true"
                darkMode := req.FormValue("dark_mode") == "true"
+
                settings := &model.Settings{
                        DefaultVisibility: visibility,
                        CopyScope:         copyScope,
@@ -479,17 +442,17 @@ func NewHandler(s Service, staticDir string) http.Handler {
                        DarkMode:          darkMode,
                }
 
-               err := s.SaveSettings(ctx, w, nil, settings)
+               err := s.SaveSettings(ctx, c, settings)
                if err != nil {
-                       s.ServeErrorPage(ctx, w, nil, err)
+                       s.ServeErrorPage(ctx, c, err)
                        return
                }
 
                w.Header().Add("Location", req.Header.Get("Referer"))
                w.WriteHeader(http.StatusFound)
-       }).Methods(http.MethodPost)
+       }
 
-       r.HandleFunc("/signout", func(w http.ResponseWriter, req *http.Request) {
+       signout := func(w http.ResponseWriter, req *http.Request) {
                // TODO remove session from database
                http.SetCookie(w, &http.Cookie{
                        Name:    "session_id",
@@ -498,32 +461,111 @@ func NewHandler(s Service, staticDir string) http.Handler {
                })
                w.Header().Add("Location", "/")
                w.WriteHeader(http.StatusFound)
-       }).Methods(http.MethodGet)
+       }
 
-       return r
-}
+       fLike := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := newCtxWithSesionCSRF(req, req.FormValue("csrf_token"))
+               id, _ := mux.Vars(req)["id"]
 
-func getContextWithSession(ctx context.Context, req *http.Request) context.Context {
-       sessionID, err := req.Cookie("session_id")
-       if err != nil {
-               return ctx
+               count, err := s.Like(ctx, c, id)
+               if err != nil {
+                       s.ServeErrorPage(ctx, c, err)
+                       return
+               }
+
+               err = serveJson(w, count)
+               if err != nil {
+                       s.ServeErrorPage(ctx, c, err)
+                       return
+               }
        }
-       return context.WithValue(ctx, "session_id", sessionID.Value)
-}
 
-func getMultipartFormValue(mf *multipart.Form, key string) (val string) {
-       vals, ok := mf.Value[key]
-       if !ok {
-               return ""
+       fUnlike := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := newCtxWithSesionCSRF(req, req.FormValue("csrf_token"))
+               id, _ := mux.Vars(req)["id"]
+               count, err := s.UnLike(ctx, c, id)
+               if err != nil {
+                       s.ServeErrorPage(ctx, c, err)
+                       return
+               }
+
+               err = serveJson(w, count)
+               if err != nil {
+                       s.ServeErrorPage(ctx, c, err)
+                       return
+               }
        }
-       if len(vals) < 1 {
-               return ""
+
+       fRetweet := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := newCtxWithSesionCSRF(req, req.FormValue("csrf_token"))
+               id, _ := mux.Vars(req)["id"]
+
+               count, err := s.Retweet(ctx, c, id)
+               if err != nil {
+                       s.ServeErrorPage(ctx, c, err)
+                       return
+               }
+
+               err = serveJson(w, count)
+               if err != nil {
+                       s.ServeErrorPage(ctx, c, err)
+                       return
+               }
        }
-       return vals[0]
-}
 
-func serveJson(w io.Writer, data interface{}) (err error) {
-       var d = make(map[string]interface{})
-       d["data"] = data
-       return json.NewEncoder(w).Encode(d)
+       fUnretweet := func(w http.ResponseWriter, req *http.Request) {
+               c := newClient(w)
+               ctx := newCtxWithSesionCSRF(req, req.FormValue("csrf_token"))
+               id, _ := mux.Vars(req)["id"]
+
+               count, err := s.UnRetweet(ctx, c, id)
+               if err != nil {
+                       s.ServeErrorPage(ctx, c, err)
+                       return
+               }
+
+               err = serveJson(w, count)
+               if err != nil {
+                       s.ServeErrorPage(ctx, c, err)
+                       return
+               }
+       }
+
+       r.HandleFunc("/", rootPage).Methods(http.MethodGet)
+       r.HandleFunc("/signin", signinPage).Methods(http.MethodGet)
+       r.HandleFunc("/timeline/{type}", timelinePage).Methods(http.MethodGet)
+       r.HandleFunc("/timeline", timelineOldPage).Methods(http.MethodGet)
+       r.HandleFunc("/thread/{id}", threadPage).Methods(http.MethodGet)
+       r.HandleFunc("/likedby/{id}", likedByPage).Methods(http.MethodGet)
+       r.HandleFunc("/retweetedby/{id}", retweetedByPage).Methods(http.MethodGet)
+       r.HandleFunc("/following/{id}", followingPage).Methods(http.MethodGet)
+       r.HandleFunc("/followers/{id}", followersPage).Methods(http.MethodGet)
+       r.HandleFunc("/notifications", notificationsPage).Methods(http.MethodGet)
+       r.HandleFunc("/user/{id}", userPage).Methods(http.MethodGet)
+       r.HandleFunc("/about", aboutPage).Methods(http.MethodGet)
+       r.HandleFunc("/emojis", emojisPage).Methods(http.MethodGet)
+       r.HandleFunc("/search", searchPage).Methods(http.MethodGet)
+       r.HandleFunc("/settings", settingsPage).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)
+       r.HandleFunc("/like/{id}", like).Methods(http.MethodPost)
+       r.HandleFunc("/unlike/{id}", unlike).Methods(http.MethodPost)
+       r.HandleFunc("/retweet/{id}", retweet).Methods(http.MethodPost)
+       r.HandleFunc("/unretweet/{id}", unretweet).Methods(http.MethodPost)
+       r.HandleFunc("/follow/{id}", follow).Methods(http.MethodPost)
+       r.HandleFunc("/unfollow/{id}", unfollow).Methods(http.MethodPost)
+       r.HandleFunc("/settings", settings).Methods(http.MethodPost)
+       r.HandleFunc("/signout", signout).Methods(http.MethodGet)
+       r.HandleFunc("/fluoride/like/{id}", fLike).Methods(http.MethodPost)
+       r.HandleFunc("/fluoride/unlike/{id}", fUnlike).Methods(http.MethodPost)
+       r.HandleFunc("/fluoride/retweet/{id}", fRetweet).Methods(http.MethodPost)
+       r.HandleFunc("/fluoride/unretweet/{id}", fUnretweet).Methods(http.MethodPost)
+       r.PathPrefix("/static").Handler(http.StripPrefix("/static",
+               http.FileServer(http.Dir(path.Join(".", staticDir)))))
+
+       return r
 }
diff --git a/static/custom.css b/static/custom.css
deleted file mode 100644 (file)
index a1c192a..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-html {
-       background: #000000;
-}
similarity index 100%
rename from static/main.css
rename to static/style.css
index 8102b26908eb429d518e6d139e929a4aec55b698..44a303b084b0723004fd30cafb117990e000082d 100644 (file)
@@ -5,7 +5,7 @@
 
 {{template "userlist.tmpl" (WithContext .Users $.Ctx)}}
 <div class="pagination">
-       {{if .HasNext}}
+       {{if .NextLink}}
                <a href="{{.NextLink}}">next</a>
        {{end}}
 </div>
index 43a60dce959111433aa8f66d434be32fad706c6d..50413d500206e12b6737e49fd8a300d71abff47b 100644 (file)
@@ -5,7 +5,7 @@
 
 {{template "userlist.tmpl" (WithContext .Users $.Ctx)}}
 <div class="pagination">
-       {{if .HasNext}}
+       {{if .NextLink}}
                <a href="{{.NextLink}}">next</a>
        {{end}}
 </div>
index e6e7f0d70655b4d537283fb09bab1cfdb0e002e2..2889ead1be6c4dc45ddba695b89046533fd70289 100644 (file)
@@ -8,7 +8,7 @@
        <meta name="csrf_token" content="{{.CSRFToken}}">
        {{end}}
        <title>{{if gt .NotificationCount 0}}({{.NotificationCount}}) {{end}}{{.Title}}</title>
-       <link rel="stylesheet" href="/static/main.css">
+       <link rel="stylesheet" href="/static/style.css">
        {{if .CustomCSS}}
        <link rel="stylesheet" href="{{.CustomCSS}}">
        {{end}}
index 86134ac62ea0eb53fc824bd18c14b8142381e973..7d0e67c799f4c8ac0fd35580ed7668e481df2f1c 100644 (file)
@@ -65,7 +65,7 @@
 {{end}}
 
 <div class="pagination">
-       {{if .HasNext}}
+       {{if .NextLink}}
                <a href="{{.NextLink}}">next</a>
        {{end}}
 </div>
index b4cd74422093263478d7c62ff4a4af3d3d35894c..acbfbdd90dc33b16ee3be6dcdce27a36b1ec7159 100644 (file)
@@ -31,7 +31,7 @@
 {{end}}
 
 <div class="pagination">
-       {{if .HasNext}}
+       {{if .NextLink}}
                <a href="{{.NextLink}}">next</a>
        {{end}}
 </div>
index aa951fc815fe58b637e2f33217835ff24f4cd149..0321c7f9dcdcad07a491fb40f2c7cef175afde51 100644 (file)
 {{end}}
 
 <div class="pagination">
-       {{if .HasPrev}}
+       {{if .PrevLink}}
                <a href="{{.PrevLink}}">prev</a>
        {{end}}
-       {{if .HasNext}}
+       {{if .NextLink}}
                <a href="{{.NextLink}}">next</a>
        {{end}}
 </div>
index abf22ec4f2d44cc09b07e75a8a7fd60655b12c9a..bab24b2add97e686d5936d8740a79e33fa501ee9 100644 (file)
@@ -56,7 +56,7 @@
 {{end}}
 
 <div class="pagination">
-       {{if .HasNext}}
+       {{if .NextLink}}
                <a href="{{.NextLink}}">next</a>
        {{end}}
 </div>
index ffe97a0da158d5149ffb9a109a0e92af3508c643..1e4ec954ff1148924b1d2909a03290ca36bd14a2 100644 (file)
@@ -10,7 +10,7 @@ var (
        runes_length = len(runes)
 )
 
-func NewRandId(n int) (string, error) {
+func NewRandID(n int) (string, error) {
        data := make([]rune, n)
        for i := range data {
                num, err := rand.Int(rand.Reader, big.NewInt(int64(runes_length)))
@@ -22,10 +22,10 @@ func NewRandId(n int) (string, error) {
        return string(data), nil
 }
 
-func NewSessionId() (string, error) {
-       return NewRandId(24)
+func NewSessionID() (string, error) {
+       return NewRandID(24)
 }
 
 func NewCSRFToken() (string, error) {
-       return NewRandId(24)
+       return NewRandID(24)
 }