feat: Display list of remote users who liked a post

This change implements the ability to display a list of remote users
who have liked a post on an individual entry page (collection post).

Key changes include:
- Added a `RemoteLikers []*RemoteUser` field to the `PublicPost` struct in `posts.go`.
- Implemented a new database function `getRemoteLikersForPost(postID string)` in `database.go` to fetch likers by joining `remote_likes` and `remoteusers` tables.
- Modified the `GetPost` function in `database.go` to call `getRemoteLikersForPost` and populate the `RemoteLikers` field.
- Updated the `templates/collection-post.tmpl` template to iterate over `RemoteLikers` and display each user's handle as a link to their profile.
This commit is contained in:
google-labs-jules[bot] 2025-05-22 07:52:11 +00:00
parent db66a885fb
commit 90dbc05bd3
3 changed files with 79 additions and 0 deletions

View file

@ -1132,6 +1132,19 @@ func (db *datastore) GetEditablePost(id, editToken string) (*PublicPost, error)
res.Owner = &PublicUser{Username: ownerName.String}
}
// Get remote likers for the post
likers, err := db.getRemoteLikersForPost(p.ID)
if err != nil {
// Log the error but don't block returning the post
// getRemoteLikersForPost is expected to return an empty slice on sql.ErrNoRows
log.Error("GetPost: Failed to get remote likers for post %s: %v", p.ID, err)
// If likers is nil due to an unexpected error, ensure it's an empty slice
if likers == nil {
likers = []*RemoteUser{}
}
}
res.RemoteLikers = likers
return &res, nil
}
@ -1462,6 +1475,57 @@ func (db *datastore) GetPostsTagged(cfg *config.Config, c *Collection, tag strin
return &posts, nil
}
// getRemoteLikersForPost retrieves a list of remote users who liked a specific post.
func (db *datastore) getRemoteLikersForPost(postID string) ([]*RemoteUser, error) {
rows, err := db.Query(`
SELECT ru.id, ru.actor_id, ru.url, ru.handle, ru.created, ru.inbox, ru.shared_inbox
FROM remoteusers ru
JOIN remote_likes rl ON ru.id = rl.remote_user_id
WHERE rl.post_id = ?
ORDER BY rl.created DESC
`, postID)
if err != nil {
// sql.ErrNoRows is not an error in this case, just means no likers.
// It's handled by the loop below which won't execute.
return nil, err
}
defer rows.Close()
var likers []*RemoteUser
for rows.Next() {
var u RemoteUser
var handle sql.NullString
var created sql.NullTime
var url sql.NullString
var inbox sql.NullString
var sharedInbox sql.NullString
err := rows.Scan(&u.ID, &u.ActorID, &url, &handle, &created, &inbox, &sharedInbox)
if err != nil {
return nil, err
}
u.URL = url.String
u.Handle = handle.String
if created.Valid {
u.Created = created.Time
}
u.Inbox = inbox.String
u.SharedInbox = sharedInbox.String
likers = append(likers, &u)
}
if err = rows.Err(); err != nil {
return nil, err
}
// If likers slice is still nil (e.g. no rows found), initialize it to an empty slice.
if likers == nil {
likers = []*RemoteUser{}
}
return likers, nil
}
func (db *datastore) GetCollLangTotalPosts(collID int64, lang string) (uint64, error) {
var articles uint64
err := db.QueryRow("SELECT COUNT(*) FROM posts WHERE collection_id = ? AND language = ? AND created <= "+db.now(), collID, lang).Scan(&articles)

View file

@ -135,6 +135,7 @@ type (
DisplayDate string `json:"-"`
Views int64 `json:"views"`
Likes int64 `json:"likes"`
RemoteLikers []*RemoteUser `json:"remote_likers,omitempty"` // New field
Owner *PublicUser `json:"-"`
IsOwner bool `json:"-"`
URL string `json:"url,omitempty"`

View file

@ -56,6 +56,20 @@
{{end}}
{{ if and .IsOwner .IsFound }}<span class="views" dir="ltr"><strong>{{largeNumFmt .Views}}</strong> {{pluralize "view" "views" .Views}}</span>
{{if .Likes}}<span class="views" dir="ltr"><strong>{{largeNumFmt .Likes}}</strong> {{pluralize "like" "likes" .Likes}}</span>{{end}}
{{/* New section for Remote Likers */}}
{{if .RemoteLikers}}
<div class="remote-likers views" dir="ltr"> {{/* Borrowing "views" class for similar styling */}}
<p style="margin-top: 5px; margin-bottom: 2px; font-size: 0.9em;">Liked by:</p>
<ul style="list-style-type: none; padding-left: 0; margin-top: 0;">
{{range .RemoteLikers}}
<li style="display: inline-block; margin-right: 5px;">
<a href="{{.URL}}" target="_blank" rel="noopener noreferrer" title="{{.ActorID}}">{{.EstimatedHandle}}</a>
</li>
{{end}}
</ul>
</div>
{{end}}
<a href="/{{if not .SingleUser}}{{.Collection.Alias}}/{{end}}{{.Slug.String}}/edit" dir="{{.Direction}}">Edit</a>
{{if .IsPinned}}<a class="xtra-feature unpin" href="/{{.Collection.Alias}}/{{.Slug.String}}/unpin" dir="{{.Direction}}" onclick="unpinPost(event, '{{.ID}}')">Unpin</a>{{end}}
{{ end }}