diff options
| author | r <r@freesoftwareextremist.com> | 2019-12-20 18:30:20 +0000 | 
|---|---|---|
| committer | r <r@freesoftwareextremist.com> | 2019-12-20 18:30:20 +0000 | 
| commit | a1f49af1d93bccdd56d4538b149884418bd2ca2c (patch) | |
| tree | add621fe17d9dbcf3625bcc7de1738ea8c89bc9f | |
| parent | 3d1e4cfa4c17eea9a64b8672df769c540fefdaeb (diff) | |
| download | bloat-a1f49af1d93bccdd56d4538b149884418bd2ca2c.tar.gz bloat-a1f49af1d93bccdd56d4538b149884418bd2ca2c.zip | |
Add user page and follow/unfollow calls
| -rw-r--r-- | mastodon/accounts.go | 43 | ||||
| -rw-r--r-- | renderer/model.go | 18 | ||||
| -rw-r--r-- | renderer/renderer.go | 5 | ||||
| -rw-r--r-- | service/auth.go | 24 | ||||
| -rw-r--r-- | service/logging.go | 24 | ||||
| -rw-r--r-- | service/service.go | 52 | ||||
| -rw-r--r-- | service/transport.go | 60 | ||||
| -rw-r--r-- | static/main.css | 39 | ||||
| -rw-r--r-- | templates/notification.tmpl | 12 | ||||
| -rw-r--r-- | templates/status.tmpl | 12 | ||||
| -rw-r--r-- | templates/user.tmpl | 54 | 
11 files changed, 310 insertions, 33 deletions
| diff --git a/mastodon/accounts.go b/mastodon/accounts.go index e6f5a6d..8cee6bb 100644 --- a/mastodon/accounts.go +++ b/mastodon/accounts.go @@ -9,27 +9,32 @@ import (  	"time"  ) +type AccountPleroma struct { +	Relationship Relationship `json:"relationship"` +} +  // Account hold information for mastodon account.  type Account struct { -	ID             string    `json:"id"` -	Username       string    `json:"username"` -	Acct           string    `json:"acct"` -	DisplayName    string    `json:"display_name"` -	Locked         bool      `json:"locked"` -	CreatedAt      time.Time `json:"created_at"` -	FollowersCount int64     `json:"followers_count"` -	FollowingCount int64     `json:"following_count"` -	StatusesCount  int64     `json:"statuses_count"` -	Note           string    `json:"note"` -	URL            string    `json:"url"` -	Avatar         string    `json:"avatar"` -	AvatarStatic   string    `json:"avatar_static"` -	Header         string    `json:"header"` -	HeaderStatic   string    `json:"header_static"` -	Emojis         []Emoji   `json:"emojis"` -	Moved          *Account  `json:"moved"` -	Fields         []Field   `json:"fields"` -	Bot            bool      `json:"bot"` +	ID             string         `json:"id"` +	Username       string         `json:"username"` +	Acct           string         `json:"acct"` +	DisplayName    string         `json:"display_name"` +	Locked         bool           `json:"locked"` +	CreatedAt      time.Time      `json:"created_at"` +	FollowersCount int64          `json:"followers_count"` +	FollowingCount int64          `json:"following_count"` +	StatusesCount  int64          `json:"statuses_count"` +	Note           string         `json:"note"` +	URL            string         `json:"url"` +	Avatar         string         `json:"avatar"` +	AvatarStatic   string         `json:"avatar_static"` +	Header         string         `json:"header"` +	HeaderStatic   string         `json:"header_static"` +	Emojis         []Emoji        `json:"emojis"` +	Moved          *Account       `json:"moved"` +	Fields         []Field        `json:"fields"` +	Bot            bool           `json:"bot"` +	Pleroma        AccountPleroma `json:"pleroma"`  }  // Field is a Mastodon account profile field. diff --git a/renderer/model.go b/renderer/model.go index ad356e2..7e52850 100644 --- a/renderer/model.go +++ b/renderer/model.go @@ -68,3 +68,21 @@ func NewNotificationPageTemplateData(notifications []*mastodon.Notification, has  		NavbarData:    navbarData,  	}  } + +type UserPageTemplateData struct { +	User       *mastodon.Account +	Statuses   []*mastodon.Status +	HasNext    bool +	NextLink   string +	NavbarData *NavbarTemplateData +} + +func NewUserPageTemplateData(user *mastodon.Account, statuses []*mastodon.Status, hasNext bool, nextLink string, navbarData *NavbarTemplateData) *UserPageTemplateData { +	return &UserPageTemplateData{ +		User:       user, +		Statuses:   statuses, +		HasNext:    hasNext, +		NextLink:   nextLink, +		NavbarData: navbarData, +	} +} diff --git a/renderer/renderer.go b/renderer/renderer.go index 394d74f..890006b 100644 --- a/renderer/renderer.go +++ b/renderer/renderer.go @@ -18,6 +18,7 @@ type Renderer interface {  	RenderTimelinePage(ctx context.Context, writer io.Writer, data *TimelinePageTemplateData) (err error)  	RenderThreadPage(ctx context.Context, writer io.Writer, data *ThreadPageTemplateData) (err error)  	RenderNotificationPage(ctx context.Context, writer io.Writer, data *NotificationPageTemplateData) (err error) +	RenderUserPage(ctx context.Context, writer io.Writer, data *UserPageTemplateData) (err error)  }  type renderer struct { @@ -65,6 +66,10 @@ func (r *renderer) RenderNotificationPage(ctx context.Context, writer io.Writer,  	return r.template.ExecuteTemplate(writer, "notification.tmpl", data)  } +func (r *renderer) RenderUserPage(ctx context.Context, writer io.Writer, data *UserPageTemplateData) (err error) { +	return r.template.ExecuteTemplate(writer, "user.tmpl", data) +} +  func WithEmojis(content string, emojis []mastodon.Emoji) string {  	var emojiNameContentPair []string  	for _, e := range emojis { diff --git a/service/auth.go b/service/auth.go index 38c0a43..2b6fdd6 100644 --- a/service/auth.go +++ b/service/auth.go @@ -119,6 +119,14 @@ func (s *authService) ServeNotificationPage(ctx context.Context, client io.Write  	return s.Service.ServeNotificationPage(ctx, client, c, maxID, minID)  } +func (s *authService) ServeUserPage(ctx context.Context, client io.Writer, c *mastodon.Client, id string, maxID string, minID string) (err error) { +	c, err = s.getClient(ctx) +	if err != nil { +		return +	} +	return s.Service.ServeUserPage(ctx, client, c, id, maxID, minID) +} +  func (s *authService) Like(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {  	c, err = s.getClient(ctx)  	if err != nil { @@ -158,3 +166,19 @@ func (s *authService) PostTweet(ctx context.Context, client io.Writer, c *mastod  	}  	return s.Service.PostTweet(ctx, client, c, content, replyToID, files)  } + +func (s *authService) Follow(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) { +	c, err = s.getClient(ctx) +	if err != nil { +		return +	} +	return s.Service.Follow(ctx, client, c, id) +} + +func (s *authService) UnFollow(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) { +	c, err = s.getClient(ctx) +	if err != nil { +		return +	} +	return s.Service.UnFollow(ctx, client, c, id) +} diff --git a/service/logging.go b/service/logging.go index aa1da68..9b398af 100644 --- a/service/logging.go +++ b/service/logging.go @@ -85,6 +85,14 @@ func (s *loggingService) ServeNotificationPage(ctx context.Context, client io.Wr  	return s.Service.ServeNotificationPage(ctx, client, c, maxID, minID)  } +func (s *loggingService) ServeUserPage(ctx context.Context, client io.Writer, c *mastodon.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) +	}(time.Now()) +	return s.Service.ServeUserPage(ctx, client, c, id, maxID, minID) +} +  func (s *loggingService) Like(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {  	defer func(begin time.Time) {  		s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n", @@ -124,3 +132,19 @@ func (s *loggingService) PostTweet(ctx context.Context, client io.Writer, c *mas  	}(time.Now())  	return s.Service.PostTweet(ctx, client, c, content, replyToID, files)  } + +func (s *loggingService) Follow(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) { +	defer func(begin time.Time) { +		s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n", +			"Follow", id, time.Since(begin), err) +	}(time.Now()) +	return s.Service.Follow(ctx, client, c, id) +} + +func (s *loggingService) UnFollow(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) { +	defer func(begin time.Time) { +		s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n", +			"UnFollow", id, time.Since(begin), err) +	}(time.Now()) +	return s.Service.UnFollow(ctx, client, c, id) +} diff --git a/service/service.go b/service/service.go index 556afa6..63f74d3 100644 --- a/service/service.go +++ b/service/service.go @@ -32,11 +32,14 @@ type Service interface {  	ServeTimelinePage(ctx context.Context, client io.Writer, c *mastodon.Client, maxID string, sinceID string, minID string) (err error)  	ServeThreadPage(ctx context.Context, client io.Writer, c *mastodon.Client, id string, reply bool) (err error)  	ServeNotificationPage(ctx context.Context, client io.Writer, c *mastodon.Client, maxID string, minID string) (err error) +	ServeUserPage(ctx context.Context, client io.Writer, c *mastodon.Client, id string, maxID string, minID string) (err error)  	Like(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error)  	UnLike(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error)  	Retweet(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error)  	UnRetweet(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error)  	PostTweet(ctx context.Context, client io.Writer, c *mastodon.Client, content string, replyToID string, files []*multipart.FileHeader) (id string, err error) +	Follow(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) +	UnFollow(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error)  }  type service struct { @@ -369,6 +372,45 @@ func (svc *service) ServeNotificationPage(ctx context.Context, client io.Writer,  	return  } +func (svc *service) ServeUserPage(ctx context.Context, client io.Writer, c *mastodon.Client, id string, maxID string, minID string) (err error) { +	user, err := c.GetAccount(ctx, id) +	if err != nil { +		return +	} + +	var hasNext bool +	var nextLink string + +	var pg = mastodon.Pagination{ +		MaxID: maxID, +		MinID: minID, +		Limit: 20, +	} + +	statuses, err := c.GetAccountStatuses(ctx, id, &pg) +	if err != nil { +		return +	} + +	if len(pg.MaxID) > 0 { +		hasNext = true +		nextLink = "/user/" + id + "?max_id=" + pg.MaxID +	} + +	navbarData, err := svc.getNavbarTemplateData(ctx, client, c) +	if err != nil { +		return +	} + +	data := renderer.NewUserPageTemplateData(user, statuses, hasNext, nextLink, navbarData) +	err = svc.renderer.RenderUserPage(ctx, client, data) +	if err != nil { +		return +	} + +	return +} +  func (svc *service) getNavbarTemplateData(ctx context.Context, client io.Writer, c *mastodon.Client) (data *renderer.NavbarTemplateData, err error) {  	notifications, err := c.GetNotifications(ctx, nil)  	if err != nil { @@ -431,6 +473,16 @@ func (svc *service) PostTweet(ctx context.Context, client io.Writer, c *mastodon  	return s.ID, nil  } +func (svc *service) Follow(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) { +	_, err = c.AccountFollow(ctx, id) +	return +} + +func (svc *service) UnFollow(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) { +	_, err = c.AccountUnfollow(ctx, id) +	return +} +  func addToReplyMap(m map[string][]mastodon.ReplyInfo, key interface{}, val string, number int) {  	if key == nil {  		return diff --git a/service/transport.go b/service/transport.go index 1326c58..6759fcc 100644 --- a/service/transport.go +++ b/service/transport.go @@ -15,14 +15,6 @@ var (  	cookieAge = "31536000"  ) -func getContextWithSession(ctx context.Context, req *http.Request) context.Context { -	sessionID, err := req.Cookie("session_id") -	if err != nil { -		return ctx -	} -	return context.WithValue(ctx, "session_id", sessionID.Value) -} -  func NewHandler(s Service, staticDir string) http.Handler {  	r := mux.NewRouter() @@ -192,6 +184,50 @@ func NewHandler(s Service, staticDir string) http.Handler {  		}  	}).Methods(http.MethodGet) +	r.HandleFunc("/user/{id}", func(w http.ResponseWriter, req *http.Request) { +		ctx := getContextWithSession(context.Background(), req) + +		id, _ := mux.Vars(req)["id"] +		maxID := req.URL.Query().Get("max_id") +		minID := req.URL.Query().Get("min_id") + +		err := s.ServeUserPage(ctx, w, nil, id, maxID, minID) +		if err != nil { +			s.ServeErrorPage(ctx, w, err) +			return +		} +	}).Methods(http.MethodGet) + +	r.HandleFunc("/follow/{id}", func(w http.ResponseWriter, req *http.Request) { +		ctx := getContextWithSession(context.Background(), req) + +		id, _ := mux.Vars(req)["id"] + +		err := s.Follow(ctx, w, nil, id) +		if err != nil { +			s.ServeErrorPage(ctx, w, err) +			return +		} + +		w.Header().Add("Location", req.Header.Get("Referer")) +		w.WriteHeader(http.StatusFound) +	}).Methods(http.MethodPost) + +	r.HandleFunc("/unfollow/{id}", func(w http.ResponseWriter, req *http.Request) { +		ctx := getContextWithSession(context.Background(), req) + +		id, _ := mux.Vars(req)["id"] + +		err := s.UnFollow(ctx, w, nil, id) +		if err != nil { +			s.ServeErrorPage(ctx, w, 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) {  		// TODO remove session from database  		w.Header().Add("Set-Cookie", fmt.Sprintf("session_id=;max-age=0")) @@ -202,6 +238,14 @@ func NewHandler(s Service, staticDir string) http.Handler {  	return r  } +func getContextWithSession(ctx context.Context, req *http.Request) context.Context { +	sessionID, err := req.Cookie("session_id") +	if err != nil { +		return ctx +	} +	return context.WithValue(ctx, "session_id", sessionID.Value) +} +  func getMultipartFormValue(mf *multipart.Form, key string) (val string) {  	vals, ok := mf.Value[key]  	if !ok { diff --git a/static/main.css b/static/main.css index b3dce7a..8341172 100644 --- a/static/main.css +++ b/static/main.css @@ -207,3 +207,42 @@  .post-attachment-div {  	margin: 2px 0;  } + +.user-profile-img-container { +	display: inline-block +} + +.user-profile-details-container { +	display: inline-block; +	vertical-align: top; +	margin: 0 4px; +} + +.user-profile-details-container>div { +	margin-bottom: 4px; +} + +.user-profile-img { +	max-height: 100px; +	max-width: 100px; +} + +.user-profile-decription { +	margin: 4px 0; +} + +.d-inline { +	display: inline; +} + +.btn-link { +    border: none; +    outline: none; +    background: none; +    cursor: pointer; +    color: #0000EE; +    padding: 0; +    text-decoration: underline; +    font-family: inherit; +    font-size: inherit; +} diff --git a/templates/notification.tmpl b/templates/notification.tmpl index 099f17e..da6164b 100644 --- a/templates/notification.tmpl +++ b/templates/notification.tmpl @@ -6,7 +6,9 @@  <div class="notification-container {{if .Pleroma}}{{if not .Pleroma.IsSeen}}unread{{end}}{{end}}">  	{{if eq .Type "follow"}}  	<div class="notification-follow-container"> -		<img class="status-profile-img" src="{{.Account.AvatarStatic}}" alt="profile-avatar" /> +		<a href="/user/{{.Account.ID}}" > +			<img class="status-profile-img" src="{{.Account.AvatarStatic}}" alt="profile-avatar" /> +		</a>  		<div>  			<div>  				<span class="status-dname"> {{WithEmojis .Account.DisplayName .Account.Emojis}} </span>   @@ -24,7 +26,9 @@  	{{else if eq .Type "reblog"}}  	<div class="notification-retweet-container"> -		<img class="status-profile-img" src="{{.Account.AvatarStatic}}" alt="profile-avatar" /> +		<a href="/user/{{.Account.ID}}" > +			<img class="status-profile-img" src="{{.Account.AvatarStatic}}" alt="profile-avatar" /> +		</a>  		<div>  			<div>  				<span class="status-dname"> {{WithEmojis .Account.DisplayName .Account.Emojis}} </span>   @@ -37,7 +41,9 @@  	{{else if eq .Type "favourite"}}  	<div class="notification-like-container"> -		<img class="status-profile-img" src="{{.Account.AvatarStatic}}" alt="profile-avatar" /> +		<a href="/user/{{.Account.ID}}" > +			<img class="status-profile-img" src="{{.Account.AvatarStatic}}" alt="profile-avatar" /> +		</a>  		<div>  			<div>  				<span class="status-dname"> {{WithEmojis .Account.DisplayName .Account.Emojis}} </span>   diff --git a/templates/status.tmpl b/templates/status.tmpl index 4dbbe3c..618398f 100644 --- a/templates/status.tmpl +++ b/templates/status.tmpl @@ -1,7 +1,9 @@  <div id="status-{{if .Reblog}}{{.Reblog.ID}}{{else}}{{.ID}}{{end}}" class="status-container-container">  	{{if .Reblog}}  	<div class="retweet-info"> -		<img class="status-profile-img" src="{{.Account.AvatarStatic}}" alt="profile-avatar" /> +		<a href="/user/{{.Account.ID}}" > +			<img class="status-profile-img" src="{{.Account.AvatarStatic}}" alt="profile-avatar" /> +		</a>  		<span class="status-dname"> {{WithEmojis .Account.DisplayName .Account.Emojis}} </span>    		<span class="icon dripicons-retweet retweeted"></span>   		retweeted @@ -12,14 +14,18 @@  	<div class="status-container">  		<div>  			{{if not .HideAccountInfo}} -			<img class="status-profile-img" src="{{.Account.AvatarStatic}}" alt="profile-avatar" /> +			<a href="/user/{{.Account.ID}}" > +				<img class="status-profile-img" src="{{.Account.AvatarStatic}}" alt="profile-avatar" /> +			</a>  			{{end}}  		</div>  		<div class="status">   			{{if not .HideAccountInfo}}  			<div class="status-name">  				<span class="status-dname"> {{WithEmojis .Account.DisplayName .Account.Emojis}} </span>  -				<span class="status-uname"> {{.Account.Acct}} </span> +				<a href="/user/{{.Account.ID}}" > +					<span class="status-uname"> {{.Account.Acct}} </span> +				</a>  			</div>  			{{end}}  			<div class="status-reply-container"> diff --git a/templates/user.tmpl b/templates/user.tmpl new file mode 100644 index 0000000..3347f92 --- /dev/null +++ b/templates/user.tmpl @@ -0,0 +1,54 @@ +{{template "header.tmpl"}} +{{template "navigation.tmpl" .NavbarData}} +<div class="page-title"> User </div> + +<div class="user-info-container"> +<div> +	<div class="user-profile-img-container"> +		<img class="user-profile-img" src="{{.User.AvatarStatic}}" alt="profile-avatar" /> +	</div> +	<div class="user-profile-details-container"> +		<div> +			<span class="status-dname"> {{WithEmojis .User.DisplayName .User.Emojis}} </span>   +			<span class="status-uname"> {{.User.Acct}} </span> +		</div> +		<div> +			<span> {{if .User.Pleroma.Relationship.FollowedBy}} follows you - {{end}} </span>   +			{{if .User.Pleroma.Relationship.Following}}  +			<form class="d-inline" action="/unfollow/{{.User.ID}}" method="post"> +			    <input type="submit" value="unfollow" class="btn-link"> +			</form> +			{{end}}  +			{{if .User.Pleroma.Relationship.Requested}}  +			<form class="d-inline" action="/unfollow/{{.User.ID}}" method="post"> +			    <input type="submit" value="cancel request" class="btn-link"> +			</form> +			{{end}}  +			{{if not .User.Pleroma.Relationship.Following}}  +			<form class="d-inline" action="/follow/{{.User.ID}}" method="post"> +			    <input type="submit" value="{{if .User.Pleroma.Relationship.Requested}}resend request{{else}}follow{{end}}" class="btn-link"> +			</form> +			{{end}}  +		</div> +		<div> +			{{.User.StatusesCount}} statuses - {{.User.FollowingCount}} following - {{.User.FollowersCount}} followers +		</div> +	</div> +	<div class="user-profile-decription"> +	{{.User.Note}} +	</div> +</div> +</div> + +{{range .Statuses}} +{{template "status.tmpl" .}} +{{end}} + +<div class="pagination"> +	{{if .HasNext}} +		<a href="{{.NextLink}}">next</a> +	{{end}} +</div> + +{{template "footer.tmpl"}} + | 
