From c41a6cb056a5380f4a1823ca1efd2c91d3e15ce6 Mon Sep 17 00:00:00 2001 From: Daniel Michelberger Date: Thu, 6 Aug 2026 16:57:23 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Collections=20f=C3=BCr=20Trails,=20Vers?= =?UTF-8?q?ionen,=20Flags,=20Marker=20und=20Kommentare?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pb_migrations/1754400000_init_schema.js | 802 +++++++++++++++++- .../1754500000_created_trails.js | 267 ++++++ frontend/src/lib/types.d.ts | 99 +++ 3 files changed, 1153 insertions(+), 15 deletions(-) create mode 100644 backend/pb_migrations/1754500000_created_trails.js 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 }