Add PocketBase schema migrations and migration pipeline

- 8 collections: vereine, gruppen, mitglieder, beitraege, einzuege,
  termine, nachrichten, push_subscriptions
- verein_id relation added to users (multi-tenant isolation)
- API rules enforce tenant separation via @request.auth.verein_id
- docker-compose: --migrationsDir=/pb_data/migrations flag + volume mount
- Makefile: migrations sync step added to deploy target
This commit is contained in:
rene 2026-05-19 20:40:47 +02:00
parent 94ca36f470
commit 375a3305bb
11 changed files with 931 additions and 2 deletions

View file

@ -20,6 +20,8 @@ TAR_EXCLUDE := --exclude='.git' \
HOOKS_SRC := pocketbase/pb_hooks
HOOKS_DST := /volume1/docker/vereinshaus/pocketbase/data/pb_hooks
MIGRATIONS_SRC := pocketbase/pb_migrations
MIGRATIONS_DST := /volume1/docker/vereinshaus/pocketbase/migrations
.PHONY: help check-ssh start stop restart status logs logs-f logs-app \
shell-pb pb-admin deploy
@ -71,6 +73,13 @@ deploy: check-ssh
cat "$$f" | ssh $(DS_HOST) "cat > $(HOOKS_DST)/$$(basename $$f)"; \
done; \
fi
@echo "→ PocketBase Migrations synchronisieren..."
@ssh $(DS_HOST) "mkdir -p $(MIGRATIONS_DST)"
@if ls $(MIGRATIONS_SRC)/*.js 2>/dev/null | grep -q .; then \
for f in $(MIGRATIONS_SRC)/*.js; do \
cat "$$f" | ssh $(DS_HOST) "cat > $(MIGRATIONS_DST)/$$(basename $$f)"; \
done; \
fi
@echo "→ Docker rebuild + restart..."
@ssh $(DS_HOST) " \
cd $(DS_PATH) && \

View file

@ -5,10 +5,12 @@ services:
image: ghcr.io/muchobien/pocketbase:latest
container_name: vereinshaus-pocketbase
restart: unless-stopped
command: ["--migrationsDir=/pb_data/migrations"]
volumes:
- /volume1/docker/vereinshaus/pocketbase/data:/pb_data
- /volume1/docker/vereinshaus/pocketbase/storage:/pb_public
- /volume1/docker/vereinshaus/pocketbase/data/pb_hooks:/pb_hooks
- /volume1/docker/vereinshaus/pocketbase/migrations:/pb_data/migrations
environment:
- TZ=Europe/Berlin
- BREVO_KEY=${BREVO_KEY}

View file

@ -0,0 +1,144 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"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"
},
{
"autogeneratePattern": "",
"help": "",
"hidden": false,
"id": "text1579384326",
"max": 0,
"min": 0,
"name": "name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"help": "",
"hidden": false,
"id": "text3525840331",
"max": 0,
"min": 0,
"name": "plz",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"help": "",
"hidden": false,
"id": "text4138466142",
"max": 0,
"min": 0,
"name": "ort",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"help": "",
"hidden": false,
"id": "select1497100012",
"maxSelect": 1,
"name": "bundesland",
"presentable": false,
"required": false,
"system": false,
"type": "select",
"values": [
"BW",
"BY",
"BE",
"BB",
"HB",
"HH",
"HE",
"MV",
"NI",
"NW",
"RP",
"SL",
"SN",
"ST",
"SH",
"TH"
]
},
{
"help": "",
"hidden": false,
"id": "select3713686397",
"maxSelect": 1,
"name": "plan",
"presentable": false,
"required": false,
"system": false,
"type": "select",
"values": [
"free",
"starter",
"wachstum",
"verband"
]
},
{
"autogeneratePattern": "",
"help": "",
"hidden": false,
"id": "text1888339527",
"max": 0,
"min": 0,
"name": "stripe_customer_id",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}
],
"id": "pbc_3589557411",
"indexes": [],
"listRule": "@request.auth.verein_id = id",
"name": "vereine",
"system": false,
"type": "base",
"updateRule": "@request.auth.verein_id = id",
"viewRule": "@request.auth.verein_id = id"
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3589557411");
return app.delete(collection);
})

View file

@ -0,0 +1,67 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": "@request.auth.verein_id = verein_id",
"deleteRule": "@request.auth.verein_id = verein_id",
"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_3589557411",
"help": "",
"hidden": false,
"id": "relation145676011",
"maxSelect": 1,
"minSelect": 0,
"name": "verein_id",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
},
{
"autogeneratePattern": "",
"help": "",
"hidden": false,
"id": "text1579384326",
"max": 0,
"min": 0,
"name": "name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
}
],
"id": "pbc_3099069179",
"indexes": [],
"listRule": "@request.auth.verein_id = verein_id",
"name": "gruppen",
"system": false,
"type": "base",
"updateRule": "@request.auth.verein_id = verein_id",
"viewRule": "@request.auth.verein_id = verein_id"
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3099069179");
return app.delete(collection);
})

View file

@ -0,0 +1,29 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// add field
collection.fields.addAt(10, new Field({
"cascadeDelete": false,
"collectionId": "pbc_3589557411",
"help": "",
"hidden": false,
"id": "relation145676011",
"maxSelect": 1,
"minSelect": 0,
"name": "verein_id",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// remove field
collection.fields.removeById("relation145676011")
return app.save(collection)
})

View file

@ -0,0 +1,97 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": "@request.auth.verein_id = verein_id",
"deleteRule": "@request.auth.verein_id = verein_id",
"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_3589557411",
"help": "",
"hidden": false,
"id": "relation145676011",
"maxSelect": 1,
"minSelect": 0,
"name": "verein_id",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
},
{
"autogeneratePattern": "",
"help": "",
"hidden": false,
"id": "text1579384326",
"max": 0,
"min": 0,
"name": "name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"help": "",
"hidden": false,
"id": "number3107246631",
"max": null,
"min": null,
"name": "betrag",
"onlyInt": false,
"presentable": false,
"required": true,
"system": false,
"type": "number"
},
{
"help": "",
"hidden": false,
"id": "select917011370",
"maxSelect": 1,
"name": "rhythmus",
"presentable": false,
"required": true,
"system": false,
"type": "select",
"values": [
"monatlich",
"quartalsweise",
"jaehrlich",
"einmalig"
]
}
],
"id": "pbc_3218207135",
"indexes": [],
"listRule": "@request.auth.verein_id = verein_id",
"name": "beitraege",
"system": false,
"type": "base",
"updateRule": "@request.auth.verein_id = verein_id",
"viewRule": "@request.auth.verein_id = verein_id"
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3218207135");
return app.delete(collection);
})

View file

@ -0,0 +1,139 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": "@request.auth.verein_id = verein_id",
"deleteRule": "@request.auth.verein_id = verein_id",
"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_3589557411",
"help": "",
"hidden": false,
"id": "relation145676011",
"maxSelect": 1,
"minSelect": 0,
"name": "verein_id",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
},
{
"autogeneratePattern": "",
"help": "",
"hidden": false,
"id": "text386110805",
"max": 0,
"min": 0,
"name": "vorname",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"help": "",
"hidden": false,
"id": "text3586640595",
"max": 0,
"min": 0,
"name": "nachname",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"exceptDomains": null,
"help": "",
"hidden": false,
"id": "email3885137012",
"name": "email",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "email"
},
{
"autogeneratePattern": "",
"help": "",
"hidden": false,
"id": "text4208291426",
"max": 0,
"min": 0,
"name": "iban",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"cascadeDelete": false,
"collectionId": "pbc_3099069179",
"help": "",
"hidden": false,
"id": "relation1077495665",
"maxSelect": 99,
"minSelect": 0,
"name": "gruppe_ids",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"help": "",
"hidden": false,
"id": "select2063623452",
"maxSelect": 1,
"name": "status",
"presentable": false,
"required": false,
"system": false,
"type": "select",
"values": [
"aktiv",
"passiv",
"ausgetreten"
]
}
],
"id": "pbc_2707111162",
"indexes": [],
"listRule": "@request.auth.verein_id = verein_id",
"name": "mitglieder",
"system": false,
"type": "base",
"updateRule": "@request.auth.verein_id = verein_id",
"viewRule": "@request.auth.verein_id = verein_id"
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_2707111162");
return app.delete(collection);
})

View file

@ -0,0 +1,123 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": "@request.auth.verein_id = mitglied_id.verein_id",
"deleteRule": "@request.auth.verein_id = mitglied_id.verein_id",
"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_2707111162",
"help": "",
"hidden": false,
"id": "relation3039789658",
"maxSelect": 1,
"minSelect": 0,
"name": "mitglied_id",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
},
{
"cascadeDelete": false,
"collectionId": "pbc_3218207135",
"help": "",
"hidden": false,
"id": "relation715527895",
"maxSelect": 1,
"minSelect": 0,
"name": "beitrag_id",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
},
{
"help": "",
"hidden": false,
"id": "number3107246631",
"max": null,
"min": null,
"name": "betrag",
"onlyInt": false,
"presentable": false,
"required": true,
"system": false,
"type": "number"
},
{
"help": "",
"hidden": false,
"id": "date3314956993",
"max": "",
"min": "",
"name": "faellig_am",
"presentable": false,
"required": false,
"system": false,
"type": "date"
},
{
"help": "",
"hidden": false,
"id": "select2063623452",
"maxSelect": 1,
"name": "status",
"presentable": false,
"required": false,
"system": false,
"type": "select",
"values": [
"ausstehend",
"bezahlt",
"fehlgeschlagen",
"storniert"
]
},
{
"autogeneratePattern": "",
"help": "",
"hidden": false,
"id": "text4235393406",
"max": 0,
"min": 0,
"name": "stripe_payment_intent_id",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}
],
"id": "pbc_659326735",
"indexes": [],
"listRule": "@request.auth.verein_id = mitglied_id.verein_id",
"name": "einzuege",
"system": false,
"type": "base",
"updateRule": "@request.auth.verein_id = mitglied_id.verein_id",
"viewRule": "@request.auth.verein_id = mitglied_id.verein_id"
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_659326735");
return app.delete(collection);
})

View file

@ -0,0 +1,105 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": "@request.auth.verein_id = verein_id",
"deleteRule": "@request.auth.verein_id = verein_id",
"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_3589557411",
"help": "",
"hidden": false,
"id": "relation145676011",
"maxSelect": 1,
"minSelect": 0,
"name": "verein_id",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
},
{
"autogeneratePattern": "",
"help": "",
"hidden": false,
"id": "text3446813636",
"max": 0,
"min": 0,
"name": "betreff",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"convertURLs": false,
"help": "",
"hidden": false,
"id": "editor999008199",
"maxSize": 0,
"name": "text",
"presentable": false,
"required": false,
"system": false,
"type": "editor"
},
{
"cascadeDelete": false,
"collectionId": "pbc_3099069179",
"help": "",
"hidden": false,
"id": "relation1077495665",
"maxSelect": 99,
"minSelect": 0,
"name": "gruppe_ids",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"help": "",
"hidden": false,
"id": "date2716153632",
"max": "",
"min": "",
"name": "gesendet_am",
"presentable": false,
"required": false,
"system": false,
"type": "date"
}
],
"id": "pbc_1415911511",
"indexes": [],
"listRule": "@request.auth.verein_id = verein_id",
"name": "nachrichten",
"system": false,
"type": "base",
"updateRule": "@request.auth.verein_id = verein_id",
"viewRule": "@request.auth.verein_id = verein_id"
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1415911511");
return app.delete(collection);
})

View file

@ -0,0 +1,94 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": "@request.auth.id != ''",
"deleteRule": "@request.auth.id = user_id",
"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": "_pb_users_auth_",
"help": "",
"hidden": false,
"id": "relation2809058197",
"maxSelect": 1,
"minSelect": 0,
"name": "user_id",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
},
{
"exceptDomains": null,
"help": "",
"hidden": false,
"id": "url3292663675",
"name": "endpoint",
"onlyDomains": null,
"presentable": false,
"required": true,
"system": false,
"type": "url"
},
{
"autogeneratePattern": "",
"help": "",
"hidden": false,
"id": "text3303707132",
"max": 0,
"min": 0,
"name": "p256dh",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"help": "",
"hidden": false,
"id": "text4175343705",
"max": 0,
"min": 0,
"name": "auth",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
}
],
"id": "pbc_1438754935",
"indexes": [],
"listRule": "@request.auth.id = user_id",
"name": "push_subscriptions",
"system": false,
"type": "base",
"updateRule": "@request.auth.id = user_id",
"viewRule": "@request.auth.id = user_id"
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1438754935");
return app.delete(collection);
})

View file

@ -0,0 +1,120 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": "@request.auth.verein_id = verein_id",
"deleteRule": "@request.auth.verein_id = verein_id",
"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_3589557411",
"help": "",
"hidden": false,
"id": "relation145676011",
"maxSelect": 1,
"minSelect": 0,
"name": "verein_id",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
},
{
"autogeneratePattern": "",
"help": "",
"hidden": false,
"id": "text2200468358",
"max": 0,
"min": 0,
"name": "titel",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"help": "",
"hidden": false,
"id": "date363840172",
"max": "",
"min": "",
"name": "beginn",
"presentable": false,
"required": true,
"system": false,
"type": "date"
},
{
"help": "",
"hidden": false,
"id": "date1404831091",
"max": "",
"min": "",
"name": "ende",
"presentable": false,
"required": false,
"system": false,
"type": "date"
},
{
"autogeneratePattern": "",
"help": "",
"hidden": false,
"id": "text4138466142",
"max": 0,
"min": 0,
"name": "ort",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"cascadeDelete": false,
"collectionId": "pbc_3099069179",
"help": "",
"hidden": false,
"id": "relation1077495665",
"maxSelect": 99,
"minSelect": 0,
"name": "gruppe_ids",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}
],
"id": "pbc_2279568741",
"indexes": [],
"listRule": "@request.auth.verein_id = verein_id",
"name": "termine",
"system": false,
"type": "base",
"updateRule": "@request.auth.verein_id = verein_id",
"viewRule": "@request.auth.verein_id = verein_id"
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_2279568741");
return app.delete(collection);
})