merge trails function added (#627)

* merge trails function added

* fix conflict resolve issues

* avoid incorrect authorship when merging trail comments

* require edit access for all selected trails before merge

* fix likes checkbox binding in trail merge modal

* improve error handling

* require editable target and deletable sources for merge

* avoid duplicate trail fetch during merge

* prevent duplicate likes when merging trails

* check photo download responses during trail merge

* add merge completion toast notifications

* cleanup

* small fixes

* move merge code to backend, option to merge single trail selection with similar one, automatic merge for integrations add as option, maintenance page to find and merge similar trails added

* fix build error

* fix

* docu, beautifying, refactoring

---------

Co-authored-by: Flomp <Flomp@users.noreply.github.com>
This commit is contained in:
slothful-vassal
2026-05-09 13:09:26 +02:00
committed by GitHub
parent d2268429c7
commit b5739cb72e
43 changed files with 5232 additions and 216 deletions

88
db/trail_merge_routes.go Normal file
View File

@@ -0,0 +1,88 @@
package main
import (
"net/http"
"github.com/meilisearch/meilisearch-go"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
"pocketbase/trailmerge"
)
type mergeExecuteRequest struct {
SourceTrailID string `json:"sourceTrailId"`
TargetTrailID string `json:"targetTrailId"`
Settings trailmerge.MergeSettings `json:"settings"`
}
func registerTrailMergeRoutes(se *core.ServeEvent, client meilisearch.ServiceManager) {
se.Router.POST("/trail-merge/suggest", func(e *core.RequestEvent) error {
if e.Auth == nil {
return apis.NewUnauthorizedError("trail_merge_auth_required", nil)
}
actor, err := e.App.FindFirstRecordByData("activitypub_actors", "user", e.Auth.Id)
if err != nil {
return apis.NewBadRequestError("trail_merge_actor_not_found", err)
}
var request trailmerge.SuggestRequest
if err := e.BindBody(&request); err != nil {
return apis.NewBadRequestError("trail_merge_invalid_request", err)
}
if request.Mode == trailmerge.SuggestModeMaintenance {
response, err := trailmerge.SuggestGroups(e.App, actor.Id, request)
if err != nil {
return apis.NewBadRequestError(err.Error(), err)
}
return e.JSON(http.StatusOK, response)
}
response, err := trailmerge.Suggest(e.App, actor.Id, request)
if err != nil {
return apis.NewBadRequestError(err.Error(), err)
}
return e.JSON(http.StatusOK, response)
})
se.Router.POST("/trail-merge", func(e *core.RequestEvent) error {
if e.Auth == nil {
return apis.NewUnauthorizedError("trail_merge_auth_required", nil)
}
actor, err := e.App.FindFirstRecordByData("activitypub_actors", "user", e.Auth.Id)
if err != nil {
return apis.NewBadRequestError("trail_merge_actor_not_found", err)
}
var request mergeExecuteRequest
if err := e.BindBody(&request); err != nil {
return apis.NewBadRequestError("trail_merge_invalid_request", err)
}
source, err := e.App.FindRecordById("trails", request.SourceTrailID)
if err != nil {
return apis.NewBadRequestError("trail_merge_source_not_found", err)
}
target, err := e.App.FindRecordById("trails", request.TargetTrailID)
if err != nil {
return apis.NewBadRequestError("trail_merge_target_not_found", err)
}
if !trailmerge.CanMerge(e.App, actor.Id, source, target, request.Settings.Delete) {
return apis.NewForbiddenError("trail_merge_not_allowed", nil)
}
if err := trailmerge.Merge(e.App, client, actor, request.SourceTrailID, request.TargetTrailID, request.Settings); err != nil {
return apis.NewBadRequestError(err.Error(), err)
}
return e.JSON(http.StatusOK, map[string]any{
"acknowledged": true,
})
})
}