diff --git a/backend/pb_migrations/1754400000_init_schema.js b/backend/pb_migrations/1754400000_init_schema.js
index 84e2561..b353646 100644
--- a/backend/pb_migrations/1754400000_init_schema.js
+++ b/backend/pb_migrations/1754400000_init_schema.js
@@ -21,6 +21,7 @@ migrate((app) => {
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
+ "help": "",
"hidden": false,
"id": "text3208210256",
"max": 15,
@@ -35,6 +36,7 @@ migrate((app) => {
},
{
"cost": 10,
+ "help": "",
"hidden": true,
"id": "password901924565",
"max": 0,
@@ -48,6 +50,7 @@ migrate((app) => {
},
{
"autogeneratePattern": "[a-zA-Z0-9_]{50}",
+ "help": "",
"hidden": true,
"id": "text2504183744",
"max": 60,
@@ -62,6 +65,7 @@ migrate((app) => {
},
{
"exceptDomains": null,
+ "help": "",
"hidden": false,
"id": "email3885137012",
"name": "email",
@@ -72,6 +76,7 @@ migrate((app) => {
"type": "email"
},
{
+ "help": "",
"hidden": false,
"id": "bool1547992806",
"name": "emailVisibility",
@@ -81,6 +86,7 @@ migrate((app) => {
"type": "bool"
},
{
+ "help": "",
"hidden": false,
"id": "bool256245529",
"name": "verified",
@@ -91,6 +97,7 @@ migrate((app) => {
},
{
"autogeneratePattern": "users[0-9]{6}",
+ "help": "",
"hidden": false,
"id": "text4166911607",
"max": 150,
@@ -105,9 +112,10 @@ migrate((app) => {
},
{
"autogeneratePattern": "",
+ "help": "",
"hidden": false,
- "id": "users_name",
- "max": 0,
+ "id": "text1579384326",
+ "max": 255,
"min": 0,
"name": "name",
"pattern": "",
@@ -118,10 +126,11 @@ migrate((app) => {
"type": "text"
},
{
+ "help": "",
"hidden": false,
- "id": "users_avatar",
+ "id": "file376926767",
"maxSelect": 1,
- "maxSize": 5242880,
+ "maxSize": 0,
"mimeTypes": [
"image/jpeg",
"image/png",
@@ -164,7 +173,7 @@ migrate((app) => {
"CREATE UNIQUE INDEX `__pb_users_auth__tokenKey_idx` ON `users` (`tokenKey`)"
],
"created": "2023-10-28 21:35:49.020Z",
- "updated": "2024-07-19 09:58:56.589Z",
+ "updated": "2026-08-06 12:06:09.028Z",
"system": false,
"authRule": "",
"manageRule": null,
@@ -246,6 +255,7 @@ migrate((app) => {
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
+ "help": "",
"hidden": false,
"id": "text3208210256",
"max": 15,
@@ -260,6 +270,7 @@ migrate((app) => {
},
{
"autogeneratePattern": "",
+ "help": "",
"hidden": false,
"id": "text1579384326",
"max": 100,
@@ -275,6 +286,7 @@ migrate((app) => {
{
"cascadeDelete": false,
"collectionId": "_pb_users_auth_",
+ "help": "",
"hidden": false,
"id": "relation3479234172",
"maxSelect": 1,
@@ -288,6 +300,7 @@ migrate((app) => {
{
"cascadeDelete": false,
"collectionId": "_pb_users_auth_",
+ "help": "",
"hidden": false,
"id": "relation2732594447",
"maxSelect": 999,
@@ -301,6 +314,7 @@ migrate((app) => {
{
"cascadeDelete": false,
"collectionId": "_pb_users_auth_",
+ "help": "",
"hidden": false,
"id": "relation344172009",
"maxSelect": 999,
@@ -333,8 +347,8 @@ migrate((app) => {
}
],
"indexes": [],
- "created": "2026-05-15 19:54:02.885Z",
- "updated": "2026-05-15 19:54:02.885Z",
+ "created": "2026-08-06 12:06:09.035Z",
+ "updated": "2026-08-06 12:06:09.035Z",
"system": false
},
{
@@ -349,6 +363,7 @@ migrate((app) => {
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
+ "help": "",
"hidden": false,
"id": "text3208210256",
"max": 15,
@@ -363,6 +378,7 @@ migrate((app) => {
},
{
"autogeneratePattern": "",
+ "help": "",
"hidden": false,
"id": "g7nkasyb",
"max": 0,
@@ -377,6 +393,7 @@ migrate((app) => {
},
{
"autogeneratePattern": "",
+ "help": "",
"hidden": false,
"id": "7n5qjiax",
"max": 0,
@@ -391,6 +408,7 @@ migrate((app) => {
},
{
"autogeneratePattern": "",
+ "help": "",
"hidden": false,
"id": "hdp9vbeh",
"max": 0,
@@ -425,6 +443,7 @@ migrate((app) => {
},
{
"autogeneratePattern": "",
+ "help": "",
"hidden": false,
"id": "text1579384326",
"max": 0,
@@ -438,6 +457,7 @@ migrate((app) => {
"type": "text"
},
{
+ "help": "",
"hidden": false,
"id": "select2063623452",
"maxSelect": 1,
@@ -455,6 +475,7 @@ migrate((app) => {
{
"cascadeDelete": false,
"collectionId": "pbc_1568971955",
+ "help": "",
"hidden": false,
"id": "relation3303056927",
"maxSelect": 1,
@@ -467,8 +488,8 @@ migrate((app) => {
}
],
"indexes": [],
- "created": "2023-10-28 21:38:00.122Z",
- "updated": "2026-05-15 19:54:27.453Z",
+ "created": "2026-08-06 12:06:09.033Z",
+ "updated": "2026-08-06 12:06:09.033Z",
"system": false
},
{
@@ -483,6 +504,7 @@ migrate((app) => {
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
+ "help": "",
"hidden": false,
"id": "text3208210256",
"max": 15,
@@ -497,6 +519,7 @@ migrate((app) => {
},
{
"autogeneratePattern": "",
+ "help": "",
"hidden": false,
"id": "lnfys3xe",
"max": 0,
@@ -512,6 +535,7 @@ migrate((app) => {
{
"cascadeDelete": false,
"collectionId": "xiqlzggqxg1e2y7",
+ "help": "",
"hidden": false,
"id": "fqqevsrf",
"maxSelect": 1,
@@ -545,6 +569,7 @@ migrate((app) => {
{
"cascadeDelete": false,
"collectionId": "pbc_1568971955",
+ "help": "",
"hidden": false,
"id": "relation3303056927",
"maxSelect": 1,
@@ -557,8 +582,8 @@ migrate((app) => {
}
],
"indexes": [],
- "created": "2023-10-30 20:50:18.294Z",
- "updated": "2026-05-15 19:54:27.393Z",
+ "created": "2026-08-06 12:06:09.034Z",
+ "updated": "2026-08-06 12:06:09.034Z",
"system": false
},
{
@@ -573,6 +598,7 @@ migrate((app) => {
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
+ "help": "",
"hidden": false,
"id": "text3208210256",
"max": 15,
@@ -587,6 +613,7 @@ migrate((app) => {
},
{
"autogeneratePattern": "",
+ "help": "",
"hidden": false,
"id": "hcc93nbj",
"max": 0,
@@ -600,6 +627,7 @@ migrate((app) => {
"type": "text"
},
{
+ "help": "",
"hidden": false,
"id": "hc38dfyc",
"maxSelect": 1,
@@ -616,6 +644,7 @@ migrate((app) => {
{
"cascadeDelete": false,
"collectionId": "xiqlzggqxg1e2y7",
+ "help": "",
"hidden": false,
"id": "anbugepo",
"maxSelect": 1,
@@ -648,6 +677,7 @@ migrate((app) => {
},
{
"autogeneratePattern": "",
+ "help": "",
"hidden": false,
"id": "text2526027604",
"max": 0,
@@ -662,6 +692,7 @@ migrate((app) => {
},
{
"autogeneratePattern": "",
+ "help": "",
"hidden": false,
"id": "text2208304744",
"max": 0,
@@ -676,6 +707,7 @@ migrate((app) => {
},
{
"autogeneratePattern": "",
+ "help": "",
"hidden": false,
"id": "text824489398",
"max": 0,
@@ -691,6 +723,7 @@ migrate((app) => {
{
"cascadeDelete": false,
"collectionId": "pbc_1568971955",
+ "help": "",
"hidden": false,
"id": "relation3303056927",
"maxSelect": 1,
@@ -703,8 +736,8 @@ migrate((app) => {
}
],
"indexes": [],
- "created": "2023-10-28 21:37:09.275Z",
- "updated": "2026-05-15 19:54:27.372Z",
+ "created": "2026-08-06 12:06:09.031Z",
+ "updated": "2026-08-06 12:06:09.031Z",
"system": false
},
{
@@ -719,6 +752,7 @@ migrate((app) => {
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
+ "help": "",
"hidden": false,
"id": "text3208210256",
"max": 15,
@@ -732,6 +766,7 @@ migrate((app) => {
"type": "text"
},
{
+ "help": "",
"hidden": false,
"id": "3k55v0ld",
"maxSelect": 1,
@@ -751,6 +786,7 @@ migrate((app) => {
{
"cascadeDelete": false,
"collectionId": "spgs7e4anufen9u",
+ "help": "",
"hidden": false,
"id": "r3ixummt",
"maxSelect": 1,
@@ -762,6 +798,7 @@ migrate((app) => {
"type": "relation"
},
{
+ "help": "",
"hidden": false,
"id": "mfhbdhhe",
"max": "",
@@ -775,6 +812,7 @@ migrate((app) => {
{
"cascadeDelete": false,
"collectionId": "_pb_users_auth_",
+ "help": "",
"hidden": false,
"id": "ybjm5n0k",
"maxSelect": 1,
@@ -786,6 +824,7 @@ migrate((app) => {
"type": "relation"
},
{
+ "help": "",
"hidden": false,
"id": "nkmjfeqt",
"max": "",
@@ -799,6 +838,7 @@ migrate((app) => {
{
"cascadeDelete": false,
"collectionId": "_pb_users_auth_",
+ "help": "",
"hidden": false,
"id": "o2fp9hvv",
"maxSelect": 1,
@@ -811,6 +851,7 @@ migrate((app) => {
},
{
"autogeneratePattern": "",
+ "help": "",
"hidden": false,
"id": "dky8o1vw",
"max": 0,
@@ -826,6 +867,7 @@ migrate((app) => {
{
"cascadeDelete": false,
"collectionId": "jo49lycyfs2sz2c",
+ "help": "",
"hidden": false,
"id": "ltfephwe",
"maxSelect": 1,
@@ -857,6 +899,7 @@ migrate((app) => {
"type": "autodate"
},
{
+ "help": "",
"hidden": false,
"id": "number2728239544",
"max": null,
@@ -871,6 +914,7 @@ migrate((app) => {
{
"cascadeDelete": false,
"collectionId": "pbc_1568971955",
+ "help": "",
"hidden": false,
"id": "relation3303056927",
"maxSelect": 1,
@@ -883,8 +927,736 @@ migrate((app) => {
}
],
"indexes": [],
- "created": "2023-10-28 21:37:47.259Z",
- "updated": "2026-05-15 19:54:27.415Z",
+ "created": "2026-08-06 12:06:09.032Z",
+ "updated": "2026-08-06 12:06:09.032Z",
+ "system": false
+ },
+ {
+ "id": "pbc_1369982065",
+ "listRule": "team.users.id ?= @request.auth.id",
+ "viewRule": "team.users.id ?= @request.auth.id",
+ "createRule": "team.users.id ?= @request.auth.id",
+ "updateRule": "stewards.id ?= @request.auth.id || team.owner.id ?= @request.auth.id || team.admins.id ?= @request.auth.id",
+ "deleteRule": "team.owner.id ?= @request.auth.id || team.admins.id ?= @request.auth.id",
+ "name": "trails",
+ "type": "base",
+ "fields": [
+ {
+ "autogeneratePattern": "[a-z0-9]{15}",
+ "help": "",
+ "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": "pbc_1568971955",
+ "help": "",
+ "hidden": false,
+ "id": "relation3303056927",
+ "maxSelect": 1,
+ "minSelect": 0,
+ "name": "team",
+ "presentable": false,
+ "required": true,
+ "system": false,
+ "type": "relation"
+ },
+ {
+ "autogeneratePattern": "",
+ "help": "",
+ "hidden": false,
+ "id": "text1579384326",
+ "max": 200,
+ "min": 0,
+ "name": "name",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": true,
+ "system": false,
+ "type": "text"
+ },
+ {
+ "convertURLs": false,
+ "help": "",
+ "hidden": false,
+ "id": "editor1843675174",
+ "maxSize": 0,
+ "name": "description",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "editor"
+ },
+ {
+ "help": "",
+ "hidden": false,
+ "id": "select2063623452",
+ "maxSelect": 1,
+ "name": "status",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "select",
+ "values": [
+ "offen",
+ "eingeschraenkt",
+ "gesperrt"
+ ]
+ },
+ {
+ "cascadeDelete": false,
+ "collectionId": "_pb_users_auth_",
+ "help": "",
+ "hidden": false,
+ "id": "relation1348713833",
+ "maxSelect": 0,
+ "minSelect": 0,
+ "name": "stewards",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "relation"
+ },
+ {
+ "cascadeDelete": false,
+ "collectionId": "xiqlzggqxg1e2y7",
+ "help": "",
+ "hidden": false,
+ "id": "relation1001261735",
+ "maxSelect": 1,
+ "minSelect": 0,
+ "name": "event",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "relation"
+ },
+ {
+ "cascadeDelete": false,
+ "collectionId": "_pb_users_auth_",
+ "help": "",
+ "hidden": false,
+ "id": "relation3725765462",
+ "maxSelect": 1,
+ "minSelect": 0,
+ "name": "created_by",
+ "presentable": false,
+ "required": false,
+ "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"
+ },
+ {
+ "cascadeDelete": false,
+ "collectionId": "pbc_1570473209",
+ "help": "",
+ "hidden": false,
+ "id": "relation3706926091",
+ "maxSelect": 1,
+ "minSelect": 0,
+ "name": "current",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "relation"
+ }
+ ],
+ "indexes": [],
+ "created": "2026-08-06 14:55:19.498Z",
+ "updated": "2026-08-06 14:55:19.503Z",
+ "system": false
+ },
+ {
+ "id": "pbc_1570473209",
+ "listRule": "trail.team.users.id ?= @request.auth.id",
+ "viewRule": "trail.team.users.id ?= @request.auth.id",
+ "createRule": "trail.stewards.id ?= @request.auth.id || trail.team.owner.id ?= @request.auth.id || trail.team.admins.id ?= @request.auth.id",
+ "updateRule": null,
+ "deleteRule": "trail.team.owner.id ?= @request.auth.id || trail.team.admins.id ?= @request.auth.id",
+ "name": "trail_versions",
+ "type": "base",
+ "fields": [
+ {
+ "autogeneratePattern": "[a-z0-9]{15}",
+ "help": "",
+ "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": "pbc_1369982065",
+ "help": "",
+ "hidden": false,
+ "id": "relation2993194383",
+ "maxSelect": 1,
+ "minSelect": 0,
+ "name": "trail",
+ "presentable": false,
+ "required": true,
+ "system": false,
+ "type": "relation"
+ },
+ {
+ "help": "",
+ "hidden": false,
+ "id": "file3275261007",
+ "maxSelect": 1,
+ "maxSize": 10485760,
+ "mimeTypes": [
+ "application/gpx+xml",
+ "application/xml",
+ "text/xml"
+ ],
+ "name": "gpx",
+ "presentable": false,
+ "protected": false,
+ "required": false,
+ "system": false,
+ "thumbs": null,
+ "type": "file"
+ },
+ {
+ "help": "",
+ "hidden": false,
+ "id": "json3664062777",
+ "maxSize": 5000000,
+ "name": "geojson",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ },
+ {
+ "help": "",
+ "hidden": false,
+ "id": "json1009404545",
+ "maxSize": 2000000,
+ "name": "elevation",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ },
+ {
+ "help": "",
+ "hidden": false,
+ "id": "json967432143",
+ "maxSize": 1000,
+ "name": "bounds",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ },
+ {
+ "help": "",
+ "hidden": false,
+ "id": "number2109440323",
+ "max": null,
+ "min": null,
+ "name": "distance_m",
+ "onlyInt": false,
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "number"
+ },
+ {
+ "help": "",
+ "hidden": false,
+ "id": "number4251987185",
+ "max": null,
+ "min": null,
+ "name": "ascent_m",
+ "onlyInt": false,
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "number"
+ },
+ {
+ "autogeneratePattern": "",
+ "help": "",
+ "hidden": false,
+ "id": "text3485334036",
+ "max": 500,
+ "min": 0,
+ "name": "note",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": false,
+ "system": false,
+ "type": "text"
+ },
+ {
+ "cascadeDelete": false,
+ "collectionId": "_pb_users_auth_",
+ "help": "",
+ "hidden": false,
+ "id": "relation3823579430",
+ "maxSelect": 1,
+ "minSelect": 0,
+ "name": "uploaded_by",
+ "presentable": false,
+ "required": false,
+ "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"
+ }
+ ],
+ "indexes": [],
+ "created": "2026-08-06 14:55:19.501Z",
+ "updated": "2026-08-06 14:55:19.501Z",
+ "system": false
+ },
+ {
+ "id": "pbc_3747930671",
+ "listRule": "team.users.id ?= @request.auth.id",
+ "viewRule": "team.users.id ?= @request.auth.id",
+ "createRule": "team.owner.id ?= @request.auth.id || team.admins.id ?= @request.auth.id",
+ "updateRule": "team.owner.id ?= @request.auth.id || team.admins.id ?= @request.auth.id",
+ "deleteRule": "team.owner.id ?= @request.auth.id || team.admins.id ?= @request.auth.id",
+ "name": "trail_flags",
+ "type": "base",
+ "fields": [
+ {
+ "autogeneratePattern": "[a-z0-9]{15}",
+ "help": "",
+ "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": "pbc_1568971955",
+ "help": "",
+ "hidden": false,
+ "id": "relation3303056927",
+ "maxSelect": 1,
+ "minSelect": 0,
+ "name": "team",
+ "presentable": false,
+ "required": true,
+ "system": false,
+ "type": "relation"
+ },
+ {
+ "autogeneratePattern": "",
+ "help": "",
+ "hidden": false,
+ "id": "text245846248",
+ "max": 60,
+ "min": 0,
+ "name": "label",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": true,
+ "system": false,
+ "type": "text"
+ },
+ {
+ "autogeneratePattern": "",
+ "help": "",
+ "hidden": false,
+ "id": "text1704208859",
+ "max": 40,
+ "min": 0,
+ "name": "icon",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": false,
+ "system": false,
+ "type": "text"
+ },
+ {
+ "autogeneratePattern": "",
+ "help": "",
+ "hidden": false,
+ "id": "text1716930793",
+ "max": 9,
+ "min": 0,
+ "name": "color",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": false,
+ "system": false,
+ "type": "text"
+ },
+ {
+ "help": "",
+ "hidden": false,
+ "id": "select4133540203",
+ "maxSelect": 1,
+ "name": "severity",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "select",
+ "values": [
+ "info",
+ "warnung",
+ "kritisch"
+ ]
+ },
+ {
+ "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": [],
+ "created": "2026-08-06 14:55:19.508Z",
+ "updated": "2026-08-06 14:55:19.508Z",
+ "system": false
+ },
+ {
+ "id": "pbc_39880540",
+ "listRule": "team.users.id ?= @request.auth.id",
+ "viewRule": "team.users.id ?= @request.auth.id",
+ "createRule": "team.users.id ?= @request.auth.id",
+ "updateRule": "created_by.id ?= @request.auth.id || trail.stewards.id ?= @request.auth.id || team.owner.id ?= @request.auth.id || team.admins.id ?= @request.auth.id",
+ "deleteRule": "created_by.id ?= @request.auth.id || trail.stewards.id ?= @request.auth.id || team.owner.id ?= @request.auth.id || team.admins.id ?= @request.auth.id",
+ "name": "trail_markers",
+ "type": "base",
+ "fields": [
+ {
+ "autogeneratePattern": "[a-z0-9]{15}",
+ "help": "",
+ "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": "pbc_1369982065",
+ "help": "",
+ "hidden": false,
+ "id": "relation2993194383",
+ "maxSelect": 1,
+ "minSelect": 0,
+ "name": "trail",
+ "presentable": false,
+ "required": true,
+ "system": false,
+ "type": "relation"
+ },
+ {
+ "cascadeDelete": true,
+ "collectionId": "pbc_1568971955",
+ "help": "",
+ "hidden": false,
+ "id": "relation3303056927",
+ "maxSelect": 1,
+ "minSelect": 0,
+ "name": "team",
+ "presentable": false,
+ "required": true,
+ "system": false,
+ "type": "relation"
+ },
+ {
+ "cascadeDelete": false,
+ "collectionId": "pbc_3747930671",
+ "help": "",
+ "hidden": false,
+ "id": "relation3522489242",
+ "maxSelect": 1,
+ "minSelect": 0,
+ "name": "flag",
+ "presentable": false,
+ "required": true,
+ "system": false,
+ "type": "relation"
+ },
+ {
+ "help": "",
+ "hidden": false,
+ "id": "number2499937429",
+ "max": null,
+ "min": null,
+ "name": "lat",
+ "onlyInt": false,
+ "presentable": false,
+ "required": true,
+ "system": false,
+ "type": "number"
+ },
+ {
+ "help": "",
+ "hidden": false,
+ "id": "number2518964612",
+ "max": null,
+ "min": null,
+ "name": "lng",
+ "onlyInt": false,
+ "presentable": false,
+ "required": true,
+ "system": false,
+ "type": "number"
+ },
+ {
+ "autogeneratePattern": "",
+ "help": "",
+ "hidden": false,
+ "id": "text3485334036",
+ "max": 500,
+ "min": 0,
+ "name": "note",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": false,
+ "system": false,
+ "type": "text"
+ },
+ {
+ "help": "",
+ "hidden": false,
+ "id": "bool3084178383",
+ "name": "resolved",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "bool"
+ },
+ {
+ "cascadeDelete": false,
+ "collectionId": "_pb_users_auth_",
+ "help": "",
+ "hidden": false,
+ "id": "relation3725765462",
+ "maxSelect": 1,
+ "minSelect": 0,
+ "name": "created_by",
+ "presentable": false,
+ "required": false,
+ "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"
+ }
+ ],
+ "indexes": [],
+ "created": "2026-08-06 14:55:19.510Z",
+ "updated": "2026-08-06 14:55:19.510Z",
+ "system": false
+ },
+ {
+ "id": "pbc_467227137",
+ "listRule": "team.users.id ?= @request.auth.id",
+ "viewRule": "team.users.id ?= @request.auth.id",
+ "createRule": "team.users.id ?= @request.auth.id",
+ "updateRule": "created_by.id ?= @request.auth.id || trail.stewards.id ?= @request.auth.id || team.owner.id ?= @request.auth.id || team.admins.id ?= @request.auth.id",
+ "deleteRule": "created_by.id ?= @request.auth.id || trail.stewards.id ?= @request.auth.id || team.owner.id ?= @request.auth.id || team.admins.id ?= @request.auth.id",
+ "name": "trail_comments",
+ "type": "base",
+ "fields": [
+ {
+ "autogeneratePattern": "[a-z0-9]{15}",
+ "help": "",
+ "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": "pbc_1369982065",
+ "help": "",
+ "hidden": false,
+ "id": "relation2993194383",
+ "maxSelect": 1,
+ "minSelect": 0,
+ "name": "trail",
+ "presentable": false,
+ "required": true,
+ "system": false,
+ "type": "relation"
+ },
+ {
+ "cascadeDelete": true,
+ "collectionId": "pbc_1568971955",
+ "help": "",
+ "hidden": false,
+ "id": "relation3303056927",
+ "maxSelect": 1,
+ "minSelect": 0,
+ "name": "team",
+ "presentable": false,
+ "required": true,
+ "system": false,
+ "type": "relation"
+ },
+ {
+ "autogeneratePattern": "",
+ "help": "",
+ "hidden": false,
+ "id": "text999008199",
+ "max": 2000,
+ "min": 0,
+ "name": "text",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": true,
+ "system": false,
+ "type": "text"
+ },
+ {
+ "cascadeDelete": false,
+ "collectionId": "_pb_users_auth_",
+ "help": "",
+ "hidden": false,
+ "id": "relation3725765462",
+ "maxSelect": 1,
+ "minSelect": 0,
+ "name": "created_by",
+ "presentable": false,
+ "required": false,
+ "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"
+ }
+ ],
+ "indexes": [],
+ "created": "2026-08-06 14:55:19.513Z",
+ "updated": "2026-08-06 14:55:19.513Z",
"system": false
}
]
diff --git a/backend/pb_migrations/1754500000_created_trails.js b/backend/pb_migrations/1754500000_created_trails.js
new file mode 100644
index 0000000..9fca1c3
--- /dev/null
+++ b/backend/pb_migrations/1754500000_created_trails.js
@@ -0,0 +1,267 @@
+///
+
+// Trails: GPX-Tracks mit Versionen, verorteten Markern und Kommentaren.
+//
+// Reihenfolge ist wichtig: trails.current zeigt auf trail_versions,
+// trail_versions.trail zeigt zurück auf trails. Deshalb wird trails zuerst
+// OHNE current angelegt, dann trail_versions, und current danach ergänzt.
+
+migrate((app) => {
+ const teams = app.findCollectionByNameOrId('teams')
+ const users = app.findCollectionByNameOrId('users')
+
+ // Für jede Collection gleich: Standardfelder, die PocketBase nicht
+ // selbst anlegt.
+ const timestamps = [
+ { name: 'created', type: 'autodate', onCreate: true, onUpdate: false },
+ { name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
+ ]
+
+ const teamScoped = (name) => ({
+ name,
+ type: 'relation',
+ required: true,
+ maxSelect: 1,
+ collectionId: teams.id,
+ cascadeDelete: true,
+ })
+
+ // --- trails (noch ohne current) --------------------------------------
+ const trails = new Collection({
+ type: 'base',
+ name: 'trails',
+ listRule: 'team.users.id ?= @request.auth.id',
+ viewRule: 'team.users.id ?= @request.auth.id',
+ createRule: 'team.users.id ?= @request.auth.id',
+ // Bearbeiten dürfen Paten und Team-Admins
+ updateRule:
+ 'stewards.id ?= @request.auth.id' +
+ ' || team.owner.id ?= @request.auth.id' +
+ ' || team.admins.id ?= @request.auth.id',
+ // Löschen nur Admins — am Trail hängen fremde Marker und Kommentare
+ deleteRule:
+ 'team.owner.id ?= @request.auth.id' +
+ ' || team.admins.id ?= @request.auth.id',
+ fields: [
+ teamScoped('team'),
+ { name: 'name', type: 'text', required: true, max: 200 },
+ { name: 'description', type: 'editor' },
+ {
+ name: 'status',
+ type: 'select',
+ maxSelect: 1,
+ values: ['offen', 'eingeschraenkt', 'gesperrt'],
+ },
+ {
+ name: 'stewards',
+ type: 'relation',
+ maxSelect: 0,
+ collectionId: users.id,
+ cascadeDelete: false,
+ },
+ {
+ name: 'event',
+ type: 'relation',
+ maxSelect: 1,
+ collectionId: app.findCollectionByNameOrId('events').id,
+ cascadeDelete: false,
+ },
+ {
+ name: 'created_by',
+ type: 'relation',
+ maxSelect: 1,
+ collectionId: users.id,
+ cascadeDelete: false,
+ },
+ ...timestamps,
+ ],
+ })
+ app.save(trails)
+
+ // --- trail_versions ---------------------------------------------------
+ const versions = new Collection({
+ type: 'base',
+ name: 'trail_versions',
+ listRule: 'trail.team.users.id ?= @request.auth.id',
+ viewRule: 'trail.team.users.id ?= @request.auth.id',
+ createRule:
+ 'trail.stewards.id ?= @request.auth.id' +
+ ' || trail.team.owner.id ?= @request.auth.id' +
+ ' || trail.team.admins.id ?= @request.auth.id',
+ // Versionen sind unveränderlich — eine Korrektur ist eine neue Version
+ updateRule: null,
+ deleteRule:
+ 'trail.team.owner.id ?= @request.auth.id' +
+ ' || trail.team.admins.id ?= @request.auth.id',
+ fields: [
+ {
+ name: 'trail',
+ type: 'relation',
+ required: true,
+ maxSelect: 1,
+ collectionId: trails.id,
+ cascadeDelete: true,
+ },
+ {
+ name: 'gpx',
+ type: 'file',
+ maxSelect: 1,
+ maxSize: 10485760,
+ mimeTypes: ['application/gpx+xml', 'application/xml', 'text/xml'],
+ },
+ { name: 'geojson', type: 'json', maxSize: 5000000 },
+ { name: 'elevation', type: 'json', maxSize: 2000000 },
+ { name: 'bounds', type: 'json', maxSize: 1000 },
+ { name: 'distance_m', type: 'number' },
+ { name: 'ascent_m', type: 'number' },
+ { name: 'note', type: 'text', max: 500 },
+ {
+ name: 'uploaded_by',
+ type: 'relation',
+ maxSelect: 1,
+ collectionId: users.id,
+ cascadeDelete: false,
+ },
+ ...timestamps,
+ ],
+ })
+ app.save(versions)
+
+ // --- trails.current nachtragen ---------------------------------------
+ trails.fields.add(new Field({
+ name: 'current',
+ type: 'relation',
+ maxSelect: 1,
+ collectionId: versions.id,
+ cascadeDelete: false,
+ }))
+ app.save(trails)
+
+ // --- trail_flags ------------------------------------------------------
+ const adminOnly =
+ 'team.owner.id ?= @request.auth.id' +
+ ' || team.admins.id ?= @request.auth.id'
+
+ const flags = new Collection({
+ type: 'base',
+ name: 'trail_flags',
+ listRule: 'team.users.id ?= @request.auth.id',
+ viewRule: 'team.users.id ?= @request.auth.id',
+ createRule: adminOnly,
+ updateRule: adminOnly,
+ deleteRule: adminOnly,
+ fields: [
+ teamScoped('team'),
+ { name: 'label', type: 'text', required: true, max: 60 },
+ { name: 'icon', type: 'text', max: 40 },
+ { name: 'color', type: 'text', max: 9 },
+ {
+ name: 'severity',
+ type: 'select',
+ maxSelect: 1,
+ values: ['info', 'warnung', 'kritisch'],
+ },
+ ...timestamps,
+ ],
+ })
+ app.save(flags)
+
+ // --- trail_markers ----------------------------------------------------
+ // Anlegen darf jedes Team-Mitglied; ändern nur der Ersteller, Paten
+ // oder Admins.
+ const ownerOrSteward =
+ 'created_by.id ?= @request.auth.id' +
+ ' || trail.stewards.id ?= @request.auth.id' +
+ ' || team.owner.id ?= @request.auth.id' +
+ ' || team.admins.id ?= @request.auth.id'
+
+ const markers = new Collection({
+ type: 'base',
+ name: 'trail_markers',
+ listRule: 'team.users.id ?= @request.auth.id',
+ viewRule: 'team.users.id ?= @request.auth.id',
+ createRule: 'team.users.id ?= @request.auth.id',
+ updateRule: ownerOrSteward,
+ deleteRule: ownerOrSteward,
+ fields: [
+ {
+ name: 'trail',
+ type: 'relation',
+ required: true,
+ maxSelect: 1,
+ collectionId: trails.id,
+ cascadeDelete: true,
+ },
+ teamScoped('team'),
+ {
+ name: 'flag',
+ type: 'relation',
+ required: true,
+ maxSelect: 1,
+ collectionId: flags.id,
+ cascadeDelete: false,
+ },
+ { name: 'lat', type: 'number', required: true },
+ { name: 'lng', type: 'number', required: true },
+ { name: 'note', type: 'text', max: 500 },
+ { name: 'resolved', type: 'bool' },
+ {
+ name: 'created_by',
+ type: 'relation',
+ maxSelect: 1,
+ collectionId: users.id,
+ cascadeDelete: false,
+ },
+ ...timestamps,
+ ],
+ })
+ app.save(markers)
+
+ // --- trail_comments ---------------------------------------------------
+ const comments = new Collection({
+ type: 'base',
+ name: 'trail_comments',
+ listRule: 'team.users.id ?= @request.auth.id',
+ viewRule: 'team.users.id ?= @request.auth.id',
+ createRule: 'team.users.id ?= @request.auth.id',
+ updateRule: ownerOrSteward,
+ deleteRule: ownerOrSteward,
+ fields: [
+ {
+ name: 'trail',
+ type: 'relation',
+ required: true,
+ maxSelect: 1,
+ collectionId: trails.id,
+ cascadeDelete: true,
+ },
+ teamScoped('team'),
+ { name: 'text', type: 'text', required: true, max: 2000 },
+ {
+ name: 'created_by',
+ type: 'relation',
+ maxSelect: 1,
+ collectionId: users.id,
+ cascadeDelete: false,
+ },
+ ...timestamps,
+ ],
+ })
+ app.save(comments)
+}, (app) => {
+ // Rückwärts in umgekehrter Reihenfolge, damit keine Relation ins Leere
+ // zeigt. trails zuletzt, weil alle anderen darauf verweisen.
+ for (const name of [
+ 'trail_comments',
+ 'trail_markers',
+ 'trail_flags',
+ 'trail_versions',
+ 'trails',
+ ]) {
+ try {
+ app.delete(app.findCollectionByNameOrId(name))
+ } catch {
+ // Bereits entfernt — nichts zu tun.
+ }
+ }
+})
diff --git a/frontend/src/lib/types.d.ts b/frontend/src/lib/types.d.ts
index 6517bdb..0b91e23 100644
--- a/frontend/src/lib/types.d.ts
+++ b/frontend/src/lib/types.d.ts
@@ -11,6 +11,11 @@ export enum Collections {
Runs = "runs",
Teams = "teams",
Times = "times",
+ TrailComments = "trail_comments",
+ TrailFlags = "trail_flags",
+ TrailMarkers = "trail_markers",
+ TrailVersions = "trail_versions",
+ Trails = "trails",
Users = "users",
}
@@ -113,6 +118,80 @@ export type TimesRecord = {
updated?: IsoDateString
}
+export type TrailCommentsRecord = {
+ created?: IsoDateString
+ created_by?: RecordIdString
+ id: string
+ team: RecordIdString
+ text: string
+ trail: RecordIdString
+ updated?: IsoDateString
+}
+
+export enum TrailFlagsSeverityOptions {
+ "info" = "info",
+ "warnung" = "warnung",
+ "kritisch" = "kritisch",
+}
+export type TrailFlagsRecord = {
+ color?: string
+ created?: IsoDateString
+ icon?: string
+ id: string
+ label: string
+ severity?: TrailFlagsSeverityOptions
+ team: RecordIdString
+ updated?: IsoDateString
+}
+
+export type TrailMarkersRecord = {
+ created?: IsoDateString
+ created_by?: RecordIdString
+ flag: RecordIdString
+ id: string
+ lat: number
+ lng: number
+ note?: string
+ resolved?: boolean
+ team: RecordIdString
+ trail: RecordIdString
+ updated?: IsoDateString
+}
+
+export type TrailVersionsRecord = {
+ ascent_m?: number
+ bounds?: null | Tbounds
+ created?: IsoDateString
+ distance_m?: number
+ elevation?: null | Televation
+ geojson?: null | Tgeojson
+ gpx?: string
+ id: string
+ note?: string
+ trail: RecordIdString
+ updated?: IsoDateString
+ uploaded_by?: RecordIdString
+}
+
+export enum TrailsStatusOptions {
+ "offen" = "offen",
+ "eingeschraenkt" = "eingeschraenkt",
+ "gesperrt" = "gesperrt",
+}
+export type TrailsRecord = {
+ created?: IsoDateString
+ created_by?: RecordIdString
+ current?: RecordIdString
+ description?: HTMLString
+ event?: RecordIdString
+ id: string
+ name: string
+ status?: TrailsStatusOptions
+ stewards?: RecordIdString[]
+ team: RecordIdString
+ updated?: IsoDateString
+}
+
export type UsersRecord = {
avatar?: string
created?: IsoDateString
@@ -133,6 +212,11 @@ export type RidersResponse = Required & BaseSys
export type RunsResponse = Required & BaseSystemFields
export type TeamsResponse = Required & BaseSystemFields
export type TimesResponse = Required & BaseSystemFields
+export type TrailCommentsResponse = Required & BaseSystemFields
+export type TrailFlagsResponse = Required & BaseSystemFields
+export type TrailMarkersResponse = Required & BaseSystemFields
+export type TrailVersionsResponse = Required> & BaseSystemFields
+export type TrailsResponse = Required & BaseSystemFields
export type UsersResponse = Required & AuthSystemFields
// Types containing all Records and Responses, useful for creating typing helper functions
@@ -143,6 +227,11 @@ export type CollectionRecords = {
runs: RunsRecord
teams: TeamsRecord
times: TimesRecord
+ trail_comments: TrailCommentsRecord
+ trail_flags: TrailFlagsRecord
+ trail_markers: TrailMarkersRecord
+ trail_versions: TrailVersionsRecord
+ trails: TrailsRecord
users: UsersRecord
}
@@ -152,6 +241,11 @@ export type CollectionResponses = {
runs: RunsResponse
teams: TeamsResponse
times: TimesResponse
+ trail_comments: TrailCommentsResponse
+ trail_flags: TrailFlagsResponse
+ trail_markers: TrailMarkersResponse
+ trail_versions: TrailVersionsResponse
+ trails: TrailsResponse
users: UsersResponse
}
@@ -164,5 +258,10 @@ export type TypedPocketBase = PocketBase & {
collection(idOrName: 'runs'): RecordService
collection(idOrName: 'teams'): RecordService
collection(idOrName: 'times'): RecordService
+ collection(idOrName: 'trail_comments'): RecordService
+ collection(idOrName: 'trail_flags'): RecordService
+ collection(idOrName: 'trail_markers'): RecordService
+ collection(idOrName: 'trail_versions'): RecordService
+ collection(idOrName: 'trails'): RecordService
collection(idOrName: 'users'): RecordService
}