Browse Source

Refactor everything

master
r 2 years ago
parent
commit
2af37d4778
  1. 2
      Makefile
  2. 6
      main.go
  3. 4
      migrations/csrfToken/main.go
  4. 2
      model/app.go
  5. 7
      model/client.go
  6. 0
      model/post.go
  7. 2
      model/session.go
  8. 9
      renderer/model.go
  9. 65
      renderer/renderer.go
  10. 12
      repo/appRepo.go
  11. 42
      repo/sessionRepo.go
  12. 64
      repository/sessionRepository.go
  13. 208
      service/auth.go
  14. 190
      service/logging.go
  15. 811
      service/service.go
  16. 650
      service/transport.go
  17. 3
      static/custom.css
  18. 0
      static/style.css
  19. 2
      templates/followers.tmpl
  20. 2
      templates/following.tmpl
  21. 2
      templates/header.tmpl
  22. 2
      templates/notification.tmpl
  23. 2
      templates/search.tmpl
  24. 4
      templates/timeline.tmpl
  25. 2
      templates/user.tmpl
  26. 8
      util/rand.go

2
Makefile

@ -1,5 +1,3 @@
.POSIX:
GO=go
all: bloat

6
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://") &&

4
migrations/csrfToken/main.go

@ -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)

2
model/app.go

@ -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)
}

7
model/client.go

@ -1,8 +1,13 @@
package model
import "mastodon"
import (
"io"
"mastodon"
)
type Client struct {
*mastodon.Client
Writer io.Writer
Session Session
}

0
model/postContext.go → model/post.go

2
model/session.go

@ -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)
}

9
renderer/model.go

@ -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
}

65
renderer/renderer.go

@ -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"
}

12
repository/appRepository.go → repo/appRepo.go

@ -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

42
repo/sessionRepo.go

@ -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
}

64
repository/sessionRepository.go

@ -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
}

208
service/auth.go

@ -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)
}

190
service/logging.go

@ -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)
}

811
service/service.go

File diff suppressed because it is too large

650
service/transport.go

@ -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,