Federation (#327)

* initial commit federation

* more federation

* more federation

* more federation

* completes follow, accept, undo

* add trail create activity

* process trail create activity

* more trail create activity

* adds missing endpoints

* adds update and delete activity

* adds comment activities

* adds activities back

* adds summit logs activities

* deletes follow counts table

* fixes migrations

* fixes migrations

* fixes migrations

* fixes migrations

* adds remote profiles

* ctd

* ctd

* we are getting closer...

* adds public summit logs

* fixes federated trails in lists

* adds remote lists

* adds list activites

* fixes lists

* adds notifications

* adds iri redirect

* fixes comments and summitlogs

* fixes follows and profiles

* adds asynchronous send

* fixes list search

* adds encryption key

* bug fixes

* fixes activity signing

* removes custon activity object types

* adds html editor

* fixes html editor

* fixes display issues on mastodon

* adds federated sharing

* finishes announcements

* fixes small summit log issues

* adds trail likes

* finalizes likes

* fixes images for komoot

* add disable federation option

* adds private profiles

* updates docs

* adds federated comments

* adds trail and comments actvitiypub routes

* adds mentions to editor

* adds mentions to trails, comments, summit logs

* updates theme

* updates theme

* update docs

* update docs

* updates docs

* fixes various frontend problems

* fixes activitypub follows api

* updates docs

* updates docs

---------

Co-authored-by: Christian Beutel <>
This commit is contained in:
Flomp
2025-06-21 10:09:22 +02:00
committed by GitHub
parent 742a6d9d30
commit 9276690d60
356 changed files with 57150 additions and 35470 deletions

View File

@@ -3,6 +3,7 @@ FROM alpine:3.16
WORKDIR /
COPY migrations ./migrations
COPY templates ./templates
ARG TARGETARCH
RUN echo ${TARGETARCH}

207
db/federation/activity.go Normal file
View File

@@ -0,0 +1,207 @@
package federation
import (
"bytes"
"context"
"crypto/x509"
"database/sql"
"encoding/pem"
"fmt"
"io"
"net/http"
"net/url"
"os"
"slices"
"strings"
"time"
pub "github.com/go-ap/activitypub"
"sync"
"github.com/go-ap/jsonld"
"github.com/go-fed/httpsig"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/security"
"golang.org/x/sync/semaphore"
)
func PostActivity(app core.App, actor *core.Record, activity *pub.Activity, recipients []string) error {
encryptionKey := os.Getenv("POCKETBASE_ENCRYPTION_KEY")
if len(encryptionKey) == 0 {
return fmt.Errorf("POCKETBASE_ENCRYPTION_KEY not set")
}
origin := os.Getenv("ORIGIN")
if origin == "" {
return fmt.Errorf("ORIGIN not set")
}
algs := []httpsig.Algorithm{httpsig.RSA_SHA256}
postHeaders := []string{"(request-target)", "Date", "Digest", "Content-Type", "Host"}
expiresIn := 60
body, err := jsonld.WithContext(
jsonld.IRI(pub.ActivityBaseURI),
jsonld.IRI(pub.SecurityContextURI),
).Marshal(activity)
if err != nil {
return err
}
decryptedPrivateKey, err := security.Decrypt(actor.GetString("private_key"), encryptionKey)
if err != nil {
return err
}
privateKey, err := x509.ParsePKCS1PrivateKey(decryptedPrivateKey)
if err != nil {
return err
}
pubID := actor.GetString("iri") + "#main-key"
client := &http.Client{}
var wg sync.WaitGroup
sem := semaphore.NewWeighted(5) // Limit to 5 concurrent sends
slices.Sort(recipients)
uniqueRecipients := slices.Compact(recipients)
for _, v := range uniqueRecipients {
wg.Add(1)
go func(inbox string) {
defer wg.Done()
signer, _, err := httpsig.NewSigner(algs, httpsig.DigestSha256, postHeaders, httpsig.Signature, int64(expiresIn))
if err != nil {
return
}
if err := sem.Acquire(context.Background(), 1); err != nil {
app.Logger().Error(fmt.Sprintf("Semaphore acquire failed: %s", err))
return
}
defer sem.Release(1)
buf := bytes.NewBuffer(body)
req, err := http.NewRequest(http.MethodPost, inbox, buf)
if err != nil {
app.Logger().Error(fmt.Sprintf("Request creation failed: %s", err))
return
}
req.Header.Add("Content-Type", "application/activity+json")
req.Header.Add("Date", strings.ReplaceAll(time.Now().UTC().Format(time.RFC1123), "UTC", "GMT"))
req.Header.Add("Host", req.Host)
if err := signer.SignRequest(privateKey, pubID, req, body); err != nil {
app.Logger().Error(fmt.Sprintf("Signing request failed: %s", err))
return
}
resp, err := client.Do(req)
if err != nil {
app.Logger().Error(fmt.Sprintf("Error sending request to inbox %s: %s", inbox, err))
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted {
body, _ := io.ReadAll(resp.Body)
app.Logger().Error(fmt.Sprintf("Inbox %s responded with %d: %s", inbox, resp.StatusCode, body))
}
app.Logger().Info(fmt.Sprintf("Sent %s to %s", activity.Type, inbox))
}(v)
}
wg.Wait()
return nil
}
func ProcessActivity(e *core.RequestEvent) error {
body, err := io.ReadAll(e.Request.Body)
if err != nil {
return err
}
var activity pub.Activity
activity.UnmarshalJSON(body)
actor, err := e.App.FindFirstRecordByData("activitypub_actors", "iri", activity.Actor.GetID().String())
if err != nil {
if err == sql.ErrNoRows {
actor, err = GetActorByIRI(e.App, activity.Actor.GetID().String(), false)
if err != nil {
return err
}
} else {
return err
}
}
verified, err := verifySignature(e.Request, actor.GetString("public_key"))
if err != nil || !verified {
return e.UnauthorizedError("Invalid http signature", err)
}
switch activity.Type {
case pub.FollowType:
ProcessFollowActivity(e.App, actor, activity)
case pub.AcceptType:
ProcessAcceptActivity(e.App, actor, activity)
case pub.UndoType:
ProcessUndoActivity(e.App, actor, activity)
case pub.UpdateType:
fallthrough
case pub.CreateType:
ProcessCreateOrUpdateActivity(e.App, actor, activity)
case pub.DeleteType:
ProcessDeleteActivity(e.App, actor, activity)
case pub.AnnounceType:
ProcessAnnounceActivity(e.App, actor, activity)
case pub.LikeType:
ProcessLikeActivity(e.App, actor, activity)
}
return e.JSON(http.StatusOK, nil)
}
func verifySignature(req *http.Request, publicKeyPem string) (bool, error) {
origin := os.Getenv("ORIGIN")
if origin == "" {
return false, fmt.Errorf("ORIGIN not set")
}
block, _ := pem.Decode([]byte(publicKeyPem))
if block == nil || block.Type != "PUBLIC KEY" {
return false, fmt.Errorf("could not decode publicKeyPem to PUBLIC KEY pem block type")
}
req.URL = &url.URL{
Path: req.Header.Get("X-Forwarded-Path"),
}
url, err := url.Parse(origin)
if err != nil {
return false, err
}
req.Header.Set("Host", url.Host)
req.Host = url.Host
publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return false, err
}
v, err := httpsig.NewVerifier(req)
if err != nil {
return false, err
}
err = v.Verify(publicKey, httpsig.RSA_SHA256)
if err != nil {
return false, err
}
return true, nil
}

284
db/federation/actor.go Normal file
View File

@@ -0,0 +1,284 @@
package federation
import (
"database/sql"
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"strings"
"time"
pub "github.com/go-ap/activitypub"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
)
type WebfingerResponse struct {
Subject string `json:"subject"`
Links []struct {
Rel string `json:"rel"`
Href string `json:"href"`
} `json:"links"`
}
func SplitHandle(handle string) (string, string) {
cleaned := strings.TrimPrefix(handle, "@")
cleaned = strings.TrimSpace(cleaned)
if !strings.Contains(cleaned, "@") {
return cleaned, ""
}
parts := strings.SplitN(cleaned, "@", 2)
user := parts[0]
domain := parts[1]
return user, domain
}
func GetActorByHandle(app core.App, handle string, includeFollows bool) (*core.Record, error) {
username, domain := SplitHandle(handle)
filter := "username={:username}&&"
if domain != "" {
filter += "domain={:domain}"
} else {
filter += "isLocal=true"
}
var dbActor *core.Record
dbActor, err := app.FindFirstRecordByFilter("activitypub_actors", filter, dbx.Params{"username": username, "domain": domain})
if err != nil && err == sql.ErrNoRows {
collection, err := app.FindCollectionByNameOrId("activitypub_actors")
if err != nil {
return nil, err
}
dbActor = core.NewRecord(collection)
dbActor.Set("isLocal", false)
iri, err := iriFromHandle(domain, username)
if err != nil {
return nil, err
}
dbActor.Set("iri", iri)
} else if err != nil {
return nil, err
}
return assembleActor(dbActor, app, includeFollows)
}
func GetActorByIRI(app core.App, iri string, includeFollows bool) (*core.Record, error) {
var dbActor *core.Record
dbActor, err := app.FindFirstRecordByFilter("activitypub_actors", "iri={:iri}", dbx.Params{"iri": iri})
if err != nil && err == sql.ErrNoRows {
collection, err := app.FindCollectionByNameOrId("activitypub_actors")
if err != nil {
return nil, err
}
dbActor = core.NewRecord(collection)
dbActor.Set("isLocal", false)
dbActor.Set("iri", iri)
} else if err != nil {
return nil, err
}
return assembleActor(dbActor, app, includeFollows)
}
func iriFromHandle(domain string, username string) (string, error) {
client := &http.Client{}
webfingerURL := fmt.Sprintf("https://%s/.well-known/webfinger?resource=acct:%s@%s", domain, username, domain)
resp, err := client.Get(webfingerURL)
if err != nil || resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("webfinger request failed: %v", err)
}
defer resp.Body.Close()
var wf WebfingerResponse
if err := json.NewDecoder(resp.Body).Decode(&wf); err != nil {
return "", err
}
for _, link := range wf.Links {
if link.Rel == "self" {
return link.Href, nil
}
}
return "", fmt.Errorf("no iri in response")
}
func assembleActor(dbActor *core.Record, app core.App, includeFollows bool) (*core.Record, error) {
origin := os.Getenv("ORIGIN")
if origin == "" {
return nil, fmt.Errorf("ORIGIN environment variable not set")
}
if dbActor.GetBool("isLocal") {
user, err := app.FindRecordById("users", dbActor.GetString("user"))
if err != nil {
return nil, err
}
settings, err := app.FindFirstRecordByData("settings", "user", user.Id)
if err != nil {
return nil, err
}
if user.GetString("avatar") != "" {
dbActor.Set("icon", fmt.Sprintf("%s/api/v1/files/users/%s/%s", origin, user.Id, user.GetString("avatar")))
}
dbActor.Set("summary", settings.GetString("bio"))
followerCount, err := app.CountRecords("follows", dbx.NewExp("followee={:user}", dbx.Params{"user": dbActor.Id}))
if err != nil {
return nil, err
}
dbActor.Set("followerCount", followerCount)
followingCount, err := app.CountRecords("follows", dbx.NewExp("follower={:user}", dbx.Params{"user": dbActor.Id}))
if err != nil {
return nil, err
}
dbActor.Set("followingCount", followingCount)
dbActor.Set("last_fetched", time.Now())
privacy := settings.GetString("privacy")
result := make(map[string]interface{})
json.Unmarshal([]byte(privacy), &result)
private := result["account"] == "private"
if private {
return nil, fmt.Errorf("profile is private")
}
} else {
// check if value is still cached
twoHoursAgo := time.Now().Add(-2 * time.Hour)
if !includeFollows && dbActor.GetDateTime("last_fetched").Time().After(twoHoursAgo) {
return dbActor, nil
}
pubActor, followers, following, err := fetchRemoteActor(dbActor.GetString("iri"), includeFollows)
if err != nil {
if dbActor.Id != "" {
return dbActor, err
}
return nil, err
}
icon := ""
if pub.IsObject(pubActor.Icon) {
iconObject, err := pub.ToObject(pubActor.Icon)
if err == nil && iconObject.URL != nil {
icon = iconObject.URL.GetID().String()
}
}
parsedUrl, err := url.Parse(dbActor.GetString("iri"))
if err != nil {
return nil, err
}
domain := strings.TrimPrefix(parsedUrl.Hostname(), "www.")
dbActor.Set("domain", domain)
dbActor.Set("followers", pubActor.Followers.GetID().String())
dbActor.Set("inbox", pubActor.Inbox.GetID().String())
dbActor.Set("iri", pubActor.GetID().String())
dbActor.Set("username", pubActor.Name.String())
dbActor.Set("preferred_username", pubActor.PreferredUsername.String())
dbActor.Set("following", pubActor.Following.GetID().String())
dbActor.Set("summary", pubActor.Summary.String())
dbActor.Set("outbox", pubActor.Outbox.GetID().String())
dbActor.Set("icon", icon)
dbActor.Set("published", pubActor.Published.String())
dbActor.Set("public_key", pubActor.PublicKey.PublicKeyPem)
dbActor.Set("last_fetched", time.Now())
if includeFollows {
dbActor.Set("followerCount", int(followers.TotalItems))
dbActor.Set("followingCount", int(following.TotalItems))
}
}
err := app.Save(dbActor)
if err != nil && err.Error() == "iri: Value must be unique." {
dbActor, err = app.FindFirstRecordByData("activitypub_actors", "iri", dbActor.GetString("iri"))
if err != nil {
return nil, err
}
return dbActor, nil
} else if err != nil {
return nil, err
}
return dbActor, nil
}
// Fetches an AP actor and optionally followers/following collections
func fetchRemoteActor(iri string, includeFollows bool) (*pub.Actor, *pub.OrderedCollection, *pub.OrderedCollection, error) {
client := &http.Client{}
headers := map[string]string{
"Accept": "application/ld+json",
}
req, _ := http.NewRequest("GET", iri, nil)
for k, v := range headers {
req.Header.Set(k, v)
}
resp, err := client.Do(req)
if err != nil {
return nil, nil, nil, fmt.Errorf("actor fetch failed: %v", err)
} else if resp.StatusCode != http.StatusOK {
return nil, nil, nil, fmt.Errorf("actor fetch failed: status %v", resp.StatusCode)
}
defer resp.Body.Close()
var pubActor pub.Actor
if err := json.NewDecoder(resp.Body).Decode(&pubActor); err != nil {
return nil, nil, nil, err
}
var followers, following pub.OrderedCollection
if includeFollows {
// Fetch followers
if data, err := fetchCollection(pubActor.Followers.GetID().String(), headers); err == nil {
followers = *data
}
// Fetch following
if data, err := fetchCollection(pubActor.Following.GetID().String(), headers); err == nil {
following = *data
}
}
return &pubActor, &followers, &following, nil
}
func fetchCollection(url string, headers map[string]string) (*pub.OrderedCollection, error) {
req, _ := http.NewRequest("GET", url, nil)
for k, v := range headers {
req.Header.Set(k, v)
}
resp, err := http.DefaultClient.Do(req)
if err != nil || resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("collection fetch failed for %s: %v", url, err)
}
defer resp.Body.Close()
var collection pub.OrderedCollection
if err := json.NewDecoder(resp.Body).Decode(&collection); err != nil {
return nil, err
}
return &collection, nil
}

276
db/federation/announce.go Normal file
View File

@@ -0,0 +1,276 @@
package federation
import (
"database/sql"
"fmt"
"net/url"
"os"
"path"
"pocketbase/util"
"strings"
"time"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/security"
pub "github.com/go-ap/activitypub"
)
type AnnounceType string
const (
TrailAnnounceType AnnounceType = "trail"
ListAnnounceType AnnounceType = "list"
)
func CreateAnnounceActivity(app core.App, record *core.Record, typ AnnounceType) error {
origin := os.Getenv("ORIGIN")
if origin == "" {
return fmt.Errorf("ORIGIN not set")
}
var subject *core.Record
var object pub.Item
var err error
if typ == TrailAnnounceType {
subject, err = app.FindRecordById("trails", record.GetString("trail"))
if err != nil {
return err
}
object, err = util.ObjectFromTrail(app, subject, nil)
if err != nil {
return err
}
} else if typ == ListAnnounceType {
subject, err = app.FindRecordById("lists", record.GetString("list"))
if err != nil {
return err
}
object, err = util.ObjectFromList(app, subject)
if err != nil {
return err
}
} else {
return fmt.Errorf("unknown announce type")
}
subjectActor, err := app.FindRecordById("activitypub_actors", subject.GetString("author"))
if err != nil {
return err
}
objectActor, err := app.FindRecordById("activitypub_actors", record.GetString("actor"))
if err != nil {
return err
}
collection, err := app.FindCollectionByNameOrId("activitypub_activities")
if err != nil {
return err
}
recordId := security.RandomStringWithAlphabet(core.DefaultIdLength, core.DefaultIdAlphabet)
id := fmt.Sprintf("%s/api/v1/activitypub/activity/%s", origin, recordId)
to := objectActor.GetString("iri")
actor := subjectActor.GetString("iri")
activity := pub.AnnounceNew(pub.IRI(id), object)
activity.To = pub.ItemCollection{pub.IRI(to)}
activity.Actor = pub.IRI(actor)
activity.Tag = pub.ItemCollection{
pub.Object{
Type: pub.NoteType,
Name: pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, "permission")),
Content: pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, record.GetString("permission"))),
},
}
err = PostActivity(app, subjectActor, activity, []string{objectActor.GetString("inbox")})
if err != nil {
return err
}
activityRecord := core.NewRecord(collection)
activityRecord.Set("id", recordId)
activityRecord.Set("iri", id)
activityRecord.Set("to", []string{to})
activityRecord.Set("type", string(pub.AnnounceType))
activityRecord.Set("object", object)
activityRecord.Set("actor", actor)
activityRecord.Set("published", time.Now())
return app.Save(activityRecord)
}
// process incoming announce activity
func ProcessAnnounceActivity(app core.App, actor *core.Record, activity pub.Activity) error {
origin := os.Getenv("ORIGIN")
if origin == "" {
return fmt.Errorf("ORIGIN not set")
}
object := activity.Object.GetID().String()
if strings.Contains(object, "/api/v1/trail") {
processTrailAnnounceActivity(app, actor, activity)
} else if strings.Contains(object, "/api/v1/list") {
processListAnnounceActivity(app, actor, activity)
} else {
return fmt.Errorf("unknown announce type")
}
return nil
}
func processTrailAnnounceActivity(app core.App, actor *core.Record, activity pub.Activity) error {
objectActor, err := app.FindFirstRecordByData("activitypub_actors", "iri", activity.To[0].GetID().String())
if err != nil {
return err
}
var trail *core.Record
if !actor.GetBool("isLocal") {
trail, err = util.TrailFromActivity(activity, app, actor)
if err != nil {
return err
}
permission := "view"
// tags, err := pub.ToItemCollection(activity.Tag)
// if err != nil {
// return err
// }
// for _, tag := range tags.Collection() {
// tagObj, err := pub.ToObject(tag)
// if err != nil {
// continue
// }
// name := tagObj.Name.First().Value.String()
// content := tagObj.Content.First().Value.String()
// if name == "permission" {
// permission = content
// }
// }
record, err := app.FindFirstRecordByFilter("trail_share", "trail={:trailId}&&actor={:actorId}", dbx.Params{"trailId": trail.Id, "actorId": objectActor.Id})
if err != nil {
if err == sql.ErrNoRows {
collection, err := app.FindCollectionByNameOrId("trail_share")
if err != nil {
return err
}
record = core.NewRecord(collection)
record.Set("trail", trail.Id)
record.Set("actor", objectActor.Id)
} else {
return err
}
}
record.Set("permission", permission)
err = app.Save(record)
if err != nil {
return err
}
} else {
trailUrl, err := url.Parse(activity.Object.GetID().String())
if err != nil {
return err
}
trailId := path.Base(trailUrl.Path)
trail, err = app.FindRecordById("trails", trailId)
if err != nil {
return err
}
}
notification := util.Notification{
Type: util.TrailShare,
Metadata: map[string]string{
"id": trail.Id,
"trail": trail.GetString("name"),
"author": fmt.Sprintf("@%s@%s", actor.GetString("username"), actor.GetString("domain")),
},
Seen: false,
Author: actor.Id,
}
err = util.SendNotification(app, notification, objectActor)
if err != nil {
return err
}
return nil
}
func processListAnnounceActivity(app core.App, actor *core.Record, activity pub.Activity) error {
objectActor, err := app.FindFirstRecordByData("activitypub_actors", "iri", activity.To[0].GetID().String())
if err != nil {
return err
}
var list *core.Record
if !actor.GetBool("isLocal") {
list, err = util.ListFromActivity(activity, app, actor)
if err != nil {
return err
}
record, err := app.FindFirstRecordByFilter("list_share", "list={:listId}&&actor={:actorId}", dbx.Params{"listId": list.Id, "actorId": objectActor.Id})
if err != nil {
if err == sql.ErrNoRows {
collection, err := app.FindCollectionByNameOrId("list_share")
if err != nil {
return err
}
record = core.NewRecord(collection)
record.Set("list", list.Id)
record.Set("actor", objectActor.Id)
record.Set("permission", "view")
} else {
return err
}
}
err = app.Save(record)
if err != nil {
return err
}
} else {
listUrl, err := url.Parse(activity.Object.GetID().String())
if err != nil {
return err
}
listId := path.Base(listUrl.Path)
list, err = app.FindRecordById("trails", listId)
if err != nil {
return err
}
}
notification := util.Notification{
Type: util.ListShare,
Metadata: map[string]string{
"id": list.Id,
"list": list.GetString("name"),
"author": fmt.Sprintf("@%s@%s", actor.GetString("username"), actor.GetString("domain")),
},
Seen: false,
Author: actor.Id,
}
err = util.SendNotification(app, notification, objectActor)
if err != nil {
return err
}
return nil
}

849
db/federation/create.go Normal file
View File

