From 901e4dce0ea2e3f7106b148cffab26c410cde567 Mon Sep 17 00:00:00 2001 From: Alessio Date: Thu, 17 Aug 2023 18:09:01 -0300 Subject: [PATCH] Add following and unfollowing --- internal/webserver/server.go | 51 +++++++++++++++++++ internal/webserver/server_test.go | 50 ++++++++++++++++++ internal/webserver/static/styles.css | 9 ++++ .../tpl/includes/following_button.tpl | 17 +++++++ internal/webserver/tpl/user_feed.tpl | 6 ++- 5 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 internal/webserver/tpl/includes/following_button.tpl diff --git a/internal/webserver/server.go b/internal/webserver/server.go index 161f395..bbeb5f4 100644 --- a/internal/webserver/server.go +++ b/internal/webserver/server.go @@ -121,6 +121,10 @@ func (app *Application) ServeHTTP(w http.ResponseWriter, r *http.Request) { app.ChangeSession(w, r) case "timeline": app.Timeline(w, r) + case "follow": + app.UserFollow(w, r) + case "unfollow": + app.UserUnfollow(w, r) default: app.UserFeed(w, r) } @@ -431,3 +435,50 @@ func parse_form(req *http.Request, result interface{}) error { } return nil } + +func (app *Application) UserFollow(w http.ResponseWriter, r *http.Request) { + app.traceLog.Printf("'UserFollow' handler (path: %q)", r.URL.Path) + + if r.Method != "POST" { + http.Error(w, "Method not allowed", 405) + return + } + + parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/") + if len(parts) != 2 { + app.error_400_with_message(w, "Bad URL: "+r.URL.Path) + return + } + user, err := app.Profile.GetUserByHandle(scraper.UserHandle(parts[1])) + if err != nil { + app.error_404(w) + return + } + + app.Profile.SetUserFollowed(&user, true) + + app.buffered_render_basic_htmx(w, "following-button", user) +} + +func (app *Application) UserUnfollow(w http.ResponseWriter, r *http.Request) { + app.traceLog.Printf("'UserUnfollow' handler (path: %q)", r.URL.Path) + + if r.Method != "POST" { + http.Error(w, "Method not allowed", 405) + return + } + + parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/") + if len(parts) != 2 { + app.error_400_with_message(w, "Bad URL: "+r.URL.Path) + return + } + user, err := app.Profile.GetUserByHandle(scraper.UserHandle(parts[1])) + if err != nil { + app.error_404(w) + return + } + + app.Profile.SetUserFollowed(&user, false) + app.buffered_render_basic_htmx(w, "following-button", user) +} diff --git a/internal/webserver/server_test.go b/internal/webserver/server_test.go index d758e90..927c2b5 100644 --- a/internal/webserver/server_test.go +++ b/internal/webserver/server_test.go @@ -4,6 +4,7 @@ import ( "net/http" "net/http/httptest" "testing" + "strings" "github.com/andybalholm/cascadia" "github.com/stretchr/testify/assert" @@ -189,6 +190,55 @@ func TestTweetDetailInvalidNumber(t *testing.T) { require.Equal(resp.StatusCode, 400) } +// Follow and unfollow +// ------------------- + +func TestFollowUnfollow(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + user, err := profile.GetUserByHandle("kwamurai") + require.NoError(err) + require.False(user.IsFollowed) + + // Follow the user + resp := do_request(httptest.NewRequest("POST", "/follow/kwamurai", nil)) + require.Equal(resp.StatusCode, 200) + + root, err := html.Parse(resp.Body) + require.NoError(err) + button := cascadia.Query(root, selector("button")) + assert.Contains(button.Attr, html.Attribute{Key: "hx-post", Val: "/unfollow/kwamurai"}) + assert.Equal(strings.TrimSpace(button.FirstChild.Data), "Unfollow") + + user, err = profile.GetUserByHandle("kwamurai") + require.NoError(err) + require.True(user.IsFollowed) + + // Unfollow the user + resp = do_request(httptest.NewRequest("POST", "/unfollow/kwamurai", nil)) + require.Equal(resp.StatusCode, 200) + + root, err = html.Parse(resp.Body) + require.NoError(err) + button = cascadia.Query(root, selector("button")) + assert.Contains(button.Attr, html.Attribute{Key: "hx-post", Val: "/follow/kwamurai"}) + assert.Equal(strings.TrimSpace(button.FirstChild.Data), "Follow") + + user, err = profile.GetUserByHandle("kwamurai") + require.NoError(err) + require.False(user.IsFollowed) +} + +func TestFollowUnfollowPostOnly(t *testing.T) { + require := require.New(t) + resp := do_request(httptest.NewRequest("GET", "/follow/kwamurai", nil)) + require.Equal(resp.StatusCode, 405) + resp = do_request(httptest.NewRequest("GET", "/unfollow/kwamurai", nil)) + require.Equal(resp.StatusCode, 405) +} + + // Static content // -------------- diff --git a/internal/webserver/static/styles.css b/internal/webserver/static/styles.css index 183613a..f607187 100644 --- a/internal/webserver/static/styles.css +++ b/internal/webserver/static/styles.css @@ -400,3 +400,12 @@ input[type="submit"] { .retweeted-by-label { margin: 0 0.2em; } + +.user-bio { + margin: 1.5em 0; +} + +.followers-followees-container { + margin-top: 1em; + gap: 4em; +} diff --git a/internal/webserver/tpl/includes/following_button.tpl b/internal/webserver/tpl/includes/following_button.tpl new file mode 100644 index 0000000..950cf7a --- /dev/null +++ b/internal/webserver/tpl/includes/following_button.tpl @@ -0,0 +1,17 @@ +{{define "following-button"}} + {{if .IsFollowed}} + + {{else}} + + {{end}} +{{end}} diff --git a/internal/webserver/tpl/user_feed.tpl b/internal/webserver/tpl/user_feed.tpl index c925048..18e3791 100644 --- a/internal/webserver/tpl/user_feed.tpl +++ b/internal/webserver/tpl/user_feed.tpl @@ -8,8 +8,10 @@ {{end}}
- {{template "author-info" $user}} - +
+ {{template "author-info" $user}} + {{template "following-button" $user}} +
{{$user.Bio}}