Federation Refactoring & Architecture Improvements (#930)
* initial commit * add lists * update permissions * fix waypoint create * needs_full_sync for activitypub trails * fixes build issues * fix list get * further CSRF protection * update API docs * adds rate limiter * fix tiptap mentions * a bit more cleanup of main.go * Fix migration order * fixes reviewed notes * require context for activitpub server calls * improve hashing for identifier * fix copy paste error * fix sync trail/list issues * fix summit log/comment duplicates * adaptions after trail merge * fix dockerignore --------- Co-authored-by: Christian Beutel <> Co-authored-by: slothful-vassal <89943360+slothful-vassal@users.noreply.github.com>
This commit is contained in:
22
db/hooks/api_tokens.go
Normal file
22
db/hooks/api_tokens.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/tools/security"
|
||||
)
|
||||
|
||||
func CreateAPITokenHandler() func(e *core.RecordEvent) error {
|
||||
return func(e *core.RecordEvent) error {
|
||||
rawToken := "wanderer_key_" + security.RandomString(32)
|
||||
|
||||
hashedKey := security.SHA256(rawToken)
|
||||
|
||||
e.Record.Set("token", hashedKey)
|
||||
|
||||
// Temporarily store rawToken so we can display it once to the user
|
||||
e.Record.WithCustomData(true)
|
||||
e.Record.Set("rawToken", rawToken)
|
||||
|
||||
return e.Next()
|
||||
}
|
||||
}
|
||||
47
db/hooks/bootstrap.go
Normal file
47
db/hooks/bootstrap.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"os"
|
||||
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
func OnBootstrapHandler() func(se *core.BootstrapEvent) error {
|
||||
return func(e *core.BootstrapEvent) error {
|
||||
if err := e.Next(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if e.App.Settings().Meta.AppName == "Acme" {
|
||||
e.App.Settings().Meta.AppName = "wanderer"
|
||||
}
|
||||
if v := os.Getenv("ORIGIN"); v != "" {
|
||||
e.App.Settings().Meta.AppURL = v
|
||||
}
|
||||
if v := cmp.Or(os.Getenv("POCKETBASE_SMTP_SENDER_ADDRESS"), os.Getenv("POCKETBASE_SMTP_SENDER_ADRESS")); v != "" {
|
||||
e.App.Settings().Meta.SenderAddress = v
|
||||
}
|
||||
if v := os.Getenv("POCKETBASE_SMTP_SENDER_NAME"); v != "" {
|
||||
e.App.Settings().Meta.SenderName = v
|
||||
}
|
||||
if v := os.Getenv("POCKETBASE_SMTP_ENABLED"); v != "" {
|
||||
e.App.Settings().SMTP.Enabled = cast.ToBool(v)
|
||||
}
|
||||
if v := os.Getenv("POCKETBASE_SMTP_HOST"); v != "" {
|
||||
e.App.Settings().SMTP.Host = v
|
||||
}
|
||||
if v := os.Getenv("POCKETBASE_SMTP_PORT"); v != "" {
|
||||
e.App.Settings().SMTP.Port = cast.ToInt(v)
|
||||
}
|
||||
if v := os.Getenv("POCKETBASE_SMTP_USERNAME"); v != "" {
|
||||
e.App.Settings().SMTP.Username = v
|
||||
}
|
||||
if v := os.Getenv("POCKETBASE_SMTP_PASSWORD"); v != "" {
|
||||
e.App.Settings().SMTP.Password = v
|
||||
}
|
||||
|
||||
return e.App.Save(e.App.Settings())
|
||||
}
|
||||
}
|
||||
65
db/hooks/comments.go
Normal file
65
db/hooks/comments.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"pocketbase/federation"
|
||||
"pocketbase/util"
|
||||
|
||||
pub "github.com/go-ap/activitypub"
|
||||
"github.com/meilisearch/meilisearch-go"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
)
|
||||
|
||||
func CreateCommentHandler() func(e *core.RecordRequestEvent) error {
|
||||
return func(e *core.RecordRequestEvent) error {
|
||||
|
||||
e.Next()
|
||||
|
||||
userActor, err := e.App.FindFirstRecordByData("activitypub_actors", "user", e.Auth.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, err := util.GetSafeActorContext(e.Request, userActor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = federation.CreateCommentActivity(e.App, ctx, e.Record, pub.CreateType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateCommentHandler() func(e *core.RecordRequestEvent) error {
|
||||
return func(e *core.RecordRequestEvent) error {
|
||||
userActor, err := e.App.FindFirstRecordByData("activitypub_actors", "user", e.Auth.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, err := util.GetSafeActorContext(e.Request, userActor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = federation.CreateCommentActivity(e.App, ctx, 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()
|
||||
}
|
||||
}
|
||||
57
db/hooks/feed.go
Normal file
57
db/hooks/feed.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"pocketbase/util"
|
||||
"strings"
|
||||
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
)
|
||||
|
||||
func ListFeedHandler() func(e *core.RecordsListRequestEvent) error {
|
||||
return func(e *core.RecordsListRequestEvent) error {
|
||||
|
||||
for _, r := range e.Records {
|
||||
var item *core.Record
|
||||
var err error
|
||||
|
||||
typ := r.GetString("type")
|
||||
typ = strings.Trim(typ, "\"")
|
||||
|
||||
itemId := r.GetString("item")
|
||||
itemId = strings.Trim(itemId, "\"")
|
||||
|
||||
switch typ {
|
||||
case string(util.TrailFeed):
|
||||
item, err = e.App.FindRecordById("trails", itemId)
|
||||
case string(util.ListFeed):
|
||||
item, err = e.App.FindRecordById("lists", itemId)
|
||||
case string(util.SummitLogFeed):
|
||||
item, err = e.App.FindRecordById("summit_logs", itemId)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if item == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
errs := e.App.ExpandRecord(item, []string{"author"}, nil)
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("failed to expand author: %v", errs)
|
||||
}
|
||||
|
||||
if typ == string(util.TrailFeed) {
|
||||
errs := e.App.ExpandRecord(item, []string{"category"}, nil)
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("failed to expand category: %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
r.MergeExpand(map[string]any{"item": item})
|
||||
}
|
||||
return e.Next()
|
||||
}
|
||||
}
|
||||
24
db/hooks/follow.go
Normal file
24
db/hooks/follow.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"pocketbase/federation"
|
||||
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
)
|
||||
|
||||
func CreateFollowHandler() func(e *core.RecordRequestEvent) error {
|
||||
return func(e *core.RecordRequestEvent) error {
|
||||
e.Next()
|
||||
federation.CreateFollowActivity(e.App, e.Record)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func DeleteFollowHandler() func(e *core.RecordRequestEvent) error {
|
||||
return func(e *core.RecordRequestEvent) error {
|
||||
federation.CreateUnfollowActivity(e.App, e.Record)
|
||||
|
||||
return e.Next()
|
||||
}
|
||||
}
|
||||
146
db/hooks/integrations.go
Normal file
146
db/hooks/integrations.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"pocketbase/util"
|
||||
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/tools/security"
|
||||
)
|
||||
|
||||
func ListIntegrationHandler() func(e *core.RecordsListRequestEvent) error {
|
||||
return func(e *core.RecordsListRequestEvent) error {
|
||||
if e.HasSuperuserAuth() {
|
||||
return e.Next()
|
||||
}
|
||||
for _, r := range e.Records {
|
||||
|
||||
err := censorIntegrationSecrets(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return e.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func CreateIntegrationHandler() func(e *core.RecordEvent) error {
|
||||
return func(e *core.RecordEvent) error {
|
||||
err := encryptIntegrationSecrets(e.App, e.Record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return e.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func CreateUpdateIntegrationSuccessHandler() func(e *core.RecordEvent) error {
|
||||
return func(e *core.RecordEvent) error {
|
||||
err := censorIntegrationSecrets(e.Record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return e.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateIntegrationHandler() func(e *core.RecordEvent) error {
|
||||
return func(e *core.RecordEvent) error {
|
||||
err := encryptIntegrationSecrets(e.App, e.Record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return e.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func censorIntegrationSecrets(r *core.Record) error {
|
||||
secrets := map[string][]string{
|
||||
"strava": {"clientSecret", "refreshToken", "accessToken", "expiresAt"},
|
||||
"komoot": {"password"},
|
||||
"hammerhead": {"password"},
|
||||
}
|
||||
for key, secretKeys := range secrets {
|
||||
if integrationString := r.GetString(key); integrationString != "" {
|
||||
var integration map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(integrationString), &integration); err != nil {
|
||||
return err
|
||||
}
|
||||
if integration == nil {
|
||||
continue
|
||||
}
|
||||
for _, secretKey := range secretKeys {
|
||||
integration[secretKey] = ""
|
||||
}
|
||||
b, err := json.Marshal(integration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Set(key, string(b))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encryptIntegrationSecrets(app core.App, r *core.Record) error {
|
||||
encryptionKey := os.Getenv("POCKETBASE_ENCRYPTION_KEY")
|
||||
if len(encryptionKey) == 0 {
|
||||
return apis.NewBadRequestError("POCKETBASE_ENCRYPTION_KEY not set", nil)
|
||||
}
|
||||
|
||||
secrets := map[string][]string{
|
||||
"strava": {"clientSecret", "refreshToken", "accessToken", "expiresAt"},
|
||||
"komoot": {"password"},
|
||||
"hammerhead": {"password"},
|
||||
}
|
||||
|
||||
original, _ := app.FindRecordById("integrations", r.Id)
|
||||
|
||||
for key, secretKeys := range secrets {
|
||||
if integrationString := r.GetString(key); integrationString != "" {
|
||||
var integration map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(integrationString), &integration); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, secretKey := range secretKeys {
|
||||
// If the secret is already encrypted, we don't re-encrypt it.
|
||||
// TODO: This is a bit of a hack, we should handle this in a more robust way (e.g.
|
||||
// storing flag on the record or prefixing encrypted strings with enc: or smilar).
|
||||
// Doing that would also potentially allow us to support key rotation in the future.
|
||||
if secret, ok := integration[secretKey].(string); ok && len(secret) > 0 && !util.CanDecryptSecret(secret) {
|
||||
encryptedSecret, err := security.Encrypt([]byte(secret), encryptionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
integration[secretKey] = encryptedSecret
|
||||
} else if original != nil {
|
||||
|
||||
originalString := original.GetString(key)
|
||||
var originalIntegration map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(originalString), &originalIntegration); err != nil {
|
||||
return err
|
||||
}
|
||||
if integration == nil {
|
||||
continue
|
||||
}
|
||||
integration[secretKey] = originalIntegration[secretKey]
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.Marshal(integration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Set(key, string(b))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
105
db/hooks/list.go
Normal file
105
db/hooks/list.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"pocketbase/federation"
|
||||
"pocketbase/util"
|
||||
|
||||
pub "github.com/go-ap/activitypub"
|
||||
"github.com/meilisearch/meilisearch-go"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
)
|
||||
|
||||
func CreateListHandler(client meilisearch.ServiceManager) func(e *core.RecordEvent) error {
|
||||
return func(e *core.RecordEvent) error {
|
||||
record := e.Record
|
||||
|
||||
author, err := e.App.FindRecordById("activitypub_actors", record.GetString(("author")))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := util.IndexLists(e.App, []*core.Record{record}, 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
|
||||
}
|
||||
|
||||
_, err = util.InsertIntoFeed(e.App, author.Id, author.Id, record.Id, util.ListFeed)
|
||||
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
|
||||
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 {
|
||||
return func(e *core.RecordEvent) error {
|
||||
record := e.Record
|
||||
_, err := client.Index("lists").DeleteDocument(record.Id, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = federation.CreateListDeleteActivity(e.App, record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = util.DeleteFromFeed(e.App, record.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return e.Next()
|
||||
}
|
||||
}
|
||||
56
db/hooks/list_share.go
Normal file
56
db/hooks/list_share.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"pocketbase/federation"
|
||||
"pocketbase/util"
|
||||
|
||||
"github.com/meilisearch/meilisearch-go"
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
)
|
||||
|
||||
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",
|
||||
dbx.NewExp("list = {:listId}", dbx.Params{"listId": listId}),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
actorIds := make([]string, len(shares))
|
||||
for i, r := range shares {
|
||||
actorIds[i] = r.GetString("actor")
|
||||
}
|
||||
err = util.UpdateListShares(listId, actorIds, client)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = federation.CreateAnnounceActivity(e.App, record, federation.ListAnnounceType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return e.Next()
|
||||
}
|
||||
}
|
||||
96
db/hooks/summit_logs.go
Normal file
96
db/hooks/summit_logs.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"pocketbase/federation"
|
||||
"pocketbase/util"
|
||||
|
||||
pub "github.com/go-ap/activitypub"
|
||||
"github.com/meilisearch/meilisearch-go"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
)
|
||||
|
||||
func CreateSummitLogHandler(client meilisearch.ServiceManager) func(e *core.RecordRequestEvent) error {
|
||||
return func(e *core.RecordRequestEvent) error {
|
||||
|
||||
err := e.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userActor, err := e.App.FindFirstRecordByData("activitypub_actors", "user", e.Auth.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, err := util.GetSafeActorContext(e.Request, userActor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
trail, err := e.App.FindRecordById("trails", e.Record.GetString("trail"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := util.IndexTrails(e.App, []*core.Record{trail}, client); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = federation.CreateSummitLogActivity(e.App, ctx, 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
|
||||
}
|
||||
|
||||
userActor, err := e.App.FindFirstRecordByData("activitypub_actors", "user", e.Auth.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, err := util.GetSafeActorContext(e.Request, userActor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = federation.CreateSummitLogActivity(e.App, ctx, e.Record, pub.UpdateType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func DeleteSummitLogHandler(client meilisearch.ServiceManager) func(e *core.RecordRequestEvent) error {
|
||||
return func(e *core.RecordRequestEvent) error {
|
||||
err := e.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
trail, err := e.App.FindRecordById("trails", e.Record.GetString("trail"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := util.IndexTrails(e.App, []*core.Record{trail}, client); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = federation.CreateSummitLogDeleteActivity(e.App, e.Record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
118
db/hooks/trail_like.go
Normal file
118
db/hooks/trail_like.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"pocketbase/federation"
|
||||
"pocketbase/util"
|
||||
|
||||
"github.com/meilisearch/meilisearch-go"
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
)
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
57
db/hooks/trail_share.go
Normal file
57
db/hooks/trail_share.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"pocketbase/federation"
|
||||
"pocketbase/util"
|
||||
|
||||
"github.com/meilisearch/meilisearch-go"
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
)
|
||||
|
||||
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")
|
||||
shares, err := e.App.FindAllRecords("trail_share",
|
||||
dbx.NewExp("trail = {:trailId}", dbx.Params{"trailId": trailId}),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
actorIds := make([]string, len(shares))
|
||||
for i, r := range shares {
|
||||
actorIds[i] = r.GetString("actor")
|
||||
}
|
||||
err = util.UpdateTrailShares(trailId, actorIds, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = federation.CreateAnnounceActivity(e.App, record, federation.TrailAnnounceType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func DeleteTrailShareHandler(client meilisearch.ServiceManager) func(e *core.RecordRequestEvent) error {
|
||||
return func(e *core.RecordRequestEvent) error {
|
||||
record := e.Record
|
||||
|
||||
trailId := record.GetString("trail")
|
||||
err := util.UpdateTrailShares(trailId, []string{}, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return e.Next()
|
||||
}
|
||||
}
|
||||
122
db/hooks/trails.go
Normal file
122
db/hooks/trails.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"log"
|
||||
"pocketbase/federation"
|
||||
"pocketbase/util"
|
||||
"time"
|
||||
|
||||
"github.com/go-ap/activitypub"
|
||||
pub "github.com/go-ap/activitypub"
|
||||
"github.com/meilisearch/meilisearch-go"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
)
|
||||
|
||||
func CreateTrailHandler(client meilisearch.ServiceManager) func(e *core.RecordEvent) error {
|
||||
return func(e *core.RecordEvent) error {
|
||||
record := e.Record
|
||||
|
||||
userActor, err := e.App.FindRecordById("activitypub_actors", record.GetString(("author")))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := util.IndexTrails(e.App, []*core.Record{record}, client); err != nil {
|
||||
return err
|
||||
}
|
||||
if !userActor.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
|
||||
}
|
||||
|
||||
ctx, err := util.GetSafeActorContext(nil, userActor)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = federation.CreateTrailActivity(e.App, ctx, e.Record, activitypub.CreateType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = util.InsertIntoFeed(e.App, userActor.Id, userActor.Id, record.Id, util.TrailFeed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateTrailHandler(client meilisearch.ServiceManager) func(e *core.RecordEvent) error {
|
||||
return func(e *core.RecordEvent) error {
|
||||
record := e.Record
|
||||
userActor, err := e.App.FindRecordById("activitypub_actors", record.GetString(("author")))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = util.UpdateTrail(e.App, record, userActor, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !userActor.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
|
||||
}
|
||||
|
||||
ctx, err := util.GetSafeActorContext(nil, userActor)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = federation.CreateTrailActivity(e.App, ctx, e.Record, pub.UpdateType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func DeleteTrailHandler(client meilisearch.ServiceManager) func(e *core.RecordEvent) error {
|
||||
return func(e *core.RecordEvent) error {
|
||||
record := e.Record
|
||||
task, err := client.Index("trails").DeleteDocument(record.Id, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
interval := 500 * time.Millisecond
|
||||
_, err = client.WaitForTask(task.TaskUID, interval)
|
||||
if err != nil {
|
||||
log.Fatalf("Error waiting for task completion: %v", err)
|
||||
}
|
||||
|
||||
err = federation.CreateTrailDeleteActivity(e.App, e.Record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = util.DeleteFromFeed(e.App, record.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return e.Next()
|
||||
}
|
||||
}
|
||||
112
db/hooks/users.go
Normal file
112
db/hooks/users.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"pocketbase/util"
|
||||
|
||||
"github.com/meilisearch/meilisearch-go"
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
)
|
||||
|
||||
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 = " + 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
|
||||
}
|
||||
e.Record.Set("token", token)
|
||||
if err := e.App.Save(e.Record); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return e.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateUserHandler(client meilisearch.ServiceManager) func(e *core.RecordEvent) error {
|
||||
return func(e *core.RecordEvent) error {
|
||||
actor, err := e.App.FindFirstRecordByData("activitypub_actors", "user", e.Record.Id)
|
||||
if err != nil {
|
||||
return e.Next()
|
||||
}
|
||||
|
||||
icon := ""
|
||||
origin := os.Getenv("ORIGIN")
|
||||
if origin != "" && e.Record.GetString("avatar") != "" {
|
||||
icon = fmt.Sprintf("%s/api/v1/files/_pb_users_auth_/%s/%s", origin, e.Record.Id, e.Record.GetString("avatar"))
|
||||
}
|
||||
actor.Set("icon", icon)
|
||||
if err := e.App.Save(actor); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
trails, err := e.App.FindRecordsByFilter("trails", "author={:author}", "", -1, 0, dbx.Params{"author": actor.Id})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(trails) > 0 {
|
||||
if err := util.IndexTrails(e.App, trails, client); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
lists, err := e.App.FindRecordsByFilter("lists", "author={:author}", "", -1, 0, dbx.Params{"author": actor.Id})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(lists) > 0 {
|
||||
if err := util.IndexLists(e.App, lists, client); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return e.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeUserEmailHandler() func(e *core.RecordRequestEmailChangeRequestEvent) error {
|
||||
return func(e *core.RecordRequestEmailChangeRequestEvent) error {
|
||||
|
||||
e.Record.Set("email", e.NewEmail)
|
||||
if err := e.App.Save(e.Record); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func createDefaultUserSettings(app core.App, userId string) error {
|
||||
collection, err := app.FindCollectionByNameOrId("settings")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
settings := core.NewRecord(collection)
|
||||
settings.Set("language", "en")
|
||||
settings.Set("unit", "metric")
|
||||
settings.Set("mapFocus", "trails")
|
||||
settings.Set("user", userId)
|
||||
return app.Save(settings)
|
||||
}
|
||||
Reference in New Issue
Block a user