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

@@ -251,7 +251,7 @@ This release contains breaking changes. Most migrations will happen automaticall
- Fixes issue with GPX export when using Google Chrome (thanks [@tofublock](https://github.com/tofublock))
## Miscellaneous
As the number of contributors to this project continues to grow (which Im very happy about), Ive set up a [Discord channel](https://discord.gg/MdpybUHc) for more direct communication. If youre interested in helping with Wanderer, feel free to join!
As the number of contributors to this project continues to grow (which Im very happy about), Ive set up a [Discord channel](https://discord.gg/MdpybUHc) for more direct communication. If youre interested in helping with wanderer, feel free to join!
# v0.11.0
## Features

View File

@@ -44,7 +44,7 @@ The first startup can take up to 90 seconds after which you can access the front
> ⚠️ if you are using wanderer in a production environment make sure to change the MEILI_MASTER_KEY variable.
You can also run wanderer on bare-metal. Check out the [documentation](https://wanderer.to/getting-started/installation/#from-source) for a detailed how-to guide.
You can also run wanderer on bare-metal. Check out the [documentation](https://wanderer.to/run/installation/#installation-from-source) for a detailed how-to guide.
## Support wanderer

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"),
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 = util.SendNotificationToFollowers(e.App, notification)
err = e.Next()
if err != nil {
return err
}
err = federation.CreateTrailActivity(e.App, e.Record, pub.CreateType)
if err != nil {
return err
}
return e.Next()
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,8 +256,25 @@ func updateTrailHandler(client meilisearch.ServiceManager) func(e *core.RecordEv
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()
}
err = e.Next()
if err != nil {
return err
}
err = federation.CreateTrailActivity(e.App, e.Record, pub.UpdateType)
if err != nil {
return err
}
return nil
}
}
func deleteTrailHandler(client meilisearch.ServiceManager) func(e *core.RecordEvent) error {
@@ -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,43 +425,179 @@ 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
}
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
}
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
}
}
func deleteListHandler(client meilisearch.ServiceManager) func(e *core.RecordEvent) error {
@@ -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}}'.",
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"),
"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": r.GetStringSlice("trails"),
"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"
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
}

Some files were not shown because too many files have changed in this diff Show More