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

View File

@@ -764,6 +764,125 @@
}
}
},
"/api/v1/trail-merge": {
"post": {
"summary": "Merge trails",
"description": "Merges a source trail into a target trail using the backend merge logic.",
"tags": [
"Trail Merge"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TrailMergeExecuteRequest"
}
}
}
},
"responses": {
"200": {
"description": "Merge acknowledged",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TrailMergeExecuteResponse"
}
}
}
},
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Forbidden",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/api/v1/trail-merge/suggest": {
"post": {
"summary": "Suggest merge target or duplicate groups",
"description": "Returns merge target suggestions for manual selection or auto-discovery, or temporary duplicate groups for maintenance workflows.",
"tags": [
"Trail Merge"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TrailMergeSuggestRequest"
}
}
}
},
"responses": {
"200": {
"description": "Suggestion response",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/TrailMergeSuggestResponse"
},
{
"$ref": "#/components/schemas/TrailMergeSuggestGroupsResponse"
}
]
}
}
}
},
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/api/v1/trail-link-share": {
"get": {
"summary": "List trail link shares",
@@ -6571,6 +6690,201 @@
}
}
},
"TrailMergeSettings": {
"type": "object",
"required": [
"summitLog",
"photos",
"comments",
"delete",
"tags",
"likes"
],
"properties": {
"summitLog": {
"type": "boolean"
},
"photos": {
"type": "boolean"
},
"comments": {
"type": "boolean"
},
"delete": {
"type": "boolean"
},
"tags": {
"type": "boolean"
},
"likes": {
"type": "boolean"
}
}
},
"TrailMergeSuggestRequest": {
"type": "object",
"required": [
"mode"
],
"properties": {
"mode": {
"type": "string",
"enum": [
"manual-selection",
"auto-discovery",
"maintenance-groups"
]
},
"trailIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Required for manual-selection"
},
"sourceTrailId": {
"type": "string",
"description": "Required for auto-discovery"
}
}
},
"TrailMergeSuggestCandidate": {
"type": "object",
"required": [
"trailId",
"score",
"reason",
"warnings",
"selectable"
],
"properties": {
"trailId": {
"type": "string"
},
"score": {
"type": "number"
},
"reason": {
"type": "string"
},
"warnings": {
"type": "array",
"items": {
"type": "string"
}
},
"selectable": {
"type": "boolean"
}
}
},
"TrailMergeSuggestResponse": {
"type": "object",
"required": [
"targetTrailId",
"reason",
"warnings",
"candidates"
],
"properties": {
"targetTrailId": {
"type": "string"
},
"reason": {
"type": "string"
},
"warnings": {
"type": "array",
"items": {
"type": "string"
}
},
"candidates": {
"type": "array",
"items": {
"$ref": "#/components/schemas/TrailMergeSuggestCandidate"
}
}
}
},
"TrailMergeSuggestGroup": {
"type": "object",
"required": [
"groupId",
"trailIds",
"targetTrailId",
"reason",
"score",
"indirect"
],
"properties": {
"groupId": {
"type": "string"
},
"trailIds": {
"type": "array",
"items": {
"type": "string"
}
},
"targetTrailId": {
"type": "string"
},
"reason": {
"type": "string"
},
"score": {
"type": "number"
},
"indirect": {
"type": "boolean"
}
}
},
"TrailMergeSuggestGroupsResponse": {
"type": "object",
"required": [
"groups"
],
"properties": {
"groups": {
"type": "array",
"items": {
"$ref": "#/components/schemas/TrailMergeSuggestGroup"
}
}
}
},
"TrailMergeExecuteRequest": {
"type": "object",
"required": [
"sourceTrailId",
"targetTrailId",
"settings"
],
"properties": {
"sourceTrailId": {
"type": "string"
},
"targetTrailId": {
"type": "string"
},
"settings": {
"$ref": "#/components/schemas/TrailMergeSettings"
}
}
},
"TrailMergeExecuteResponse": {
"type": "object",
"required": [
"acknowledged"
],
"properties": {
"acknowledged": {
"type": "boolean"
}
}
},
"ListResult": {
"type": "object",
"properties": {