Add /user/followers and /user/followees pages
This commit is contained in:
parent
dd68ee1fce
commit
3117a1364c
@ -18,5 +18,5 @@ func (app *Application) Lists(w http.ResponseWriter, r *http.Request) {
|
||||
where is_followed = 1`)
|
||||
panic_if(err)
|
||||
|
||||
app.buffered_render_basic_page(w, "tpl/list.tpl", users)
|
||||
app.buffered_render_basic_page(w, "tpl/list.tpl", ListData{Title: "Offline Follows", Users: users})
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"context"
|
||||
|
||||
"gitlab.com/offline-twitter/twitter_offline_engine/pkg/persistence"
|
||||
"gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
|
||||
@ -151,3 +152,19 @@ func (app *Application) TweetDetail(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
app.buffered_render_tweet_page(w, "tpl/tweet_detail.tpl", data)
|
||||
}
|
||||
|
||||
type key string
|
||||
|
||||
const TWEET_KEY = key("tweet")
|
||||
|
||||
func add_tweet_to_context(ctx context.Context, tweet scraper.Tweet) context.Context {
|
||||
return context.WithValue(ctx, TWEET_KEY, tweet)
|
||||
}
|
||||
|
||||
func get_tweet_from_context(ctx context.Context) scraper.Tweet {
|
||||
tweet, is_ok := ctx.Value(TWEET_KEY).(scraper.Tweet)
|
||||
if !is_ok {
|
||||
panic("Tweet not found in context")
|
||||
}
|
||||
return tweet
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package webserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
@ -49,6 +50,15 @@ func (app *Application) UserFeed(w http.ResponseWriter, r *http.Request) {
|
||||
panic_if(app.Profile.DownloadUserContentFor(&user))
|
||||
}
|
||||
|
||||
if len(parts) > 1 && parts[1] == "followers" {
|
||||
app.UserFollowers(w, r, user)
|
||||
return
|
||||
}
|
||||
if len(parts) > 1 && parts[1] == "followees" {
|
||||
app.UserFollowees(w, r, user)
|
||||
return
|
||||
}
|
||||
|
||||
if r.URL.Query().Has("scrape") {
|
||||
if app.IsScrapingDisabled {
|
||||
app.InfoLog.Printf("Would have scraped: %s", r.URL.Path)
|
||||
@ -118,3 +128,21 @@ func (app *Application) UserFeed(w http.ResponseWriter, r *http.Request) {
|
||||
app.buffered_render_tweet_page(w, "tpl/user_feed.tpl", data)
|
||||
}
|
||||
}
|
||||
|
||||
type ListData struct {
|
||||
Title string
|
||||
Users []scraper.User
|
||||
}
|
||||
|
||||
func (app *Application) UserFollowees(w http.ResponseWriter, r *http.Request, user scraper.User) {
|
||||
app.buffered_render_basic_page(w, "tpl/list.tpl", ListData{
|
||||
Title: fmt.Sprintf("Followed by @%s", user.Handle),
|
||||
Users: app.Profile.GetFollowees(user.ID),
|
||||
})
|
||||
}
|
||||
func (app *Application) UserFollowers(w http.ResponseWriter, r *http.Request, user scraper.User) {
|
||||
app.buffered_render_basic_page(w, "tpl/list.tpl", ListData{
|
||||
Title: fmt.Sprintf("Followers of @%s", user.Handle),
|
||||
Users: app.Profile.GetFollowers(user.ID),
|
||||
})
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package webserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
@ -249,19 +248,3 @@ func cursor_to_query_params(c persistence.Cursor) string {
|
||||
result.Set("sort-order", c.SortOrder.String())
|
||||
return result.Encode()
|
||||
}
|
||||
|
||||
type key string
|
||||
|
||||
const TWEET_KEY = key("tweet")
|
||||
|
||||
func add_tweet_to_context(ctx context.Context, tweet scraper.Tweet) context.Context {
|
||||
return context.WithValue(ctx, TWEET_KEY, tweet)
|
||||
}
|
||||
|
||||
func get_tweet_from_context(ctx context.Context) scraper.Tweet {
|
||||
tweet, is_ok := ctx.Value(TWEET_KEY).(scraper.Tweet)
|
||||
if !is_ok {
|
||||
panic("Tweet not found in context")
|
||||
}
|
||||
return tweet
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ func TestUserFeed(t *testing.T) {
|
||||
root, err := html.Parse(resp.Body)
|
||||
require.NoError(err)
|
||||
title_node := cascadia.Query(root, selector("title"))
|
||||
assert.Equal(title_node.FirstChild.Data, "Offline Twitter | @Cernovich")
|
||||
assert.Equal(title_node.FirstChild.Data, "@Cernovich | Offline Twitter")
|
||||
|
||||
tweet_nodes := cascadia.QueryAll(root, selector(".timeline > .tweet"))
|
||||
assert.Len(tweet_nodes, 7)
|
||||
@ -119,7 +119,7 @@ func TestUserFeedWithCursor(t *testing.T) {
|
||||
root, err := html.Parse(resp.Body)
|
||||
require.NoError(err)
|
||||
title_node := cascadia.Query(root, selector("title"))
|
||||
assert.Equal(title_node.FirstChild.Data, "Offline Twitter | @Cernovich")
|
||||
assert.Equal(title_node.FirstChild.Data, "@Cernovich | Offline Twitter")
|
||||
|
||||
tweet_nodes := cascadia.QueryAll(root, selector(".timeline > .tweet"))
|
||||
assert.Len(tweet_nodes, 2)
|
||||
@ -181,6 +181,34 @@ func TestUserFeedLikesTab(t *testing.T) {
|
||||
assert.Len(tweets, 4)
|
||||
}
|
||||
|
||||
// Followers and followees
|
||||
// -----------------------
|
||||
|
||||
func TestUserFollowers(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
resp := do_request(httptest.NewRequest("GET", "/Offline_Twatter/followers", nil))
|
||||
require.Equal(resp.StatusCode, 200)
|
||||
|
||||
root, err := html.Parse(resp.Body)
|
||||
require.NoError(err)
|
||||
assert.Len(cascadia.QueryAll(root, selector(".users-list-container > .user")), 2)
|
||||
}
|
||||
|
||||
|
||||
func TestUserFollowees(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
resp := do_request(httptest.NewRequest("GET", "/Offline_Twatter/followees", nil))
|
||||
require.Equal(resp.StatusCode, 200)
|
||||
|
||||
root, err := html.Parse(resp.Body)
|
||||
require.NoError(err)
|
||||
assert.Len(cascadia.QueryAll(root, selector(".users-list-container > .user")), 1)
|
||||
}
|
||||
|
||||
// Timeline page
|
||||
// -------------
|
||||
|
||||
@ -194,7 +222,7 @@ func TestTimeline(t *testing.T) {
|
||||
root, err := html.Parse(resp.Body)
|
||||
require.NoError(err)
|
||||
title_node := cascadia.Query(root, selector("title"))
|
||||
assert.Equal(title_node.FirstChild.Data, "Offline Twitter | Timeline")
|
||||
assert.Equal(title_node.FirstChild.Data, "Timeline | Offline Twitter")
|
||||
|
||||
tweet_nodes := cascadia.QueryAll(root, selector(".timeline > .tweet"))
|
||||
assert.Len(tweet_nodes, 18)
|
||||
@ -210,7 +238,7 @@ func TestTimelineWithCursor(t *testing.T) {
|
||||
root, err := html.Parse(resp.Body)
|
||||
require.NoError(err)
|
||||
title_node := cascadia.Query(root, selector("title"))
|
||||
assert.Equal(title_node.FirstChild.Data, "Offline Twitter | Timeline")
|
||||
assert.Equal(title_node.FirstChild.Data, "Timeline | Offline Twitter")
|
||||
|
||||
tweet_nodes := cascadia.QueryAll(root, selector(".timeline > .tweet"))
|
||||
assert.Len(tweet_nodes, 10)
|
||||
@ -245,7 +273,7 @@ func TestSearch(t *testing.T) {
|
||||
root, err := html.Parse(resp.Body)
|
||||
require.NoError(err)
|
||||
title_node := cascadia.Query(root, selector("title"))
|
||||
assert.Equal(title_node.FirstChild.Data, "Offline Twitter | Search")
|
||||
assert.Equal(title_node.FirstChild.Data, "Search | Offline Twitter")
|
||||
|
||||
tweet_nodes := cascadia.QueryAll(root, selector(".timeline > .tweet"))
|
||||
assert.Len(tweet_nodes, 1)
|
||||
|
@ -3,7 +3,7 @@
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<title>Offline Twitter | {{template "title" .}}</title>
|
||||
<title>{{template "title" .}} | Offline Twitter</title>
|
||||
<link rel='stylesheet' href='/static/styles.css'>
|
||||
<link rel='shortcut icon' href='/static/img/favicon.ico' type='image/x-icon'>
|
||||
<link rel='stylesheet' href='/static/vendor/fonts.css'>
|
||||
|
@ -1,5 +1,5 @@
|
||||
{{define "title"}}Followed Users{{end}}
|
||||
{{define "title"}}{{.Title}}{{end}}
|
||||
|
||||
{{define "main"}}
|
||||
{{template "list" .}}
|
||||
{{template "list" .Users}}
|
||||
{{end}}
|
||||
|
@ -37,14 +37,14 @@
|
||||
</div>
|
||||
|
||||
<div class="followers-followees-container row">
|
||||
<div class="followers-container">
|
||||
<a href="/{{$user.Handle}}/followers" class="followers-container unstyled-link">
|
||||
<span class="followers-count">{{$user.FollowersCount}}</span>
|
||||
<span class="followers-label">followers</span>
|
||||
</div>
|
||||
<div class="followees-container">
|
||||
</a>
|
||||
<a href="/{{$user.Handle}}/followees" class="followers-container unstyled-link">
|
||||
<span class="following-label">is following</span>
|
||||
<span class="following-count">{{$user.FollowingCount}}</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div class="spacer"></div>
|
||||
|
||||
|
@ -39,18 +39,26 @@ func (p Profile) IsXFollowingY(follower_id UserID, followee_id UserID) bool {
|
||||
return rows.Next() // true if there is a row, false otherwise
|
||||
}
|
||||
|
||||
func (p Profile) GetFollowers(followee_id UserID) []UserID {
|
||||
var ret []UserID
|
||||
err := p.DB.Select(&ret, `select follower_id from follows where followee_id = ?`, followee_id)
|
||||
func (p Profile) GetFollowers(followee_id UserID) []User {
|
||||
var ret []User
|
||||
err := p.DB.Select(&ret, `
|
||||
select `+USERS_ALL_SQL_FIELDS+`
|
||||
from users
|
||||
where id in (select follower_id from follows where followee_id = ?)
|
||||
`, followee_id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (p Profile) GetFollowees(follower_id UserID) []UserID {
|
||||
var ret []UserID
|
||||
err := p.DB.Select(&ret, `select followee_id from follows where follower_id = ?`, follower_id)
|
||||
func (p Profile) GetFollowees(follower_id UserID) []User {
|
||||
var ret []User
|
||||
err := p.DB.Select(&ret, `
|
||||
select `+USERS_ALL_SQL_FIELDS+`
|
||||
from users
|
||||
where id in (select followee_id from follows where follower_id = ?)
|
||||
`, follower_id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -34,11 +34,11 @@ func TestSaveAndLoadFollows(t *testing.T) {
|
||||
|
||||
// Save and reload it
|
||||
profile.SaveAsFolloweesList(follower.ID, trove)
|
||||
new_followee_ids := profile.GetFollowees(follower.ID)
|
||||
new_followees := profile.GetFollowees(follower.ID)
|
||||
|
||||
assert.Len(new_followee_ids, len(followee_ids))
|
||||
for _, id := range new_followee_ids {
|
||||
_, is_ok := trove.Users[id]
|
||||
assert.Len(new_followees, len(followee_ids))
|
||||
for _, followee := range new_followees {
|
||||
_, is_ok := trove.Users[followee.ID]
|
||||
assert.True(is_ok)
|
||||
}
|
||||
}
|
||||
|
@ -421,7 +421,8 @@ create index if not exists index_follows_followee_id on follows (followee_id);
|
||||
create index if not exists index_follows_follower_id on follows (follower_id);
|
||||
insert into follows values
|
||||
(1, 1178839081222115328, 1488963321701171204),
|
||||
(2, 1032468021485293568, 1488963321701171204);
|
||||
(2, 1032468021485293568, 1488963321701171204),
|
||||
(3, 1488963321701171204, 1240784920831762433);
|
||||
|
||||
|
||||
create table fake_user_sequence(latest_fake_id integer not null);
|
||||
|
Loading…
x
Reference in New Issue
Block a user