@@ -0,0 +1,849 @@
package federation
import (
"context"
"database/sql"
"fmt"
"net/url"
"os"
"path"
"strconv"
"strings"
"time"
"pocketbase/util"
pub "github.com/go-ap/activitypub"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/filesystem"
"github.com/pocketbase/pocketbase/tools/security"
"golang.org/x/net/html"
)
func CreateTrailActivity(app core.App, trail *core.Record, typ pub.ActivityVocabularyType) error {
if !trail.GetBool("public") {
// only broadcast the trail if it is public
return nil
}
origin := os.Getenv("ORIGIN")
if origin == "" {
return fmt.Errorf("ORIGIN not set")
}
trailAuthor, err := app.FindRecordById("activitypub_actors", trail.GetString("author"))
if err != nil {
return err
}
collection, err := app.FindCollectionByNameOrId("activitypub_activities")
if err != nil {
return err
}
recordId := security.RandomStringWithAlphabet(core.DefaultIdLength, core.DefaultIdAlphabet)
id := fmt.Sprintf("%s/api/v1/activitypub/activity/%s", origin, recordId)
to := "https://www.w3.org/ns/activitystreams#Public"
mentionedActors, handles, err := ActorsFromMentions(app, trail.GetString("description"))
if err != nil {
return err
}
mentions := []string{}
cc := pub.ItemCollection{pub.IRI(trailAuthor.GetString("followers"))}
tags := pub.ItemCollection{}
for i, m := range mentionedActors {
inbox := m.GetString("inbox")
mention := pub.MentionNew(pub.IRI(m.GetString("iri")))
mention.Href = pub.IRI(m.GetString("iri"))
mention.Name = pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, handles[i]))
tags.Append(mention)
mentions = append(mentions, inbox)
cc.Append(pub.IRI(inbox))
}
trailObject, err := util.ObjectFromTrail(app, trail, &tags)
if err != nil {
return err
}
activity := pub.ActivityNew(pub.IRI(id), typ, trailObject)
activity.Actor = pub.IRI(trailAuthor.GetString("iri"))
activity.To = pub.ItemCollection{pub.IRI(to)}
activity.CC = cc
activity.Published = time.Now()
record := core.NewRecord(collection)
record.Set("id", recordId)
record.Set("iri", id)
record.Set("to", []string{to})
record.Set("cc", cc)
record.Set("type", string(typ))
record.Set("object", trailObject)
record.Set("actor", trailAuthor.GetString("iri"))
record.Set("published", time.Now())
err = app.Save(record)
if err != nil {
return err
}
follows, err := app.FindRecordsByFilter("follows", "followee={:followee}&&status='accepted'", "", -1, 0, dbx.Params{"followee": trailAuthor.Id})
if err != nil {
return err
}
recipients := mentions
for _, f := range follows {
follower, err := app.FindRecordById("activitypub_actors", f.GetString("follower"))
if err != nil {
return err
}
recipients = append(recipients, follower.GetString("inbox"))
}
return PostActivity(app, trailAuthor, activity, recipients)
}
func CreateCommentActivity(app core.App, comment *core.Record, typ pub.ActivityVocabularyType) error {
origin := os.Getenv("ORIGIN")
if origin == "" {
return fmt.Errorf("ORIGIN not set")
}
// author of the comment
commentAuthor, err := app.FindRecordById("activitypub_actors", comment.GetString("author"))
if err != nil {
return err
}
commentTrail, err := app.FindRecordById("trails", comment.GetString("trail"))
if err != nil {
return err
}
commentTrailAuthor, err := app.FindRecordById("activitypub_actors", commentTrail.GetString("author"))
if err != nil {
return err
}
activityRecordId := security.RandomStringWithAlphabet(core.DefaultIdLength, core.DefaultIdAlphabet)
id := fmt.Sprintf("%s/api/v1/activitypub/activity/%s", origin, activityRecordId)
to := "https://www.w3.org/ns/activitystreams#Public"
mentionedActors, handles, err := ActorsFromMentions(app, comment.GetString("text"))
if err != nil {
return err
}
recipients := []string{}
tags := pub.ItemCollection{}
for i, m := range mentionedActors {
mention := pub.MentionNew(pub.IRI(m.GetString("iri")))
mention.Href = pub.IRI(m.GetString("iri"))
mention.Name = pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, handles[i]))
tags.Append(mention)
recipients = append(recipients, m.GetString("inbox"))
}
recipients = append(recipients, commentTrailAuthor.GetString("inbox"))
cc := pub.ItemCollection{}
for _, r := range recipients {
cc.Append(pub.IRI(r))
}
author := commentAuthor.GetString("iri")
commentObject, err := util.ObjectFromComment(app, comment, &tags)
if err != nil {
return err
}
activity := pub.ActivityNew(pub.IRI(id), typ, commentObject)
activity.Actor = pub.IRI(author)
activity.To = pub.ItemCollection{pub.IRI(to)}
activity.CC = cc
activity.Published = time.Now()
activity.Object = commentObject
collection, err := app.FindCollectionByNameOrId("activitypub_activities")
if err != nil {
return err
}
record := core.NewRecord(collection)
record.Set("id", activityRecordId)
record.Set("iri", id)
record.Set("to", []string{to})
record.Set("cc", recipients)
record.Set("type", string(typ))
record.Set("object", commentObject)
record.Set("actor", author)
record.Set("published", time.Now())
err = app.Save(record)
if err != nil {
return err
}
return PostActivity(app, commentAuthor, activity, recipients)
}
func CreateSummitLogActivity(app core.App, summitLog *core.Record, typ pub.ActivityVocabularyType) error {
origin := os.Getenv("ORIGIN")
if origin == "" {
return fmt.Errorf("ORIGIN not set")
}
summitLogAuthor, err := app.FindRecordById("activitypub_actors", summitLog.GetString("author"))
if err != nil {
return err
}
var summitLogAuthorId string
// first check if we find the trail locally
summitLogTrail, err := app.FindRecordById("trails", summitLog.GetString("trail"))
if err != nil {
return err
}
if !summitLogTrail.GetBool("public") {
// only broadcast the log if the trail it belongs to is public
return nil
}
summitLogAuthorId = summitLogTrail.GetString("author")
summitLogTrailAuthor, err := app.FindRecordById("activitypub_actors", summitLogAuthorId)
if err != nil {
return err
}
collection, err := app.FindCollectionByNameOrId("activitypub_activities")
if err != nil {
return err
}
var trailIRI pub.IRI
if summitLogTrailAuthor.GetBool("isLocal") {
trailId := summitLog.GetString("trail")
trailIRI = pub.IRI(fmt.Sprintf("%s/api/v1/trail/%s", origin, trailId))
} else {
trailIRI = pub.IRI(summitLogTrail.GetString("iri"))
}
recordId := security.RandomStringWithAlphabet(core.DefaultIdLength, core.DefaultIdAlphabet)
id := fmt.Sprintf("%s/api/v1/activitypub/activity/%s", origin, recordId)
to := pub.ItemCollection{pub.IRI("https://www.w3.org/ns/activitystreams#Public")}
// someone else created the summit log on the trail -> inform the trail's author
if summitLogAuthor.Id != summitLogTrailAuthor.Id {
to.Append(pub.IRI(summitLogTrailAuthor.GetString("iri")))
}
mentionedActors, handles, err := ActorsFromMentions(app, summitLog.GetString("text"))
if err != nil {
return err
}
mentions := []string{}
cc := pub.ItemCollection{pub.IRI(summitLogAuthor.GetString("followers"))}
mentionTags := pub.ItemCollection{}
for i, m := range mentionedActors {
inbox := m.GetString("inbox")
mention := pub.MentionNew(pub.IRI(m.GetString("iri")))
mention.Href = pub.IRI(m.GetString("iri"))
mention.Name = pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, handles[i]))
mentionTags.Append(mention)
mentions = append(mentions, inbox)
cc.Append(pub.IRI(inbox))
}
photos := summitLog.GetStringSlice("photos")
gpx := ""
if summitLog.GetString("gpx") != "" {
gpx = fmt.Sprintf("%s/api/v1/files/summit_logs/%s/%s", origin, summitLog.Id, summitLog.GetString("gpx"))
}
attachments := make(pub.ItemCollection, max(len(photos), 2))
for i := range len(photos) {
iri := fmt.Sprintf("%s/api/v1/files/summit_logs/%s/%s", origin, summitLog.Id, photos[i])
attachments[i] = pub.Document{
Type: pub.ImageType,
MediaType: "image/jpeg",
URL: pub.IRI(iri),
}
}
if gpx != "" {
attachments.Append(pub.Document{
Type: pub.DocumentType,
MediaType: "application/xml+gpx",
URL: pub.IRI(gpx),
})
}
tags := pub.ItemCollection{
pub.Object{
Type: pub.NoteType,
Name: pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, "elevation_gain")),
Content: pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, fmt.Sprintf("%fm", summitLog.GetFloat("elevation_gain")))),
},
pub.Object{
Type: pub.NoteType,
Name: pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, "elevation_loss")),
Content: pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, fmt.Sprintf("%fm", summitLog.GetFloat("elevation_loss")))),
},
pub.Object{
Type: pub.NoteType,
Name: pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, "distance")),
Content: pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, fmt.Sprintf("%fm", summitLog.GetFloat("distance")))),
},
pub.Object{
Type: pub.NoteType,
Name: pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, "duration")),
Content: pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, fmt.Sprintf("%fm", summitLog.GetFloat("duration")))),
},
}
for _, m := range mentionTags {
tags.Append(m)
}
logObject := pub.ObjectNew(pub.NoteType)
logObject.Content = pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, summitLog.GetString("text")))
logObject.AttributedTo = pub.IRI(summitLogAuthor.GetString("iri"))
logObject.Published = summitLog.GetDateTime("created").Time()
logObject.ID = pub.IRI(fmt.Sprintf("%s/api/v1/summit-log/%s", origin, summitLog.Id))
logObject.URL = pub.IRI(fmt.Sprintf("%s/trail/view/@%s/%s", origin, summitLogTrailAuthor.GetString("username"), summitLog.GetString("trail")))
logObject.InReplyTo = trailIRI
logObject.Tag = tags
logObject.StartTime = summitLog.GetDateTime("date").Time()
logObject.Attachment = attachments
activity := pub.ActivityNew(pub.IRI(id), typ, logObject)
activity.Actor = pub.IRI(summitLogAuthor.GetString("iri"))
activity.To = to
activity.CC = cc
activity.Published = time.Now()
follows, err := app.FindRecordsByFilter("follows", "followee={:followee}&&status='accepted'", "", -1, 0, dbx.Params{"followee": summitLogAuthor.Id})
if err != nil {
return err
}
recipients := mentions
for _, f := range follows {
follower, err := app.FindRecordById("activitypub_actors", f.GetString("follower"))
if err != nil {
return err
}
recipients = append(recipients, follower.GetString("inbox"))
}
if summitLogAuthor.Id != summitLogTrailAuthor.Id {
recipients = append(recipients, summitLogTrailAuthor.GetString("inbox"))
}
err = PostActivity(app, summitLogAuthor, activity, recipients)
if err != nil {
return err
}
record := core.NewRecord(collection)
record.Set("id", recordId)
record.Set("iri", id)
record.Set("to", to)
record.Set("cc", cc)
record.Set("type", string(typ))
record.Set("object", logObject)
record.Set("actor", summitLogAuthor.GetString("iri"))
record.Set("published", time.Now())
return app.Save(record)
}
func CreateListActivity(app core.App, list *core.Record, typ pub.ActivityVocabularyType) error {
if !list.GetBool("public") {
// only broadcast the list if it is public
return nil
}
origin := os.Getenv("ORIGIN")
if origin == "" {
return fmt.Errorf("ORIGIN not set")
}
// author of the list
listAuthor, err := app.FindRecordById("activitypub_actors", list.GetString("author"))
if err != nil {
return err
}
activityRecordId := security.RandomStringWithAlphabet(core.DefaultIdLength, core.DefaultIdAlphabet)
id := fmt.Sprintf("%s/api/v1/activitypub/activity/%s", origin, activityRecordId)
to := "https://www.w3.org/ns/activitystreams#Public"
cc := listAuthor.GetString("followers")
author := listAuthor.GetString("iri")
listObject, err := util.ObjectFromList(app, list)
if err != nil {
return err
}
activity := pub.ActivityNew(pub.IRI(id), typ, listObject)
activity.Actor = pub.IRI(author)
activity.To = pub.ItemCollection{pub.IRI(to)}
activity.CC = pub.ItemCollection{pub.IRI(cc)}
activity.Published = time.Now()
activity.Object = listObject
collection, err := app.FindCollectionByNameOrId("activitypub_activities")
if err != nil {
return err
}
follows, err := app.FindRecordsByFilter("follows", "followee={:followee}&&status='accepted'", "", -1, 0, dbx.Params{"followee": listAuthor.Id})
if err != nil {
return err
}
recipients := []string{}
for _, f := range follows {
follower, err := app.FindRecordById("activitypub_actors", f.GetString("follower"))
if err != nil {
return err
}
recipients = append(recipients, follower.GetString("inbox"))
}
err = PostActivity(app, listAuthor, activity, recipients)
if err != nil {
return err
}
record := core.NewRecord(collection)
record.Set("id", activityRecordId)
record.Set("iri", id)
record.Set("to", []string{to})
record.Set("cc", []string{cc})
record.Set("type", string(typ))
record.Set("object", listObject)
record.Set("actor", author)
record.Set("published", time.Now())
return app.Save(record)
}
func ProcessCreateOrUpdateActivity(app core.App, actor *core.Record, activity pub.Activity) error {
var err error
if strings.Contains(activity.Object.GetID().String(), "/api/v1/trail") {
err = processCreateOrUpdateTrailActivity(activity, app, actor)
} else if strings.Contains(activity.Object.GetID().String(), "/api/v1/summit-log") {
err = processCreateOrUpdateSummitLogActivity(activity, app, actor)
} else if strings.Contains(activity.Object.GetID().String(), "/api/v1/list") {
err = processCreateOrUpdateListActivity(activity, app, actor)
} else {
err = processCreateOrUpdateCommentActivity(activity, app, actor)
}
if err != nil {
return err
}
return nil
}
func processCreateOrUpdateTrailActivity(activity pub.Activity, app core.App, actor *core.Record) error {
// no need to do anything if the actor is local
if actor.GetBool("isLocal") {
return nil
}
trail, err := util.TrailFromActivity(activity, app, actor)
trailObject, _ := pub.ToObject(activity.Object)
for _, t := range trailObject.Tag {
if t.GetType() == pub.MentionType {
mention := t.(*pub.Mention)
mentionedActor, err := app.FindFirstRecordByData("activitypub_actors", "iri", mention.Href.GetID().String())
if err != nil {
continue
}
notification := util.Notification{
Type: util.TrailMention,
Metadata: map[string]string{
"id": trail.Id,
"author": fmt.Sprintf("@%s@%s", actor.GetString("username"), actor.GetString("domain")),
},
Seen: false,
Author: actor.Id,
}
return util.SendNotification(app, notification, mentionedActor)
}
}
return err
}
func processCreateOrUpdateCommentActivity(activity pub.Activity, app core.App, actor *core.Record) error {
commentObject, err := pub.ToObject(activity.Object)
if err != nil {
return err
}
if commentObject.InReplyTo == nil {
return fmt.Errorf("error processing comment: InReplyTo empty")
}
trailUrl, err := url.Parse(commentObject.InReplyTo.GetLink().String())
if err != nil {
return err
}
trailId := path.Base(trailUrl.Path)
var trail *core.Record
trail, err = app.FindFirstRecordByFilter("trails", "iri={:iri} || id={:id}", dbx.Params{"id": trailId, "iri": commentObject.InReplyTo.GetID().String()})
// if the trail is not present on this instance fetch it
if err != nil {
if err == sql.ErrNoRows {
trailObject, err := util.TrailObjectFromIRI(commentObject.InReplyTo.GetLink().String())
if err != nil {
return err
}
activity := pub.ActivityNew(pub.IRI("new"), pub.CreateType, trailObject)
trail, err = util.TrailFromActivity(*activity, app, actor)
if err != nil {
return err
}
} else {
return err
}
}
trailAuthor, err := app.FindRecordById("activitypub_actors", trail.GetString("author"))
if err != nil {
return err
}
// no need to do anything else if the actor is local
if actor.GetBool("isLocal") {
return nil
}
record, err := app.FindFirstRecordByData("comments", "iri", commentObject.ID.String())
if err != nil {
if err == sql.ErrNoRows {
collection, err := app.FindCollectionByNameOrId("comments")
if err != nil {
return err
}
record = core.NewRecord(collection)
} else {
return err
}
}
record.Set("iri", commentObject.ID.String())
record.Set("text", commentObject.Content.First().Value)
record.Set("author", actor.Id)
record.Set("trail", trail.Id)
err = app.Save(record)
if err != nil {
return err
}
// send notifications to all mentioned actors
for _, t := range commentObject.Tag {
if t.GetType() == pub.MentionType {
mention := t.(*pub.Mention)
mentionedActor, err := app.FindFirstRecordByData("activitypub_actors", "iri", mention.Href.GetID().String())
if err != nil {
continue
}
notification := util.Notification{
Type: util.CommentMention,
Metadata: map[string]string{
"comment": commentObject.Content.First().Value.String(),
"trail_id": trail.Id,
"trail_name": trail.GetString("name"),
"trail_author": fmt.Sprintf("@%s@%s", trailAuthor.GetString("username"), trailAuthor.GetString("domain")),
},
Seen: false,
Author: actor.Id,
}
return util.SendNotification(app, notification, mentionedActor)
}
}
if activity.Type == pub.CreateType {
// send a notification to the trail author
notification := util.Notification{
Type: util.TrailComment,
Metadata: map[string]string{
"comment": commentObject.Content.First().Value.String(),
"trail_id": trail.Id,
"trail_name": trail.GetString("name"),
"trail_author": fmt.Sprintf("@%s@%s", trailAuthor.GetString("username"), trailAuthor.GetString("domain")),
},
Seen: false,
Author: actor.Id,
}
return util.SendNotification(app, notification, trailAuthor)
}
return nil
}
func processCreateOrUpdateSummitLogActivity(activity pub.Activity, app core.App, actor *core.Record) error {
logObject, err := pub.ToObject(activity.Object)
if err != nil {
return err
}
trailIRI, err := url.Parse(logObject.InReplyTo.GetID().String())
if err != nil {
return err
}
trailId := path.Base(trailIRI.Path)
trail, err := app.FindFirstRecordByFilter("trails", "iri={:iri} || id={:id}", dbx.Params{"id": trailId, "iri": logObject.InReplyTo.GetID().String()})
// if the trail is not present on this instance fetch it
if err != nil {
if err == sql.ErrNoRows {
trailObject, err := util.TrailObjectFromIRI(logObject.InReplyTo.GetLink().String())
if err != nil {
return err
}
activity := pub.ActivityNew(pub.IRI("new"), pub.CreateType, trailObject)
trail, err = util.TrailFromActivity(*activity, app, actor)
if err != nil {
return err
}
} else {
return err
}
}
trailAuthor, err := app.FindRecordById("activitypub_actors", trail.GetString("author"))
if err != nil {
return err
}
newSummitLog := false
record, err := app.FindFirstRecordByData("summit_logs", "iri", logObject.ID.String())
if err != nil {
if err == sql.ErrNoRows {
collection, err := app.FindCollectionByNameOrId("summit_logs")
if err != nil {
return err
}
record = core.NewRecord(collection)
newSummitLog = true
} else {
return err
}
}
// no need to do anything else if the actor is local
if actor.GetBool("isLocal") {
return nil
}
var distance, duration, elevation_gain, elevation_loss float64
tags, err := pub.ToItemCollection(logObject.Tag)
if err != nil {
return err
}
for _, tag := range tags.Collection() {
tagObj, err := pub.ToObject(tag)
if err != nil {
continue
}
content := tagObj.Content.First().Value.String()
switch tagObj.Name.First().Value.String() {
case "elevation_gain":
elevation_gain, err = strconv.ParseFloat(content[:len(content)-1], 64)
case "elevation_loss":
elevation_loss, err = strconv.ParseFloat(content[:len(content)-1], 64)
case "duration":
duration, err = strconv.ParseFloat(content[:len(content)-1], 64)
case "distance":
distance, err = strconv.ParseFloat(content[:len(content)-1], 64)
}
if err != nil {
continue
}
}
record.Set("date", logObject.StartTime)
record.Set("text", logObject.Content.First().Value)
record.Set("distance", distance)
record.Set("duration", duration)
record.Set("elevation_gain", elevation_gain)
record.Set("elevation_loss", elevation_loss)
record.Set("author", actor.Id)
record.Set("trail", trail.Id)
record.Set("iri", logObject.ID.String())
if logObject.Attachment != nil {
attachments, err := pub.ToItemCollection(logObject.Attachment)
if err != nil {
return err
}
photoURLs := []string{}
gpxURL := ""
for _, a := range attachments.Collection() {
attachment, err := pub.ToObject(a)
if err != nil {
continue
}
if attachment.Type == pub.DocumentType && attachment.MediaType == "application/xml+gpx" {
gpxURL = attachment.URL.GetLink().String()
} else if attachment.Type == pub.ImageType {
photoURLs = append(photoURLs, attachment.URL.GetLink().String())
}
}
if len(photoURLs) > 0 {
photos := make([]*filesystem.File, len(photoURLs))
for i, purl := range photoURLs {
photo, err := filesystem.NewFileFromURL(context.Background(), purl)
if err != nil {
continue
}
photos[i] = photo
}
record.Set("photos", photos)
}
if gpxURL != "" {
gpx, err := filesystem.NewFileFromURL(context.Background(), gpxURL)
if err != nil {
return err
}
record.Set("gpx", gpx)
}
}
err = app.Save(record)
if err != nil {
return err
}
// send notifications to all mentioned actors
for _, t := range logObject.Tag {
if t.GetType() == pub.MentionType {
mention := t.(*pub.Mention)
mentionedActor, err := app.FindFirstRecordByData("activitypub_actors", "iri", mention.Href.GetID().String())
if err != nil {
continue
}
notification := util.Notification{
Type: util.SummitLogMention,
Metadata: map[string]string{
"trail_id": trail.Id,
"trail_name": trail.GetString("name"),
"trail_author": fmt.Sprintf("@%s@%s", trailAuthor.GetString("username"), trailAuthor.GetString("domain")),
},
Seen: false,
Author: actor.Id,
}
return util.SendNotification(app, notification, mentionedActor)
}
}
if newSummitLog {
// send a notification to the trail author
notification := util.Notification{
Type: util.SummitLogCreate,
Metadata: map[string]string{
"trail_id": trail.Id,
"trail_name": trail.GetString("name"),
"trail_author": fmt.Sprintf("@%s@%s", trailAuthor.GetString("username"), trailAuthor.GetString("domain")),
},
Seen: false,
Author: actor.Id,
}
return util.SendNotification(app, notification, trailAuthor)
}
return nil
}
func processCreateOrUpdateListActivity(activity pub.Activity, app core.App, actor *core.Record) error {
// no need to do anything if the actor is local
if actor.GetBool("isLocal") {
return nil
}
_, err := util.ListFromActivity(activity, app, actor)
return err
}
func ActorsFromMentions(app core.App, htmlStr string) ([]*core.Record, []string, error) {
doc, err := html.Parse(strings.NewReader(htmlStr))
if err != nil {
return nil, nil, err
}
var handles []string
var actors []*core.Record
var f func(*html.Node)
f = func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == "a" {
var isMention bool
for _, attr := range n.Attr {
if attr.Key == "class" && strings.Contains(attr.Val, "mention") {
isMention = true
break
}
}
if isMention && n.FirstChild != nil && n.FirstChild.Type == html.TextNode {
handle := strings.TrimSpace(n.FirstChild.Data)
if strings.HasPrefix(handle, "@") {
handles = append(handles, handle)
}
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
f(c)
}
}
f(doc)
for _, h := range handles {
actor, err := GetActorByHandle(app, h, false)
if err != nil {
continue
}
actors = append(actors, actor)
}
return actors, handles, nil
}

377
db/federation/delete.go Normal file
View File

@@ -0,0 +1,377 @@
package federation
import (
"fmt"
"net/url"
"os"
"path"
"strings"
"time"
pub "github.com/go-ap/activitypub"
"github.com/meilisearch/meilisearch-go"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/security"
)
func CreateTrailDeleteActivity(app core.App, r *core.Record) error {
origin := os.Getenv("ORIGIN")
if origin == "" {
return fmt.Errorf("ORIGIN not set")
}
author, err := app.FindRecordById("activitypub_actors", r.GetString("author"))
if err != nil {
return err
}
collection, err := app.FindCollectionByNameOrId("activitypub_activities")
if err != nil {
return err
}
recordId := security.RandomStringWithAlphabet(core.DefaultIdLength, core.DefaultIdAlphabet)
id := fmt.Sprintf("%s/api/v1/activitypub/activity/%s", origin, recordId)
to := "https://www.w3.org/ns/activitystreams#Public"
cc := author.GetString("iri") + "/followers"
object := fmt.Sprintf("%s/api/v1/trail/%s", origin, r.Id)
record := core.NewRecord(collection)
record.Set("id", recordId)
record.Set("iri", id)
record.Set("type", string(pub.DeleteType))
record.Set("to", to)
record.Set("cc", cc)
record.Set("object", object)
record.Set("actor", author.GetString("iri"))
record.Set("published", time.Now())
err = app.Save(record)
if err != nil {
return err
}
activity := pub.DeleteNew(pub.IRI(id), pub.IRI(object))
activity.Actor = pub.IRI(author.GetString("iri"))
activity.To = pub.ItemCollection{pub.IRI(to)}
activity.CC = pub.ItemCollection{pub.IRI(cc)}
activity.Published = time.Now()
follows, err := app.FindRecordsByFilter("follows", "followee={:followee}&&status='accepted'", "", -1, 0, dbx.Params{"followee": author.Id})
if err != nil {
return err
}
recipients := []string{}
for _, f := range follows {
follower, err := app.FindRecordById("activitypub_actors", f.GetString("follower"))
if err != nil {
return err
}
recipients = append(recipients, follower.GetString("inbox"))
}
return PostActivity(app, author, activity, recipients)
}
func CreateCommentDeleteActivity(app core.App, client meilisearch.ServiceManager, r *core.Record) error {
origin := os.Getenv("ORIGIN")
if origin == "" {
return fmt.Errorf("ORIGIN not set")
}
author, err := app.FindRecordById("activitypub_actors", r.GetString("author"))
if err != nil {
return err
}
if !author.GetBool("isLocal") {
return nil
}
commentTrail, err := app.FindRecordById("trails", r.GetString("trail"))
if err != nil {
return err
}
commentTrailAuthor, err := app.FindRecordById("activitypub_actors", commentTrail.GetString("author"))
if err != nil {
return err
}
if commentTrailAuthor.GetBool("isLocal") {
return nil
}
collection, err := app.FindCollectionByNameOrId("activitypub_activities")
if err != nil {
return err
}
recordId := security.RandomStringWithAlphabet(core.DefaultIdLength, core.DefaultIdAlphabet)
id := fmt.Sprintf("%s/api/v1/activitypub/activity/%s", origin, recordId)
to := commentTrailAuthor.GetString("iri")
object := fmt.Sprintf("%s/api/v1/comment/%s", origin, r.Id)
activity := pub.DeleteNew(pub.IRI(id), pub.IRI(object))
activity.Actor = pub.IRI(author.GetString("iri"))
activity.To = pub.ItemCollection{pub.IRI(to)}
activity.Published = time.Now()
err = PostActivity(app, author, activity, []string{to + "/inbox"})
if err != nil {
return err
}
record := core.NewRecord(collection)
record.Set("id", recordId)
record.Set("iri", id)
record.Set("type", string(pub.DeleteType))
record.Set("to", to)
record.Set("object", object)
record.Set("actor", author.GetString("iri"))
record.Set("published", time.Now())
return app.Save(record)
}
func CreateSummitLogDeleteActivity(app core.App, r *core.Record) error {
origin := os.Getenv("ORIGIN")
if origin == "" {
return fmt.Errorf("ORIGIN not set")
}
author, err := app.FindRecordById("activitypub_actors", r.GetString("author"))
if err != nil {
return err
}
if !author.GetBool("isLocal") {
return nil
}
summitLogTrail, err := app.FindRecordById("trails", r.GetString("trail"))
if err != nil {
return err
}
summitLogTrailAuthor, err := app.FindRecordById("activitypub_actors", summitLogTrail.GetString("author"))
if err != nil {
return err
}
collection, err := app.FindCollectionByNameOrId("activitypub_activities")
if err != nil {
return err
}
recordId := security.RandomStringWithAlphabet(core.DefaultIdLength, core.DefaultIdAlphabet)
id := fmt.Sprintf("%s/api/v1/activitypub/activity/%s", origin, recordId)
to := summitLogTrailAuthor.GetString("iri")
object := fmt.Sprintf("%s/api/v1/summit-log/%s", origin, r.Id)
cc := pub.ItemCollection{pub.IRI(author.GetString("iri") + "/followers")}
activity := pub.DeleteNew(pub.IRI(id), pub.IRI(object))
activity.Actor = pub.IRI(author.GetString("iri"))
activity.To = pub.ItemCollection{pub.IRI(to)}
activity.CC = cc
activity.Published = time.Now()
follows, err := app.FindRecordsByFilter("follows", "followee={:followee}&&status='accepted'", "", -1, 0, dbx.Params{"followee": author.Id})
if err != nil {
return err
}
recipients := []string{}
for _, f := range follows {
follower, err := app.FindRecordById("activitypub_actors", f.GetString("follower"))
if err != nil {
return err
}
recipients = append(recipients, follower.GetString("inbox"))
}
if author.Id != summitLogTrailAuthor.Id {
recipients = append(recipients, summitLogTrailAuthor.GetString("inbox"))
}
err = PostActivity(app, author, activity, recipients)
if err != nil {
return err
}
record := core.NewRecord(collection)
record.Set("id", recordId)
record.Set("iri", id)
record.Set("type", string(pub.DeleteType))
record.Set("to", to)
record.Set("cc", cc)
record.Set("object", object)
record.Set("actor", author.GetString("iri"))
record.Set("published", time.Now())
return app.Save(record)
}
func CreateListDeleteActivity(app core.App, r *core.Record) error {
origin := os.Getenv("ORIGIN")
if origin == "" {
return fmt.Errorf("ORIGIN not set")
}
author, err := app.FindRecordById("activitypub_actors", r.GetString("author"))
if err != nil {
return err
}
if !author.GetBool("isLocal") {
return nil
}
collection, err := app.FindCollectionByNameOrId("activitypub_activities")
if err != nil {
return err
}
recordId := security.RandomStringWithAlphabet(core.DefaultIdLength, core.DefaultIdAlphabet)
id := fmt.Sprintf("%s/api/v1/activitypub/activity/%s", origin, recordId)
to := "https://www.w3.org/ns/activitystreams#Public"
cc := author.GetString("iri") + "/followers"
object := fmt.Sprintf("%s/api/v1/list/%s", origin, r.Id)
activity := pub.DeleteNew(pub.IRI(id), pub.IRI(object))
activity.Actor = pub.IRI(author.GetString("iri"))
activity.To = pub.ItemCollection{pub.IRI(to)}
activity.CC = pub.ItemCollection{pub.IRI(cc)}
activity.Published = time.Now()
follows, err := app.FindRecordsByFilter("follows", "followee={:followee}&&status='accepted'", "", -1, 0, dbx.Params{"followee": author.Id})
if err != nil {
return err
}
recipients := []string{}
for _, f := range follows {
follower, err := app.FindRecordById("activitypub_actors", f.GetString("follower"))
if err != nil {
return err
}
recipients = append(recipients, follower.GetString("inbox"))
}
err = PostActivity(app, author, activity, recipients)
if err != nil {
return err
}
record := core.NewRecord(collection)
record.Set("id", recordId)
record.Set("iri", id)
record.Set("type", string(pub.DeleteType))
record.Set("to", to)
record.Set("cc", cc)
record.Set("object", object)
record.Set("actor", author.GetString("iri"))
record.Set("published", time.Now())
return app.Save(record)
}
func ProcessDeleteActivity(app core.App, actor *core.Record, activity pub.Activity) error {
// no need to do anything if the actor is local
if actor.GetBool("isLocal") {
return nil
}
object := activity.Object.GetID().String()
var err error
switch {
case strings.Contains(object, "trail"):
err = processDeleteTrailActivity(app, activity)
case strings.Contains(object, "comment"):
err = processDeleteCommentActivity(app, actor, activity)
case strings.Contains(object, "summit-log"):
err = processDeleteSummitLogActivity(app, actor, activity)
case strings.Contains(object, "list"):
err = processDeleteListActivity(app, actor, activity)
}
if err != nil {
return err
}
return nil
}
func processDeleteTrailActivity(app core.App, activity pub.Activity) error {
trailUrl, err := url.Parse(activity.Object.GetID().String())
if err != nil {
return err
}
recordId := path.Base(trailUrl.Path)
trail, err := app.FindRecordById("trails", recordId)
if err != nil {
return err
}
return app.Delete(trail)
}
func processDeleteCommentActivity(app core.App, actor *core.Record, activity pub.Activity) error {
object := activity.Object.GetID().String()
comment, err := app.FindFirstRecordByData("comments", "iri", object)
if err != nil {
return err
}
if comment.GetString("author") != actor.Id {
return fmt.Errorf("actor is not comment author")
}
err = app.Delete(comment)
if err != nil {
return err
}
return nil
}
func processDeleteSummitLogActivity(app core.App, actor *core.Record, activity pub.Activity) error {
object := activity.Object.GetID().String()
summitLog, err := app.FindFirstRecordByData("summit_logs", "iri", object)
if err != nil {
return err
}
if summitLog.GetString("author") != actor.Id {
return fmt.Errorf("actor is not summit log author")
}
return app.Delete(summitLog)
}
func processDeleteListActivity(app core.App, actor *core.Record, activity pub.Activity) error {
object := activity.Object.GetID().String()
list, err := app.FindFirstRecordByData("lists", "iri", object)
if err != nil {
return err
}
if list.GetString("author") != actor.Id {
return fmt.Errorf("actor is not summit log author")
}
return app.Delete(list)
}

