Files
wanderer/db/routes/remote_trail_comment.go
Flomp c04a719ca6 Feat add actors to meilisearch (#1048)
* initial commit

* update api docs

* adds search token versioning

* make id filterable

* isLocal -> is_local

* add iri to actor index

* update gitignore

* Merge remote-tracking branch 'origin/main' into feat-add-actors-to-meilisearch

* fix sharing for newly discovered actors

* fix ActorSearchResult type

---------

Co-authored-by: Christian Beutel <>
2026-06-09 21:02:05 +02:00

189 lines
4.4 KiB
Go

package routes
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"path"
"pocketbase/federation"
"pocketbase/util"
"strconv"
"strings"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
)
func RemoteTrailCommentsList(e *core.RequestEvent) error {
trailID := e.Request.PathValue("id")
expandQuery := e.Request.URL.Query().Get("expand")
sort := e.Request.URL.Query().Get("sort")
if sort == "" {
sort = "-created"
}
page, _ := strconv.Atoi(e.Request.URL.Query().Get("page"))
if page < 1 {
page = 1
}
perPage, _ := strconv.Atoi(e.Request.URL.Query().Get("perPage"))
if perPage < 1 {
perPage = 30
}
trail, err := e.App.FindRecordById("trails", trailID)
if err != nil {
return err
}
trailAuthor, err := e.App.FindRecordById("activitypub_actors", trail.GetString("author"))
if err != nil {
return err
}
// Sync remote data first (Fetch + Save)
if trail.GetString("iri") != "" && !trailAuthor.GetBool("is_local") {
_ = syncRemoteComments(e, trail)
}
// 1. Calculate Offset
offset := (page - 1) * perPage
// 2. Fetch the records using FindRecordsByFilter
records, err := e.App.FindRecordsByFilter(
"comments",
"trail = {:trailId}",
sort,
perPage,
offset,
dbx.Params{"trailId": trail.Id},
)
if err != nil {
return err
}
reqInfo, err := e.RequestInfo()
if err != nil {
return err
}
filteredRecords := []*core.Record{}
for _, record := range records {
canAccess, _ := e.App.CanAccessRecord(record, reqInfo, record.Collection().ListRule)
if canAccess {
filteredRecords = append(filteredRecords, record)
}
}
// 3. Get total count for pagination metadata
var totalItems int
err = e.App.DB().
Select("count(*)").
From("comments").
Where(dbx.HashExp{"trail": trail.Id}).
Row(&totalItems)
if err != nil {
return err
}
// 4. Handle Expand
if expandQuery != "" {
errs := e.App.ExpandRecords(filteredRecords, strings.Split(expandQuery, ","), nil)
if len(errs) > 0 {
fmt.Printf("Expand errors: %v\n", errs)
}
}
// 5. Manually construct the response object
return e.JSON(http.StatusOK, map[string]any{
"page": page,
"perPage": perPage,
"totalItems": totalItems,
"totalPages": (totalItems + perPage - 1) / perPage,
"items": filteredRecords,
})
}
func syncRemoteComments(e *core.RequestEvent, trail *core.Record) error {
client := util.SafeHTTPClient()
var userActor *core.Record
if e.Auth != nil {
userActor, _ = e.App.FindFirstRecordByData("activitypub_actors", "user", e.Auth.Id)
}
ctx, err := util.GetSafeActorContext(e.Request, userActor)
if err != nil {
return err
}
trailIRI := trail.GetString("iri")
u, _ := url.Parse(trailIRI)
remoteTrailID := path.Base(u.Path)
remoteURL := fmt.Sprintf("%s://%s/api/v1/comment?filter=trail='%s'&expand=author", u.Scheme, u.Host, remoteTrailID)
req, _ := http.NewRequestWithContext(ctx, "GET", remoteURL, nil)
res, err := client.Do(req)
if err != nil || res.StatusCode != 200 {
if errors.Is(err, util.ErrRateLimited) {
return e.TooManyRequestsError("Too many requests", err)
}
return fmt.Errorf("remote fetch failed: %w", err)
}
defer res.Body.Close()
var remoteData struct {
Items []map[string]any `json:"items"`
}
if err := json.NewDecoder(res.Body).Decode(&remoteData); err != nil {
return err
}
collection, _ := e.App.FindCollectionByNameOrId("comments")
return e.App.RunInTransaction(func(txApp core.App) error {
for _, raw := range remoteData.Items {
remoteIRI, _ := raw["iri"].(string)
if remoteIRI == "" {
remoteID, _ := raw["id"].(string)
remoteIRI = fmt.Sprintf("%s://%s/api/v1/comment/%s", u.Scheme, u.Host, remoteID)
}
// Find existing record by IRI or ID to avoid duplicates
commentRecord, _ := txApp.FindFirstRecordByData("comments", "iri", remoteIRI)
if commentRecord == nil {
commentRecord = core.NewRecord(collection)
commentRecord.Set("iri", remoteIRI)
commentRecord.Set("trail", trail.Id)
}
// Resolve federated author
if expand, ok := raw["expand"].(map[string]any); ok {
if author, ok := expand["author"].(map[string]any); ok {
authorIRI, _ := author["iri"].(string)
actor, err := federation.GetActorByIRI(txApp, ctx, authorIRI, false)
if err == nil {
raw["author"] = actor.Id
}
}
}
delete(raw, "id")
delete(raw, "trail")
delete(raw, "expand")
delete(raw, "iri")
commentRecord.Load(raw)
if err := txApp.Save(commentRecord); err != nil {
continue
}
}
return nil
})
}