160
db/federation/follow.go Normal file
View File

@@ -0,0 +1,160 @@
package federation
import (
"fmt"
"os"
"pocketbase/util"
"time"
pub "github.com/go-ap/activitypub"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/security"
)
// create outgoing follow activity
func CreateFollowActivity(app core.App, follow *core.Record) error {
origin := os.Getenv("ORIGIN")
if origin == "" {
return fmt.Errorf("ORIGIN not set")
}
follower := follow.GetString("follower")
followee := follow.GetString("followee")
followerActor, err := app.FindRecordById("activitypub_actors", follower)
if err != nil {
return err
}
followeeActor, err := app.FindRecordById("activitypub_actors", followee)
if err != nil {
return err
}
collection, err := app.FindCollectionByNameOrId("activitypub_activities")
if err != nil {
return err
}
recordId := security.RandomStringWithAlphabet(core.DefaultIdLength, core.DefaultIdAlphabet)
id := fmt.Sprintf("%s/api/v1/activitypub/activity/%s", origin, recordId)
activity := pub.FollowNew(pub.IRI(id), pub.IRI(followeeActor.GetString("iri")))
activity.Actor = pub.IRI(followerActor.GetString("iri"))
err = PostActivity(app, followerActor, activity, []string{followeeActor.GetString("inbox")})
if err != nil {
return err
}
record := core.NewRecord(collection)
record.Set("id", recordId)
record.Set("iri", id)
record.Set("type", string(pub.FollowType))
record.Set("object", followeeActor.GetString("iri"))
record.Set("actor", followerActor.GetString("iri"))
record.Set("published", time.Now())
return app.Save(record)
}
// process incoming follow activity
func ProcessFollowActivity(app core.App, actor *core.Record, activity pub.Activity) error {
origin := os.Getenv("ORIGIN")
if origin == "" {
return fmt.Errorf("ORIGIN not set")
}
// find the followee in our db
object, err := app.FindFirstRecordByData("activitypub_actors", "iri", activity.Object)
if err != nil {
return err
}
// a remote actor has requested the follow
// this means we have not yet created a follow entry in our db
// we accept it immediately
if !actor.GetBool("isLocal") {
followCollection, err := app.FindCollectionByNameOrId("follows")
if err != nil {
return err
}
followRecord := core.NewRecord(followCollection)
followRecord.Set("follower", actor.Id)
followRecord.Set("followee", object.Id)
followRecord.Set("status", "accepted")
err = app.Save(followRecord)
if err != nil {
return err
}
}
recordId := security.RandomStringWithAlphabet(core.DefaultIdLength, core.DefaultIdAlphabet)
id := fmt.Sprintf("%s/api/v1/activitypub/activity/%s", origin, recordId)
// send the accept activity back to the actor's inbox
acceptActivity := pub.AcceptNew(pub.IRI(id), activity)
acceptActivity.Actor = activity.Object
err = PostActivity(app, object, acceptActivity, []string{actor.GetString("inbox")})
if err != nil {
return err
}
// create record of the accept activity in our db
collection, err := app.FindCollectionByNameOrId("activitypub_activities")
if err != nil {
return err
}
record := core.NewRecord(collection)
record.Set("id", recordId)
record.Set("iri", id)
record.Set("type", string(pub.AcceptType))
record.Set("object", activity)
record.Set("actor", object.GetString("iri"))
record.Set("published", time.Now())
err = app.Save(record)
if err != nil {
return err
}
// send a notification to the followee
notification := util.Notification{
Type: util.NewFollower,
Metadata: map[string]string{
"follower": fmt.Sprintf("@%s@%s", actor.GetString("username"), actor.GetString("domain")),
},
Seen: false,
Author: actor.Id,
}
return util.SendNotification(app, notification, object)
}
func ProcessAcceptActivity(app core.App, actor *core.Record, activity pub.Activity) error {
followActivity := activity.Object.(*pub.Activity)
follower, err := app.FindFirstRecordByData("activitypub_actors", "iri", followActivity.Actor)
if err != nil {
return err
}
follow, err := app.FindFirstRecordByFilter("follows", "follower={:follower} && followee={:followee}", dbx.Params{"follower": follower.Id, "followee": actor.Id})
if err != nil {
return err
}
follow.Set("status", "accepted")
err = app.Save(follow)
if err != nil {
return err
}
// err = util.SyncOutbox(app, actor)
// if err != nil {
// return err
// }
return nil
}

118
db/federation/like.go Normal file
View File

@@ -0,0 +1,118 @@
package federation
import (
"fmt"
"os"
"path"
"pocketbase/util"
"time"
pub "github.com/go-ap/activitypub"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/security"
)
// create outgoing follow activity
func CreateLikeActivity(app core.App, like *core.Record) error {
origin := os.Getenv("ORIGIN")
if origin == "" {
return fmt.Errorf("ORIGIN not set")
}
actor, err := app.FindRecordById("activitypub_actors", like.GetString("actor"))
if err != nil {
return err
}
trail, err := app.FindRecordById("trails", like.GetString("trail"))
if err != nil {
return err
}
trailAuthor, err := app.FindRecordById("activitypub_actors", trail.GetString("author"))
if err != nil {
return err
}
object := trail.GetString("iri")
if object == "" {
// trail is local
object = fmt.Sprintf("%s/api/v1/trail/%s", origin, trail.Id)
}
collection, err := app.FindCollectionByNameOrId("activitypub_activities")
if err != nil {
return err
}
recordId := security.RandomStringWithAlphabet(core.DefaultIdLength, core.DefaultIdAlphabet)
id := fmt.Sprintf("%s/api/v1/activitypub/activity/%s", origin, recordId)
activity := pub.LikeNew(pub.IRI(id), pub.IRI(object))
activity.Actor = pub.IRI(actor.GetString("iri"))
err = PostActivity(app, actor, activity, []string{trailAuthor.GetString("inbox")})
if err != nil {
return err
}
record := core.NewRecord(collection)
record.Set("id", recordId)
record.Set("iri", id)
record.Set("type", string(pub.LikeType))
record.Set("object", object)
record.Set("actor", actor.GetString("iri"))
record.Set("published", time.Now())
return app.Save(record)
}
// process incoming like activity
func ProcessLikeActivity(app core.App, actor *core.Record, activity pub.Activity) error {
origin := os.Getenv("ORIGIN")
if origin == "" {
return fmt.Errorf("ORIGIN not set")
}
trailId := path.Base(activity.Object.GetID().String())
trail, err := app.FindRecordById("trails", trailId)
if err != nil {
return err
}
trailAuthor, err := app.FindRecordById("activitypub_actors", trail.GetString("author"))
if err != nil {
return err
}
if !actor.GetBool("isLocal") {
trailLikeCollection, err := app.FindCollectionByNameOrId("trail_like")
if err != nil {
return err
}
likeRecord := core.NewRecord(trailLikeCollection)
likeRecord.Set("trail", trail.Id)
likeRecord.Set("actor", actor.Id)
err = app.Save(likeRecord)
if err != nil {
return err
}
}
// send a notification to the trail author
notification := util.Notification{
Type: util.TrailLike,
Metadata: map[string]string{
"trail_id": trail.Id,
"trail_name": trail.GetString("name"),
"trail_author": fmt.Sprintf("@%s", trailAuthor.GetString("username")),
"liker": fmt.Sprintf("@%s@%s", actor.GetString("username"), actor.GetString("domain")),
},
Seen: false,
Author: actor.Id,
}
return util.SendNotification(app, notification, trailAuthor)
}

194
db/federation/undo.go Normal file
View File

@@ -0,0 +1,194 @@
package federation
import (
"fmt"
"os"
"path"
"time"
pub "github.com/go-ap/activitypub"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/security"
)
// create outgoing follow activity
func CreateUnfollowActivity(app core.App, follow *core.Record) error {
origin := os.Getenv("ORIGIN")
if origin == "" {
return fmt.Errorf("ORIGIN not set")
}
follower := follow.GetString("follower")
followee := follow.GetString("followee")
followerActor, err := app.FindRecordById("activitypub_actors", follower)
if err != nil {
return err
}
followeeActor, err := app.FindRecordById("activitypub_actors", followee)
if err != nil {
return err
}
// find the original follow activity
followActivityRecord, err := app.FindFirstRecordByFilter("activitypub_activities", "actor={:actor}&&object={:object}&&type={:type}", dbx.Params{"actor": followerActor.GetString("iri"), "object": followeeActor.GetString("iri"), "type": string(pub.FollowType)})
if err != nil {
return err
}
followActivity := pub.FollowNew(pub.IRI(followActivityRecord.GetString("iri")), pub.IRI(followeeActor.GetString("iri")))
followActivity.Actor = pub.IRI(followActivityRecord.GetString("actor"))
collection, err := app.FindCollectionByNameOrId("activitypub_activities")
if err != nil {
return err
}
recordId := security.RandomStringWithAlphabet(core.DefaultIdLength, core.DefaultIdAlphabet)
id := fmt.Sprintf("%s/api/v1/activitypub/activity/%s", origin, recordId)
record := core.NewRecord(collection)
record.Set("id", recordId)
record.Set("iri", id)
record.Set("type", string(pub.UndoType))
record.Set("object", followActivity)
record.Set("actor", followerActor.GetString("iri"))
record.Set("published", time.Now())
err = app.Save(record)
if err != nil {
return err
}
activity := pub.UndoNew(pub.IRI(id), followActivity)
activity.Actor = pub.IRI(followerActor.GetString("iri"))
return PostActivity(app, followerActor, activity, []string{followeeActor.GetString("inbox")})
}
// create outgoing unlike activity
func CreateUnlikeActivity(app core.App, like *core.Record) error {
origin := os.Getenv("ORIGIN")
if origin == "" {
return fmt.Errorf("ORIGIN not set")
}
actor, err := app.FindRecordById("activitypub_actors", like.GetString("actor"))
if err != nil {
return err
}
trail, err := app.FindRecordById("trails", like.GetString("trail"))
if err != nil {
return err
}
trailAuthor, err := app.FindRecordById("activitypub_actors", trail.GetString("author"))
if err != nil {
return err
}
object := trail.GetString("iri")
if object == "" {
// trail is local
object = fmt.Sprintf("%s/api/v1/trail/%s", origin, trail.Id)
}
// find the original follow activity
likeActivityRecord, err := app.FindFirstRecordByFilter("activitypub_activities", "actor={:actor}&&object={:object}&&type={:type}", dbx.Params{"actor": actor.GetString("iri"), "object": object, "type": string(pub.LikeType)})
if err != nil {
return err
}
likeActivity := pub.LikeNew(pub.IRI(likeActivityRecord.GetString("iri")), pub.IRI(object))
likeActivity.Actor = pub.IRI(likeActivityRecord.GetString("actor"))
collection, err := app.FindCollectionByNameOrId("activitypub_activities")
if err != nil {
return err
}
recordId := security.RandomStringWithAlphabet(core.DefaultIdLength, core.DefaultIdAlphabet)
id := fmt.Sprintf("%s/api/v1/activitypub/activity/%s", origin, recordId)
record := core.NewRecord(collection)
record.Set("id", recordId)
record.Set("iri", id)
record.Set("type", string(pub.UndoType))
record.Set("object", likeActivity)
record.Set("actor", actor.GetString("iri"))
record.Set("published", time.Now())
err = app.Save(record)
if err != nil {
return err
}
activity := pub.UndoNew(pub.IRI(id), likeActivity)
activity.Actor = pub.IRI(actor.GetString("iri"))
return PostActivity(app, actor, activity, []string{trailAuthor.GetString("inbox")})
}
func ProcessUndoActivity(app core.App, actor *core.Record, activity pub.Activity) error {
if activity.Object.GetType() == pub.FollowType {
return processUnfollowActivity(app, actor, activity)
} else if activity.Object.GetType() == pub.LikeType {
return processUnlikeActivity(app, actor, activity)
} else {
return fmt.Errorf("unknown undo activity object type")
}
}
func processUnfollowActivity(app core.App, actor *core.Record, activity pub.Activity) error {
// this was a local follow
if actor.GetBool("isLocal") {
return nil
}
followActivity := activity.Object.(*pub.Activity)
followee, err := app.FindFirstRecordByData("activitypub_actors", "iri", followActivity.Object)
if err != nil {
return err
}
follow, err := app.FindFirstRecordByFilter("follows", "follower={:follower} && followee={:followee}", dbx.Params{"follower": actor.Id, "followee": followee.Id})
if err != nil {
return err
}
err = app.Delete(follow)
if err != nil {
return err
}
return nil
}
func processUnlikeActivity(app core.App, actor *core.Record, activity pub.Activity) error {
if actor.GetBool("isLocal") {
return nil
}
likeActivity := activity.Object.(*pub.Activity)
trailId := path.Base(likeActivity.Object.GetID().String())
trail, err := app.FindRecordById("trails", trailId)
if err != nil {
return err
}
like, err := app.FindFirstRecordByFilter("trail_like", "actor={:actor} && trail={:trail}", dbx.Params{"actor": actor.Id, "trail": trail.Id})
if err != nil {
return err
}
err = app.Delete(like)
if err != nil {
return err
}
return nil
}

View File

@@ -11,9 +11,15 @@ require (
)
require (
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/go-ap/errors v0.0.0-20250409143711-5686c11ae650 // indirect
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/twpayne/go-geom v1.6.1 // indirect
github.com/valyala/fastjson v1.6.4 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
)
@@ -26,6 +32,8 @@ require (
github.com/fatih/color v1.18.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/ganigeorgiev/fexpr v0.4.1 // indirect
github.com/go-ap/activitypub v0.0.0-20250409143848-7113328b1f3d
github.com/go-fed/httpsig v1.1.0
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
github.com/google/uuid v1.6.0 // indirect
@@ -34,6 +42,7 @@ require (
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/microcosm-cc/bluemonday v1.0.27
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/spf13/cast v1.7.1

View File

@@ -1,5 +1,7 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg=
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
github.com/alecthomas/assert/v2 v2.10.0 h1:jjRCHsj6hBJhkmhznrCzoNpbA3zqy0fYiUcYZP/GkPY=
github.com/alecthomas/assert/v2 v2.10.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
@@ -9,6 +11,8 @@ github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer5
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -28,6 +32,14 @@ github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3G
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/ganigeorgiev/fexpr v0.4.1 h1:hpUgbUEEWIZhSDBtf4M9aUNfQQ0BZkGRaMePy7Gcx5k=
github.com/ganigeorgiev/fexpr v0.4.1/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE=
github.com/go-ap/activitypub v0.0.0-20250409143848-7113328b1f3d h1:IWrWGnmKzpHqginJ18ljKkty/X8glxM8Mg3pk6bkb8g=
github.com/go-ap/activitypub v0.0.0-20250409143848-7113328b1f3d/go.mod h1:EUtZuXtHo4yKkTJmcbAZYW+X1G2poeT8icmBh24eq7o=
github.com/go-ap/errors v0.0.0-20250409143711-5686c11ae650 h1:tlwla5IQUea0CuktkBd2FLDwVzts4OeTWPPkhQPSK5Q=
github.com/go-ap/errors v0.0.0-20250409143711-5686c11ae650/go.mod h1:Vkh+Z3f24K8nMsJKXo1FHn5ebPsXvB/WDH5JRtYqdNo=
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 h1:GMKIYXyXPGIp+hYiWOhfqK4A023HdgisDT4YGgf99mw=
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73/go.mod h1:jyveZeGw5LaADntW+UEsMjl3IlIwk+DxlYNsbofQkGA=
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es=
github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
@@ -45,6 +57,8 @@ github.com/google/pprof v0.0.0-20250315033105-103756e64e1d h1:tx51Lf+wdE+aavqH8T
github.com/google/pprof v0.0.0-20250315033105-103756e64e1d/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@@ -63,6 +77,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/meilisearch/meilisearch-go v0.29.0 h1:HZ9NEKN59USINQ/DXJge/aaXq8IrsKbXGTdAoBaaDz4=
github.com/meilisearch/meilisearch-go v0.29.0/go.mod h1:2cRCAn4ddySUsFfNDLVPod/plRibQsJkXF/4gLhxbOk=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -99,7 +115,10 @@ github.com/twpayne/go-gpx v1.5.0 h1:HvFSJ+0r0sbhOQ8mTvd0/n0FhcgjTFsKQGG6o7PV6G4=
github.com/twpayne/go-gpx v1.5.0/go.mod h1:vjvu/125399qj6k+px2v2v8dm08DM4I4dFBJmHHt2TE=
github.com/twpayne/go-polyline v1.1.1 h1:/tSF1BR7rN4HWj4XKqvRUNrCiYVMCvywxTFVofvDV0w=
github.com/twpayne/go-polyline v1.1.1/go.mod h1:ybd9IWWivW/rlXPXuuckeKUyF3yrIim+iqA7kSl4NFY=
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
@@ -109,6 +128,7 @@ golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
@@ -117,6 +137,7 @@ golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=

View File

@@ -33,6 +33,15 @@ func SyncKomoot(app core.App) error {
}
userId := i.GetString("user")
actor, err := app.FindFirstRecordByData("activitypub_actors", "user", userId)
if err != nil {
warning := fmt.Sprintf("no actor found for user: %s\n", userId)
fmt.Print(warning)
app.Logger().Warn(warning)
continue
}
actorId := actor.Id
komootString := i.GetString("komoot")
komootIntegration := KomootIntegration{
Planned: true,
@@ -71,7 +80,7 @@ func SyncKomoot(app core.App) error {
continue
}
hasNewTours, err = syncTrailWithTours(app, k, komootIntegration, userId, tours)
hasNewTours, err = syncTrailWithTours(app, k, komootIntegration, userId, actorId, tours)
if err != nil {
warning := fmt.Sprintf("error syncing komoot tours with trails: %v\n", err)
fmt.Print(warning)
@@ -165,7 +174,7 @@ func (k *KomootApi) fetchTours(page int) ([]KomootTour, error) {
}
func (k *KomootApi) fetchDetailedTour(tour KomootTour) (*DetailedKomootTour, error) {
url := fmt.Sprintf("https://api.komoot.de/v007/tours/%d?_embedded=coordinates,way_types,surfaces,directions,participants,timeline,cover_images&directions=v2&fields=timeline&format=coordinate_array&timeline_highlights_fields=tips,recommenders", tour.ID)
url := fmt.Sprintf("https://api.komoot.de/v007/tours/%d?_embedded=coordinates,way_types,surfaces,directions,participants,timeline,cover_images&directions=v2&fields=timeline&format=coordinate_array&timeline_highlights_fields=tips,recommenders&page=2", tour.ID)
body, err := sendRequest(url, k.buildHeader())
if err != nil {
return nil, err
@@ -176,7 +185,7 @@ func (k *KomootApi) fetchDetailedTour(tour KomootTour) (*DetailedKomootTour, err
return data, nil
}
func syncTrailWithTours(app core.App, k *KomootApi, i KomootIntegration, user string, tours []KomootTour) (bool, error) {
func syncTrailWithTours(app core.App, k *KomootApi, i KomootIntegration, user string, actor string, tours []KomootTour) (bool, error) {
hasNewTours := false
for _, tour := range tours {
trails, err := app.FindRecordsByFilter("trails", "external_id = {:id}", "", 1, 0, dbx.Params{"id": strconv.Itoa(int(tour.ID))})
@@ -202,7 +211,7 @@ func syncTrailWithTours(app core.App, k *KomootApi, i KomootIntegration, user st
app.Logger().Warn(fmt.Sprintf("Unable to create waypoints for tour '%s': %v", tour.Name, err))
continue
}
err = createTrailFromTour(app, detailedTour, gpx, user, wpIds)
err = createTrailFromTour(app, k, detailedTour, gpx, actor, wpIds)
if err != nil {
app.Logger().Warn(fmt.Sprintf("Unable to create trail for tour '%s': %v", tour.Name, err))
continue
@@ -212,27 +221,9 @@ func syncTrailWithTours(app core.App, k *KomootApi, i KomootIntegration, user st
return hasNewTours, nil
}
func createTrailFromTour(app core.App, detailedTour *DetailedKomootTour, gpx *filesystem.File, user string, wpIds []string) error {
var summitLogRecord *core.Record
if detailedTour.Type == "tour_recorded" {
collection, err := app.FindCollectionByNameOrId("summit_logs")
if err != nil {
return err
}
func createTrailFromTour(app core.App, k *KomootApi, detailedTour *DetailedKomootTour, gpx *filesystem.File, actor string, wpIds []string) error {
trailid := security.RandomStringWithAlphabet(core.DefaultIdLength, core.DefaultIdAlphabet)
summitLogRecord = core.NewRecord(collection)
summitLogRecord.Load(map[string]any{
"distance": detailedTour.Distance,
"elevation_gain": detailedTour.ElevationUp,
"elevation_loss": detailedTour.ElevationDown,
"duration": detailedTour.Duration,
"date": detailedTour.Date,
"author": user,
})
if err := app.Save(summitLogRecord); err != nil {
return err
}
}
collection, err := app.FindCollectionByNameOrId("trails")
if err != nil {
return err
@@ -259,7 +250,7 @@ func createTrailFromTour(app core.App, detailedTour *DetailedKomootTour, gpx *fi
var photos []*filesystem.File
if len(detailedTour.Embedded.CoverImages.Embedded.Items) > 0 {
photos, err = fetchRoutePhotos(detailedTour)
photos, err = fetchRoutePhotos(k, detailedTour)
if err != nil {
return err
}
@@ -277,6 +268,7 @@ func createTrailFromTour(app core.App, detailedTour *DetailedKomootTour, gpx *fi
}
record.Load(map[string]any{
"id": trailid,
"name": detailedTour.Name,
"public": detailedTour.Status == "public",
"distance": detailedTour.Distance,
@@ -291,12 +283,9 @@ func createTrailFromTour(app core.App, detailedTour *DetailedKomootTour, gpx *fi
"difficulty": diffculty,
"category": categoryId,
"waypoints": wpIds,
"author": user,
"author": actor,
})
if summitLogRecord != nil {
record.Set("summit_logs", summitLogRecord.Id)
}
if photos != nil {
record.Set("photos", photos)
}
@@ -308,6 +297,27 @@ func createTrailFromTour(app core.App, detailedTour *DetailedKomootTour, gpx *fi
return err
}
if detailedTour.Type == "tour_recorded" {
collection, err := app.FindCollectionByNameOrId("summit_logs")
if err != nil {
return err
}
summitLogRecord := core.NewRecord(collection)
summitLogRecord.Load(map[string]any{
"distance": detailedTour.Distance,
"elevation_gain": detailedTour.ElevationUp,
"elevation_loss": detailedTour.ElevationDown,
"duration": detailedTour.Duration,
"date": detailedTour.Date,
"author": actor,
"trail": trailid,
})
if err := app.Save(summitLogRecord); err != nil {
return err
}
}
return nil
}
@@ -365,11 +375,22 @@ func createWaypointsFromTour(app core.App, tour *DetailedKomootTour, user string
return wpIds, nil
}
func fetchRoutePhotos(tour *DetailedKomootTour) ([]*filesystem.File, error) {
func fetchRoutePhotos(k *KomootApi, tour *DetailedKomootTour) ([]*filesystem.File, error) {
url := fmt.Sprintf("https://api.komoot.de/v007/tours/%d/cover_images/", tour.ID)
body, err := sendRequest(url, k.buildHeader())
if err != nil {
return nil, err
}
photos := make([]*filesystem.File, len(tour.Embedded.CoverImages.Embedded.Items))
var data *CoverImages
err = json.Unmarshal(body, &data)
if err != nil {
return nil, err
}
for i, img := range tour.Embedded.CoverImages.Embedded.Items {
photos := make([]*filesystem.File, data.Page.TotalElements)
for i, img := range data.Embedded.Items {
photo, err := fetchPhoto(img.Src, "", "")
if err != nil {
return nil, err

View File

@@ -36,9 +36,18 @@ func SyncStrava(app core.App) error {
}
userId := i.GetString("user")
actor, err := app.FindFirstRecordByData("activitypub_actors", "user", userId)
if err != nil {
warning := fmt.Sprintf("no actor found for user: %s\n", userId)
fmt.Print(warning)
app.Logger().Warn(warning)
continue
}
actorId := actor.Id
stravaString := i.GetString("strava")
var stravaIntegration StravaIntegration
err := json.Unmarshal([]byte(stravaString), &stravaIntegration)
err = json.Unmarshal([]byte(stravaString), &stravaIntegration)
if err != nil {
return err
}
@@ -103,7 +112,7 @@ func SyncStrava(app core.App) error {
app.Logger().Warn(warning)
break
}
hasNewRoutes, err = syncTrailsWithRoutes(app, r.AccessToken, userId, routes)
hasNewRoutes, err = syncTrailsWithRoutes(app, r.AccessToken, userId, actorId, routes)
if err != nil {
warning := fmt.Sprintf("error syncing strava routes with trails: %v\n", err)
fmt.Print(warning)
@@ -124,7 +133,7 @@ func SyncStrava(app core.App) error {
app.Logger().Warn(warning)
break
}
hasNewActivities, err = syncTrailsWithActivities(app, r.AccessToken, userId, activities)
hasNewActivities, err = syncTrailsWithActivities(app, r.AccessToken, userId, actorId, activities)
if err != nil {
warning := fmt.Sprintf("error syncing strava activities with trails: %v", err)
fmt.Print(warning)
@@ -226,7 +235,7 @@ func fetchStravaActivities(accessToken string, page int) ([]StravaActivity, erro
return activities, nil
}
func syncTrailsWithRoutes(app core.App, accessToken string, user string, routes []StravaRoute) (bool, error) {
func syncTrailsWithRoutes(app core.App, accessToken string, user string, actor string, routes []StravaRoute) (bool, error) {
hasNewRoutes := false
for _, route := range routes {
trails, err := app.FindRecordsByFilter("trails", "external_id = {:id}", "", 1, 0, dbx.Params{"id": route.IDStr})
@@ -247,7 +256,7 @@ func syncTrailsWithRoutes(app core.App, accessToken string, user string, routes
app.Logger().Warn(fmt.Sprintf("Unable to create waypoints for route '%s': %v", route.Name, err))
continue
}
err = createTrailFromRoute(app, route, gpx, user, wpIds)
err = createTrailFromRoute(app, route, gpx, actor, wpIds)
if err != nil {
app.Logger().Warn(fmt.Sprintf("Unable to create trail for route '%s': %v", route.Name, err))
continue
@@ -296,7 +305,7 @@ func fetchRouteGPX(route StravaRoute, accessToken string) (*filesystem.File, err
return gpxFile, nil
}
func createTrailFromRoute(app core.App, route StravaRoute, gpx *filesystem.File, user string, wpIds []string) error {
func createTrailFromRoute(app core.App, route StravaRoute, gpx *filesystem.File, actor string, wpIds []string) error {
collection, err := app.FindCollectionByNameOrId("trails")
if err != nil {
return err
@@ -342,7 +351,7 @@ func createTrailFromRoute(app core.App, route StravaRoute, gpx *filesystem.File,
"waypoints": wpIds,
"difficulty": "easy",
"category": category,
"author": user,
"author": actor,
})
if gpx != nil {
@@ -383,7 +392,7 @@ func createWaypointsFromRoute(app core.App, route StravaRoute, user string) ([]s
return wpIds, nil
}
func syncTrailsWithActivities(app core.App, accessToken string, user string, activities []StravaActivity) (bool, error) {
func syncTrailsWithActivities(app core.App, accessToken string, user string, actor string, activities []StravaActivity) (bool, error) {
hasNewActivites := false
for _, activity := range activities {
trails, err := app.FindRecordsByFilter("trails", "external_id = {:id}", "", 1, 0, dbx.Params{"id": strconv.Itoa(int(activity.ID))})
@@ -404,7 +413,7 @@ func syncTrailsWithActivities(app core.App, accessToken string, user string, act
app.Logger().Warn(fmt.Sprintf("Unable to fetch GPX for activity '%s': %v", activity.Name, err))
continue
}
err = createTrailFromActivity(app, detailedActivity, gpx, user)
err = createTrailFromActivity(app, detailedActivity, gpx, actor)
if err != nil {
app.Logger().Warn(fmt.Sprintf("Unable to create trail from activity '%s': %v", activity.Name, err))
continue

View File

@@ -1,13 +1,12 @@
package main
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"math/rand/v2"
"net/http"
"os"
"strconv"
"strings"
"time"
@@ -21,11 +20,15 @@ import (
"github.com/pocketbase/pocketbase/tools/security"
"github.com/spf13/cast"
"pocketbase/federation"
"pocketbase/integrations/komoot"
"pocketbase/integrations/strava"
_ "pocketbase/migrations"
"pocketbase/util"
pub "github.com/go-ap/activitypub"
"github.com/microcosm-cc/bluemonday"
)
const defaultMeiliMasterKey = "vODkljPcfFANYNepCHyDyGjzAMPcdHnrb6X5KyXQPWo"
@@ -86,18 +89,29 @@ func setupEventHandlers(app *pocketbase.PocketBase, client meilisearch.ServiceMa
app.OnRecordAfterUpdateSuccess("trails").BindFunc(updateTrailHandler(client))
app.OnRecordAfterDeleteSuccess("trails").BindFunc(deleteTrailHandler(client))
app.OnRecordAfterCreateSuccess("trail_share").BindFunc(createTrailShareHandler(client))
app.OnRecordAfterDeleteSuccess("trail_share").BindFunc(deleteTrailShareHandler(client))
app.OnRecordCreateRequest("summit_logs").BindFunc(createSummitLogHandler())
app.OnRecordUpdateRequest("summit_logs").BindFunc(updateSummitLogHandler())
app.OnRecordDeleteRequest("summit_logs").BindFunc(deleteSummitLogHandler())
app.OnRecordCreateRequest("comments").BindFunc(createCommentHandler())
app.OnRecordUpdateRequest("comments").BindFunc(updateCommentHandler())
app.OnRecordDeleteRequest("comments").BindFunc(deleteCommentHandler(client))
app.OnRecordCreateRequest("trail_share").BindFunc(createTrailShareHandler(client))
app.OnRecordDeleteRequest("trail_share").BindFunc(deleteTrailShareHandler(client))
app.OnRecordAfterCreateSuccess("trail_like").BindFunc(createTrailLikeHandler(client))
app.OnRecordAfterDeleteSuccess("trail_like").BindFunc(deleteTrailLikeHandler(client))
app.OnRecordAfterCreateSuccess("lists").BindFunc(createListHandler(client))
app.OnRecordAfterUpdateSuccess("lists").BindFunc(updateListHandler(client))
app.OnRecordAfterDeleteSuccess("lists").BindFunc(deleteListHandler(client))
app.OnRecordAfterCreateSuccess("list_share").BindFunc(createListShareHandler(client))
app.OnRecordAfterDeleteSuccess("list_share").BindFunc(deleteListShareHandler(client))
app.OnRecordCreateRequest("list_share").BindFunc(createListShareHandler(client))
app.OnRecordDeleteRequest("list_share").BindFunc(deleteListShareHandler(client))
app.OnRecordAfterCreateSuccess("follows").BindFunc(createFollowHandler())
app.OnRecordAfterCreateSuccess("comments").BindFunc(createCommentHandler())
app.OnRecordCreateRequest("follows").BindFunc(createFollowHandler())
app.OnRecordDeleteRequest("follows").BindFunc(deleteFollowHandler())
app.OnRecordsListRequest("integrations").BindFunc(listIntegrationHandler())
app.OnRecordCreate("integrations").BindFunc(createIntegrationHandler())
@@ -105,22 +119,72 @@ func setupEventHandlers(app *pocketbase.PocketBase, client meilisearch.ServiceMa
app.OnRecordUpdate("integrations").BindFunc(updateIntegrationHandler())
app.OnRecordAfterUpdateSuccess("integrations").BindFunc(createUpdateIntegrationSuccessHandler())
app.OnRecordCreateRequest().BindFunc(sanitizeHTML())
app.OnRecordUpdateRequest().BindFunc(sanitizeHTML())
app.OnRecordRequestEmailChangeRequest("users").BindFunc(changeUserEmailHandler())
app.OnServe().BindFunc(onBeforeServeHandler(client))
app.OnBootstrap().BindFunc(onBootstrapHandler())
}
func sanitizeHTML() func(e *core.RecordRequestEvent) error {
return func(e *core.RecordRequestEvent) error {
fieldsToSanitize := map[string][]string{
"lists": {"description"},
"settings": {"bio"},
"summit_logs": {"text"},
"trails": {"description"},
"comments": {"text"},
"waypoints": {"description"},
}
collection := e.Collection.Name
fields, ok := fieldsToSanitize[collection]
if !ok {
return e.Next()
}
p := bluemonday.NewPolicy()
p.AllowStandardAttributes()
p.AllowStandardURLs()
p.AllowLists()
p.AllowElements("br", "div", "hr", "p", "span", "wbr")
p.AllowElements("b", "strong", "em", "u", "blockquote", "a")
p.AllowAttrs("href").OnElements("a")
p.AllowAttrs("target").OnElements("a")
p.AllowAttrs("class").OnElements("a")
for _, field := range fields {
if val, ok := e.Record.Get(field).(string); ok {
sanitizedValue := p.Sanitize(val)
e.Record.Set(field, sanitizedValue)
}
}
return e.Next()
}
}
func createUserHandler(client meilisearch.ServiceManager) func(e *core.RecordEvent) error {
return func(e *core.RecordEvent) error {
userId := e.Record.Id
err := createDefaultUserSettings(e.App, e.Record.Id)
if err != nil {
return err
}
actor, err := util.ActorFromUser(e.App, e.Record)
if err != nil {
return err
}
searchRules := map[string]interface{}{
"lists": map[string]string{
"filter": "public = true OR author = " + userId + " OR shares = " + userId,
"filter": "public = true OR author = " + actor.Id + " OR shares = " + userId,
},
"trails": map[string]string{
"filter": "public = true OR author = " + userId + " OR shares = " + userId,
"filter": "public = true OR author = " + actor.Id + " OR shares = " + userId,
},
}
@@ -133,10 +197,6 @@ func createUserHandler(client meilisearch.ServiceManager) func(e *core.RecordEve
return err
}
err = createDefaultUserSettings(e.App, e.Record.Id)
if err != nil {
return err
}
return e.Next()
}
}
@@ -157,37 +217,38 @@ func createDefaultUserSettings(app core.App, userId string) error {
func createTrailHandler(client meilisearch.ServiceManager) func(e *core.RecordEvent) error {
return func(e *core.RecordEvent) error {
record := e.Record
author, err := e.App.FindRecordById("users", record.GetString(("author")))
author, err := e.App.FindRecordById("activitypub_actors", record.GetString(("author")))
if err != nil {
return err
}
if err := util.IndexTrail(e.App, record, author, client); err != nil {
return err
}
if record.GetBool("public") {
notification := util.Notification{
Type: util.TrailCreate,
Metadata: map[string]string{
"id": record.Id,
"trail": record.GetString("name"),
},
Seen: false,
Author: record.GetString("author"),
}
err = util.SendNotificationToFollowers(e.App, notification)
if err != nil {
return err
}
if !author.GetBool("isLocal") {
// this happens if someone fetches a remote trail
// we create a stub trail record for later reference
// no need to create an activity for that
return e.Next()
}
return e.Next()
err = e.Next()
if err != nil {
return err
}
err = federation.CreateTrailActivity(e.App, e.Record, pub.CreateType)
if err != nil {
return err
}
return nil
}
}
func updateTrailHandler(client meilisearch.ServiceManager) func(e *core.RecordEvent) error {
return func(e *core.RecordEvent) error {
record := e.Record
author, err := e.App.FindRecordById("users", record.GetString(("author")))
author, err := e.App.FindRecordById("activitypub_actors", record.GetString(("author")))
if err != nil {
return err
}
@@ -195,7 +256,24 @@ func updateTrailHandler(client meilisearch.ServiceManager) func(e *core.RecordEv
if err != nil {
return err
}
return e.Next()
if !author.GetBool("isLocal") {
// this happens if someone fetches a remote trail
// we create a stub trail record for later reference
// no need to create an activity for that
return e.Next()
}
err = e.Next()
if err != nil {
return err
}
err = federation.CreateTrailActivity(e.App, e.Record, pub.UpdateType)
if err != nil {
return err
}
return nil
}
}
@@ -213,12 +291,100 @@ func deleteTrailHandler(client meilisearch.ServiceManager) func(e *core.RecordEv
log.Fatalf("Error waiting for task completion: %v", err)
}
err = federation.CreateTrailDeleteActivity(e.App, e.Record)
if err != nil {
return err
}
return e.Next()
}
}
func createTrailShareHandler(client meilisearch.ServiceManager) func(e *core.RecordEvent) error {
return func(e *core.RecordEvent) error {
func createSummitLogHandler() func(e *core.RecordRequestEvent) error {
return func(e *core.RecordRequestEvent) error {
err := e.Next()
if err != nil {
return err
}
err = federation.CreateSummitLogActivity(e.App, e.Record, pub.CreateType)
if err != nil {
return err
}
return nil
}
}
func updateSummitLogHandler() func(e *core.RecordRequestEvent) error {
return func(e *core.RecordRequestEvent) error {
err := e.Next()
if err != nil {
return err
}
err = federation.CreateSummitLogActivity(e.App, e.Record, pub.UpdateType)
if err != nil {
return err
}
return nil
}
}
func deleteSummitLogHandler() func(e *core.RecordRequestEvent) error {
return func(e *core.RecordRequestEvent) error {
err := federation.CreateSummitLogDeleteActivity(e.App, e.Record)
if err != nil {
return err
}
return e.Next()
}
}
func createCommentHandler() func(e *core.RecordRequestEvent) error {
return func(e *core.RecordRequestEvent) error {
e.Next()
err := federation.CreateCommentActivity(e.App, e.Record, pub.CreateType)
if err != nil {
return err
}
return nil
}
}
func updateCommentHandler() func(e *core.RecordRequestEvent) error {
return func(e *core.RecordRequestEvent) error {
err := federation.CreateCommentActivity(e.App, e.Record, pub.UpdateType)
if err != nil {
return err
}
return e.Next()
}
}
func deleteCommentHandler(client meilisearch.ServiceManager) func(e *core.RecordRequestEvent) error {
return func(e *core.RecordRequestEvent) error {
err := federation.CreateCommentDeleteActivity(e.App, client, e.Record)
if err != nil {
return err
}
return e.Next()
}
}
func createTrailShareHandler(client meilisearch.ServiceManager) func(e *core.RecordRequestEvent) error {
return func(e *core.RecordRequestEvent) error {
err := e.Next()
if err != nil {
return err
}
record := e.Record
trailId := record.GetString("trail")
@@ -228,42 +394,26 @@ func createTrailShareHandler(client meilisearch.ServiceManager) func(e *core.Rec
if err != nil {
return err
}
userIds := make([]string, len(shares))
actorIds := make([]string, len(shares))
for i, r := range shares {
userIds[i] = r.GetString("user")
actorIds[i] = r.GetString("actor")
}
err = util.UpdateTrailShares(trailId, userIds, client)
err = util.UpdateTrailShares(trailId, actorIds, client)
if err != nil {
return err
}
if errs := e.App.ExpandRecord(record, []string{"trail", "trail.author"}, nil); len(errs) > 0 {
return fmt.Errorf("failed to expand: %v", errs)
}
shareTrail := record.ExpandedOne("trail")
shareTrailAuthor := shareTrail.ExpandedOne("author")
notification := util.Notification{
Type: util.TrailShare,
Metadata: map[string]string{
"id": shareTrail.Id,
"trail": shareTrail.GetString("name"),
"author": shareTrailAuthor.GetString("username"),
},
Seen: false,
Author: shareTrailAuthor.Id,
}
err = util.SendNotification(e.App, notification, record.GetString("user"))
err = federation.CreateAnnounceActivity(e.App, record, federation.TrailAnnounceType)
if err != nil {
return err
}
return e.Next()
return nil
}
}
func deleteTrailShareHandler(client meilisearch.ServiceManager) func(e *core.RecordEvent) error {
return func(e *core.RecordEvent) error {
func deleteTrailShareHandler(client meilisearch.ServiceManager) func(e *core.RecordRequestEvent) error {
return func(e *core.RecordRequestEvent) error {
record := e.Record
trailId := record.GetString("trail")
@@ -275,42 +425,178 @@ func deleteTrailShareHandler(client meilisearch.ServiceManager) func(e *core.Rec
}
}
func createTrailLikeHandler(client meilisearch.ServiceManager) func(e *core.RecordEvent) error {
return func(e *core.RecordEvent) error {
err := e.Next()
if err != nil {
return err
}
record := e.Record
trailId := record.GetString("trail")
actorId := record.GetString("actor")
actor, err := e.App.FindRecordById("activitypub_actors", actorId)
if err != nil {
return err
}
trail, err := e.App.FindRecordById("trails", trailId)
if err != nil {
return err
}
likes, err := e.App.FindAllRecords("trail_like",
dbx.NewExp("trail = {:trailId}", dbx.Params{"trailId": trailId}),
)
if err != nil {
return err
}
trail.Set("like_count", len(likes))
err = e.App.UnsafeWithoutHooks().Save(trail)
if err != nil {
return err
}
actorIds := make([]string, len(likes))
for i, r := range likes {
actorIds[i] = r.GetString("actor")
}
err = util.UpdateTrailLikes(trailId, actorIds, client)
if err != nil {
return err
}
if !actor.GetBool("isLocal") {
// this happens if someone likes a remote trail
// we create a local copy
// no need to create an activity for that
return nil
}
err = federation.CreateLikeActivity(e.App, record)
if err != nil {
return err
}
return nil
}
}
func deleteTrailLikeHandler(client meilisearch.ServiceManager) func(e *core.RecordEvent) error {
return func(e *core.RecordEvent) error {
record := e.Record
trailId := record.GetString("trail")
actorId := record.GetString("actor")
actor, err := e.App.FindRecordById("activitypub_actors", actorId)
if err != nil {
return err
}
// trail might deleted be already if this is called as part of a cascade
trail, err := e.App.FindRecordById("trails", trailId)
if err != nil && err == sql.ErrNoRows {
return nil
} else if err != nil {
return err
}
likes, err := e.App.CountRecords("trail_like", dbx.NewExp("trail={:trail}", dbx.Params{"trail": trailId}))
if err != nil {
return err
}
trail.Set("like_count", likes)
err = e.App.UnsafeWithoutHooks().Save(trail)
if err != nil {
return err
}
err = util.UpdateTrailLikes(trailId, []string{}, client)
if err != nil {
return err
}
if !actor.GetBool("isLocal") {
// this happens if someone likes a remote trail
// we create a local copy
// no need to create an activity for that
return nil
}
err = federation.CreateUnlikeActivity(e.App, record)
if err != nil {
return err
}
return e.Next()
}
}
func createListHandler(client meilisearch.ServiceManager) func(e *core.RecordEvent) error {
return func(e *core.RecordEvent) error {
record := e.Record
if err := util.IndexList(record, client); err != nil {
return err
}
if !record.GetBool("public") {
return e.Next()
}
notification := util.Notification{
Type: util.ListCreate,
Metadata: map[string]string{
"id": record.Id,
"list": record.GetString("name"),
},
Seen: false,
Author: record.GetString("author"),
}
err := util.SendNotificationToFollowers(e.App, notification)
author, err := e.App.FindRecordById("activitypub_actors", record.GetString(("author")))
if err != nil {
return err
}
return e.Next()
if err := util.IndexList(e.App, record, author, client); err != nil {
return err
}
if !author.GetBool("isLocal") {
// this happens if someone fetches a remote list
// we create a stub list record for later reference
// no need to create an activity for that
return e.Next()
}
err = e.Next()
if err != nil {
return err
}
err = federation.CreateListActivity(e.App, e.Record, pub.CreateType)
if err != nil {
return err
}
return nil
}
}
func updateListHandler(client meilisearch.ServiceManager) func(e *core.RecordEvent) error {
return func(e *core.RecordEvent) error {
record := e.Record
err := util.UpdateList(record, client)
author, err := e.App.FindRecordById("activitypub_actors", record.GetString(("author")))
if err != nil {
return err
}
return e.Next()
err = util.UpdateList(e.App, record, author, client)
if err != nil {
return err
}
if !author.GetBool("isLocal") {
// this happens if someone fetches a remote list
// we create a stub list record for later reference
// no need to create an activity for that
return e.Next()
}
err = e.Next()
if err != nil {
return err
}
err = federation.CreateListActivity(e.App, e.Record, pub.CreateType)
if err != nil {
return err
}
return nil
}
}
@@ -322,12 +608,22 @@ func deleteListHandler(client meilisearch.ServiceManager) func(e *core.RecordEve
return err
}
err = federation.CreateListDeleteActivity(e.App, record)
if err != nil {
return err
}
return e.Next()
}
}
func createListShareHandler(client meilisearch.ServiceManager) func(e *core.RecordEvent) error {
return func(e *core.RecordEvent) error {
func createListShareHandler(client meilisearch.ServiceManager) func(e *core.RecordRequestEvent) error {
return func(e *core.RecordRequestEvent) error {
err := e.Next()
if err != nil {
return err
}
record := e.Record
listId := record.GetString("list")
shares, err := e.App.FindAllRecords("list_share",
@@ -336,42 +632,27 @@ func createListShareHandler(client meilisearch.ServiceManager) func(e *core.Reco
if err != nil {
return err
}
userIds := make([]string, len(shares))
actorIds := make([]string, len(shares))
for i, r := range shares {
userIds[i] = r.GetString("user")
actorIds[i] = r.GetString("actor")
}
err = util.UpdateListShares(listId, userIds, client)
err = util.UpdateListShares(listId, actorIds, client)
if err != nil {
return err
}
if errs := e.App.ExpandRecord(record, []string{"list", "list.author"}, nil); len(errs) > 0 {
return fmt.Errorf("failed to expand: %v", errs)
}
shareList := record.ExpandedOne("list")
shareListAuthor := shareList.ExpandedOne("author")
notification := util.Notification{
Type: util.ListShare,
Metadata: map[string]string{
"id": shareList.Id,
"list": shareList.GetString("name"),
"author": shareListAuthor.GetString("username"),
},
Seen: false,
Author: shareListAuthor.Id,
}
err = util.SendNotification(e.App, notification, record.GetString("user"))
err = federation.CreateAnnounceActivity(e.App, record, federation.ListAnnounceType)
if err != nil {
return err
}
return e.Next()
return nil
}
}
func deleteListShareHandler(client meilisearch.ServiceManager) func(e *core.RecordEvent) error {
return func(e *core.RecordEvent) error {
func deleteListShareHandler(client meilisearch.ServiceManager) func(e *core.RecordRequestEvent) error {
return func(e *core.RecordRequestEvent) error {
record := e.Record
listId := record.GetString("list")
err := util.UpdateListShares(listId, []string{}, client)
@@ -382,55 +663,56 @@ func deleteListShareHandler(client meilisearch.ServiceManager) func(e *core.Reco
}
}
func createFollowHandler() func(e *core.RecordEvent) error {
return func(e *core.RecordEvent) error {
record := e.Record
if errs := e.App.ExpandRecord(record, []string{"follower"}, nil); len(errs) > 0 {
return fmt.Errorf("failed to expand: %v", errs)
}
follower := record.ExpandedOne("follower")
func createFollowHandler() func(e *core.RecordRequestEvent) error {
return func(e *core.RecordRequestEvent) error {
// record := e.Record
// if errs := e.App.ExpandRecord(record, []string{"follower"}, nil); len(errs) > 0 {
// return fmt.Errorf("failed to expand: %v", errs)
// }
// follower := record.ExpandedOne("follower")
notification := util.Notification{
Type: util.NewFollower,
Metadata: map[string]string{
"follower": follower.GetString("username"),
},
Seen: false,
Author: record.GetString("follower"),
}
err := util.SendNotification(e.App, notification, record.GetString("followee"))
if err != nil {
return err
}
return e.Next()
// notification := util.Notification{
// Type: util.NewFollower,
// Metadata: map[string]string{
// "follower": follower.GetString("username"),
// },
// Seen: false,
// Author: record.GetString("follower"),
// }
// err := util.SendNotification(e.App, notification, record.GetString("followee"))
// if err != nil {
// return err
// }
e.Next()
federation.CreateFollowActivity(e.App, e.Record)
return nil
}
}
func createCommentHandler() func(e *core.RecordEvent) error {
return func(e *core.RecordEvent) error {
record := e.Record
func deleteFollowHandler() func(e *core.RecordRequestEvent) error {
return func(e *core.RecordRequestEvent) error {
// record := e.Record
// if errs := e.App.ExpandRecord(record, []string{"follower"}, nil); len(errs) > 0 {
// return fmt.Errorf("failed to expand: %v", errs)
// }
// follower := record.ExpandedOne("follower")
if errs := e.App.ExpandRecord(record, []string{"trail", "author"}, nil); len(errs) > 0 {
return fmt.Errorf("failed to expand: %v", errs)
}
commentAuthor := record.ExpandedOne("author")
commentTrail := record.ExpandedOne("trail")
// notification := util.Notification{
// Type: util.NewFollower,
// Metadata: map[string]string{
// "follower": follower.GetString("username"),
// },
// Seen: false,
// Author: record.GetString("follower"),
// }
// err := util.SendNotification(e.App, notification, record.GetString("followee"))
// if err != nil {
// return err
// }
federation.CreateUnfollowActivity(e.App, e.Record)
notification := util.Notification{
Type: util.TrailComment,
Metadata: map[string]string{
"id": commentTrail.Id,
"author": commentAuthor.GetString("username"),
"trail": commentTrail.GetString("name"),
"comment": record.GetString("text"),
},
Seen: false,
Author: record.GetString("author"),
}
err := util.SendNotification(e.App, notification, commentTrail.GetString("author"))
if err != nil {
return err
}
return e.Next()
}
}
@@ -637,46 +919,6 @@ func registerRoutes(se *core.ServeEvent, client meilisearch.ServiceManager) {
}
return e.JSON(http.StatusOK, map[string]string{"token": token})
})
se.Router.GET("/trail/recommend", func(e *core.RequestEvent) error {
qSize := e.Request.URL.Query().Get("size")
size, err := strconv.Atoi(qSize)
if err != nil {
size = 4
}
userId := ""
if e.Auth != nil {
userId = e.Auth.Id
}
trails, err := e.App.FindRecordsByFilter(
"trails",
"author = {:userId} || public = true || ({:userId} != '' && trail_share_via_trail.user ?= {:userId})",
"",
-1,
0,
dbx.Params{"userId": userId},
)
if err != nil {
return err
}
for _, t := range trails {
errs := e.App.ExpandRecord(t, []string{"tags"}, nil)
if len(errs) > 0 {
return err
}
}
if len(trails) < size {
size = len(trails)
}
rand.Shuffle(len(trails), func(i, j int) {
trails[i], trails[j] = trails[j], trails[i]
})
randomTrails := trails[:size]
return e.JSON(http.StatusOK, randomTrails)
})
se.Router.POST("/integration/strava/token", func(e *core.RequestEvent) error {
encryptionKey := os.Getenv("POCKETBASE_ENCRYPTION_KEY")
@@ -792,6 +1034,61 @@ func registerRoutes(se *core.ServeEvent, client meilisearch.ServiceManager) {
return e.JSON(http.StatusOK, nil)
})
se.Router.POST("/activitypub/activity/process", federation.ProcessActivity)
se.Router.GET("/activitypub/actor", func(e *core.RequestEvent) error {
resource := e.Request.URL.Query().Get("resource")
resource = strings.TrimPrefix(resource, "acct:")
iri := e.Request.URL.Query().Get("iri")
follows := e.Request.URL.Query().Get("follows") == "true"
var actor *core.Record
var err error
if resource != "" {
actor, err = federation.GetActorByHandle(e.App, resource, follows)
} else {
actor, err = federation.GetActorByIRI(e.App, iri, follows)
}
if err != nil && actor == nil {
if strings.HasPrefix(err.Error(), "webfinger") || err.Error() == "profile is private" {
return e.NotFoundError("Not found", err)
}
return err
} else if err != nil && actor != nil {
// we could not fetch the remote actor so we return our local cached copy
return e.JSON(http.StatusOK, map[string]any{"actor": actor, "error": err.Error()})
}
return e.JSON(http.StatusOK, map[string]any{"actor": actor, "error": nil})
})
se.Router.GET("/activitypub/trail/{id}", func(e *core.RequestEvent) error {
id := e.Request.PathValue("id")
trail, err := e.App.FindRecordById("trails", id)
if err != nil {
return err
}
trailObject, err := util.ObjectFromTrail(e.App, trail, nil)
if err != nil {
return err
}
return e.JSON(http.StatusOK, trailObject)
})
se.Router.GET("/activitypub/comment/{id}", func(e *core.RequestEvent) error {
id := e.Request.PathValue("id")
comment, err := e.App.FindRecordById("comments", id)
if err != nil {
return err
}
commentObject, err := util.ObjectFromComment(e.App, comment, nil)
if err != nil {
return err
}
return e.JSON(http.StatusOK, commentObject)
})
}
func registerCronJobs(app core.App) {
@@ -818,7 +1115,7 @@ func registerCronJobs(app core.App) {
func bootstrapData(app core.App, client meilisearch.ServiceManager) error {
bootstrapCategories(app)
bootstrapMeilisearchTrails(app, client)
go bootstrapMeilisearchDocuments(app, client)
return nil
}
@@ -847,7 +1144,7 @@ func bootstrapCategories(app core.App) error {
return nil
}
func bootstrapMeilisearchTrails(app core.App, client meilisearch.ServiceManager) error {
func bootstrapMeilisearchDocuments(app core.App, client meilisearch.ServiceManager) error {
query := app.RecordQuery("trails")
trails := []*core.Record{}
@@ -860,7 +1157,7 @@ func bootstrapMeilisearchTrails(app core.App, client meilisearch.ServiceManager)
return err
}
for _, trail := range trails {
author, err := app.FindRecordById("users", trail.GetString(("author")))
author, err := app.FindRecordById("activitypub_actors", trail.GetString(("author")))
if err != nil {
return err
}
@@ -875,16 +1172,67 @@ func bootstrapMeilisearchTrails(app core.App, client meilisearch.ServiceManager)
if err != nil {
return err
}
userIds := make([]string, len(shares))
actorIds := make([]string, len(shares))
for i, r := range shares {
userIds[i] = r.GetString("user")
actorIds[i] = r.GetString("actor")
}
err = util.UpdateTrailShares(trail.Id, userIds, client)
err = util.UpdateTrailShares(trail.Id, actorIds, client)
if err != nil {
app.Logger().Warn(fmt.Sprintf("Unable to update trail shares '%s': %v", trail.GetString("name"), err))
continue
}
likes, err := app.FindAllRecords("trail_like",
dbx.NewExp("trail = {:trailId}", dbx.Params{"trailId": trail.Id}),
)
if err != nil {
return err
}
actorIds = make([]string, len(likes))
for i, r := range likes {
actorIds[i] = r.GetString("actor")
}
err = util.UpdateTrailLikes(trail.Id, actorIds, client)
if err != nil {
app.Logger().Warn(fmt.Sprintf("Unable to update trail likes '%s': %v", trail.GetString("name"), err))
continue
}
}
lists, err := app.FindAllRecords("lists")
if err != nil {
return err
}
_, err = client.Index("lists").DeleteAllDocuments()
if err != nil {
return err
}
for _, list := range lists {
author, err := app.FindRecordById("activitypub_actors", list.GetString(("author")))
if err != nil {
return err
}
if err := util.IndexList(app, list, author, client); err != nil {
app.Logger().Warn(fmt.Sprintf("Unable to index list '%s': %v", list.GetString("name"), err))
continue
}
shares, err := app.FindAllRecords("list_share",
dbx.NewExp("list = {:listId}", dbx.Params{"listId": list.Id}),
)
if err != nil {
return err
}
actorIds := make([]string, len(shares))
for i, r := range shares {
actorIds[i] = r.GetString("actor")
}
err = util.UpdateListShares(list.Id, actorIds, client)
if err != nil {
app.Logger().Warn(fmt.Sprintf("Unable to update list shares '%s': %v", list.GetString("name"), err))
continue
}
}
return nil
}

View File

@@ -0,0 +1,102 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("j6w72f0kb5ivd7x")
if err != nil {
return nil
}
return app.Delete(collection)
}, func(app core.App) error {
jsonData := `{
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "",
"hidden": false,
"id": "text3208210256",
"max": 0,
"min": 0,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"cascadeDelete": false,
"collectionId": "pbc_1295301207",
"hidden": false,
"id": "relation1148540665",
"maxSelect": 1,
"minSelect": 0,
"name": "actor",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "_clone_kce8",
"maxSelect": 1,
"minSelect": 0,
"name": "user",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"hidden": false,
"id": "json2215181735",
"maxSize": 1,
"name": "followers",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json1908379107",
"maxSize": 1,
"name": "following",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}
],
"id": "j6w72f0kb5ivd7x",
"indexes": [],
"listRule": "@request.auth.id = user || (@collection.users_anonymous.id ?= user && @collection.users_anonymous.private ?= false)",
"name": "follow_counts",
"system": false,
"type": "view",
"updateRule": null,
"viewQuery": "SELECT \n (ROW_NUMBER() OVER()) as id,\n activitypub_actors.id as actor,\n activitypub_actors.user as user,\n COALESCE(followers.count, 0) AS followers,\n COALESCE(following.count, 0) AS following\nFROM activitypub_actors\nLEFT JOIN (\n SELECT followee AS actor_id, COUNT(*) AS count\n FROM follows\n GROUP BY followee\n) AS followers ON activitypub_actors.id = followers.actor_id\nLEFT JOIN (\n SELECT follower AS actor_id, COUNT(*) AS count\n FROM follows\n GROUP BY follower\n) AS following ON activitypub_actors.id = following.actor_id",
"viewRule": "@request.auth.id = user || (@collection.users_anonymous.id ?= user && @collection.users_anonymous.private ?= false)"
}`
collection := &core.Collection{}
if err := json.Unmarshal([]byte(jsonData), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,186 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("t9lphichi5xwyeu")
if err != nil {
return nil
}
return app.Delete(collection)
}, func(app core.App) error {
jsonData := `{
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "",
"hidden": false,
"id": "text3208210256",
"max": 0,
"min": 0,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"hidden": false,
"id": "json2310347867",
"maxSize": 1,
"name": "trail_id",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json2862495610",
"maxSize": 1,
"name": "date",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json1579384326",
"maxSize": 1,
"name": "name",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json1843675174",
"maxSize": 1,
"name": "description",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json3275261007",
"maxSize": 1,
"name": "gpx",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json3182418120",
"maxSize": 1,
"name": "author",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json142008537",
"maxSize": 1,
"name": "photos",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json479369857",
"maxSize": 1,
"name": "distance",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json2254405824",
"maxSize": 1,
"name": "duration",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json3015100073",
"maxSize": 1,
"name": "elevation_gain",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json3171089056",
"maxSize": 1,
"name": "elevation_loss",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json2990389176",
"maxSize": 1,
"name": "created",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json2363381545",
"maxSize": 1,
"name": "type",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}
],
"id": "t9lphichi5xwyeu",
"indexes": [],
"listRule": "(@collection.users_anonymous.id ?= author && @collection.users_anonymous.private ?= false\n&&\n@collection.trails.id ?= trail_id && \n (\n @collection.trails.author ?= @request.auth.id ||\n @collection.trails.public ?= true || \n (@request.auth.id != \"\" && @collection.trails.trail_share_via_trail.user ?= @request.auth.id)\n )) && @request.auth.id = author",
"name": "activities",
"system": false,
"type": "view",
"updateRule": null,
"viewQuery": "SELECT id,trail_id,date,name,description,gpx,author,photos,distance,duration,elevation_gain,elevation_loss,created,type \nFROM (\n SELECT summit_logs.id,trails.id as trail_id,summit_logs.date,trails.name,text as description,summit_logs.gpx,summit_logs.author,summit_logs.photos,summit_logs.distance,summit_logs.duration,summit_logs.elevation_gain,summit_logs.elevation_loss,summit_logs.created,\"summit_log\" as type \n FROM summit_logs\n JOIN trails ON summit_logs.id IN (\n SELECT value\n FROM json_each(trails.summit_logs)\n )\n UNION\n SELECT id,id as trail_id,date,name,description,gpx,author,photos,distance,duration,elevation_gain,elevation_loss,created,\"trail\" as type \n FROM trails\n)\nORDER BY created DESC",
"viewRule": "(@collection.users_anonymous.id ?= author && @collection.users_anonymous.private ?= false\n&&\n@collection.trails.id ?= trail_id && \n (\n @collection.trails.author ?= @request.auth.id ||\n @collection.trails.public ?= true || \n (@request.auth.id != \"\" && @collection.trails.trail_share_via_trail.user ?= @request.auth.id)\n )) && @request.auth.id = author"
}`
collection := &core.Collection{}
if err := json.Unmarshal([]byte(jsonData), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,285 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
jsonData := `[
{
"id": "pbc_1295301207",
"listRule": "",
"viewRule": "",
"createRule": null,
"updateRule": null,
"deleteRule": null,
"name": "activitypub_actors",
"type": "base",
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text4166911607",
"max": 0,
"min": 0,
"name": "username",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text4002953752",
"max": 0,
"min": 0,
"name": "preferred_username",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text2812878347",
"max": 0,
"min": 0,
"name": "domain",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text3458754147",
"max": 0,
"min": 0,
"name": "summary",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "number1386272118",
"max": null,
"min": null,
"name": "followerCount",
"onlyInt": true,
"presentable": false,
"required": false,
"system": false,
"type": "number"
},
{
"hidden": false,
"id": "number3430500629",
"max": null,
"min": null,
"name": "followingCount",
"onlyInt": true,
"presentable": false,
"required": false,
"system": false,
"type": "number"
},
{
"hidden": false,
"id": "date1748787223",
"max": "",
"min": "",
"name": "published",
"presentable": false,
"required": false,
"system": false,
"type": "date"
},
{
"exceptDomains": null,
"hidden": false,
"id": "url126331327",
"name": "iri",
"onlyDomains": null,
"presentable": false,
"required": true,
"system": false,
"type": "url"
},
{
"exceptDomains": null,
"hidden": false,
"id": "url2115105593",
"name": "inbox",
"onlyDomains": null,
"presentable": false,
"required": true,
"system": false,
"type": "url"
},
{
"exceptDomains": null,
"hidden": false,
"id": "url1793578352",
"name": "outbox",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
},
{
"exceptDomains": null,
"hidden": false,
"id": "url1704208859",
"name": "icon",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
},
{
"exceptDomains": null,
"hidden": false,
"id": "url2215181735",
"name": "followers",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
},
{
"exceptDomains": null,
"hidden": false,
"id": "url1908379107",
"name": "following",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
},
{
"hidden": false,
"id": "bool2193750486",
"name": "isLocal",
"presentable": false,
"required": false,
"system": false,
"type": "bool"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text1727648867",
"max": 0,
"min": 0,
"name": "public_key",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": true,
"id": "text4160324774",
"max": 0,
"min": 0,
"name": "private_key",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "relation2375276105",
"maxSelect": 1,
"minSelect": 0,
"name": "user",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"hidden": false,
"id": "date2062531289",
"max": "",
"min": "",
"name": "last_fetched",
"presentable": false,
"required": false,
"system": false,
"type": "date"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"indexes": [
"CREATE UNIQUE INDEX ` + "`idx_rpT7QJwWTm` ON `activitypub_actors` (`iri`)" + `"
],
"system": false
}
]`
return app.ImportCollectionsByMarshaledJSON([]byte(jsonData), false)
}, func(app core.App) error {
return nil
})
}

View File

@@ -0,0 +1,156 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
jsonData := `[
{
"id": "pbc_3752774184",
"listRule": "",
"viewRule": "",
"createRule": null,
"updateRule": null,
"deleteRule": null,
"name": "activitypub_activities",
"type": "base",
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"exceptDomains": [],
"hidden": false,
"id": "url2434853685",
"name": "iri",
"onlyDomains": [],
"presentable": false,
"required": true,
"system": false,
"type": "url"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text2363381545",
"max": 0,
"min": 0,
"name": "type",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "json3616002756",
"maxSize": 0,
"name": "to",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json3685882489",
"maxSize": 0,
"name": "cc",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json2893285722",
"maxSize": 0,
"name": "object",
"presentable": false,
"required": true,
"system": false,
"type": "json"
},
{
"exceptDomains": [],
"hidden": false,
"id": "url1148540665",
"name": "actor",
"onlyDomains": [],
"presentable": false,
"required": true,
"system": false,
"type": "url"
},
{
"hidden": false,
"id": "date1748787223",
"max": "",
"min": "",
"name": "published",
"presentable": false,
"required": true,
"system": false,
"type": "date"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text1653163849",
"max": 15,
"min": 15,
"name": "relation",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"indexes": [],
"system": false
}
]`
return app.ImportCollectionsByMarshaledJSON([]byte(jsonData), false)
}, func(app core.App) error {
return nil
})
}

View File

@@ -0,0 +1,30 @@
package migrations
import (
"pocketbase/util"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
users, err := app.FindAllRecords("users")
if err != nil {
return err
}
for _, u := range users {
_, err = util.ActorFromUser(app, u)
if err != nil {
return err
}
}
return nil
}, func(app core.App) error {
// add down queries...
return nil
})
}

View File

@@ -0,0 +1,78 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4")
if err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(15, []byte(`{
"cascadeDelete": true,
"collectionId": "pbc_1295301207",
"hidden": false,
"id": "relation3182418120",
"maxSelect": 1,
"minSelect": 0,
"name": "author",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
// update field
if err := collection.Fields.AddMarshaledJSONAt(16, []byte(`{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "1utgul91",
"maxSelect": 1,
"minSelect": 0,
"name": "user",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4")
if err != nil {
return err
}
// remove field
collection.Fields.RemoveById("relation3182418120")
// update field
if err := collection.Fields.AddMarshaledJSONAt(15, []byte(`{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "1utgul91",
"maxSelect": 1,
"minSelect": 0,
"name": "author",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,33 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
trails, err := app.FindAllRecords("trails")
if err != nil {
return err
}
for _, t := range trails {
actor, err := app.FindFirstRecordByData("activitypub_actors", "user", t.GetString("user"))
if err != nil {
return err
}
t.Set("author", actor.Id)
err = app.UnsafeWithoutHooks().Save(t)
if err != nil {
return err
}
}
return nil
}, func(app core.App) error {
// add down queries...
return nil
})
}

View File

@@ -0,0 +1,78 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("comments")
if err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(3, []byte(`{
"cascadeDelete": false,
"collectionId": "pbc_1295301207",
"hidden": false,
"id": "relation3182418120",
"maxSelect": 1,
"minSelect": 0,
"name": "author",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
// update field
if err := collection.Fields.AddMarshaledJSONAt(16, []byte(`{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "7lwo1mxx",
"maxSelect": 1,
"minSelect": 0,
"name": "user",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("comments")
if err != nil {
return err
}
// remove field
collection.Fields.RemoveById("relation3182418120")
// update field
if err := collection.Fields.AddMarshaledJSONAt(15, []byte(`{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "7lwo1mxx",
"maxSelect": 1,
"minSelect": 0,
"name": "author",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,33 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
comments, err := app.FindAllRecords("comments")
if err != nil {
return err
}
for _, c := range comments {
actor, err := app.FindFirstRecordByData("activitypub_actors", "user", c.GetString("user"))
if err != nil {
continue
}
c.Set("author", actor.Id)
err = app.UnsafeWithoutHooks().Save(c)
if err != nil {
continue
}
}
return nil
}, func(app core.App) error {
// add down queries...
return nil
})
}

View File

@@ -0,0 +1,78 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("summit_logs")
if err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(9, []byte(`{
"cascadeDelete": true,
"collectionId": "pbc_1295301207",
"hidden": false,
"id": "relation3182418120",
"maxSelect": 1,
"minSelect": 0,
"name": "author",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
// update field
if err := collection.Fields.AddMarshaledJSONAt(16, []byte(`{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "r0mj3tkr",
"maxSelect": 1,
"minSelect": 0,
"name": "user",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("summit_logs")
if err != nil {
return err
}
// remove field
collection.Fields.RemoveById("relation3182418120")
// update field
if err := collection.Fields.AddMarshaledJSONAt(15, []byte(`{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "r0mj3tkr",
"maxSelect": 1,
"minSelect": 0,
"name": "author",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,46 @@
package migrations
import (
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
logs, err := app.FindAllRecords("summit_logs")
if err != nil {
return err
}
for _, l := range logs {
if l.GetString("user") == "" {
trail, err := app.FindFirstRecordByFilter("trails", "summit_logs ?~ {:id}", dbx.Params{"id": l.Id})
if err != nil {
// orphaned
err = app.UnsafeWithoutHooks().Delete(l)
if err != nil {
return err
}
continue
}
l.Set("user", trail.GetString("user"))
}
actor, err := app.FindFirstRecordByData("activitypub_actors", "user", l.GetString("user"))
if err != nil {
return err
}
l.Set("author", actor.Id)
err = app.UnsafeWithoutHooks().Save(l)
if err != nil {
return err
}
}
return nil
}, func(app core.App) error {
// add down queries...
return nil
})
}

View File

@@ -0,0 +1,55 @@
package migrations
import (
"os"
"pocketbase/util"
"github.com/meilisearch/meilisearch-go"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
client := meilisearch.New(os.Getenv("MEILI_URL"), meilisearch.WithAPIKey(os.Getenv("MEILI_MASTER_KEY")))
m.Register(func(app core.App) error {
users, err := app.FindAllRecords("users")
if err != nil {
return err
}
for _, u := range users {
userId := u.Id
actor, err := app.FindFirstRecordByData("activitypub_actors", "user", userId)
if err != nil {
return err
}
searchRules := map[string]interface{}{
"lists": map[string]string{
"filter": "public = true OR author = " + actor.Id + " OR shares = " + userId,
},
"trails": map[string]string{
"filter": "public = true OR author = " + actor.Id + " OR shares = " + userId,
},
}
token, err := util.GenerateMeilisearchToken(searchRules, client)
if err != nil {
return err
}
u.Set("token", token)
if err := app.Save(u); err != nil {
return err
}
}
return nil
}, func(app core.App) error {
// add down queries...
return nil
})
}

View File

@@ -0,0 +1,44 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("dd2l9a4vxpy2ni8")
if err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(10, []byte(`{
"cascadeDelete": true,
"collectionId": "e864strfxo14pm4",
"hidden": false,
"id": "relation2993194383",
"maxSelect": 1,
"minSelect": 0,
"name": "trail",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("dd2l9a4vxpy2ni8")
if err != nil {
return err
}
// remove field
collection.Fields.RemoveById("relation2993194383")
return app.Save(collection)
})
}

View File

@@ -0,0 +1,34 @@
package migrations
import (
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
logs, err := app.FindAllRecords("summit_logs")
if err != nil {
return err
}
for _, l := range logs {
trail, err := app.FindFirstRecordByFilter("trails", "summit_logs ?~ {:id}", dbx.Params{"id": l.Id})
if err != nil {
continue
}
l.Set("trail", trail.Id)
err = app.UnsafeWithoutHooks().Save(l)
if err != nil {
return err
}
}
return nil
}, func(app core.App) error {
// add down queries...
return nil
})
}

View File

@@ -0,0 +1,44 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4")
if err != nil {
return err
}
// remove field
collection.Fields.RemoveById("e1lwowvd")
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4")
if err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(17, []byte(`{
"cascadeDelete": false,
"collectionId": "dd2l9a4vxpy2ni8",
"hidden": false,
"id": "e1lwowvd",
"maxSelect": 2147483647,
"minSelect": 0,
"name": "summit_logs",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,150 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("8obn1ukumze565i")
if err != nil {
return err
}
// update field
if err := collection.Fields.AddMarshaledJSONAt(1, []byte(`{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "in1traur",
"maxSelect": 1,
"minSelect": 0,
"name": "old_follower",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
// update field
if err := collection.Fields.AddMarshaledJSONAt(2, []byte(`{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "wxwomfd5",
"maxSelect": 1,
"minSelect": 0,
"name": "old_followee",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(3, []byte(`{
"hidden": false,
"id": "select2063623452",
"maxSelect": 1,
"name": "status",
"presentable": false,
"required": true,
"system": false,
"type": "select",
"values": [
"pending",
"accepted"
]
}`)); err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(1, []byte(`{
"cascadeDelete": true,
"collectionId": "pbc_1295301207",
"hidden": false,
"id": "relation3117812038",
"maxSelect": 1,
"minSelect": 0,
"name": "follower",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(2, []byte(`{
"cascadeDelete": true,
"collectionId": "pbc_1295301207",
"hidden": false,
"id": "relation973442177",
"maxSelect": 1,
"minSelect": 0,
"name": "followee",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("8obn1ukumze565i")
if err != nil {
return err
}
// update field
if err := collection.Fields.AddMarshaledJSONAt(1, []byte(`{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "in1traur",
"maxSelect": 1,
"minSelect": 0,
"name": "follower",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
// update field
if err := collection.Fields.AddMarshaledJSONAt(2, []byte(`{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "wxwomfd5",
"maxSelect": 1,
"minSelect": 0,
"name": "followee",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
// remove field
collection.Fields.RemoveById("relation3117812038")
// remove field
collection.Fields.RemoveById("relation973442177")
return app.Save(collection)
})
}

View File

@@ -0,0 +1,60 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
follows, err := app.FindAllRecords("follows")
if err != nil {
return err
}
seen := map[string]string{}
for _, f := range follows {
oldFollower := f.GetString("old_follower")
oldFollowee := f.GetString("old_followee")
if oldFollowee == "" || oldFollower == "" {
continue
}
seenFollowee, ok := seen[oldFollower]
if ok && seenFollowee == oldFollowee {
err = app.Delete(f)
if err != nil {
return err
}
continue
}
seen[oldFollower] = oldFollowee
followerActor, err := app.FindFirstRecordByData("activitypub_actors", "user", oldFollower)
if err != nil {
return err
}
followeeActor, err := app.FindFirstRecordByData("activitypub_actors", "user", oldFollowee)
if err != nil {
return err
}
f.Set("follower", followerActor.Id)
f.Set("followee", followeeActor.Id)
f.Set("status", "accepted")
err = app.UnsafeWithoutHooks().Save(f)
if err != nil {
return err
}
}
return nil
}, func(app core.App) error {
// add down queries...
return nil
})
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,44 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("lf06qip3f4d11yk")
if err != nil {
return err
}
// remove field
collection.Fields.RemoveById("7lwo1mxx")
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("lf06qip3f4d11yk")
if err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(6, []byte(`{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "7lwo1mxx",
"maxSelect": 1,
"minSelect": 0,
"name": "user",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,44 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("dd2l9a4vxpy2ni8")
if err != nil {
return err
}
// remove field
collection.Fields.RemoveById("r0mj3tkr")
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("dd2l9a4vxpy2ni8")
if err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(13, []byte(`{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "r0mj3tkr",
"maxSelect": 1,
"minSelect": 0,
"name": "user",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,44 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4")
if err != nil {
return err
}
// remove field
collection.Fields.RemoveById("1utgul91")
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4")
if err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(24, []byte(`{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "1utgul91",
"maxSelect": 1,
"minSelect": 0,
"name": "user",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,26 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("8obn1ukumze565i")
if err != nil {
return err
}
// // remove field
collection.Fields.RemoveById("in1traur")
// // remove field
collection.Fields.RemoveById("wxwomfd5")
return app.Save(collection)
}, func(app core.App) error {
return nil
})
}

View File

@@ -0,0 +1,186 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
jsonData := `{
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "",
"hidden": false,
"id": "text3208210256",
"max": 0,
"min": 0,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"hidden": false,
"id": "json2310347867",
"maxSize": 1,
"name": "trail_id",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json2862495610",
"maxSize": 1,
"name": "date",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json1579384326",
"maxSize": 1,
"name": "name",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json1843675174",
"maxSize": 1,
"name": "description",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json3275261007",
"maxSize": 1,
"name": "gpx",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json3182418120",
"maxSize": 1,
"name": "author",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json142008537",
"maxSize": 1,
"name": "photos",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json479369857",
"maxSize": 1,
"name": "distance",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json2254405824",
"maxSize": 1,
"name": "duration",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json3015100073",
"maxSize": 1,
"name": "elevation_gain",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json3171089056",
"maxSize": 1,
"name": "elevation_loss",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json2990389176",
"maxSize": 1,
"name": "created",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json2363381545",
"maxSize": 1,
"name": "type",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}
],
"id": "pbc_468398817",
"indexes": [],
"listRule": "@collection.trails.id ?= trail_id && @collection.trails.public ?= true",
"name": "timeline",
"system": false,
"type": "view",
"updateRule": null,
"viewQuery": "-- database: /Users/christianbeutel/Documents/svelte/wanderer/db/pb_data/data.db\nSELECT\n (ROW_NUMBER() OVER ()) as id,\n trail_id,\n date,\n name,\n description,\n gpx,\n author,\n photos,\n distance,\n duration,\n elevation_gain,\n elevation_loss,\n created,\n type\nFROM\n (\n SELECT\n summit_logs.trail as trail_id,\n summit_logs.date,\n trails.name,\n text as description,\n summit_logs.gpx,\n activitypub_actors.iri as author,\n summit_logs.photos,\n summit_logs.distance,\n summit_logs.duration,\n summit_logs.elevation_gain,\n summit_logs.elevation_loss,\n summit_logs.created,\n \"summit_log\" as type\n FROM\n summit_logs\n JOIN trails ON summit_logs.trail = trails.id\n JOIN activitypub_actors ON activitypub_actors.id = summit_logs.author\n UNION\n SELECT\n trails.id as trail_id,\n date,\n trails.name,\n description,\n gpx,\n activitypub_actors.iri as author,\n photos,\n distance,\n duration,\n elevation_gain,\n elevation_loss,\n trails.created,\n \"trail\" as type\n FROM\n trails\n JOIN activitypub_actors ON activitypub_actors.id = trails.author\n )\nORDER BY\n created DESC;\n",
"viewRule": "@collection.trails.id ?= trail_id && @collection.trails.public ?= true"
}`
collection := &core.Collection{}
if err := json.Unmarshal([]byte(jsonData), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("pbc_468398817")
if err != nil {
return err
}
return app.Delete(collection)
})
}

View File

@@ -0,0 +1,40 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("pbc_468398817")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"viewQuery": "SELECT\n (ROW_NUMBER() OVER ()) as id,\n trail_id,\n date,\n name,\n description,\n gpx,\n author,\n photos,\n distance,\n duration,\n elevation_gain,\n elevation_loss,\n created,\n type\nFROM\n (\n SELECT\n summit_logs.trail as trail_id,\n summit_logs.date,\n trails.name,\n text as description,\n summit_logs.gpx,\n activitypub_actors.iri as author,\n summit_logs.photos,\n summit_logs.distance,\n summit_logs.duration,\n summit_logs.elevation_gain,\n summit_logs.elevation_loss,\n summit_logs.created,\n \"summit_log\" as type\n FROM\n summit_logs\n JOIN trails ON summit_logs.trail = trails.id\n JOIN activitypub_actors ON activitypub_actors.id = summit_logs.author\n UNION\n SELECT\n trails.id as trail_id,\n date,\n trails.name,\n description,\n gpx,\n activitypub_actors.iri as author,\n photos,\n distance,\n duration,\n elevation_gain,\n elevation_loss,\n trails.created,\n \"trail\" as type\n FROM\n trails\n JOIN activitypub_actors ON activitypub_actors.id = trails.author\n )\nORDER BY\n created DESC;\n"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("pbc_468398817")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"viewQuery": "-- database: /Users/christianbeutel/Documents/svelte/wanderer/db/pb_data/data.db\nSELECT\n (ROW_NUMBER() OVER ()) as id,\n trail_id,\n date,\n name,\n description,\n gpx,\n author,\n photos,\n distance,\n duration,\n elevation_gain,\n elevation_loss,\n created,\n type\nFROM\n (\n SELECT\n summit_logs.trail as trail_id,\n summit_logs.date,\n trails.name,\n text as description,\n summit_logs.gpx,\n activitypub_actors.iri as author,\n summit_logs.photos,\n summit_logs.distance,\n summit_logs.duration,\n summit_logs.elevation_gain,\n summit_logs.elevation_loss,\n summit_logs.created,\n \"summit_log\" as type\n FROM\n summit_logs\n JOIN trails ON summit_logs.trail = trails.id\n JOIN activitypub_actors ON activitypub_actors.id = summit_logs.author\n UNION\n SELECT\n trails.id as trail_id,\n date,\n trails.name,\n description,\n gpx,\n activitypub_actors.iri as author,\n photos,\n distance,\n duration,\n elevation_gain,\n elevation_loss,\n trails.created,\n \"trail\" as type\n FROM\n trails\n JOIN activitypub_actors ON activitypub_actors.id = trails.author\n )\nORDER BY\n created DESC;\n"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,40 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("pbc_468398817")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"viewQuery": "SELECT\n id,\n trail_id,\n date,\n name,\n description,\n gpx,\n author,\n photos,\n distance,\n duration,\n elevation_gain,\n elevation_loss,\n created,\n type\nFROM\n (\n SELECT\n summit_logs.id,\n summit_logs.trail as trail_id,\n summit_logs.date,\n trails.name,\n text as description,\n summit_logs.gpx,\n activitypub_actors.iri as author,\n summit_logs.photos,\n summit_logs.distance,\n summit_logs.duration,\n summit_logs.elevation_gain,\n summit_logs.elevation_loss,\n summit_logs.created,\n \"summit_log\" as type\n FROM\n summit_logs\n JOIN trails ON summit_logs.trail = trails.id\n JOIN activitypub_actors ON activitypub_actors.id = summit_logs.author\n UNION\n SELECT\n trails.id,\n trails.id as trail_id,\n date,\n trails.name,\n description,\n gpx,\n activitypub_actors.iri as author,\n photos,\n distance,\n duration,\n elevation_gain,\n elevation_loss,\n trails.created,\n \"trail\" as type\n FROM\n trails\n JOIN activitypub_actors ON activitypub_actors.id = trails.author\n )\nORDER BY\n created DESC;\n"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("pbc_468398817")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"viewQuery": "SELECT\n (ROW_NUMBER() OVER ()) as id,\n trail_id,\n date,\n name,\n description,\n gpx,\n author,\n photos,\n distance,\n duration,\n elevation_gain,\n elevation_loss,\n created,\n type\nFROM\n (\n SELECT\n summit_logs.trail as trail_id,\n summit_logs.date,\n trails.name,\n text as description,\n summit_logs.gpx,\n activitypub_actors.iri as author,\n summit_logs.photos,\n summit_logs.distance,\n summit_logs.duration,\n summit_logs.elevation_gain,\n summit_logs.elevation_loss,\n summit_logs.created,\n \"summit_log\" as type\n FROM\n summit_logs\n JOIN trails ON summit_logs.trail = trails.id\n JOIN activitypub_actors ON activitypub_actors.id = summit_logs.author\n UNION\n SELECT\n trails.id as trail_id,\n date,\n trails.name,\n description,\n gpx,\n activitypub_actors.iri as author,\n photos,\n distance,\n duration,\n elevation_gain,\n elevation_loss,\n trails.created,\n \"trail\" as type\n FROM\n trails\n JOIN activitypub_actors ON activitypub_actors.id = trails.author\n )\nORDER BY\n created DESC;\n"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,43 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("lf06qip3f4d11yk")
if err != nil {
return err
}
// remove field
collection.Fields.RemoveById("fhgxdiam")
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("lf06qip3f4d11yk")
if err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(6, []byte(`{
"hidden": false,
"id": "fhgxdiam",
"max": null,
"min": null,
"name": "rating",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
}`)); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,42 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4")
if err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(22, []byte(`{
"exceptDomains": [],
"hidden": false,
"id": "url1760183548",
"name": "iri",
"onlyDomains": [],
"presentable": false,
"required": false,
"system": false,
"type": "url"
}`)); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4")
if err != nil {
return err
}
// remove field
collection.Fields.RemoveById("url1760183548")
return app.Save(collection)
})
}

View File

@@ -0,0 +1,40 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"createRule": "@request.auth.id != \"\" && (@request.body.author.user = @request.auth.id || author.isLocal = false)"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"createRule": "@request.auth.id != \"\" && (@request.body.author.user = @request.auth.id)"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,40 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"updateRule": "author.user = @request.auth.id || (@request.auth.id != \"\" && trail_share_via_trail.trail = id && trail_share_via_trail.user ?= @request.auth.id && trail_share_via_trail.permission = \"edit\") || (@request.auth.id != \"\" && author.isLocal = false)"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"updateRule": "author.user = @request.auth.id || (@request.auth.id != \"\" && trail_share_via_trail.trail = id && trail_share_via_trail.user ?= @request.auth.id && trail_share_via_trail.permission = \"edit\")"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,42 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("lf06qip3f4d11yk")
if err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(4, []byte(`{
"exceptDomains": null,
"hidden": false,
"id": "url2434853685",
"name": "iri",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}`)); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("lf06qip3f4d11yk")
if err != nil {
return err
}
// remove field
collection.Fields.RemoveById("url2434853685")
return app.Save(collection)
})
}

View File

@@ -0,0 +1,42 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("dd2l9a4vxpy2ni8")
if err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(11, []byte(`{
"exceptDomains": null,
"hidden": false,
"id": "url2434853685",
"name": "iri",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}`)); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("dd2l9a4vxpy2ni8")
if err != nil {
return err
}
// remove field
collection.Fields.RemoveById("url2434853685")
return app.Save(collection)
})
}

View File

@@ -0,0 +1,40 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("dd2l9a4vxpy2ni8")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"createRule": "@request.auth.id != \"\" && (trail.author.user = @request.auth.id || trail.public = true)"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("dd2l9a4vxpy2ni8")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"createRule": "@request.auth.id != \"\""
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,78 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("r6gu2ajyidy1x69")
if err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(5, []byte(`{
"cascadeDelete": true,
"collectionId": "pbc_1295301207",
"hidden": false,
"id": "relation3182418120",
"maxSelect": 1,
"minSelect": 0,
"name": "author",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
// update field
if err := collection.Fields.AddMarshaledJSONAt(6, []byte(`{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "kwm6zdet",
"maxSelect": 1,
"minSelect": 0,
"name": "user",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("r6gu2ajyidy1x69")
if err != nil {
return err
}
// remove field
collection.Fields.RemoveById("relation3182418120")
// update field
if err := collection.Fields.AddMarshaledJSONAt(5, []byte(`{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "kwm6zdet",
"maxSelect": 1,
"minSelect": 0,
"name": "author",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,33 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
lists, err := app.FindAllRecords("lists")
if err != nil {
return err
}
for _, l := range lists {
actor, err := app.FindFirstRecordByData("activitypub_actors", "user", l.GetString("user"))
if err != nil {
continue
}
l.Set("author", actor.Id)
err = app.UnsafeWithoutHooks().Save(l)
if err != nil {
continue
}
}
return nil
}, func(app core.App) error {
// add down queries...
return nil
})
}

View File

@@ -0,0 +1,44 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("r6gu2ajyidy1x69")
if err != nil {
return err
}
// remove field
collection.Fields.RemoveById("kwm6zdet")
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("r6gu2ajyidy1x69")
if err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(6, []byte(`{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "kwm6zdet",
"maxSelect": 1,
"minSelect": 0,
"name": "user",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,48 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("r6gu2ajyidy1x69")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"createRule": "@request.auth.id != \"\" && (@request.body.author.user = @request.auth.id)",
"deleteRule": "author.user = @request.auth.id ",
"listRule": "author.user = @request.auth.id || public = true || (@request.auth.id != \"\" && list_share_via_list.user ?= @request.auth.id)",
"updateRule": "author.user = @request.auth.id || (@request.auth.id != \"\" && list_share_via_list.list = id && list_share_via_list.user ?= @request.auth.id && list_share_via_list.permission = \"edit\")",
"viewRule": "author.user = @request.auth.id || public = true || (@request.auth.id != \"\" && list_share_via_list.user ?= @request.auth.id)"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("r6gu2ajyidy1x69")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"createRule": "@request.auth.id != \"\" && (@request.body.author = @request.auth.id)",
"deleteRule": "author = @request.auth.id ",
"listRule": "author = @request.auth.id || public = true || (@request.auth.id != \"\" && list_share_via_list.user ?= @request.auth.id)",
"updateRule": "author = @request.auth.id || (@request.auth.id != \"\" && list_share_via_list.list = id && list_share_via_list.user ?= @request.auth.id && list_share_via_list.permission = \"edit\")",
"viewRule": "author = @request.auth.id || public = true || (@request.auth.id != \"\" && list_share_via_list.user ?= @request.auth.id)"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,42 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("r6gu2ajyidy1x69")
if err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(7, []byte(`{
"exceptDomains": null,
"hidden": false,
"id": "url2434853685",
"name": "iri",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}`)); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("r6gu2ajyidy1x69")
if err != nil {
return err
}
// remove field
collection.Fields.RemoveById("url2434853685")
return app.Save(collection)
})
}

View File

@@ -0,0 +1,40 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("r6gu2ajyidy1x69")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"createRule": "@request.auth.id != \"\" && (@request.body.author.user = @request.auth.id || author.isLocal = false)"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("r6gu2ajyidy1x69")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"createRule": "@request.auth.id != \"\" && (@request.body.author.user = @request.auth.id)"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,40 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("r6gu2ajyidy1x69")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"updateRule": "author.user = @request.auth.id || (@request.auth.id != \"\" && list_share_via_list.list = id && list_share_via_list.user ?= @request.auth.id && list_share_via_list.permission = \"edit\") || (@request.auth.id != \"\" && author.isLocal = false)"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("r6gu2ajyidy1x69")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"updateRule": "author.user = @request.auth.id || (@request.auth.id != \"\" && list_share_via_list.list = id && list_share_via_list.user ?= @request.auth.id && list_share_via_list.permission = \"edit\")"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,57 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("pbc_468398817")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"viewQuery": "SELECT\n id,\n trail_id,\n iri,\n date,\n name,\n description,\n gpx,\n author,\n photos,\n distance,\n duration,\n elevation_gain,\n elevation_loss,\n created,\n type\nFROM\n (\n SELECT\n summit_logs.id,\n summit_logs.trail as trail_id,\n trails.iri,\n summit_logs.date,\n trails.name,\n text as description,\n summit_logs.gpx,\n activitypub_actors.iri as author,\n summit_logs.photos,\n summit_logs.distance,\n summit_logs.duration,\n summit_logs.elevation_gain,\n summit_logs.elevation_loss,\n summit_logs.created,\n \"summit_log\" as type\n FROM\n summit_logs\n JOIN trails ON summit_logs.trail = trails.id\n JOIN activitypub_actors ON activitypub_actors.id = summit_logs.author\n UNION\n SELECT\n trails.id,\n trails.id as trail_id,\n trails.iri,\n date,\n trails.name,\n description,\n gpx,\n activitypub_actors.iri as author,\n photos,\n distance,\n duration,\n elevation_gain,\n elevation_loss,\n trails.created,\n \"trail\" as type\n FROM\n trails\n JOIN activitypub_actors ON activitypub_actors.id = trails.author\n )\nORDER BY\n created DESC;\n"
}`), &collection); err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(2, []byte(`{
"hidden": false,
"id": "json2434853685",
"maxSize": 1,
"name": "iri",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}`)); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("pbc_468398817")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"viewQuery": "SELECT\n id,\n trail_id,\n date,\n name,\n description,\n gpx,\n author,\n photos,\n distance,\n duration,\n elevation_gain,\n elevation_loss,\n created,\n type\nFROM\n (\n SELECT\n summit_logs.id,\n summit_logs.trail as trail_id,\n summit_logs.date,\n trails.name,\n text as description,\n summit_logs.gpx,\n activitypub_actors.iri as author,\n summit_logs.photos,\n summit_logs.distance,\n summit_logs.duration,\n summit_logs.elevation_gain,\n summit_logs.elevation_loss,\n summit_logs.created,\n \"summit_log\" as type\n FROM\n summit_logs\n JOIN trails ON summit_logs.trail = trails.id\n JOIN activitypub_actors ON activitypub_actors.id = summit_logs.author\n UNION\n SELECT\n trails.id,\n trails.id as trail_id,\n date,\n trails.name,\n description,\n gpx,\n activitypub_actors.iri as author,\n photos,\n distance,\n duration,\n elevation_gain,\n elevation_loss,\n trails.created,\n \"trail\" as type\n FROM\n trails\n JOIN activitypub_actors ON activitypub_actors.id = trails.author\n )\nORDER BY\n created DESC;\n"
}`), &collection); err != nil {
return err
}
// remove field
collection.Fields.RemoveById("json2434853685")
return app.Save(collection)
})
}

View File

@@ -0,0 +1,42 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"indexes": [
"CREATE INDEX ` + "`" + `idx_6tD5RqfVk2` + "`" + ` ON ` + "`" + `trails` + "`" + ` (` + "`" + `iri` + "`" + `)"
]
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"indexes": []
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,42 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("r6gu2ajyidy1x69")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"indexes": [
"CREATE INDEX ` + "`" + `idx_hLtEU5XGWL` + "`" + ` ON ` + "`" + `lists` + "`" + ` (` + "`" + `iri` + "`" + `)"
]
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("r6gu2ajyidy1x69")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"indexes": []
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,108 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("pbc_468398817")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"viewQuery": "SELECT\n id,\n trail_id,\n trail_author_username,\n trail_author_domain,\n trail_iri,\n date,\n name,\n description,\n gpx,\n author,\n photos,\n distance,\n duration,\n elevation_gain,\n elevation_loss,\n created,\n type\nFROM\n (\n SELECT\n summit_logs.id,\n summit_logs.trail as trail_id,\n tapa.username as trail_author_username,\n tapa.domain as trail_author_domain,\n trails.iri as trail_iri,\n summit_logs.date,\n trails.name,\n text as description,\n summit_logs.gpx,\n sapa.iri as author,\n summit_logs.photos,\n summit_logs.distance,\n summit_logs.duration,\n summit_logs.elevation_gain,\n summit_logs.elevation_loss,\n summit_logs.created,\n \"summit_log\" as type\n FROM\n summit_logs\n JOIN trails ON summit_logs.trail = trails.id\n JOIN activitypub_actors sapa ON sapa.id = summit_logs.author\n JOIN activitypub_actors tapa ON tapa.id = trails.author\n UNION\n SELECT\n trails.id,\n trails.id as trail_id,\n activitypub_actors.username as trail_author_username,\n activitypub_actors.domain as trail_author_domain,\n trails.iri as trail_iri,\n date,\n trails.name,\n description,\n gpx,\n activitypub_actors.iri as author,\n photos,\n distance,\n duration,\n elevation_gain,\n elevation_loss,\n trails.created,\n \"trail\" as type\n FROM\n trails\n JOIN activitypub_actors ON activitypub_actors.id = trails.author\n )\nORDER BY\n created DESC;\n"
}`), &collection); err != nil {
return err
}
// remove field
collection.Fields.RemoveById("json2434853685")
// add field
if err := collection.Fields.AddMarshaledJSONAt(2, []byte(`{
"hidden": false,
"id": "json3184124860",
"maxSize": 1,
"name": "trail_author_username",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}`)); err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(3, []byte(`{
"hidden": false,
"id": "json2887874732",
"maxSize": 1,
"name": "trail_author_domain",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}`)); err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(4, []byte(`{
"hidden": false,
"id": "json113557190",
"maxSize": 1,
"name": "trail_iri",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}`)); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("pbc_468398817")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"viewQuery": "SELECT\n id,\n trail_id,\n iri,\n date,\n name,\n description,\n gpx,\n author,\n photos,\n distance,\n duration,\n elevation_gain,\n elevation_loss,\n created,\n type\nFROM\n (\n SELECT\n summit_logs.id,\n summit_logs.trail as trail_id,\n trails.iri,\n summit_logs.date,\n trails.name,\n text as description,\n summit_logs.gpx,\n activitypub_actors.iri as author,\n summit_logs.photos,\n summit_logs.distance,\n summit_logs.duration,\n summit_logs.elevation_gain,\n summit_logs.elevation_loss,\n summit_logs.created,\n \"summit_log\" as type\n FROM\n summit_logs\n JOIN trails ON summit_logs.trail = trails.id\n JOIN activitypub_actors ON activitypub_actors.id = summit_logs.author\n UNION\n SELECT\n trails.id,\n trails.id as trail_id,\n trails.iri,\n date,\n trails.name,\n description,\n gpx,\n activitypub_actors.iri as author,\n photos,\n distance,\n duration,\n elevation_gain,\n elevation_loss,\n trails.created,\n \"trail\" as type\n FROM\n trails\n JOIN activitypub_actors ON activitypub_actors.id = trails.author\n )\nORDER BY\n created DESC;\n"
}`), &collection); err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(2, []byte(`{
"hidden": false,
"id": "json2434853685",
"maxSize": 1,
"name": "iri",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}`)); err != nil {
return err
}
// remove field
collection.Fields.RemoveById("json3184124860")
// remove field
collection.Fields.RemoveById("json2887874732")
// remove field
collection.Fields.RemoveById("json113557190")
return app.Save(collection)
})
}

View File

@@ -0,0 +1,104 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("khrcci2uqknny8h")
if err != nil {
return err
}
// remove field
collection.Fields.RemoveById("tmghd4vo")
// remove field
collection.Fields.RemoveById("exqo1whj")
// add field
if err := collection.Fields.AddMarshaledJSONAt(4, []byte(`{
"cascadeDelete": true,
"collectionId": "pbc_1295301207",
"hidden": false,
"id": "relation1745156937",
"maxSelect": 1,
"minSelect": 0,
"name": "recipient",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(5, []byte(`{
"cascadeDelete": true,
"collectionId": "pbc_1295301207",
"hidden": false,
"id": "relation3182418120",
"maxSelect": 1,
"minSelect": 0,
"name": "author",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("khrcci2uqknny8h")
if err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(4, []byte(`{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "tmghd4vo",
"maxSelect": 1,
"minSelect": 0,
"name": "recipient",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(5, []byte(`{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "exqo1whj",
"maxSelect": 1,
"minSelect": 0,
"name": "author",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
// remove field
collection.Fields.RemoveById("relation1745156937")
// remove field
collection.Fields.RemoveById("relation3182418120")
return app.Save(collection)
})
}

View File

@@ -0,0 +1,44 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("khrcci2uqknny8h")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"listRule": "@request.auth.id = recipient.user",
"updateRule": "@request.auth.id = recipient.user && (@request.body.type = null||@request.body.type = type) && (@request.body.metadata = null||@request.body.metadata = metadata) && (@request.body.recipient = null||@request.body.recipient = recipient) && (@request.body.author = null||@request.body.author = author) && @request.body.seen = true",
"viewRule": "@request.auth.id = recipient.user"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("khrcci2uqknny8h")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"listRule": "@request.auth.id = recipient",
"updateRule": "@request.auth.id = recipient && (@request.body.type = null||@request.body.type = type) && (@request.body.metadata = null||@request.body.metadata = metadata) && (@request.body.recipient = null||@request.body.recipient = recipient) && (@request.body.author = null||@request.body.author = author) && @request.body.seen = true",
"viewRule": "@request.auth.id = recipient"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,67 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("khrcci2uqknny8h")
if err != nil {
return err
}
// update field
if err := collection.Fields.AddMarshaledJSONAt(1, []byte(`{
"hidden": false,
"id": "b57prsbu",
"maxSelect": 1,
"name": "type",
"presentable": false,
"required": true,
"system": false,
"type": "select",
"values": [
"new_follower",
"trail_comment",
"trail_share",
"list_share",
"summit_log_create"
]
}`)); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("khrcci2uqknny8h")
if err != nil {
return err
}
// update field
if err := collection.Fields.AddMarshaledJSONAt(1, []byte(`{
"hidden": false,
"id": "b57prsbu",
"maxSelect": 1,
"name": "type",
"presentable": false,
"required": true,
"system": false,
"type": "select",
"values": [
"trail_create",
"list_create",
"new_follower",
"trail_comment",
"trail_share",
"list_share"
]
}`)); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,42 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("8obn1ukumze565i")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"indexes": [
"CREATE UNIQUE INDEX `+"`"+`idx_oM5KTHL3Lf`+"`"+` ON `+"`"+`follows`+"`"+` (\n `+"`"+`follower`+"`"+`,\n `+"`"+`followee`+"`"+`\n)"
]
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("8obn1ukumze565i")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"indexes": []
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,42 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("dd2l9a4vxpy2ni8")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"listRule": "(@request.auth.id != \"\" && (author.user = @request.auth.id || trail.author.user = @request.auth.id)) || trail.public = true || (@collection.trail_share.trail ?= trail && @collection.trail_share.trail ?= @request.auth.id)",
"viewRule": "(@request.auth.id != \"\" && (author.user = @request.auth.id || trail.author.user = @request.auth.id)) || trail.public = true || (@collection.trail_share.trail ?= trail && @collection.trail_share.trail ?= @request.auth.id)"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("dd2l9a4vxpy2ni8")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"listRule": "(@request.auth.id != \"\" && (author.user = @request.auth.id || trail.author.user = @request.auth.id)) || trail.public = true || \ntrail.trail_share_via_trail.user = @request.auth.id",
"viewRule": "(@request.auth.id != \"\" && (author.user = @request.auth.id || trail.author.user = @request.auth.id)) || trail.public = true || \ntrail.trail_share_via_trail.user = @request.auth.id"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,42 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("8obn1ukumze565i")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"listRule": "",
"viewRule": ""
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("8obn1ukumze565i")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"listRule": "@request.auth.id = follower.user.id || @request.auth.id = followee.user.id",
"viewRule": "@request.auth.id = follower.user.id || @request.auth.id = followee.user.id"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,44 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("1mns8mlal6uf9ku")
if err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(2, []byte(`{
"cascadeDelete": true,
"collectionId": "pbc_1295301207",
"hidden": false,
"id": "relation2375276105",
"maxSelect": 1,
"minSelect": 0,
"name": "actor",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("1mns8mlal6uf9ku")
if err != nil {
return err
}
// remove field
collection.Fields.RemoveById("relation2375276105")
return app.UnsafeWithoutHooks().Save(collection)
})
}

View File

@@ -0,0 +1,31 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
shares, err := app.FindAllRecords("trail_share")
if err != nil {
return err
}
for _, s := range shares {
actor, err := app.FindFirstRecordByData("activitypub_actors", "user", s.GetString("user"))
if err != nil {
continue
}
s.Set("actor", actor.Id)
err = app.Save(s)
if err != nil {
return err
}
}
return nil
}, func(app core.App) error {
return nil
})
}

View File

@@ -0,0 +1,80 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("4wbv9tz5zjdrjh1")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"viewQuery": "SELECT activitypub_actors.id, activitypub_actors.user, COALESCE(printf(\"%.2f\", MAX(trails.distance)), 0) AS max_distance,\n COALESCE(printf(\"%.2f\", MAX(trails.elevation_gain)), 0) AS max_elevation_gain, \n COALESCE(printf(\"%.2f\", MAX(trails.elevation_loss)), 0) AS max_elevation_loss, \n COALESCE(printf(\"%.2f\", MAX(trails.duration)), 0) AS max_duration, \n COALESCE(printf(\"%.2f\", MIN(trails.distance)), 0) AS min_distance, \n COALESCE(printf(\"%.2f\", MIN(trails.elevation_gain)), 0) AS min_elevation_gain, \n COALESCE(printf(\"%.2f\", MIN(trails.elevation_loss)), 0) AS min_elevation_loss, \n COALESCE(printf(\"%.2f\", MIN(trails.duration)), 0) AS min_duration \nFROM activitypub_actors \n LEFT JOIN trails ON \n activitypub_actors.id = trails.author OR \n trails.public = 1 OR \n EXISTS (\n SELECT 1 \n FROM trail_share \n WHERE trail_share.trail = trails.id \n AND trail_share.actor = activitypub_actors.id\n ) GROUP BY activitypub_actors.id;"
}`), &collection); err != nil {
return err
}
// remove field
collection.Fields.RemoveById("_clone_U0cX")
// add field
if err := collection.Fields.AddMarshaledJSONAt(1, []byte(`{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "_clone_rnMQ",
"maxSelect": 1,
"minSelect": 0,
"name": "user",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("4wbv9tz5zjdrjh1")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"viewQuery": "SELECT activitypub_actors.id, activitypub_actors.user, COALESCE(printf(\"%.2f\", MAX(trails.distance)), 0) AS max_distance,\n COALESCE(printf(\"%.2f\", MAX(trails.elevation_gain)), 0) AS max_elevation_gain, \n COALESCE(printf(\"%.2f\", MAX(trails.elevation_loss)), 0) AS max_elevation_loss, \n COALESCE(printf(\"%.2f\", MAX(trails.duration)), 0) AS max_duration, \n COALESCE(printf(\"%.2f\", MIN(trails.distance)), 0) AS min_distance, \n COALESCE(printf(\"%.2f\", MIN(trails.elevation_gain)), 0) AS min_elevation_gain, \n COALESCE(printf(\"%.2f\", MIN(trails.elevation_loss)), 0) AS min_elevation_loss, \n COALESCE(printf(\"%.2f\", MIN(trails.duration)), 0) AS min_duration \nFROM activitypub_actors \n LEFT JOIN trails ON \n activitypub_actors.id = trails.author OR \n trails.public = 1 OR \n EXISTS (\n SELECT 1 \n FROM trail_share \n WHERE trail_share.trail = trails.id \n AND trail_share.user = activitypub_actors.user\n ) GROUP BY activitypub_actors.id;"
}`), &collection); err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(1, []byte(`{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "_clone_U0cX",
"maxSelect": 1,
"minSelect": 0,
"name": "user",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
// remove field
collection.Fields.RemoveById("_clone_rnMQ")
return app.Save(collection)
})
}

View File

@@ -0,0 +1,80 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("urytyc428mwlbqq")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"viewQuery": "SELECT \n activitypub_actors.id, activitypub_actors.user, \n COALESCE(MAX(trails.lat), 0) AS max_lat, \n COALESCE(MAX(trails.lon), 0) AS max_lon, \n COALESCE(MIN(trails.lat), 0) AS min_lat, \n COALESCE(MIN(trails.lon), 0) AS min_lon \nFROM activitypub_actors \nLEFT JOIN trails \n ON activitypub_actors.id = trails.author \n OR trails.public = TRUE \n OR EXISTS (\n SELECT 1 \n FROM trail_share \n WHERE trail_share.trail = trails.id \n AND trail_share.actor = activitypub_actors.id\n ) \nGROUP BY activitypub_actors.id;"
}`), &collection); err != nil {
return err
}
// remove field
collection.Fields.RemoveById("_clone_2GUq")
// add field
if err := collection.Fields.AddMarshaledJSONAt(1, []byte(`{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "_clone_4wMO",
"maxSelect": 1,
"minSelect": 0,
"name": "user",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("urytyc428mwlbqq")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"viewQuery": "SELECT \n activitypub_actors.id, activitypub_actors.user, \n COALESCE(MAX(trails.lat), 0) AS max_lat, \n COALESCE(MAX(trails.lon), 0) AS max_lon, \n COALESCE(MIN(trails.lat), 0) AS min_lat, \n COALESCE(MIN(trails.lon), 0) AS min_lon \nFROM activitypub_actors \nLEFT JOIN trails \n ON activitypub_actors.id = trails.author \n OR trails.public = TRUE \n OR EXISTS (\n SELECT 1 \n FROM trail_share \n WHERE trail_share.trail = trails.id \n AND trail_share.user = activitypub_actors.id\n ) \nGROUP BY activitypub_actors.id;"
}`), &collection); err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(1, []byte(`{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "_clone_2GUq",
"maxSelect": 1,
"minSelect": 0,
"name": "user",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
// remove field
collection.Fields.RemoveById("_clone_4wMO")
return app.Save(collection)
})
}

View File

@@ -0,0 +1,42 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("goeo2ubp103rzp9")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"listRule": "author = @request.auth.id || trails_via_waypoints.author.user ?= @request.auth.id || trails_via_waypoints.public ?= true || \n(@collection.trail_share.trail.id ?= trails_via_waypoints.id && @collection.trail_share.actor.user ?= @request.auth.id)",
"viewRule": "author = @request.auth.id || trails_via_waypoints.author.user ?= @request.auth.id || trails_via_waypoints.public ?= true || \n(@collection.trail_share.trail.id ?= trails_via_waypoints.id && @collection.trail_share.actor.user ?= @request.auth.id)"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("goeo2ubp103rzp9")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"listRule": "author = @request.auth.id || trails_via_waypoints.author.user ?= @request.auth.id || trails_via_waypoints.public ?= true || \n(@collection.trail_share.trail.id ?= trails_via_waypoints.id && @collection.trail_share.user ?= @request.auth.id)",
"viewRule": "author = @request.auth.id || trails_via_waypoints.author.user ?= @request.auth.id || trails_via_waypoints.public ?= true || \n(@collection.trail_share.trail.id ?= trails_via_waypoints.id && @collection.trail_share.user ?= @request.auth.id)"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,44 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"listRule": "author.user = @request.auth.id || public = true || (@request.auth.id != \"\" && trail_share_via_trail.actor.user ?= @request.auth.id)",
"updateRule": "author.user = @request.auth.id || (@request.auth.id != \"\" && trail_share_via_trail.trail = id && trail_share_via_trail.actor.user ?= @request.auth.id && trail_share_via_trail.permission = \"edit\") || (@request.auth.id != \"\" && author.isLocal = false)",
"viewRule": "author.user = @request.auth.id || public = true || (@request.auth.id != \"\" && trail_share_via_trail.actor.user ?= @request.auth.id)"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"listRule": "author.user = @request.auth.id || public = true || (@request.auth.id != \"\" && trail_share_via_trail.user ?= @request.auth.id)",
"updateRule": "author.user = @request.auth.id || (@request.auth.id != \"\" && trail_share_via_trail.trail = id && trail_share_via_trail.user ?= @request.auth.id && trail_share_via_trail.permission = \"edit\") || (@request.auth.id != \"\" && author.isLocal = false)",
"viewRule": "author.user = @request.auth.id || public = true || (@request.auth.id != \"\" && trail_share_via_trail.user ?= @request.auth.id)"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,42 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("dd2l9a4vxpy2ni8")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"listRule": "(@request.auth.id != \"\" && (author.user = @request.auth.id || trail.author.user = @request.auth.id)) || trail.public = true || (@collection.trail_share.trail ?= trail && @collection.trail_share.actor.user ?= @request.auth.id)",
"viewRule": "(@request.auth.id != \"\" && (author.user = @request.auth.id || trail.author.user = @request.auth.id)) || trail.public = true || (@collection.trail_share.trail ?= trail && @collection.trail_share.actor.user ?= @request.auth.id)"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("dd2l9a4vxpy2ni8")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"listRule": "(@request.auth.id != \"\" && (author.user = @request.auth.id || trail.author.user = @request.auth.id)) || trail.public = true || (@collection.trail_share.trail ?= trail && @collection.trail_share.trail ?= @request.auth.id)",
"viewRule": "(@request.auth.id != \"\" && (author.user = @request.auth.id || trail.author.user = @request.auth.id)) || trail.public = true || (@collection.trail_share.trail ?= trail && @collection.trail_share.trail ?= @request.auth.id)"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,44 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("lf06qip3f4d11yk")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"createRule": "@request.auth.id != \"\" && (trail.author.user = @request.auth.id || trail.public = true || trail.trail_share_via_trail.actor.user ?= @request.auth.id)",
"listRule": "((@request.auth.id != \"\" && trail.author.user = @request.auth.id) || trail.public = true) || author = @request.auth.id || trail.trail_share_via_trail.actor.user ?= @request.auth.id",
"viewRule": "((@request.auth.id != \"\" && trail.author.user = @request.auth.id) || trail.public = true) || author = @request.auth.id || trail.trail_share_via_trail.actor.user ?= @request.auth.id"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("lf06qip3f4d11yk")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"createRule": "@request.auth.id != \"\" && (trail.author.user = @request.auth.id || trail.public = true || trail.trail_share_via_trail.user ?= @request.auth.id)",
"listRule": "((@request.auth.id != \"\" && trail.author.user = @request.auth.id) || trail.public = true) || author = @request.auth.id || trail.trail_share_via_trail.user ?= @request.auth.id",
"viewRule": "((@request.auth.id != \"\" && trail.author.user = @request.auth.id) || trail.public = true) || author = @request.auth.id || trail.trail_share_via_trail.user ?= @request.auth.id"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,62 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("1mns8mlal6uf9ku")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"listRule": "trail.author.user = @request.auth.id || actor.user = @request.auth.id",
"viewRule": "trail.author.user = @request.auth.id || actor.user = @request.auth.id"
}`), &collection); err != nil {
return err
}
// remove field
collection.Fields.RemoveById("yyzimwee")
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("1mns8mlal6uf9ku")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"listRule": "trail.author.user = @request.auth.id || user = @request.auth.id",
"viewRule": "trail.author.user = @request.auth.id || user = @request.auth.id"
}`), &collection); err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(3, []byte(`{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "yyzimwee",
"maxSelect": 1,
"minSelect": 0,
"name": "user",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,55 @@
package migrations
import (
"os"
"pocketbase/util"
"github.com/meilisearch/meilisearch-go"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
client := meilisearch.New(os.Getenv("MEILI_URL"), meilisearch.WithAPIKey(os.Getenv("MEILI_MASTER_KEY")))
m.Register(func(app core.App) error {
users, err := app.FindAllRecords("users")
if err != nil {
return err
}
for _, u := range users {
userId := u.Id
actor, err := app.FindFirstRecordByData("activitypub_actors", "user", userId)
if err != nil {
return err
}
searchRules := map[string]interface{}{
"lists": map[string]string{
"filter": "public = true OR author = " + actor.Id + " OR shares = " + actor.Id,
},
"trails": map[string]string{
"filter": "public = true OR author = " + actor.Id + " OR shares = " + actor.Id,
},
}
token, err := util.GenerateMeilisearchToken(searchRules, client)
if err != nil {
return err
}
u.Set("token", token)
if err := app.Save(u); err != nil {
return err
}
}
return nil
}, func(app core.App) error {
// add down queries...
return nil
})
}

View File

@@ -0,0 +1,62 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("1kot7t9na3hi0gl")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"listRule": "list.author = @request.auth.id || actor.user = @request.auth.id",
"viewRule": "list.author = @request.auth.id || actor.user = @request.auth.id"
}`), &collection); err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(3, []byte(`{
"cascadeDelete": true,
"collectionId": "pbc_1295301207",
"hidden": false,
"id": "relation1148540665",
"maxSelect": 1,
"minSelect": 0,
"name": "actor",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("1kot7t9na3hi0gl")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"listRule": "list.author = @request.auth.id || user = @request.auth.id",
"viewRule": "list.author = @request.auth.id || user = @request.auth.id"
}`), &collection); err != nil {
return err
}
// remove field
collection.Fields.RemoveById("relation1148540665")
return app.Save(collection)
})
}

View File

@@ -0,0 +1,31 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
shares, err := app.FindAllRecords("list_share")
if err != nil {
return err
}
for _, s := range shares {
actor, err := app.FindFirstRecordByData("activitypub_actors", "user", s.GetString("user"))
if err != nil {
continue
}
s.Set("actor", actor.Id)
err = app.Save(s)
if err != nil {
return err
}
}
return nil
}, func(app core.App) error {
return nil
})
}

View File

@@ -0,0 +1,44 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("r6gu2ajyidy1x69")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"listRule": "author.user = @request.auth.id || public = true || (@request.auth.id != \"\" && list_share_via_list.actor.user ?= @request.auth.id)",
"updateRule": "author.user = @request.auth.id || (@request.auth.id != \"\" && list_share_via_list.list = id && list_share_via_list.actor.user ?= @request.auth.id && list_share_via_list.permission = \"edit\") || (@request.auth.id != \"\" && author.isLocal = false)",
"viewRule": "author.user = @request.auth.id || public = true || (@request.auth.id != \"\" && list_share_via_list.actor.user ?= @request.auth.id)"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("r6gu2ajyidy1x69")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"listRule": "author.user = @request.auth.id || public = true || (@request.auth.id != \"\" && list_share_via_list.user ?= @request.auth.id)",
"updateRule": "author.user = @request.auth.id || (@request.auth.id != \"\" && list_share_via_list.list = id && list_share_via_list.user ?= @request.auth.id && list_share_via_list.permission = \"edit\") || (@request.auth.id != \"\" && author.isLocal = false)",
"viewRule": "author.user = @request.auth.id || public = true || (@request.auth.id != \"\" && list_share_via_list.user ?= @request.auth.id)"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,44 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("1kot7t9na3hi0gl")
if err != nil {
return err
}
// remove field
collection.Fields.RemoveById("mix12kkh")
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("1kot7t9na3hi0gl")
if err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(2, []byte(`{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "mix12kkh",
"maxSelect": 1,
"minSelect": 0,
"name": "user",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
}`)); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,48 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("1kot7t9na3hi0gl")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"createRule": "list.author.user = @request.auth.id",
"deleteRule": "list.author.user = @request.auth.id",
"listRule": "list.author.user = @request.auth.id || actor.user = @request.auth.id",
"updateRule": "list.author.user = @request.auth.id",
"viewRule": "list.author.user = @request.auth.id || actor.user = @request.auth.id"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("1kot7t9na3hi0gl")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"createRule": "list.author = @request.auth.id",
"deleteRule": "list.author = @request.auth.id",
"listRule": "list.author = @request.auth.id || actor.user = @request.auth.id",
"updateRule": "list.author = @request.auth.id",
"viewRule": "list.author = @request.auth.id || actor.user = @request.auth.id"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,40 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("dd2l9a4vxpy2ni8")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"createRule": "@request.auth.id != \"\" && (trail.author.user = @request.auth.id || trail.public = true || trail.trail_share_via_trail.actor.user ?= @request.auth.id)"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("dd2l9a4vxpy2ni8")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"createRule": "@request.auth.id != \"\" && (trail.author.user = @request.auth.id || trail.public = true)"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,42 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("dd2l9a4vxpy2ni8")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"listRule": "(@request.auth.id != \"\" && (author.user = @request.auth.id || trail.author.user = @request.auth.id)) || trail.public = true || trail.trail_share_via_trail.actor.user ?= @request.auth.id",
"viewRule": "(@request.auth.id != \"\" && (author.user = @request.auth.id || trail.author.user = @request.auth.id)) || trail.public = true || trail.trail_share_via_trail.actor.user ?= @request.auth.id"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("dd2l9a4vxpy2ni8")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"listRule": "(@request.auth.id != \"\" && (author.user = @request.auth.id || trail.author.user = @request.auth.id)) || trail.public = true || (@collection.trail_share.trail ?= trail && @collection.trail_share.actor.user ?= @request.auth.id)",
"viewRule": "(@request.auth.id != \"\" && (author.user = @request.auth.id || trail.author.user = @request.auth.id)) || trail.public = true || (@collection.trail_share.trail ?= trail && @collection.trail_share.actor.user ?= @request.auth.id)"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,103 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
jsonData := `{
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"cascadeDelete": true,
"collectionId": "e864strfxo14pm4",
"hidden": false,
"id": "relation2993194383",
"maxSelect": 1,
"minSelect": 0,
"name": "trail",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
},
{
"cascadeDelete": true,
"collectionId": "pbc_1295301207",
"hidden": false,
"id": "relation1148540665",
"maxSelect": 1,
"minSelect": 0,
"name": "actor",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"id": "pbc_1995454416",
"indexes": [
"CREATE UNIQUE INDEX ` + "`" + `idx_ywIkmeaSFo` + "`" + ` ON ` + "`" + `trail_like` + "`" + ` (\n ` + "`" + `trail` + "`" + `,\n ` + "`" + `actor` + "`" + `\n)"
],
"listRule": null,
"name": "trail_like",
"system": false,
"type": "base",
"updateRule": null,
"viewRule": null
}`
collection := &core.Collection{}
if err := json.Unmarshal([]byte(jsonData), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("pbc_1995454416")
if err != nil {
return err
}
return app.Delete(collection)
})
}

View File

@@ -0,0 +1,46 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("pbc_1995454416")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"createRule": "trail.author.user = @request.auth.id || trail.public = true || trail.trail_share_via_trail.actor.user ?= @request.auth.id || actor.user = @request.auth.id",
"deleteRule": "actor.user = @request.auth.id",
"listRule": "trail.author.user = @request.auth.id || trail.public = true || trail.trail_share_via_trail.actor.user ?= @request.auth.id || actor.user = @request.auth.id",
"viewRule": "trail.author.user = @request.auth.id || trail.public = true || trail.trail_share_via_trail.actor.user ?= @request.auth.id || actor.user = @request.auth.id"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("pbc_1995454416")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"createRule": null,
"deleteRule": null,
"listRule": null,
"viewRule": null
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,42 @@
package migrations
import (
"os"
"github.com/meilisearch/meilisearch-go"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
client := meilisearch.New(os.Getenv("MEILI_URL"), meilisearch.WithAPIKey(os.Getenv("MEILI_MASTER_KEY")))
m.Register(func(app core.App) error {
_, err := client.Index("trails").UpdateSortableAttributes(&[]string{
"created", "date", "difficulty", "distance", "elevation_gain", "elevation_loss", "name", "duration", "author", "like_count",
})
if err != nil {
return err
}
_, err = client.Index("trails").UpdateFilterableAttributes(&[]string{
"_geo", "author", "category", "completed", "date", "difficulty", "distance", "elevation_gain", "elevation_loss", "public", "shares", "tags", "likes",
})
return err
}, func(app core.App) error {
_, err := client.Index("trails").UpdateSortableAttributes(&[]string{
"created", "date", "difficulty", "distance", "elevation_gain", "elevation_loss", "name", "duration", "author",
})
if err != nil {
return err
}
_, err = client.Index("trails").UpdateFilterableAttributes(&[]string{
"_geo", "author", "category", "completed", "date", "difficulty", "distance", "elevation_gain", "elevation_loss", "public", "shares", "tags",
})
return err
})
}

View File

@@ -0,0 +1,67 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("khrcci2uqknny8h")
if err != nil {
return err
}
// update field
if err := collection.Fields.AddMarshaledJSONAt(1, []byte(`{
"hidden": false,
"id": "b57prsbu",
"maxSelect": 1,
"name": "type",
"presentable": false,
"required": true,
"system": false,
"type": "select",
"values": [
"new_follower",
"trail_comment",
"trail_share",
"list_share",
"summit_log_create",
"trail_like"
]
}`)); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("khrcci2uqknny8h")
if err != nil {
return err
}
// update field
if err := collection.Fields.AddMarshaledJSONAt(1, []byte(`{
"hidden": false,
"id": "b57prsbu",
"maxSelect": 1,
"name": "type",
"presentable": false,
"required": true,
"system": false,
"type": "select",
"values": [
"new_follower",
"trail_comment",
"trail_share",
"list_share",
"summit_log_create"
]
}`)); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,43 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4")
if err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(23, []byte(`{
"hidden": false,
"id": "number851275141",
"max": null,
"min": 0,
"name": "like_count",
"onlyInt": true,
"presentable": false,
"required": false,
"system": false,
"type": "number"
}`)); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4")
if err != nil {
return err
}
// remove field
collection.Fields.RemoveById("number851275141")
return app.Save(collection)
})
}

View File

@@ -0,0 +1,40 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("pbc_1995454416")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"viewRule": "actor.user = @request.auth.id"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("pbc_1995454416")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"viewRule": "trail.author.user = @request.auth.id || trail.public = true || trail.trail_share_via_trail.actor.user ?= @request.auth.id || actor.user = @request.auth.id"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,82 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4")
if err != nil {
return err
}
// update field
if err := collection.Fields.AddMarshaledJSONAt(11, []byte(`{
"hidden": false,
"id": "aqbpyawe",
"maxSelect": 99,
"maxSize": 20971520,
"mimeTypes": [
"image/jpeg",
"image/vnd.mozilla.apng",
"image/png",
"image/webp",
"image/svg+xml",
"image/heic",
"video/mp4",
"video/webm",
"video/ogg"
],
"name": "photos",
"presentable": false,
"protected": false,
"required": false,
"system": false,
"thumbs": [
"600x0"
],
"type": "file"
}`)); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4")
if err != nil {
return err
}
// update field
if err := collection.Fields.AddMarshaledJSONAt(11, []byte(`{
"hidden": false,
"id": "aqbpyawe",
"maxSelect": 99,
"maxSize": 20971520,
"mimeTypes": [
"image/jpeg",
"image/vnd.mozilla.apng",
"image/png",
"image/webp",
"image/svg+xml",
"image/heic",
"video/mp4",
"video/webm",
"video/ogg"
],
"name": "photos",
"presentable": false,
"protected": false,
"required": false,
"system": false,
"thumbs": [],
"type": "file"
}`)); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,42 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("pbc_1295301207")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"listRule": "user.settings_via_user.privacy.account != 'private' || user = @request.auth.id",
"viewRule": "user.settings_via_user.privacy.account != 'private' || user = @request.auth.id"
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("pbc_1295301207")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"listRule": "",
"viewRule": ""
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,42 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("lf06qip3f4d11yk")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"indexes": [
"CREATE UNIQUE INDEX ` + "`" + `idx_4T3m08OsP1` + "`" + ` ON ` + "`" + `comments` + "`" + ` (` + "`" + `iri` + "`" + `) WHERE iri IS NOT NULL AND iri != \"\";"
]
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("lf06qip3f4d11yk")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"indexes": []
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,44 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("r6gu2ajyidy1x69")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"indexes": [
"CREATE UNIQUE INDEX ` + "`" + `idx_hLtEU5XGWL` + "`" + ` ON ` + "`" + `lists` + "`" + ` (` + "`" + `iri` + "`" + `) WHERE iri IS NOT NULL AND iri != \"\";"
]
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("r6gu2ajyidy1x69")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"indexes": [
"CREATE INDEX ` + "`" + `idx_hLtEU5XGWL` + "`" + ` ON ` + "`" + `lists` + "`" + ` (` + "`" + `iri` + "`" + `)"
]
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,42 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("dd2l9a4vxpy2ni8")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"indexes": [
"CREATE UNIQUE INDEX ` + "`" + `idx_iSbmEqYXbV` + "`" + ` ON ` + "`" + `summit_logs` + "`" + ` (` + "`" + `iri` + "`" + `) WHERE iri IS NOT NULL AND iri != \"\";"
]
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("dd2l9a4vxpy2ni8")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"indexes": []
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,44 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"indexes": [
"CREATE UNIQUE INDEX ` + "`" + `idx_6tD5RqfVk2` + "`" + ` ON ` + "`" + `trails` + "`" + ` (` + "`" + `iri` + "`" + `) WHERE iri IS NOT NULL AND iri != \"\";"
]
}`), &collection); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4")
if err != nil {
return err
}
// update collection data
if err := json.Unmarshal([]byte(`{
"indexes": [
"CREATE INDEX ` + "`" + `idx_6tD5RqfVk2` + "`" + ` ON ` + "`" + `trails` + "`" + ` (` + "`" + `iri` + "`" + `)"
]
}`), &collection); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,70 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("khrcci2uqknny8h")
if err != nil {
return err
}
// update field
if err := collection.Fields.AddMarshaledJSONAt(1, []byte(`{
"hidden": false,
"id": "b57prsbu",
"maxSelect": 1,
"name": "type",
"presentable": false,
"required": true,
"system": false,
"type": "select",
"values": [
"new_follower",
"trail_comment",
"trail_share",
"list_share",
"summit_log_create",
"trail_like",
"comment_mention",
"trail_mention"
]
}`)); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("khrcci2uqknny8h")
if err != nil {
return err
}
// update field
if err := collection.Fields.AddMarshaledJSONAt(1, []byte(`{
"hidden": false,
"id": "b57prsbu",
"maxSelect": 1,
"name": "type",
"presentable": false,
"required": true,
"system": false,
"type": "select",
"values": [
"new_follower",
"trail_comment",
"trail_share",
"list_share",
"summit_log_create",
"trail_like"
]
}`)); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -0,0 +1,73 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("khrcci2uqknny8h")
if err != nil {
return err
}
// update field
if err := collection.Fields.AddMarshaledJSONAt(1, []byte(`{
"hidden": false,
"id": "b57prsbu",
"maxSelect": 1,
"name": "type",
"presentable": false,
"required": true,
"system": false,
"type": "select",
"values": [
"new_follower",
"trail_comment",
"trail_share",
"list_share",
"summit_log_create",
"trail_like",
"comment_mention",
"trail_mention",
"summit_log_mention"
]
}`)); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("khrcci2uqknny8h")
if err != nil {
return err
}
// update field
if err := collection.Fields.AddMarshaledJSONAt(1, []byte(`{
"hidden": false,
"id": "b57prsbu",
"maxSelect": 1,
"name": "type",
"presentable": false,
"required": true,
"system": false,
"type": "select",
"values": [
"new_follower",
"trail_comment",
"trail_share",
"list_share",
"summit_log_create",
"trail_like",
"comment_mention",
"trail_mention"
]
}`)); err != nil {
return err
}
return app.Save(collection)
})
}

595
db/util/activitypub.go Normal file
View File

@@ -0,0 +1,595 @@
package util
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"database/sql"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
pub "github.com/go-ap/activitypub"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/filesystem"
"github.com/pocketbase/pocketbase/tools/security"
)
func ActorFromUser(app core.App, u *core.Record) (*core.Record, error) {
encryptionKey := os.Getenv("POCKETBASE_ENCRYPTION_KEY")
if len(encryptionKey) == 0 {
return nil, fmt.Errorf("POCKETBASE_ENCRYPTION_KEY not set")
}
collection, err := app.FindCollectionByNameOrId("activitypub_actors")
if err != nil {
return nil, err
}
priv, pub, err := generateKeyPair()
if err != nil {
return nil, err
}
privBytes := x509.MarshalPKCS1PrivateKey(priv)
privEncrypted, err := security.Encrypt(privBytes, encryptionKey)
if err != nil {
return nil, err
}
pubBytes, err := x509.MarshalPKIXPublicKey(pub)
if err != nil {
return nil, err
}
pubPem := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: pubBytes,
})
settings, err := app.FindFirstRecordByData("settings", "user", u.Id)
if err != nil {
return nil, err
}
record := core.NewRecord(collection)
origin := os.Getenv("ORIGIN")
if origin == "" {
return nil, fmt.Errorf("ORIGIN environment variable not set")
}
id := fmt.Sprintf("%s/api/v1/activitypub/user/%s", origin, strings.ToLower(u.GetString("username")))
url, err := url.Parse(origin)
if err != nil {
return nil, err
}
domain := strings.TrimPrefix(url.Hostname(), "www.")
record.Set("username", strings.ToLower(u.GetString("username")))
record.Set("preferred_username", u.GetString("username"))
record.Set("domain", domain)
record.Set("summary", settings.GetString("bio"))
record.Set("published", u.GetDateTime("created"))
record.Set("iri", id)
if u.GetString("avatar") != "" {
record.Set("icon", fmt.Sprintf("%s/api/v1/files/users/%s/%s", origin, u.Id, u.GetString("avatar")))
}
record.Set("inbox", id+"/inbox")
record.Set("outbox", id+"/outbox")
record.Set("followers", id+"/followers")
record.Set("following", id+"/following")
record.Set("isLocal", true)
record.Set("public_key", string(pubPem))
record.Set("private_key", privEncrypted)
record.Set("user", u.Id)
record.Set("last_fetched", time.Now())
err = app.Save(record)
if err != nil {
return nil, err
}
return record, nil
}
func generateKeyPair() (*rsa.PrivateKey, *rsa.PublicKey, error) {
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
pub := &priv.PublicKey
return priv, pub, nil
}
func SyncOutbox(app core.App, actor *core.Record) error {
return fetchOutboxPage(app, actor, actor.GetString("outbox")+"?page=1")
}
func fetchOutboxPage(app core.App, actor *core.Record, pageURL string) error {
client := &http.Client{}
req, err := http.NewRequest(http.MethodGet, pageURL, nil)
if err != nil {
return err
}
req.Header.Add("Accept", `application/ld+json; profile="https://www.w3.org/ns/activitystreams"`)
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
var page pub.OrderedCollectionPage
err = json.Unmarshal(body, &page)
if err != nil {
return err
}
for _, item := range page.OrderedItems {
activity, err := pub.ToActivity(item)
if err != nil {
return err
}
if activity.Type != pub.CreateType {
continue
}
}
if page.Next != nil {
return fetchOutboxPage(app, actor, page.Next.GetID().String())
}
return nil
}
func TrailFromActivity(activity pub.Activity, app core.App, actor *core.Record) (*core.Record, error) {
t, err := pub.ToObject(activity.Object)
if err != nil {
return nil, err
}
record, err := app.FindFirstRecordByData("trails", "iri", t.ID.String())
if err != nil {
if err == sql.ErrNoRows {
collection, err := app.FindCollectionByNameOrId("trails")
if err != nil {
return nil, err
}
record = core.NewRecord(collection)
record.Set("id", security.RandomStringWithAlphabet(core.DefaultIdLength, core.DefaultIdAlphabet))
} else {
return nil, err
}
}
var distance, duration, elevation_gain, elevation_loss float64
var diffculty, category string
trailTags := []string{}
tags, err := pub.ToItemCollection(t.Tag)
if err != nil {
return nil, err
}
for _, tag := range tags.Collection() {
tagObj, err := pub.ToObject(tag)
if err != nil {
continue
}
content := tagObj.Content.First().Value.String()
switch tagObj.Name.First().Value.String() {
case "category":
category = content
case "difficulty":
diffculty = content
case "elevation_gain":
elevation_gain, err = strconv.ParseFloat(content[:len(content)-1], 64)
case "elevation_loss":
elevation_loss, err = strconv.ParseFloat(content[:len(content)-1], 64)
case "duration":
duration, err = strconv.ParseFloat(content[:len(content)-1], 64)
case "distance":
distance, err = strconv.ParseFloat(content[:len(content)-1], 64)
case "tag":
existingTag, err := app.FindFirstRecordByData("tags", "name", content)
if err != nil {
if err == sql.ErrNoRows {
collection, err := app.FindCollectionByNameOrId("tags")
if err != nil {
continue
}
existingTag = core.NewRecord(collection)
existingTag.Set("name", content)
err = app.Save(existingTag)
if err != nil {
continue
}
} else {
continue
}
}
trailTags = append(trailTags, existingTag.Id)
}
if err != nil {
continue
}
}
record.Set("name", t.Name.First().Value)
record.Set("description", t.Content.First().Value)
record.Set("location", t.Location.(*pub.Place).Name.First().Value)
record.Set("lat", t.Location.(*pub.Place).Latitude)
record.Set("lon", t.Location.(*pub.Place).Longitude)
record.Set("distance", distance)
record.Set("elevation_gain", elevation_gain)
record.Set("elevation_loss", elevation_loss)
record.Set("duration", duration)
record.Set("difficulty", diffculty)
record.Set("date", t.StartTime.Unix())
record.Set("tags", trailTags)
record.Set("public", true)
record.Set("iri", t.ID.String())
record.Set("author", actor.Id)
categoryRecord, err := app.FindFirstRecordByData("categories", "name", category)
if err == nil {
record.Set("category", categoryRecord.Id)
}
if t.Attachment != nil {
attachments, err := pub.ToItemCollection(t.Attachment)
if err != nil {
return nil, err
}
photoURLs := []string{}
gpxURL := ""
for _, a := range attachments.Collection() {
attachment, err := pub.ToObject(a)
if err != nil {
continue
}
if attachment.Type == pub.DocumentType && attachment.MediaType == "application/xml+gpx" {
gpxURL = attachment.URL.GetLink().String()
} else if attachment.Type == pub.ImageType {
photoURLs = append(photoURLs, attachment.URL.GetLink().String())
}
}
if len(photoURLs) > 0 {
photos := make([]*filesystem.File, len(photoURLs))
for i, purl := range photoURLs {
photo, err := filesystem.NewFileFromURL(context.Background(), purl)
if err != nil {
continue
}
photos[i] = photo
}
record.Set("photos", photos)
}
if gpxURL != "" {
gpx, err := filesystem.NewFileFromURL(context.Background(), gpxURL)
if err != nil {
return nil, err
}
record.Set("gpx", gpx)
}
}
return record, app.Save(record)
}
func ObjectFromTrail(app core.App, trail *core.Record, mentions *pub.ItemCollection) (*pub.Object, error) {
origin := os.Getenv("ORIGIN")
if origin == "" {
return nil, fmt.Errorf("ORIGIN not set")
}
trailAuthor, err := app.FindRecordById("activitypub_actors", trail.GetString("author"))
if err != nil {
return nil, err
}
errs := app.ExpandRecord(trail, []string{"tags"}, nil)
if len(errs) > 0 {
return nil, fmt.Errorf("failed to expand tags: %v", errs)
}
errs = app.ExpandRecord(trail, []string{"category"}, nil)
if len(errs) > 0 {
return nil, fmt.Errorf("failed to expand category: %v", errs)
}
category := ""
categoryRecord := trail.ExpandedOne("category")
if categoryRecord != nil {
category = categoryRecord.GetString("name")
}
tagRecords := trail.ExpandedAll("tags")
tags := pub.ItemCollection{
pub.Object{
Type: pub.NoteType,
Name: pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, "category")),
Content: pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, category)),
},
pub.Object{
Type: pub.NoteType,
Name: pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, "difficulty")),
Content: pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, trail.GetString("difficulty"))),
},
pub.Object{
Type: pub.NoteType,
Name: pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, "elevation_gain")),
Content: pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, fmt.Sprintf("%fm", trail.GetFloat("elevation_gain")))),
},
pub.Object{
Type: pub.NoteType,
Name: pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, "elevation_loss")),
Content: pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, fmt.Sprintf("%fm", trail.GetFloat("elevation_loss")))),
},
pub.Object{
Type: pub.NoteType,
Name: pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, "distance")),
Content: pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, fmt.Sprintf("%fm", trail.GetFloat("distance")))),
},
pub.Object{
Type: pub.NoteType,
Name: pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, "duration")),
Content: pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, fmt.Sprintf("%fm", trail.GetFloat("duration")))),
},
}
if mentions != nil {
for _, m := range *mentions {
tags.Append(m)
}
}
for _, v := range tagRecords {
hashtag := pub.ObjectNew(pub.NoteType)
hashtag.Name = pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, "tag"))
hashtag.Content = pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, v.GetString("name")))
tags.Append(hashtag)
}
photos := trail.GetStringSlice("photos")
gpx := ""
if trail.GetString("gpx") != "" {
gpx = fmt.Sprintf("%s/api/v1/files/trails/%s/%s", origin, trail.Id, trail.GetString("gpx"))
}
attachments := make(pub.ItemCollection, max(len(photos), 2))
for i := range min(len(photos), 3) {
iri := fmt.Sprintf("%s/api/v1/files/trails/%s/%s", origin, trail.Id, photos[i])
attachments[i] = pub.Image{
Type: pub.ImageType,
MediaType: "image/jpeg",
URL: pub.IRI(iri),
}
}
if gpx != "" {
attachments.Append(pub.Document{
Type: pub.DocumentType,
MediaType: "application/xml+gpx",
URL: pub.IRI(gpx),
})
}
activityURL := fmt.Sprintf("%s/trail/view/@%s/%s", origin, trailAuthor.GetString("username"), trail.Id)
activityContent := fmt.Sprintf("<h1>%s</h1>%s<p><a href=\"%s\">%s</a></p>", trail.GetString("name"), trail.GetString("description"), activityURL, activityURL)
trailObject := pub.ObjectNew(pub.NoteType)
trailObject.Name = pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, trail.GetString("name")))
trailObject.Content = pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, activityContent))
trailObject.Location = pub.Place{
Type: pub.PlaceType,
Name: pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, trail.GetString("location"))),
Latitude: trail.GetFloat("lat"),
Longitude: trail.GetFloat("lon"),
}
trailObject.AttributedTo = pub.IRI(trailAuthor.GetString("iri"))
trailObject.Published = trail.GetDateTime("created").Time()
trailObject.ID = pub.IRI(fmt.Sprintf("%s/api/v1/trail/%s", origin, trail.Id))
trailObject.URL = pub.IRI(activityURL)
trailObject.StartTime = trail.GetDateTime("date").Time()
trailObject.Attachment = attachments
trailObject.Tag = tags
return trailObject, nil
}
func ListFromActivity(activity pub.Activity, app core.App, actor *core.Record) (*core.Record, error) {
l, err := pub.ToObject(activity.Object)
if err != nil {
return nil, err
}
record, err := app.FindFirstRecordByData("lists", "iri", l.ID.String())
if err != nil {
if err == sql.ErrNoRows {
collection, err := app.FindCollectionByNameOrId("lists")
if err != nil {
return nil, err
}
record = core.NewRecord(collection)
record.Set("id", security.RandomStringWithAlphabet(core.DefaultIdLength, core.DefaultIdAlphabet))
} else {
return nil, err
}
}
record.Set("name", l.Name.First().Value)
record.Set("description", l.Content.First().Value)
record.Set("public", true)
record.Set("iri", l.ID.String())
record.Set("author", actor.Id)
if l.Attachment != nil {
avatarURL := ""
attachments, err := pub.ToItemCollection(l.Attachment)
if err != nil {
return nil, err
}
for _, a := range attachments.Collection() {
attachment, err := pub.ToObject(a)
if err != nil {
continue
}
if attachment.Type == pub.ImageType {
avatarURL = attachment.URL.GetLink().String()
}
}
if avatarURL != "" {
avatar, err := filesystem.NewFileFromURL(context.Background(), avatarURL)
if err != nil {
return nil, err
}
record.Set("avatar", avatar)
}
}
err = app.Save(record)
return record, err
}
func ObjectFromList(app core.App, list *core.Record) (*pub.Object, error) {
origin := os.Getenv("ORIGIN")
if origin == "" {
return nil, fmt.Errorf("ORIGIN not set")
}
listAuthor, err := app.FindRecordById("activitypub_actors", list.GetString("author"))
if err != nil {
return nil, err
}
avatar := ""
if list.GetString("avatar") != "" {
avatar = fmt.Sprintf("%s/api/v1/files/lists/%s/%s", origin, list.Id, list.GetString("avatar"))
}
attachments := make(pub.ItemCollection, 2)
if avatar != "" {
attachments[0] = pub.Image{
Type: pub.ImageType,
MediaType: "image/jpeg",
URL: pub.IRI(avatar),
}
}
activityURL := fmt.Sprintf("%s/lists/@%s/%s", origin, listAuthor.GetString("username"), list.Id)
activityContent := fmt.Sprintf("%s<p><a href=\"%s\">%s</a></p>", list.GetString("description"), activityURL, activityURL)
listObject := pub.ObjectNew(pub.NoteType)
listObject.Name = pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, list.GetString("name")))
listObject.Content = pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, activityContent))
listObject.AttributedTo = pub.IRI(listAuthor.GetString("iri"))
listObject.Published = list.GetDateTime("created").Time()
listObject.ID = pub.IRI(fmt.Sprintf("%s/api/v1/list/%s", origin, list.Id))
listObject.URL = pub.IRI(activityURL)
listObject.Attachment = attachments
return listObject, nil
}
func ObjectFromComment(app core.App, comment *core.Record, mentions *pub.ItemCollection) (*pub.Object, error) {
origin := os.Getenv("ORIGIN")
if origin == "" {
return nil, fmt.Errorf("ORIGIN not set")
}
commentAuthor, err := app.FindRecordById("activitypub_actors", comment.GetString("author"))
if err != nil {
return nil, err
}
commentTrail, err := app.FindRecordById("trails", comment.GetString("trail"))
if err != nil {
return nil, err
}
commentTrailAuthor, err := app.FindRecordById("activitypub_actors", commentTrail.GetString("author"))
if err != nil {
return nil, err
}
trailURL := ""
if commentTrailAuthor.GetBool("isLocal") {
trailURL = fmt.Sprintf("https://%s/api/v1/trail/%s", commentTrailAuthor.GetString("domain"), comment.GetString("trail"))
} else {
trailURL = commentTrail.GetString("iri")
}
commentObject := pub.ObjectNew(pub.NoteType)
commentObject.ID = pub.IRI(fmt.Sprintf("%s/api/v1/comment/%s", origin, comment.Id))
commentObject.Content = pub.NaturalLanguageValuesNew(pub.LangRefValueNew(pub.NilLangRef, comment.GetString("text")))
commentObject.Published = comment.GetDateTime("created").Time()
commentObject.AttributedTo = pub.IRI(commentAuthor.GetString("iri"))
commentObject.InReplyTo = pub.IRI(trailURL)
if mentions != nil {
commentObject.Tag = *mentions
}
return commentObject, nil
}
func TrailObjectFromIRI(iri string) (*pub.Object, error) {
fetchURL := strings.Replace(iri, "api/v1/trail", "api/v1/activitypub/trail", 1)
client := &http.Client{}
req, err := http.NewRequest(http.MethodGet, fetchURL, nil)
if err != nil {
return nil, err
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var object pub.Object
err = json.Unmarshal(body, &object)
if err != nil {
return nil, err
}
return &object, nil
}

View File

@@ -17,12 +17,15 @@ type EmailData struct {
}
var notificationTemplates = map[NotificationType]string{
TrailCreate: "{{.Author}} has created a new trail: {{.trail}}.",
TrailShare: "{{.Author}} has shared a trail with you: {{.trail}}.",
ListCreate: "{{.Author}} has created a new list: {{.list}}.",
ListShare: "{{.Author}} has shared a list with you: {{.list}}.",
NewFollower: "Good news! You have a new follower: {{.Author}}.",
TrailComment: "{{.Author}} commented on your trail '{{.trail}}': '{{.comment}}'.",
TrailShare: "{{.Author}} has shared a trail with you: {{.trail}}.",
ListShare: "{{.Author}} has shared a list with you: {{.list}}.",
NewFollower: "Good news! You have a new follower: {{.Author}}.",
TrailComment: "{{.Author}} commented on your trail '{{.trail_name}}': '{{.comment}}'.",
SummitLogCreate: "{{.Author}} created a summit log on your trail '{{.trail_name}}'.",
TrailLike: "{{.Author}} liked your trail '{{.trail_name}}'.",
CommentMention: "{{.Author}} mentioned you in a comment.",
TrailMention: "{{.Author}} mentioned you in a trail.",
SummitLogMention: "{{.Author}} mentioned you in a summit log.",
}
func GenerateHTML(appUrl string, recipientName string, authorName string, notificationType NotificationType, metadata map[string]string) (string, error) {

View File

@@ -2,12 +2,17 @@ package util
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"net/url"
"path"
"github.com/meilisearch/meilisearch-go"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
"github.com/twpayne/go-gpx"
"github.com/twpayne/go-polyline"
@@ -31,16 +36,32 @@ func documentFromTrailRecord(app core.App, r *core.Record, author *core.Record,
tags[i] = v.GetString("name")
}
polyline, err := getPolyline(app, r)
category := ""
trailCategory := r.ExpandedOne("category")
if trailCategory != nil {
category = trailCategory.GetString("name")
}
// polyline, err := getPolyline(app, r)
// if err != nil {
// polyline = ""
// }
domain := ""
if !author.GetBool("isLocal") {
domain = author.GetString("domain")
}
logCount, err := app.CountRecords("summit_logs", dbx.NewExp("trail={:id}", dbx.Params{"id": r.Id}))
if err != nil {
return nil, err
}
document := map[string]interface{}{
document := map[string]any{
"id": r.Id,
"author": r.GetString("author"),
"author": author.Id,
"author_name": author.GetString("username"),
"author_avatar": author.GetString("avatar"),
"author_avatar": author.GetString("icon"),
"name": r.GetString("name"),
"description": r.GetString("description"),
"location": r.GetString("location"),
@@ -49,15 +70,17 @@ func documentFromTrailRecord(app core.App, r *core.Record, author *core.Record,
"elevation_loss": r.GetFloat("elevation_loss"),
"duration": r.GetFloat("duration"),
"difficulty": r.Get("difficulty"),
"category": r.Get("category"),
"completed": len(r.GetStringSlice("summit_logs")) > 0,
"category": category,
"completed": logCount > 0,
"date": r.GetDateTime("date").Time().Unix(),
"created": r.GetDateTime("created").Time().Unix(),
"public": r.GetBool("public"),
"thumbnail": thumbnail,
"gpx": r.GetString("gpx"),
"tags": tags,
"polyline": polyline,
// "polyline": polyline,
"domain": domain,
"iri": r.GetString("iri"),
"_geo": map[string]float64{
"lat": r.GetFloat("lat"),
"lng": r.GetFloat("lon"),
@@ -66,6 +89,9 @@ func documentFromTrailRecord(app core.App, r *core.Record, author *core.Record,
if includeShares {
document["shares"] = []string{}
document["likes"] = []string{}
document["like_count"] = 0
}
return document, nil
@@ -109,28 +135,124 @@ func getPolyline(app core.App, r *core.Record) (string, error) {
return string(polyline.EncodeCoords(coordinates)), nil
}
func documentFromListRecord(r *core.Record, includeShares bool) map[string]interface{} {
func documentFromListRecord(r *core.Record, author *core.Record, includeShares bool) (map[string]interface{}, error) {
totalElevationGain := 0.0
totalElevationLoss := 0.0
totalDistance := 0.0
totalDuration := 0.0
trails := len(r.GetStringSlice("trails"))
if r.GetString("iri") != "" {
doc, err := documentFromRemoteRecord(r, "lists")
if err == nil {
totalElevationGain = doc["elevation_gain"].(float64)
totalElevationLoss = doc["elevation_loss"].(float64)
totalDistance = doc["distance"].(float64)
totalDuration = doc["duration"].(float64)
trails = int(doc["trails"].(float64))
}
} else {
allTrails := r.ExpandedAll("trails")
for _, t := range allTrails {
totalElevationGain += t.GetFloat("elevation_gain")
totalElevationLoss += t.GetFloat("elevation_loss")
totalDistance += t.GetFloat("distance")
totalDuration += t.GetFloat("duration")
}
}
document := map[string]interface{}{
"id": r.Id,
"author": r.GetString("author"),
"name": r.GetString("name"),
"description": r.GetString("description"),
"public": r.GetBool("public"),
"created": r.GetDateTime("created").Time().Unix(),
"trails": r.GetStringSlice("trails"),
"id": r.Id,
"author": author.Id,
"author_name": author.GetString("username"),
"author_avatar": author.GetString("icon"),
"avatar": r.GetString("avatar"),
"name": r.GetString("name"),
"description": r.GetString("description"),
"elevation_gain": totalElevationGain,
"elevation_loss": totalElevationLoss,
"distance": totalDistance,
"duration": totalDuration,
"public": r.GetBool("public"),
"created": r.GetDateTime("created").Time().Unix(),
"trails": trails,
"iri": r.GetString("iri"),
}
if includeShares {
document["shares"] = []string{}
}
return document
return document, nil
}
func documentFromRemoteRecord(r *core.Record, index string) (map[string]interface{}, error) {
client := &http.Client{}
if r.GetString("iri") == "" {
return nil, fmt.Errorf("record has no iri")
}
iri := r.GetString("iri")
url, err := url.Parse(iri)
if err != nil {
return nil, err
}
remoteRecordId := path.Base(url.Path)
searchURL := fmt.Sprintf("%s://%s/api/v1/search/%s", url.Scheme, url.Host, index)
body := []byte(fmt.Sprintf(`{"q": "%s"}`, remoteRecordId))
req, err := http.NewRequest("POST", searchURL, bytes.NewBuffer(body))
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to fetch remote record: received status %d", resp.StatusCode)
}
respBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var searchResponse meilisearch.SearchResponse
json.Unmarshal(respBytes, &searchResponse)
if len(searchResponse.Hits) == 0 {
return nil, fmt.Errorf("no documents in result set")
}
document, ok := searchResponse.Hits[0].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("unexpected document format")
}
return document, nil
}
func IndexTrail(app core.App, r *core.Record, author *core.Record, client meilisearch.ServiceManager) error {
errs := app.ExpandRecord(r, []string{"tags"}, nil)
if len(errs) > 0 {
return fmt.Errorf("failed to expand: %v", errs)
return fmt.Errorf("failed to expand tags: %v", errs)
}
errs = app.ExpandRecord(r, []string{"category"}, nil)
if len(errs) > 0 {
return fmt.Errorf("failed to expand category: %v", errs)
}
doc, err := documentFromTrailRecord(app, r, author, true)
if err != nil {
@@ -148,10 +270,14 @@ func IndexTrail(app core.App, r *core.Record, author *core.Record, client meilis
func UpdateTrail(app core.App, r *core.Record, author *core.Record, client meilisearch.ServiceManager) error {
errs := app.ExpandRecord(r, []string{"tags"}, nil)
if len(errs) > 0 {
return fmt.Errorf("failed to expand: %v", errs)
return fmt.Errorf("failed to expand tags: %v", errs)
}
errs = app.ExpandRecord(r, []string{"category"}, nil)
if len(errs) > 0 {
return fmt.Errorf("failed to expand category: %v", errs)
}
doc, err := documentFromTrailRecord(app, r, author, true)
doc, err := documentFromTrailRecord(app, r, author, false)
if err != nil {
return err
}
@@ -177,20 +303,49 @@ func UpdateTrailShares(trailId string, shares []string, client meilisearch.Servi
return nil
}
func IndexList(r *core.Record, client meilisearch.ServiceManager) error {
documents := []map[string]interface{}{documentFromListRecord(r, true)}
func UpdateTrailLikes(trailId string, likes []string, client meilisearch.ServiceManager) error {
documents := []map[string]interface{}{
{
"id": trailId,
"like_count": len(likes),
"likes": likes,
},
}
if _, err := client.Index("trails").UpdateDocuments(documents); err != nil {
return err
}
return nil
}
if _, err := client.Index("lists").AddDocuments(documents); err != nil {
func IndexList(app core.App, r *core.Record, author *core.Record, client meilisearch.ServiceManager) error {
errs := app.ExpandRecord(r, []string{"trails"}, nil)
if len(errs) > 0 {
return fmt.Errorf("failed to expand trails: %v", errs)
}
documents, err := documentFromListRecord(r, author, true)
if err != nil {
return err
}
if _, err = client.Index("lists").AddDocuments(documents); err != nil {
return err
}
return nil
}
func UpdateList(r *core.Record, client meilisearch.ServiceManager) error {
documents := documentFromListRecord(r, false)
func UpdateList(app core.App, r *core.Record, author *core.Record, client meilisearch.ServiceManager) error {
errs := app.ExpandRecord(r, []string{"trails"}, nil)
if len(errs) > 0 {
return fmt.Errorf("failed to expand trails: %v", errs)
}
if _, err := client.Index("lists").UpdateDocuments(documents); err != nil {
documents, err := documentFromListRecord(r, author, false)
if err != nil {
return err
}
if _, err = client.Index("lists").UpdateDocuments(documents); err != nil {
return err
}

View File

@@ -11,12 +11,15 @@ import (
type NotificationType string
const (
TrailCreate NotificationType = "trail_create"
TrailShare NotificationType = "trail_share"
ListCreate NotificationType = "list_create"
ListShare NotificationType = "list_share"
NewFollower NotificationType = "new_follower"
TrailComment NotificationType = "trail_comment"
TrailShare NotificationType = "trail_share"
ListShare NotificationType = "list_share"
NewFollower NotificationType = "new_follower"
TrailComment NotificationType = "trail_comment"
TrailLike NotificationType = "trail_like"
SummitLogCreate NotificationType = "summit_log_create"
CommentMention NotificationType = "comment_mention"
TrailMention NotificationType = "trail_mention"
SummitLogMention NotificationType = "summit_log_mention"
)
type Notification struct {
@@ -54,11 +57,14 @@ func getNotificationPermissions(app core.App, user string, notificationType Noti
return &settingsForType, nil
}
func SendNotification(app core.App, notification Notification, recipient string) error {
if notification.Author == recipient {
func SendNotification(app core.App, notification Notification, recipient *core.Record) error {
if notification.Author == recipient.Id {
return nil
}
permissions, err := getNotificationPermissions(app, recipient, notification.Type)
if !recipient.GetBool("isLocal") {
return nil
}
permissions, err := getNotificationPermissions(app, recipient.GetString("user"), notification.Type)
if err != nil {
return err
}
@@ -73,7 +79,7 @@ func SendNotification(app core.App, notification Notification, recipient string)
n.Set("type", string(notification.Type))
n.Set("metadata", notification.Metadata)
n.Set("seen", notification.Seen)
n.Set("recipient", recipient)
n.Set("recipient", recipient.Id)
n.Set("author", notification.Author)
if err := app.Save(n); err != nil {
@@ -82,15 +88,15 @@ func SendNotification(app core.App, notification Notification, recipient string)
}
if permissions.Email {
recipientUser, err := app.FindRecordById("users", recipient)
recipientActor, err := app.FindRecordById("activitypub_actors", recipient.Id)
if err != nil {
return err
}
authorUser, err := app.FindRecordById("users", notification.Author)
authorActor, err := app.FindRecordById("activitypub_actors", notification.Author)
if err != nil {
return err
}
html, err := GenerateHTML(app.Settings().Meta.AppURL, recipientUser.GetString("username"), authorUser.GetString("username"), notification.Type, notification.Metadata)
html, err := GenerateHTML(app.Settings().Meta.AppURL, recipientActor.GetString("username"), authorActor.GetString("username"), notification.Type, notification.Metadata)
if err != nil {
return err
}
@@ -100,27 +106,12 @@ func SendNotification(app core.App, notification Notification, recipient string)
Address: app.Settings().Meta.SenderAddress,
Name: app.Settings().Meta.SenderName,
},
To: []mail.Address{{Address: recipientUser.Email()}},
To: []mail.Address{{Address: recipientActor.Email()}},
Subject: "wanderer - New Notification",
HTML: html,
}
app.NewMailClient().Send(message)
}
return nil
}
func SendNotificationToFollowers(app core.App, notification Notification) error {
followers, err := app.FindRecordsByFilter("follows", "followee={:user}", "", -1, 0, dbx.Params{"user": notification.Author})
if err != nil {
return err
}
for _, f := range followers {
recipient := f.GetString("follower")
SendNotification(app, notification, recipient)
return app.NewMailClient().Send(message)
}
return nil
}