diff --git a/Makefile b/Makefile
index 9711355..5a6b184 100644
--- a/Makefile
+++ b/Makefile
@@ -1,19 +1,16 @@
# ==============================================================
# VEREINS.HAUS — Makefile
# Deploy-Strategie: SSH zur DS, Docker Compose
-# Stack: SvelteKit + better-sqlite3 (kein PocketBase)
# ==============================================================
DS_HOST := ds
DS_IP := 10.47.11.10
DS_SSH_PORT := 4711
DS_PATH := /volume1/docker/vereinshaus
+CONTAINER_PB := vereinshaus-pocketbase
CONTAINER_APP := vereinshaus-app
DOCKER := sudo /usr/local/bin/docker
-STAGING_PATH := /volume1/docker/vereinshaus-staging
-CONTAINER_APP_STAGING := vereinshaus-staging-app
-
TAR_EXCLUDE := --exclude='.git' \
--exclude='./app/node_modules' \
--exclude='./app/.svelte-kit' \
@@ -21,8 +18,11 @@ TAR_EXCLUDE := --exclude='.git' \
--exclude='./.env' \
--exclude='./.DS_Store'
-.PHONY: help check-ssh start stop restart status logs logs-app logs-f deploy \
- staging-deploy staging-reset staging-seed staging-logs staging-status staging-stop
+HOOKS_SRC := pocketbase/pb_hooks
+HOOKS_DST := /volume1/docker/vereinshaus/pocketbase/data/pb_hooks
+
+.PHONY: help check-ssh start stop restart status logs logs-f logs-app \
+ shell-pb pb-admin deploy
# ----------------------------------------------------------
# Hilfe
@@ -31,17 +31,17 @@ help:
@echo ""
@echo " vereins.haus — verfügbare Befehle:"
@echo ""
- @echo " make deploy App bauen + zur DS übertragen + Container neu starten"
- @echo " make start Container starten"
- @echo " make stop Container stoppen"
- @echo " make restart Container neu starten"
- @echo " make status Container-Status anzeigen"
- @echo " make logs App-Logs (100 Zeilen)"
- @echo " make logs-f App Live-Log"
+ @echo " make deploy App bauen + zur DS übertragen + Container neu starten"
+ @echo " make start Alle Container starten"
+ @echo " make stop Alle Container stoppen"
+ @echo " make restart Alle Container neu starten"
+ @echo " make status Container-Status anzeigen"
@echo ""
- @echo " make staging-deploy Staging deployen"
- @echo " make staging-seed Testdaten einfügen"
- @echo " make staging-reset Staging-DB löschen (Neustart)"
+ @echo " make logs PocketBase-Logs (100 Zeilen)"
+ @echo " make logs-app App-Logs (100 Zeilen)"
+ @echo " make logs-f PocketBase Live-Log"
+ @echo " make shell-pb Shell in PocketBase-Container"
+ @echo " make pb-admin PocketBase Admin-URL anzeigen"
@echo ""
# ----------------------------------------------------------
@@ -56,14 +56,20 @@ check-ssh:
fi
# ----------------------------------------------------------
-# DEPLOY (Production)
+# DEPLOY
# ----------------------------------------------------------
deploy: check-ssh
@echo "→ Sync zu DS..."
@COPYFILE_DISABLE=1 tar czf - $(TAR_EXCLUDE) . | ssh $(DS_HOST) "tar xzf - -C $(DS_PATH)/"
@echo "→ .env auf DS aktualisieren..."
@if [ -f .env ]; then \
- cat .env | ssh $(DS_HOST) "cat > $(DS_PATH)/.env"; \
+ grep -E "BREVO_KEY" .env | ssh $(DS_HOST) "cat > $(DS_PATH)/.env"; \
+ fi
+ @echo "→ PocketBase Hooks synchronisieren..."
+ @if ls $(HOOKS_SRC)/*.pb.js 2>/dev/null | grep -q .; then \
+ for f in $(HOOKS_SRC)/*.pb.js; do \
+ cat "$$f" | ssh $(DS_HOST) "cat > $(HOOKS_DST)/$$(basename $$f)"; \
+ done; \
fi
@echo "→ Docker rebuild + restart..."
@ssh $(DS_HOST) " \
@@ -72,7 +78,7 @@ deploy: check-ssh
$(DOCKER) compose build app && \
$(DOCKER) compose up -d"
@echo " ✓ Deploy fertig."
- @ssh $(DS_HOST) "$(DOCKER) logs $(CONTAINER_APP) --tail=15"
+ @ssh $(DS_HOST) "$(DOCKER) logs $(CONTAINER_APP) --tail=10"
# ----------------------------------------------------------
# Container-Steuerung
@@ -98,61 +104,19 @@ status: check-ssh
# Logs
# ----------------------------------------------------------
logs: check-ssh
+ @ssh $(DS_HOST) "$(DOCKER) logs $(CONTAINER_PB) --tail=100"
+
+logs-app: check-ssh
@ssh $(DS_HOST) "$(DOCKER) logs $(CONTAINER_APP) --tail=100"
-logs-app: logs
-
logs-f: check-ssh
- @ssh $(DS_HOST) "$(DOCKER) logs $(CONTAINER_APP) -f"
+ @ssh $(DS_HOST) "$(DOCKER) logs $(CONTAINER_PB) -f"
-# ==============================================================
-# STAGING
-# ==============================================================
+# ----------------------------------------------------------
+# Shell + Admin
+# ----------------------------------------------------------
+shell-pb: check-ssh
+ @ssh -t $(DS_HOST) "$(DOCKER) exec -it $(CONTAINER_PB) sh"
-staging-deploy: check-ssh
- @echo "→ Sync zu DS (Staging)..."
- @COPYFILE_DISABLE=1 tar czf - $(TAR_EXCLUDE) . | ssh $(DS_HOST) "tar xzf - -C $(STAGING_PATH)/"
- @echo "→ .env auf DS (Staging)..."
- @if [ -f .env ]; then \
- cat .env | ssh $(DS_HOST) "cat > $(STAGING_PATH)/.env"; \
- fi
- @echo "→ Docker rebuild + restart (Staging)..."
- @ssh $(DS_HOST) " \
- cd $(STAGING_PATH) && \
- $(DOCKER) compose -f docker-compose.staging.yml down && \
- $(DOCKER) compose -f docker-compose.staging.yml build app-staging && \
- $(DOCKER) compose -f docker-compose.staging.yml up -d"
- @echo " ✓ Staging bereit."
- @echo " App: https://staging.vereins.haus"
-
-# Löscht die SQLite-DB auf Staging → frischer Start
-# Danach: make staging-deploy && make staging-seed
-staging-reset: check-ssh staging-stop
- @echo "→ Staging-Daten löschen..."
- @ssh $(DS_HOST) "rm -f \
- $(STAGING_PATH)/data/vereinshaus.db \
- $(STAGING_PATH)/data/vereinshaus.db-wal \
- $(STAGING_PATH)/data/vereinshaus.db-shm && \
- rm -rf $(STAGING_PATH)/data/uploads"
- @echo " ✓ Reset fertig. Jetzt: make staging-deploy && make staging-seed"
-
-staging-seed:
- @echo "→ Testdaten in Staging einfügen..."
- @if [ -f .env ]; then \
- export $$(grep -v '^#' .env | xargs) && \
- APP_URL=https://staging.vereins.haus node scripts/seed.js; \
- else \
- APP_URL=https://staging.vereins.haus node scripts/seed.js; \
- fi
-
-staging-logs: check-ssh
- @ssh $(DS_HOST) "$(DOCKER) logs $(CONTAINER_APP_STAGING) --tail=50"
-
-staging-status: check-ssh
- @ssh $(DS_HOST) "$(DOCKER) ps \
- --filter name=vereinshaus-staging \
- --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'"
-
-staging-stop: check-ssh
- @ssh $(DS_HOST) "cd $(STAGING_PATH) && $(DOCKER) compose -f docker-compose.staging.yml down"
- @echo " ✓ Staging gestoppt."
+pb-admin:
+ @echo " PocketBase Admin: https://api.vereins.haus/_/"
diff --git a/app/Dockerfile b/app/Dockerfile
index bd6f749..7831668 100644
--- a/app/Dockerfile
+++ b/app/Dockerfile
@@ -1,5 +1,4 @@
FROM node:22-alpine AS builder
-RUN apk add --no-cache python3 make g++
WORKDIR /app
COPY package*.json ./
RUN npm ci
@@ -7,7 +6,6 @@ COPY . .
RUN npm run build
FROM node:22-alpine
-RUN apk add --no-cache python3 make g++
WORKDIR /app
COPY --from=builder /app/build ./build
COPY --from=builder /app/package*.json ./
diff --git a/app/package-lock.json b/app/package-lock.json
index f27e45c..7d728af 100644
--- a/app/package-lock.json
+++ b/app/package-lock.json
@@ -8,31 +8,17 @@
"name": "vereinshaus",
"version": "0.1.0",
"dependencies": {
- "@event-calendar/core": "^5.7.0",
- "bcryptjs": "^3.0.3",
- "better-sqlite3": "^12.10.0",
- "ical-generator": "^10.2.0",
- "jose": "^6.2.3",
- "papaparse": "^5.5.3",
- "pocketbase": "^0.26.9",
- "rrule": "^2.8.1",
- "web-push": "^3.6.7"
+ "pocketbase": "^0.26.9"
},
"devDependencies": {
"@sveltejs/adapter-node": "^5.5.4",
"@sveltejs/kit": "^2.57.0",
"@sveltejs/vite-plugin-svelte": "^7.0.0",
- "@types/bcryptjs": "^2.4.6",
- "@types/better-sqlite3": "^7.6.13",
- "@types/papaparse": "^5.5.2",
- "@types/web-push": "^3.6.4",
"svelte": "^5.55.2",
"svelte-check": "^4.4.6",
"typescript": "^6.0.2",
"vite": "^8.0.7",
- "vite-plugin-pwa": "^1.3.0",
- "workbox-core": "^7.4.1",
- "workbox-precaching": "^7.4.1"
+ "vite-plugin-pwa": "^1.3.0"
}
},
"node_modules/@apideck/better-ajv-errors": {
@@ -1628,15 +1614,6 @@
"tslib": "^2.4.0"
}
},
- "node_modules/@event-calendar/core": {
- "version": "5.7.0",
- "resolved": "https://registry.npmjs.org/@event-calendar/core/-/core-5.7.0.tgz",
- "integrity": "sha512-16S9TncV/az52qFjvmB691fMQA8qIo/Iz4kxrvOMLwD46oFzin07aWQQPBcgCFUN+58m9AbFYWnxkvO7OYAldg==",
- "license": "MIT",
- "dependencies": {
- "svelte": "^5.55.4"
- }
- },
"node_modules/@isaacs/cliui": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz",
@@ -1651,6 +1628,7 @@
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
@@ -1661,6 +1639,7 @@
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
@@ -1671,6 +1650,7 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@@ -1691,12 +1671,14 @@
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@@ -2532,6 +2514,7 @@
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz",
"integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==",
+ "dev": true,
"license": "MIT",
"peerDependencies": {
"acorn": "^8.9.0"
@@ -2642,23 +2625,6 @@
"tslib": "^2.4.0"
}
},
- "node_modules/@types/bcryptjs": {
- "version": "2.4.6",
- "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz",
- "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/better-sqlite3": {
- "version": "7.6.13",
- "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
- "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
"node_modules/@types/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
@@ -2670,28 +2636,9 @@
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "dev": true,
"license": "MIT"
},
- "node_modules/@types/node": {
- "version": "25.9.1",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz",
- "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "undici-types": ">=7.24.0 <7.24.7"
- }
- },
- "node_modules/@types/papaparse": {
- "version": "5.5.2",
- "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.5.2.tgz",
- "integrity": "sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
"node_modules/@types/resolve": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
@@ -2703,22 +2650,14 @@
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
- "license": "MIT"
- },
- "node_modules/@types/web-push": {
- "version": "3.6.4",
- "resolved": "https://registry.npmjs.org/@types/web-push/-/web-push-3.6.4.tgz",
- "integrity": "sha512-GnJmSr40H3RAnj0s34FNTcJi1hmWFV5KXugE0mYWnYhgTAHLJ/dJKAwDmvPJYMke0RplY2XE9LnM4hqSqKIjhQ==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
+ "license": "MIT"
},
"node_modules/acorn": {
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+ "dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
@@ -2727,15 +2666,6 @@
"node": ">=0.4.0"
}
},
- "node_modules/agent-base": {
- "version": "7.1.4",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
- "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 14"
- }
- },
"node_modules/ajv": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
@@ -2757,6 +2687,7 @@
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz",
"integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==",
+ "dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">= 0.4"
@@ -2801,18 +2732,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/asn1.js": {
- "version": "5.4.1",
- "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
- "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
- "license": "MIT",
- "dependencies": {
- "bn.js": "^4.0.0",
- "inherits": "^2.0.1",
- "minimalistic-assert": "^1.0.0",
- "safer-buffer": "^2.1.0"
- }
- },
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
@@ -2860,6 +2779,7 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
+ "dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">= 0.4"
@@ -2917,26 +2837,6 @@
"node": "18 || 20 || >=22"
}
},
- "node_modules/base64-js": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
- "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
"node_modules/baseline-browser-mapping": {
"version": "2.10.31",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.31.tgz",
@@ -2950,55 +2850,6 @@
"node": ">=6.0.0"
}
},
- "node_modules/bcryptjs": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz",
- "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==",
- "license": "BSD-3-Clause",
- "bin": {
- "bcrypt": "bin/bcrypt"
- }
- },
- "node_modules/better-sqlite3": {
- "version": "12.10.0",
- "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.10.0.tgz",
- "integrity": "sha512-CyzaZRQKyHkB2ZInfTTl2nvT33EbDpjkLEbE8/Zck3Ll6O0qqvuGdrJ45HgtH+HykRg88ITY3AdreBGN70aBSQ==",
- "hasInstallScript": true,
- "license": "MIT",
- "dependencies": {
- "bindings": "^1.5.0",
- "prebuild-install": "^7.1.1"
- },
- "engines": {
- "node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x"
- }
- },
- "node_modules/bindings": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
- "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
- "license": "MIT",
- "dependencies": {
- "file-uri-to-path": "1.0.0"
- }
- },
- "node_modules/bl": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
- "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
- "license": "MIT",
- "dependencies": {
- "buffer": "^5.5.0",
- "inherits": "^2.0.4",
- "readable-stream": "^3.4.0"
- }
- },
- "node_modules/bn.js": {
- "version": "4.12.3",
- "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz",
- "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==",
- "license": "MIT"
- },
"node_modules/brace-expansion": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
@@ -3046,36 +2897,6 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
- "node_modules/buffer": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
- "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "base64-js": "^1.3.1",
- "ieee754": "^1.1.13"
- }
- },
- "node_modules/buffer-equal-constant-time": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
- "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
- "license": "BSD-3-Clause"
- },
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -3170,16 +2991,11 @@
"url": "https://paulmillr.com/funding/"
}
},
- "node_modules/chownr": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
- "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
- "license": "ISC"
- },
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -3323,6 +3139,7 @@
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -3336,30 +3153,6 @@
}
}
},
- "node_modules/decompress-response": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
- "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
- "license": "MIT",
- "dependencies": {
- "mimic-response": "^3.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/deep-extend": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
- "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
- "license": "MIT",
- "engines": {
- "node": ">=4.0.0"
- }
- },
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
@@ -3410,6 +3203,7 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
@@ -3419,6 +3213,7 @@
"version": "5.8.1",
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz",
"integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==",
+ "dev": true,
"license": "MIT"
},
"node_modules/dunder-proto": {
@@ -3436,15 +3231,6 @@
"node": ">= 0.4"
}
},
- "node_modules/ecdsa-sig-formatter": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
- "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "safe-buffer": "^5.0.1"
- }
- },
"node_modules/ejs": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
@@ -3468,15 +3254,6 @@
"dev": true,
"license": "ISC"
},
- "node_modules/end-of-stream": {
- "version": "1.4.5",
- "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
- "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
- "license": "MIT",
- "dependencies": {
- "once": "^1.4.0"
- }
- },
"node_modules/es-abstract": {
"version": "1.24.2",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz",
@@ -3627,12 +3404,14 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
+ "dev": true,
"license": "MIT"
},
"node_modules/esrap": {
"version": "2.2.9",
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.9.tgz",
"integrity": "sha512-4KijP+NxCWthMCUC3qHbE6n4vCjqgJS1uAYKhuT/GWfFTf1Qyive2TgOjep+gzbSzRfnNyaN/UU9YmdOt8Eg0A==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
@@ -3676,15 +3455,6 @@
"url": "https://github.com/bgub/eta?sponsor=1"
}
},
- "node_modules/expand-template": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
- "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
- "license": "(MIT OR WTFPL)",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -3734,12 +3504,6 @@
}
}
},
- "node_modules/file-uri-to-path": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
- "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
- "license": "MIT"
- },
"node_modules/filelist": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz",
@@ -3813,12 +3577,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/fs-constants": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
- "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
- "license": "MIT"
- },
"node_modules/fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
@@ -3975,12 +3733,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/github-from-package": {
- "version": "0.0.0",
- "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
- "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
- "license": "MIT"
- },
"node_modules/glob": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz",
@@ -4127,76 +3879,6 @@
"node": ">= 0.4"
}
},
- "node_modules/http_ece": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/http_ece/-/http_ece-1.2.0.tgz",
- "integrity": "sha512-JrF8SSLVmcvc5NducxgyOrKXe3EsyHMgBFgSaIUGmArKe+rwr0uphRkRXvwiom3I+fpIfoItveHrfudL8/rxuA==",
- "license": "MIT",
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/https-proxy-agent": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
- "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
- "license": "MIT",
- "dependencies": {
- "agent-base": "^7.1.2",
- "debug": "4"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/ical-generator": {
- "version": "10.2.0",
- "resolved": "https://registry.npmjs.org/ical-generator/-/ical-generator-10.2.0.tgz",
- "integrity": "sha512-XR5FsiDWCsz5MwBwMA/sQqR3A9H240xkXIeXOabV7uNAiieP+TA9rleVvlwPLRXMz+CXME8cGuDd7cdnE5At6w==",
- "license": "MIT",
- "engines": {
- "node": "20 || 22 || >=24"
- },
- "peerDependencies": {
- "@touch4it/ical-timezones": ">=1.6.0",
- "@types/luxon": ">= 1.26.0",
- "@types/mocha": ">= 8.2.1",
- "dayjs": ">= 1.10.0",
- "luxon": ">= 1.26.0",
- "moment": ">= 2.29.0",
- "moment-timezone": ">= 0.5.33",
- "rrule": ">= 2.6.8"
- },
- "peerDependenciesMeta": {
- "@touch4it/ical-timezones": {
- "optional": true
- },
- "@types/luxon": {
- "optional": true
- },
- "@types/mocha": {
- "optional": true
- },
- "@types/node": {
- "optional": true
- },
- "dayjs": {
- "optional": true
- },
- "luxon": {
- "optional": true
- },
- "moment": {
- "optional": true
- },
- "moment-timezone": {
- "optional": true
- },
- "rrule": {
- "optional": true
- }
- }
- },
"node_modules/idb": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
@@ -4204,38 +3886,6 @@
"dev": true,
"license": "ISC"
},
- "node_modules/ieee754": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
- "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "BSD-3-Clause"
- },
- "node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "license": "ISC"
- },
- "node_modules/ini": {
- "version": "1.3.8",
- "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
- "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
- "license": "ISC"
- },
"node_modules/internal-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
@@ -4708,15 +4358,6 @@
"node": ">=10"
}
},
- "node_modules/jose": {
- "version": "6.2.3",
- "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz",
- "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/panva"
- }
- },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -4780,27 +4421,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/jwa": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
- "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
- "license": "MIT",
- "dependencies": {
- "buffer-equal-constant-time": "^1.0.1",
- "ecdsa-sig-formatter": "1.0.11",
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/jws": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
- "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
- "license": "MIT",
- "dependencies": {
- "jwa": "^2.0.1",
- "safe-buffer": "^5.0.1"
- }
- },
"node_modules/kleur": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
@@ -5086,6 +4706,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
+ "dev": true,
"license": "MIT"
},
"node_modules/lodash.debounce": {
@@ -5116,6 +4737,7 @@
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5"
@@ -5131,24 +4753,6 @@
"node": ">= 0.4"
}
},
- "node_modules/mimic-response": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
- "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/minimalistic-assert": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
- "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
- "license": "ISC"
- },
"node_modules/minimatch": {
"version": "10.2.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
@@ -5165,15 +4769,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/minimist": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
- "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/minipass": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
@@ -5184,12 +4779,6 @@
"node": ">=16 || 14 >=14.17"
}
},
- "node_modules/mkdirp-classic": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
- "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
- "license": "MIT"
- },
"node_modules/mri": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
@@ -5214,6 +4803,7 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
"license": "MIT"
},
"node_modules/nanoid": {
@@ -5235,36 +4825,6 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
- "node_modules/napi-build-utils": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
- "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
- "license": "MIT"
- },
- "node_modules/node-abi": {
- "version": "3.92.0",
- "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz",
- "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==",
- "license": "MIT",
- "dependencies": {
- "semver": "^7.3.5"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/node-abi/node_modules/semver": {
- "version": "7.8.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
- "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/node-releases": {
"version": "2.0.44",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.44.tgz",
@@ -5327,15 +4887,6 @@
],
"license": "MIT"
},
- "node_modules/once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "license": "ISC",
- "dependencies": {
- "wrappy": "1"
- }
- },
"node_modules/own-keys": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
@@ -5361,12 +4912,6 @@
"dev": true,
"license": "BlueOak-1.0.0"
},
- "node_modules/papaparse": {
- "version": "5.5.3",
- "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.3.tgz",
- "integrity": "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==",
- "license": "MIT"
- },
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -5476,33 +5021,6 @@
"node": "^10 || ^12 || >=14"
}
},
- "node_modules/prebuild-install": {
- "version": "7.1.3",
- "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
- "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
- "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
- "license": "MIT",
- "dependencies": {
- "detect-libc": "^2.0.0",
- "expand-template": "^2.0.3",
- "github-from-package": "0.0.0",
- "minimist": "^1.2.3",
- "mkdirp-classic": "^0.5.3",
- "napi-build-utils": "^2.0.0",
- "node-abi": "^3.3.0",
- "pump": "^3.0.0",
- "rc": "^1.2.7",
- "simple-get": "^4.0.0",
- "tar-fs": "^2.0.0",
- "tunnel-agent": "^0.6.0"
- },
- "bin": {
- "prebuild-install": "bin.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/pretty-bytes": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz",
@@ -5516,16 +5034,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/pump": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
- "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
- "license": "MIT",
- "dependencies": {
- "end-of-stream": "^1.1.0",
- "once": "^1.3.1"
- }
- },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -5536,35 +5044,6 @@
"node": ">=6"
}
},
- "node_modules/rc": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
- "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
- "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
- "dependencies": {
- "deep-extend": "^0.6.0",
- "ini": "~1.3.0",
- "minimist": "^1.2.0",
- "strip-json-comments": "~2.0.1"
- },
- "bin": {
- "rc": "cli.js"
- }
- },
- "node_modules/readable-stream": {
- "version": "3.6.2",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
- "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
- "license": "MIT",
- "dependencies": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
@@ -5799,15 +5278,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/rrule": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/rrule/-/rrule-2.8.1.tgz",
- "integrity": "sha512-hM3dHSBMeaJ0Ktp7W38BJZ7O1zOgaFEsn41PDk+yHoEtfLV+PoJt9E9xAlZiWgf/iqEqionN0ebHFZIDAp+iGw==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
"node_modules/sade": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
@@ -5841,26 +5311,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
"node_modules/safe-push-apply": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
@@ -5896,12 +5346,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/safer-buffer": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "license": "MIT"
- },
"node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
@@ -6090,51 +5534,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/simple-concat": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
- "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/simple-get": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
- "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "decompress-response": "^6.0.0",
- "once": "^1.3.1",
- "simple-concat": "^1.0.0"
- }
- },
"node_modules/sirv": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz",
@@ -6219,15 +5618,6 @@
"node": ">= 0.4"
}
},
- "node_modules/string_decoder": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
- "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "~5.2.0"
- }
- },
"node_modules/string.prototype.matchall": {
"version": "4.0.12",
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz",
@@ -6340,15 +5730,6 @@
"node": ">=10"
}
},
- "node_modules/strip-json-comments": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
- "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
@@ -6366,6 +5747,7 @@
"version": "5.55.8",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.55.8.tgz",
"integrity": "sha512-4D6lyrMHmDaZalQOEBMCWCCidyZjSnec14/oPn0k627G6goxcck9xqMwz1tFLlQz+ZFvtTTHfFOlUayuAz0z6Q==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/remapping": "^2.3.4",
@@ -6417,39 +5799,12 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.6"
}
},
- "node_modules/tar-fs": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
- "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
- "license": "MIT",
- "dependencies": {
- "chownr": "^1.1.1",
- "mkdirp-classic": "^0.5.2",
- "pump": "^3.0.0",
- "tar-stream": "^2.1.4"
- }
- },
- "node_modules/tar-stream": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
- "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
- "license": "MIT",
- "dependencies": {
- "bl": "^4.0.3",
- "end-of-stream": "^1.4.1",
- "fs-constants": "^1.0.0",
- "inherits": "^2.0.3",
- "readable-stream": "^3.1.1"
- },
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/temp-dir": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz",
@@ -6539,19 +5894,9 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "license": "0BSD"
- },
- "node_modules/tunnel-agent": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
- "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
- "license": "Apache-2.0",
- "dependencies": {
- "safe-buffer": "^5.0.1"
- },
- "engines": {
- "node": "*"
- }
+ "dev": true,
+ "license": "0BSD",
+ "optional": true
},
"node_modules/type-fest": {
"version": "0.16.0",
@@ -6677,13 +6022,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/undici-types": {
- "version": "7.24.6",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
- "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/unicode-canonical-property-names-ecmascript": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz",
@@ -6793,12 +6131,6 @@
"browserslist": ">= 4.21.0"
}
},
- "node_modules/util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
- "license": "MIT"
- },
"node_modules/vite": {
"version": "8.0.13",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz",
@@ -6928,25 +6260,6 @@
}
}
},
- "node_modules/web-push": {
- "version": "3.6.7",
- "resolved": "https://registry.npmjs.org/web-push/-/web-push-3.6.7.tgz",
- "integrity": "sha512-OpiIUe8cuGjrj3mMBFWY+e4MMIkW3SVT+7vEIjvD9kejGUypv8GPDf84JdPWskK8zMRIJ6xYGm+Kxr8YkPyA0A==",
- "license": "MPL-2.0",
- "dependencies": {
- "asn1.js": "^5.3.0",
- "http_ece": "1.2.0",
- "https-proxy-agent": "^7.0.0",
- "jws": "^4.0.0",
- "minimist": "^1.2.5"
- },
- "bin": {
- "web-push": "src/cli.js"
- },
- "engines": {
- "node": ">= 16"
- }
- },
"node_modules/webidl-conversions": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
@@ -7291,12 +6604,6 @@
"workbox-core": "7.4.1"
}
},
- "node_modules/wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "license": "ISC"
- },
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
@@ -7308,6 +6615,7 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
"integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
+ "dev": true,
"license": "MIT"
}
}
diff --git a/app/package.json b/app/package.json
index 9f4431d..e0fc5a5 100644
--- a/app/package.json
+++ b/app/package.json
@@ -15,27 +15,13 @@
"@sveltejs/adapter-node": "^5.5.4",
"@sveltejs/kit": "^2.57.0",
"@sveltejs/vite-plugin-svelte": "^7.0.0",
- "@types/bcryptjs": "^2.4.6",
- "@types/better-sqlite3": "^7.6.13",
- "@types/papaparse": "^5.5.2",
- "@types/web-push": "^3.6.4",
"svelte": "^5.55.2",
"svelte-check": "^4.4.6",
"typescript": "^6.0.2",
"vite": "^8.0.7",
- "vite-plugin-pwa": "^1.3.0",
- "workbox-core": "^7.4.1",
- "workbox-precaching": "^7.4.1"
+ "vite-plugin-pwa": "^1.3.0"
},
"dependencies": {
- "@event-calendar/core": "^5.7.0",
- "bcryptjs": "^3.0.3",
- "better-sqlite3": "^12.10.0",
- "ical-generator": "^10.2.0",
- "jose": "^6.2.3",
- "papaparse": "^5.5.3",
- "pocketbase": "^0.26.9",
- "rrule": "^2.8.1",
- "web-push": "^3.6.7"
+ "pocketbase": "^0.26.9"
}
}
diff --git a/app/src/app.d.ts b/app/src/app.d.ts
deleted file mode 100644
index d437a87..0000000
--- a/app/src/app.d.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-declare global {
- namespace App {
- interface Locals {
- user: {
- id: string;
- verein_id: string;
- rolle: string | null;
- name: string;
- email: string;
- } | null;
- }
- }
-}
-
-export {};
diff --git a/app/src/app.html b/app/src/app.html
index 7842f62..6aac5ef 100644
--- a/app/src/app.html
+++ b/app/src/app.html
@@ -4,12 +4,13 @@
-
+
-
+
-
+
+
%sveltekit.head%
diff --git a/app/src/lib/api.ts b/app/src/lib/api.ts
deleted file mode 100644
index 5db04ec..0000000
--- a/app/src/lib/api.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { get } from 'svelte/store';
-import { user } from './user';
-import { goto } from '$app/navigation';
-
-function token() { return get(user)?.token ?? ''; }
-
-function headers(extra: Record = {}): Record {
- return { Authorization: `Bearer ${token()}`, ...extra };
-}
-
-async function handleRes(res: Response): Promise {
- if (res.status === 401) { user.clear(); goto('/login'); throw new Error('Nicht angemeldet'); }
- if (!res.ok) {
- const e = await res.json().catch(() => ({}));
- throw new Error((e as { message?: string }).message || res.statusText);
- }
- if (res.status === 204) return undefined as T;
- return res.json() as Promise;
-}
-
-export const api = {
- async get(path: string, query: Record = {}): Promise {
- const url = new URL('/api' + path, location.origin);
- Object.entries(query).forEach(([k, v]) => v !== undefined && url.searchParams.set(k, v));
- return handleRes(await fetch(url.toString(), { headers: headers() }));
- },
-
- async post(path: string, data?: unknown): Promise {
- return handleRes(await fetch('/api' + path, {
- method: 'POST',
- headers: headers({ 'Content-Type': 'application/json' }),
- body: JSON.stringify(data ?? {}),
- }));
- },
-
- async put(path: string, data?: unknown): Promise {
- return handleRes(await fetch('/api' + path, {
- method: 'PUT',
- headers: headers({ 'Content-Type': 'application/json' }),
- body: JSON.stringify(data ?? {}),
- }));
- },
-
- async patch(path: string, data?: unknown): Promise {
- return handleRes(await fetch('/api' + path, {
- method: 'PATCH',
- headers: headers({ 'Content-Type': 'application/json' }),
- body: JSON.stringify(data ?? {}),
- }));
- },
-
- async del(path: string): Promise {
- return handleRes(await fetch('/api' + path, { method: 'DELETE', headers: headers() }));
- },
-
- async postForm(path: string, form: FormData): Promise {
- return handleRes(await fetch('/api' + path, {
- method: 'POST', headers: headers(), body: form,
- }));
- },
-
- fileUrl(verein_id: string, record_id: string, filename: string, thumb = false): string {
- const base = `/api/files/${verein_id}/${record_id}/${filename}`;
- return thumb ? base + '?thumb=1' : base;
- }
-};
diff --git a/app/src/lib/components/Icon.svelte b/app/src/lib/components/Icon.svelte
deleted file mode 100644
index 3af0fc2..0000000
--- a/app/src/lib/components/Icon.svelte
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
- {@html icons[name]}
-
-
-
diff --git a/app/src/lib/event-calendar.d.ts b/app/src/lib/event-calendar.d.ts
deleted file mode 100644
index 703104d..0000000
--- a/app/src/lib/event-calendar.d.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-declare module '@event-calendar/core' {
- import type { SvelteComponent } from 'svelte';
-
- export class Calendar extends SvelteComponent<{
- plugins: any[];
- options: Record;
- }> {}
-
- export const TimeGrid: any;
- export const DayGrid: any;
- export const List: any;
- export const Interaction: any;
-}
diff --git a/app/src/lib/icons.ts b/app/src/lib/icons.ts
deleted file mode 100644
index bf74341..0000000
--- a/app/src/lib/icons.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import house from './icons/house.svg?raw';
-import users from './icons/users.svg?raw';
-import calendar from './icons/calendar.svg?raw';
-import currencyEur from './icons/currency-eur.svg?raw';
-import envelope from './icons/envelope.svg?raw';
-import gear from './icons/gear.svg?raw';
-import images from './icons/images.svg?raw';
-
-export const icons = {
- house,
- users,
- calendar,
- 'currency-eur': currencyEur,
- envelope,
- gear,
- images,
-} as const;
-
-export type IconName = keyof typeof icons;
diff --git a/app/src/lib/icons/calendar.svg b/app/src/lib/icons/calendar.svg
deleted file mode 100644
index 3b91cc4..0000000
--- a/app/src/lib/icons/calendar.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/app/src/lib/icons/currency-eur.svg b/app/src/lib/icons/currency-eur.svg
deleted file mode 100644
index c23e136..0000000
--- a/app/src/lib/icons/currency-eur.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/app/src/lib/icons/envelope.svg b/app/src/lib/icons/envelope.svg
deleted file mode 100644
index f07a98b..0000000
--- a/app/src/lib/icons/envelope.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/app/src/lib/icons/gear.svg b/app/src/lib/icons/gear.svg
deleted file mode 100644
index 9a20171..0000000
--- a/app/src/lib/icons/gear.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/app/src/lib/icons/house.svg b/app/src/lib/icons/house.svg
deleted file mode 100644
index 6346350..0000000
--- a/app/src/lib/icons/house.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/app/src/lib/icons/images.svg b/app/src/lib/icons/images.svg
deleted file mode 100644
index 48ffc9a..0000000
--- a/app/src/lib/icons/images.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/app/src/lib/icons/users.svg b/app/src/lib/icons/users.svg
deleted file mode 100644
index 7f7b6ca..0000000
--- a/app/src/lib/icons/users.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/app/src/lib/sepa.ts b/app/src/lib/sepa.ts
deleted file mode 100644
index a49ff21..0000000
--- a/app/src/lib/sepa.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-export interface SepaKopf {
- glaeubigerid: string;
- vereinIban: string;
- vereinBic: string;
- vereinName: string;
- einzugsdatum: string; // YYYY-MM-DD
-}
-
-export interface SepaPosition {
- endToEndId: string;
- betrag: number;
- mandatsreferenz: string;
- mandatsdatum: string; // YYYY-MM-DD
- debitorName: string;
- debitorIban: string;
- debitorBic: string;
- verwendungszweck: string;
-}
-
-function x(str: string): string {
- return str
- .replace(/&/g, '&')
- .replace(//g, '>')
- .replace(/"/g, '"');
-}
-
-function iban(raw: string): string {
- return raw.replace(/\s/g, '').toUpperCase();
-}
-
-export function generatePain008(kopf: SepaKopf, positionen: SepaPosition[]): string {
- if (positionen.length === 0) throw new Error('Keine Positionen für den SEPA-Export.');
-
- const now = new Date().toISOString().slice(0, 19);
- const msgId = `VH-${Date.now()}`;
- const ctrlSum = positionen.reduce((s, p) => s + p.betrag, 0).toFixed(2);
- const nbOfTxs = positionen.length;
-
- const txXml = positionen.map((p) => `
-
- ${x(p.endToEndId)}
- ${p.betrag.toFixed(2)}
-
-
- ${x(p.mandatsreferenz)}
- ${p.mandatsdatum}
-
-
- ${x(kopf.glaeubigerid)}
- SEPA
-
-
- ${p.debitorBic ? `${x(p.debitorBic)}` : 'NOTPROVIDED'}
- ${x(p.debitorName)}
- ${iban(p.debitorIban)}
- ${x(p.verwendungszweck.slice(0, 140))}
- `).join('');
-
- return `
-
-
-
- ${msgId}
- ${now}
- ${nbOfTxs}
- ${ctrlSum}
- ${x(kopf.vereinName)}
-
-
- ${msgId}-PI
- DD
- ${nbOfTxs}
- ${ctrlSum}
-
- SEPA
- CORE
- RCUR
-
- ${kopf.einzugsdatum}
- ${x(kopf.vereinName)}
- ${iban(kopf.vereinIban)}
- ${x(kopf.vereinBic)}
-
- ${x(kopf.glaeubigerid)}
- SEPA
- ${txXml}
-
-
-`;
-}
-
-export function downloadXml(xml: string, dateiname: string) {
- const blob = new Blob([xml], { type: 'application/xml;charset=utf-8' });
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = dateiname;
- a.click();
- URL.revokeObjectURL(url);
-}
-
-export function minEinzugsdatum(): string {
- // SEPA CORE RCUR: mindestens 2 Bankarbeitstage Vorlaufzeit (vereinfacht: +3 Tage)
- const d = new Date();
- d.setDate(d.getDate() + 3);
- return d.toISOString().slice(0, 10);
-}
diff --git a/app/src/lib/server/auth.ts b/app/src/lib/server/auth.ts
deleted file mode 100644
index 1bae1ff..0000000
--- a/app/src/lib/server/auth.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { SignJWT, jwtVerify } from 'jose';
-import bcrypt from 'bcryptjs';
-import { error } from '@sveltejs/kit';
-
-const JWT_SECRET = new TextEncoder().encode(
- process.env.JWT_SECRET || 'vereinshaus-dev-secret-change-in-production'
-);
-
-export interface JwtPayload {
- sub: string;
- verein_id: string;
- rolle: string | null;
- name: string;
- email: string;
-}
-
-export async function signJwt(payload: JwtPayload): Promise {
- return new SignJWT({ ...payload })
- .setProtectedHeader({ alg: 'HS256' })
- .setExpirationTime('30d')
- .sign(JWT_SECRET);
-}
-
-export async function verifyJwt(token: string): Promise {
- try {
- const { payload } = await jwtVerify(token, JWT_SECRET);
- return payload as unknown as JwtPayload;
- } catch {
- return null;
- }
-}
-
-export async function hashPassword(password: string): Promise {
- return bcrypt.hash(password, 12);
-}
-
-export async function checkPassword(password: string, hash: string): Promise {
- return bcrypt.compare(password, hash);
-}
-
-export function bearerToken(request: Request): string | null {
- const h = request.headers.get('Authorization');
- return h?.startsWith('Bearer ') ? h.slice(7) : null;
-}
-
-export async function requireAuth(request: Request): Promise {
- const token = bearerToken(request);
- if (!token) throw error(401, 'Nicht authentifiziert');
- const user = await verifyJwt(token);
- if (!user) throw error(401, 'Ungültiger Token');
- return user;
-}
diff --git a/app/src/lib/server/db.ts b/app/src/lib/server/db.ts
deleted file mode 100644
index c7919f7..0000000
--- a/app/src/lib/server/db.ts
+++ /dev/null
@@ -1,219 +0,0 @@
-import Database from 'better-sqlite3';
-import { mkdirSync, existsSync } from 'fs';
-import { dirname } from 'path';
-
-const DB_PATH = process.env.DB_PATH || './data/vereinshaus.db';
-
-const SCHEMA = `
-PRAGMA journal_mode = WAL;
-PRAGMA foreign_keys = ON;
-PRAGMA busy_timeout = 5000;
-
-CREATE TABLE IF NOT EXISTS vereine (
- id TEXT PRIMARY KEY NOT NULL DEFAULT (lower(hex(randomblob(8)))),
- name TEXT NOT NULL,
- adresse TEXT, plz TEXT, ort TEXT, bundesland TEXT,
- plan TEXT NOT NULL DEFAULT 'free',
- dosb_mitglied INTEGER NOT NULL DEFAULT 0,
- email TEXT, telefon TEXT, website TEXT,
- glaeubigerid TEXT, iban TEXT, bic TEXT,
- created TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
- updated TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
-);
-
-CREATE TABLE IF NOT EXISTS users (
- id TEXT PRIMARY KEY NOT NULL DEFAULT (lower(hex(randomblob(8)))),
- verein_id TEXT NOT NULL REFERENCES vereine(id) ON DELETE CASCADE,
- email TEXT NOT NULL UNIQUE,
- password_hash TEXT NOT NULL,
- name TEXT NOT NULL DEFAULT '',
- rolle TEXT DEFAULT NULL,
- created TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
- updated TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
-);
-
-CREATE TABLE IF NOT EXISTS gruppen (
- id TEXT PRIMARY KEY NOT NULL DEFAULT (lower(hex(randomblob(8)))),
- verein_id TEXT NOT NULL REFERENCES vereine(id) ON DELETE CASCADE,
- name TEXT NOT NULL,
- beschreibung TEXT,
- trainer_ids TEXT NOT NULL DEFAULT '[]',
- created TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
- updated TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
-);
-
-CREATE TABLE IF NOT EXISTS mitglieder (
- id TEXT PRIMARY KEY NOT NULL DEFAULT (lower(hex(randomblob(8)))),
- verein_id TEXT NOT NULL REFERENCES vereine(id) ON DELETE CASCADE,
- vorname TEXT NOT NULL,
- nachname TEXT NOT NULL,
- email TEXT, telefon TEXT,
- geburtsdatum TEXT, eintrittsdatum TEXT, austrittsdatum TEXT,
- strasse TEXT, plz TEXT, ort TEXT,
- iban TEXT, bic TEXT,
- gruppe_ids TEXT NOT NULL DEFAULT '[]',
- status TEXT NOT NULL DEFAULT 'aktiv',
- notizen TEXT,
- mandatsreferenz TEXT, mandatsdatum TEXT,
- created TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
- updated TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
-);
-
-CREATE TABLE IF NOT EXISTS beitraege (
- id TEXT PRIMARY KEY NOT NULL DEFAULT (lower(hex(randomblob(8)))),
- verein_id TEXT NOT NULL REFERENCES vereine(id) ON DELETE CASCADE,
- name TEXT NOT NULL,
- betrag REAL NOT NULL,
- rhythmus TEXT NOT NULL DEFAULT 'jaehrlich',
- beschreibung TEXT,
- created TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
- updated TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
-);
-
-CREATE TABLE IF NOT EXISTS einzuege (
- id TEXT PRIMARY KEY NOT NULL DEFAULT (lower(hex(randomblob(8)))),
- verein_id TEXT NOT NULL REFERENCES vereine(id) ON DELETE CASCADE,
- mitglied_id TEXT NOT NULL REFERENCES mitglieder(id) ON DELETE CASCADE,
- beitrag_id TEXT NOT NULL REFERENCES beitraege(id),
- betrag REAL NOT NULL,
- faellig_am TEXT,
- status TEXT NOT NULL DEFAULT 'ausstehend',
- created TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
- updated TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
-);
-
-CREATE TABLE IF NOT EXISTS veranstaltungsorte (
- id TEXT PRIMARY KEY NOT NULL DEFAULT (lower(hex(randomblob(8)))),
- verein_id TEXT NOT NULL REFERENCES vereine(id) ON DELETE CASCADE,
- name TEXT NOT NULL,
- adresse TEXT,
- typ TEXT DEFAULT 'sonstiges',
- aktiv INTEGER NOT NULL DEFAULT 1,
- created TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
- updated TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
-);
-
-CREATE TABLE IF NOT EXISTS ort_ausfaelle (
- id TEXT PRIMARY KEY NOT NULL DEFAULT (lower(hex(randomblob(8)))),
- ort_id TEXT NOT NULL REFERENCES veranstaltungsorte(id) ON DELETE CASCADE,
- von TEXT NOT NULL, bis TEXT NOT NULL, grund TEXT,
- created TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
- updated TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
-);
-
-CREATE TABLE IF NOT EXISTS termine (
- id TEXT PRIMARY KEY NOT NULL DEFAULT (lower(hex(randomblob(8)))),
- verein_id TEXT NOT NULL REFERENCES vereine(id) ON DELETE CASCADE,
- titel TEXT NOT NULL,
- beschreibung TEXT,
- beginn TEXT NOT NULL,
- ende TEXT,
- ort TEXT,
- ort_id TEXT REFERENCES veranstaltungsorte(id) ON DELETE SET NULL,
- gruppe_ids TEXT NOT NULL DEFAULT '[]',
- durchfuehrender_id TEXT,
- verfuegbarkeit TEXT DEFAULT 'offen',
- rrule TEXT,
- serie_id TEXT,
- created TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
- updated TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
-);
-
-CREATE TABLE IF NOT EXISTS nachrichten (
- id TEXT PRIMARY KEY NOT NULL DEFAULT (lower(hex(randomblob(8)))),
- verein_id TEXT NOT NULL REFERENCES vereine(id) ON DELETE CASCADE,
- autor_id TEXT NOT NULL,
- betreff TEXT NOT NULL,
- text TEXT NOT NULL DEFAULT '',
- gruppe_ids TEXT NOT NULL DEFAULT '[]',
- gesendet_am TEXT,
- created TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
- updated TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
-);
-
-CREATE TABLE IF NOT EXISTS neuigkeiten (
- id TEXT PRIMARY KEY NOT NULL DEFAULT (lower(hex(randomblob(8)))),
- verein_id TEXT NOT NULL REFERENCES vereine(id) ON DELETE CASCADE,
- autor_id TEXT NOT NULL,
- autor_name TEXT NOT NULL DEFAULT '',
- text TEXT,
- medien TEXT NOT NULL DEFAULT '[]',
- gruppe_ids TEXT NOT NULL DEFAULT '[]',
- termin_id TEXT REFERENCES termine(id) ON DELETE SET NULL,
- created TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
- updated TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
-);
-
-CREATE TABLE IF NOT EXISTS reaktionen (
- id TEXT PRIMARY KEY NOT NULL DEFAULT (lower(hex(randomblob(8)))),
- beitrag_id TEXT NOT NULL REFERENCES neuigkeiten(id) ON DELETE CASCADE,
- user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
- created TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
- UNIQUE(beitrag_id, user_id)
-);
-
-CREATE TABLE IF NOT EXISTS einladungen (
- id TEXT PRIMARY KEY NOT NULL DEFAULT (lower(hex(randomblob(8)))),
- verein_id TEXT NOT NULL REFERENCES vereine(id) ON DELETE CASCADE,
- token TEXT NOT NULL UNIQUE DEFAULT (lower(hex(randomblob(16)))),
- rolle TEXT NOT NULL DEFAULT 'trainer',
- genutzt INTEGER NOT NULL DEFAULT 0,
- created TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
- updated TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
-);
-
-CREATE TABLE IF NOT EXISTS push_subscriptions (
- id TEXT PRIMARY KEY NOT NULL DEFAULT (lower(hex(randomblob(8)))),
- user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
- verein_id TEXT NOT NULL REFERENCES vereine(id) ON DELETE CASCADE,
- endpoint TEXT NOT NULL UNIQUE,
- p256dh TEXT NOT NULL,
- auth TEXT NOT NULL,
- created TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
-);
-`;
-
-let _db: Database.Database | null = null;
-
-export function getDb(): Database.Database {
- if (_db) return _db;
- const dir = dirname(DB_PATH);
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
- _db = new Database(DB_PATH);
- _db.exec(SCHEMA);
- return _db;
-}
-
-export function newId(): string {
- const bytes = new Uint8Array(8);
- crypto.getRandomValues(bytes);
- return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
-}
-
-export function parseArr(val: unknown): string[] {
- if (Array.isArray(val)) return val;
- if (typeof val === 'string') { try { return JSON.parse(val); } catch { return []; } }
- return [];
-}
-
-export function toArr(val: unknown): string {
- return JSON.stringify(Array.isArray(val) ? val : []);
-}
-
-export function row>(r: T): T {
- const out: Record = {};
- for (const [k, v] of Object.entries(r)) {
- if (typeof v === 'string' && (k.endsWith('_ids') || k === 'medien' || k === 'trainer_ids')) {
- out[k] = parseArr(v);
- } else if (typeof v === 'number' && (k === 'aktiv' || k === 'dosb_mitglied' || k === 'genutzt')) {
- out[k] = Boolean(v);
- } else {
- out[k] = v;
- }
- }
- return out as T;
-}
-
-export function rows>(rs: T[]): T[] {
- return rs.map(r => row(r));
-}
diff --git a/app/src/lib/styles/theme.css b/app/src/lib/styles/theme.css
deleted file mode 100644
index 220bdcb..0000000
--- a/app/src/lib/styles/theme.css
+++ /dev/null
@@ -1,59 +0,0 @@
-:root {
- /* Primärfarbe */
- --c-primary: #1e40af;
- --c-primary-dark: #1d3a9e;
- --c-primary-light: #e0e7ff;
- --c-primary-subtle: #eff6ff;
-
- /* Text */
- --c-text: #1e293b;
- --c-text-secondary: #475569;
- --c-text-muted: #64748b;
- --c-text-hint: #94a3b8;
-
- /* Hintergrund & Rahmen */
- --c-border: #e2e8f0;
- --c-bg: #f1f5f9;
- --c-bg-card: #ffffff;
- --c-bg-subtle: #f8fafc;
-
- /* Dunkel (Theme-Farbe, Header, PWA) */
- --c-dark: #0f172a;
-
- /* Fehler / Rot */
- --c-error: #dc2626;
- --c-error-dark: #b91c1c;
- --c-error-light: #fca5a5;
- --c-error-bg: #fee2e2;
- --c-error-subtle: #fef2f2;
-
- /* Erfolg / Grün */
- --c-success: #16a34a;
- --c-success-light: #86efac;
- --c-success-bg: #dcfce7;
-
- /* Warnung / Gelb-Amber */
- --c-warning: #f59e0b;
- --c-warning-light: #fde047;
- --c-warning-bg: #fef9c3;
- --c-warning-subtle: #fffbeb;
- --c-warning-dark: #92400e;
- --c-warning-darker: #713f12;
-
- /* Akzent / Lila (Plan-Badges) */
- --c-accent: #7c3aed;
- --c-accent-subtle: #ede9fe;
-
- /* Primärfarbe – weitere Töne */
- --c-primary-100: #bfdbfe;
- --c-primary-200: #c7d2fe;
- --c-primary-bg: #f0f9ff;
-
- /* Erfolg – weitere Töne */
- --c-success-dark: #166534;
- --c-success-subtle: #f0fdf4;
-
- /* Warnung – weitere Töne */
- --c-warning-amber: #854d0e;
- --c-warning-pale: #fefce8;
-}
diff --git a/app/src/lib/types.ts b/app/src/lib/types.ts
index 159b98b..07bad1f 100644
--- a/app/src/lib/types.ts
+++ b/app/src/lib/types.ts
@@ -1,23 +1,18 @@
export type Plan = 'free' | 'starter' | 'wachstum' | 'verband';
export type MitgliedStatus = 'aktiv' | 'passiv' | 'ausgetreten';
-export type EinzugStatus = 'ausstehend' | 'eingezogen' | 'fehlgeschlagen' | 'storniert';
-export type Rhythmus = 'monatlich' | 'quartalsweise' | 'halbjaehrlich' | 'jaehrlich' | 'einmalig';
+export type EinzugStatus = 'ausstehend' | 'eingezogen' | 'fehlgeschlagen';
+export type Rhythmus = 'monatlich' | 'quartalsweise' | 'halbjaehrlich' | 'jaehrlich';
export interface Verein {
id: string;
name: string;
- adresse?: string;
- plz?: string;
- ort?: string;
- bundesland?: string;
+ adresse: string;
+ plz: string;
+ ort: string;
+ bundesland: string;
plan: Plan;
+ stripe_customer_id?: string;
dosb_mitglied: boolean;
- email?: string;
- telefon?: string;
- website?: string;
- glaeubigerid?: string;
- iban?: string;
- bic?: string;
}
export interface Mitglied {
@@ -25,77 +20,26 @@ export interface Mitglied {
verein_id: string;
vorname: string;
nachname: string;
- email?: string;
+ email: string;
telefon?: string;
geburtsdatum?: string;
- eintrittsdatum?: string;
+ eintrittsdatum: string;
austrittsdatum?: string;
- strasse?: string;
- plz?: string;
- ort?: string;
+ strasse: string;
+ plz: string;
+ ort: string;
iban?: string;
bic?: string;
gruppe_ids: string[];
status: MitgliedStatus;
notizen?: string;
- mandatsreferenz?: string;
- mandatsdatum?: string;
}
-export type Rolle = 'admin' | 'trainer';
-
-export interface Neuigkeit {
- id: string;
- verein_id: string;
- autor_id: string;
- autor_name?: string;
- text?: string;
- medien: string[];
- gruppe_ids: string[];
- termin_id?: string;
- created: string;
-}
-
-export interface Reaktion {
- id: string;
- beitrag_id: string;
- user_id: string;
-}
-export type Verfuegbarkeit = 'offen' | 'bestaetigt' | 'abgesagt' | 'vertretung_gesucht';
-
export interface Gruppe {
id: string;
verein_id: string;
name: string;
beschreibung?: string;
- trainer_ids: string[];
-}
-
-export type OrtTyp = 'halle' | 'platz' | 'gebaeude' | 'sonstiges';
-
-export interface Veranstaltungsort {
- id: string;
- verein_id: string;
- name: string;
- adresse?: string;
- typ?: OrtTyp;
- aktiv: boolean;
-}
-
-export interface OrtAusfall {
- id: string;
- ort_id: string;
- von: string;
- bis: string;
- grund?: string;
-}
-
-export interface Einladung {
- id: string;
- verein_id: string;
- rolle: Rolle;
- token: string;
- genutzt: boolean;
}
export interface Beitrag {
@@ -109,11 +53,13 @@ export interface Beitrag {
export interface Einzug {
id: string;
+ verein_id: string;
mitglied_id: string;
beitrag_id: string;
betrag: number;
- faellig_am?: string;
+ faelligkeitsdatum: string;
status: EinzugStatus;
+ stripe_payment_intent_id?: string;
}
export interface Termin {
@@ -125,11 +71,6 @@ export interface Termin {
ende?: string;
ort?: string;
gruppe_ids: string[];
- durchfuehrender_id?: string;
- verfuegbarkeit?: Verfuegbarkeit;
- rrule?: string;
- serie_id?: string;
- ort_id?: string;
}
export interface Nachricht {
@@ -139,5 +80,5 @@ export interface Nachricht {
betreff: string;
text: string;
gruppe_ids: string[];
- gesendet_am?: string;
+ gesendet_am: string;
}
diff --git a/app/src/lib/user.ts b/app/src/lib/user.ts
deleted file mode 100644
index 42f59a7..0000000
--- a/app/src/lib/user.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { writable } from 'svelte/store';
-import { browser } from '$app/environment';
-
-export interface AppUser {
- id: string;
- verein_id: string;
- rolle: string | null;
- name: string;
- email: string;
- token: string;
-}
-
-function createUserStore() {
- let initial: AppUser | null = null;
- if (browser) {
- try { initial = JSON.parse(localStorage.getItem('vh_user') || 'null'); } catch { /* */ }
- }
-
- const { subscribe, set } = writable(initial);
-
- return {
- subscribe,
- set(u: AppUser | null) {
- if (browser) {
- if (u) localStorage.setItem('vh_user', JSON.stringify(u));
- else localStorage.removeItem('vh_user');
- }
- set(u);
- },
- clear() { this.set(null); }
- };
-}
-
-export const user = createUserStore();
diff --git a/app/src/routes/(app)/+layout.svelte b/app/src/routes/(app)/+layout.svelte
index d6eec7c..b435cee 100644
--- a/app/src/routes/(app)/+layout.svelte
+++ b/app/src/routes/(app)/+layout.svelte
@@ -2,82 +2,34 @@
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { onMount } from 'svelte';
- import { user } from '$lib/user';
- import Icon from '$lib/components/Icon.svelte';
- import type { IconName } from '$lib/icons';
+ import { pb } from '$lib/pb';
let { children } = $props();
- let zeigInstallBanner = $state(false);
-
onMount(() => {
- if (!$user) { goto('/login'); return; }
- if (!$user.verein_id) { goto('/onboarding'); return; }
- registerPush();
-
- // iOS Safari: Banner anzeigen wenn App noch nicht installiert
- const isIos = /iphone|ipad|ipod/i.test(navigator.userAgent);
- const isStandalone = window.matchMedia('(display-mode: standalone)').matches
- || (navigator as any).standalone === true;
- const dismissed = sessionStorage.getItem('install-banner-dismissed');
- if (isIos && !isStandalone && !dismissed) zeigInstallBanner = true;
+ if (!pb.authStore.isValid) {
+ goto('/login');
+ }
});
- async function registerPush() {
- if (!('serviceWorker' in navigator) || !('PushManager' in window)) return;
- const permission = await Notification.requestPermission();
- if (permission !== 'granted') return;
- try {
- const reg = await navigator.serviceWorker.ready;
- const keyRes = await fetch('/api/push/key');
- const { publicKey } = await keyRes.json();
- if (!publicKey) return;
- let sub = await reg.pushManager.getSubscription();
- if (!sub) {
- sub = await reg.pushManager.subscribe({
- userVisibleOnly: true,
- applicationServerKey: urlBase64ToUint8Array(publicKey) as BufferSource,
- });
- }
- await fetch('/api/push/subscribe', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${$user?.token}` },
- body: JSON.stringify({ subscription: sub.toJSON(), userId: $user?.id }),
- });
- } catch (e) {
- console.warn('[push] Registrierung fehlgeschlagen:', e);
- }
+ function logout() {
+ pb.authStore.clear();
+ goto('/login');
}
- function urlBase64ToUint8Array(base64String: string): Uint8Array {
- const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
- const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
- const raw = atob(base64);
- return Uint8Array.from([...raw].map((c) => c.charCodeAt(0)));
- }
-
- const isAdmin = () => !$user?.rolle || $user?.rolle === 'admin';
-
- const allNavItems: { href: string; label: string; icon: IconName; adminOnly?: boolean }[] = [
- { href: '/neuigkeiten', label: 'Neuigkeiten', icon: 'images' },
- { href: '/mitglieder', label: 'Mitglieder', icon: 'users' },
- { href: '/termine', label: 'Termine', icon: 'calendar' },
- { href: '/beitraege', label: 'Beiträge', icon: 'currency-eur', adminOnly: true },
- { href: '/nachrichten', label: 'Nachrichten', icon: 'envelope' },
+ const navItems = [
+ { href: '/', label: 'Übersicht', icon: '⊞' },
+ { href: '/mitglieder', label: 'Mitglieder', icon: '👥' },
+ { href: '/termine', label: 'Termine', icon: '📅' },
+ { href: '/beitraege', label: 'Beiträge', icon: '💶' },
+ { href: '/nachrichten', label: 'Nachrichten', icon: '✉️' },
];
-
- const navItems = $derived(allNavItems.filter(i => !i.adminOnly || isAdmin()));
@@ -90,23 +42,13 @@
href={item.href}
class:active={$page.url.pathname === item.href}
>
-
+ {item.icon}
{item.label}
{/each}
-{#if zeigInstallBanner}
-
-
- Zum Homescreen hinzufügen
- Tippe auf Teilen → Zum Home-Bildschirm für die Vollbild-App
-
-
-
-{/if}
-
diff --git a/app/src/routes/(app)/+page.svelte b/app/src/routes/(app)/+page.svelte
index 1074e92..32a9972 100644
--- a/app/src/routes/(app)/+page.svelte
+++ b/app/src/routes/(app)/+page.svelte
@@ -1,141 +1,72 @@
Übersicht — vereins.haus
-{#if !loading && verein}
-
+Willkommen, {vereinsname}
- {#if termine.length > 0}
-
- Nächste Termine
-
- {#each termine as t (t.id)}
- -
- {t.titel}
- {formatTermin(t)}
-
- {/each}
-
- Alle Termine →
-
- {:else}
- Noch keine Termine geplant.
- {/if}
-{/if}
+
diff --git a/app/src/routes/(app)/beitraege/+page.svelte b/app/src/routes/(app)/beitraege/+page.svelte
index 306fc99..d1494b1 100644
--- a/app/src/routes/(app)/beitraege/+page.svelte
+++ b/app/src/routes/(app)/beitraege/+page.svelte
@@ -1,531 +1,27 @@
Beiträge — vereins.haus
-
+
-{#if !loading && !hatSepa}
-
-{:else if sepaFehlt && !loading}
-
- SEPA-Einzug nicht möglich: Gläubiger-ID, IBAN und BIC des Vereins fehlen noch in den
Einstellungen.
-
-{/if}
-
-{#if loading}
-
Laden…
-{:else if beitraege.length === 0}
-
Noch keine Beitragsarten — lege die erste an!
-{:else}
-
- {#each beitraege as b (b.id)}
- -
-
- {b.name}
- {#if b.beschreibung}
- {b.beschreibung}
- {/if}
-
- {b.betrag.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}
- · {rhythmusLabel[b.rhythmus]}
-
-
-
-
-
-
-
-
- {/each}
-
-{/if}
-
-
-{#if showForm}
-
-
-
{editId ? 'Beitragsart bearbeiten' : 'Neue Beitragsart'}
-
-
-
-
-{/if}
-
-
-{#if sepaFor}
-
-
-
SEPA-Einzug
-
{sepaFor.name} · {sepaFor.betrag.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} · {rhythmusLabel[sepaFor.rhythmus]}
-
-
-
-
- SEPA CORE: mind. 2 Bankarbeitstage Vorlauf
-
-
- {#if sepaLoading}
-
Laden…
- {:else if sepaPreview}
-
-
- Mitglieder mit IBAN
- {sepaPreview.mitglieder.length}
-
- {#if sepaPreview.ohne > 0}
-
- Ohne IBAN (übersprungen)
- {sepaPreview.ohne}
-
- {/if}
-
- Gesamtsumme
- {gesamtbetrag.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}
-
-
-
- {#if sepaPreview.mitglieder.length === 0}
-
Keine aktiven Mitglieder mit IBAN vorhanden.
- {/if}
- {/if}
-
- {#if sepaError}
-
{sepaError}
- {/if}
-
-
-
-
-
-
-
-{/if}
+
SEPA-Beitragseinzug — in Entwicklung
diff --git a/app/src/routes/(app)/einstellungen/+page.svelte b/app/src/routes/(app)/einstellungen/+page.svelte
deleted file mode 100644
index 6ec5012..0000000
--- a/app/src/routes/(app)/einstellungen/+page.svelte
+++ /dev/null
@@ -1,570 +0,0 @@
-
-
-
Einstellungen — vereins.haus
-
-
Einstellungen
-
-{#if loading}
-
Laden…
-{:else}
-
-
- {@const pi = planInfo[plan] ?? planInfo.free}
-
-
-
- {#if limitErreicht}
-
- ⚠ Du hast das Limit von 50 Mitgliedern überschritten. Neue Mitglieder können nicht angelegt werden.
-
- {/if}
-
-
- {#each pi.features as f}
- - ✓ {f}
- {/each}
- {#if istFree}
- - ✗ SEPA-Export Starter
- - ✗ iCal-Kalender-Abo Starter
- {/if}
-
-
- {#if istFree}
-
- Auf Starter upgraden – 7 €/Monat →
-
- {/if}
-
-
-
-
-
-
-
-
-
-
-
-
Import / Export
-
-
-
-
-{/if}
-
-
diff --git a/app/src/routes/(app)/import-export/+page.svelte b/app/src/routes/(app)/import-export/+page.svelte
deleted file mode 100644
index 9fe214e..0000000
--- a/app/src/routes/(app)/import-export/+page.svelte
+++ /dev/null
@@ -1,429 +0,0 @@
-
-
-
Import / Export — vereins.haus
-
-
-
-{#if loading}
-
Laden…
-{:else}
-
-
-
- Mitglieder exportieren
-
-
-
-
-
- {#if exportStatus}
- ✓ {exportStatus}
- {/if}
-
-
-
- Datensicherung
- Vollständige Sicherungskopie aller Vereinsdaten als JSON – für Archivierung oder Wechsel der Software (DSGVO Art. 20).
-
-
-
-
-
- Mitglieder importieren
- CSV-Datei hochladen – die Spalten werden automatisch erkannt und können vor dem Import angepasst werden.
-
- {#if importPhase === 'idle'}
-
-
- {:else if importPhase === 'mapping'}
-
-
-
-
- {#each csvHeaders as h}
-
- {h}
-
- {csvRows[0]?.[h] ?? ''}
-
- {/each}
-
-
-
-
Vor- und Nachname sind Pflichtfelder.
-
-
-
- {:else if importPhase === 'done'}
- 0}>
-
✓ {importResult.ok} Mitglieder importiert
- {#if importResult.fehler.length > 0}
-
{importResult.fehler.length} Probleme:
-
- {#each importResult.fehler as f}
- - {f}
- {/each}
-
- {/if}
-
-
- {/if}
-
-
-{/if}
-
-
diff --git a/app/src/routes/(app)/mitglieder/+page.svelte b/app/src/routes/(app)/mitglieder/+page.svelte
index 881b71e..21cfe58 100644
--- a/app/src/routes/(app)/mitglieder/+page.svelte
+++ b/app/src/routes/(app)/mitglieder/+page.svelte
@@ -1,174 +1,27 @@
Mitglieder — vereins.haus
-
+
-
-
-{#if loading}
-
Laden…
-{:else if filtered.length === 0}
-
- {search ? 'Keine Treffer.' : 'Noch keine Mitglieder — lege das erste an!'}
-
-{:else}
-
-{/if}
-
-
+
+
Mitgliederverwaltung — in Entwicklung
diff --git a/app/src/routes/(app)/mitglieder/[id]/+page.svelte b/app/src/routes/(app)/mitglieder/[id]/+page.svelte
deleted file mode 100644
index 0d33a1a..0000000
--- a/app/src/routes/(app)/mitglieder/[id]/+page.svelte
+++ /dev/null
@@ -1,503 +0,0 @@
-
-
-
{vorname} {nachname} — vereins.haus
-
-
-
← Mitglieder
- {#if !loading}
-
- {/if}
-
-
-{#if loading}
-
Laden…
-
-{:else if !editMode}
-
-
-
{vorname[0]}{nachname[0]}
-
{vorname} {nachname}
-
{status}
-
-
-
-
Kontakt
- {#if email}
-
- {/if}
- {#if telefon}
-
- {/if}
- {#if strasse || plz || ort}
-
- Adresse
- {[strasse, [plz, ort].filter(Boolean).join(' ')].filter(Boolean).join(', ')}
-
- {/if}
- {#if !email && !telefon && !strasse}
-
Keine Kontaktdaten hinterlegt.
- {/if}
-
-
-
-
Mitgliedschaft
- {#if eintrittsdatum}
-
- Eintritt
- {formatDatum(eintrittsdatum)}
-
- {/if}
- {#if geburtsdatum}
-
- Geburtsdatum
- {formatDatum(geburtsdatum)}
-
- {/if}
- {#if austrittsdatum}
-
- Austritt
- {formatDatum(austrittsdatum)}
-
- {/if}
- {#if gruppe_ids?.length}
-
- Gruppen
- {gruppenName(gruppe_ids)}
-
- {/if}
-
-
- {#if iban || bic || mandatsreferenz}
-
-
SEPA-Lastschrift
- {#if iban}
-
- IBAN
- {iban}
-
- {/if}
- {#if bic}
-
- BIC
- {bic}
-
- {/if}
- {#if mandatsreferenz}
-
- Mandat
- {mandatsreferenz}{mandatsdatum ? ' · ' + formatDatum(mandatsdatum) : ''}
-
- {/if}
-
- {/if}
-
- {#if notizen}
-
- {/if}
-
-
-
-{:else}
-
-
-{/if}
-
-
-{#if showDelete}
-
-
-
{vorname} {nachname} wirklich löschen?
-
Diese Aktion kann nicht rückgängig gemacht werden.
-
-
-
-
-
-
-{/if}
-
-
diff --git a/app/src/routes/(app)/mitglieder/neu/+page.svelte b/app/src/routes/(app)/mitglieder/neu/+page.svelte
deleted file mode 100644
index 7830989..0000000
--- a/app/src/routes/(app)/mitglieder/neu/+page.svelte
+++ /dev/null
@@ -1,257 +0,0 @@
-
-
-
Neues Mitglied — vereins.haus
-
-
-
-
-
-
diff --git a/app/src/routes/(app)/nachrichten/+page.svelte b/app/src/routes/(app)/nachrichten/+page.svelte
index 33a6a34..6956026 100644
--- a/app/src/routes/(app)/nachrichten/+page.svelte
+++ b/app/src/routes/(app)/nachrichten/+page.svelte
@@ -1,330 +1,27 @@
Nachrichten — vereins.haus
-
+
-{#if sendSuccess}
-
{sendSuccess}
-{/if}
-
-{#if loading}
-
Laden…
-{:else if nachrichten.length === 0}
-
Noch keine Nachrichten versendet.
-{:else}
-
-{/if}
-
-
-{#if showForm}
-
-
-
Neue Nachricht
-
-
-
-
-{/if}
+
Nachrichten & Push-Benachrichtigungen — in Entwicklung
diff --git a/app/src/routes/(app)/neuigkeiten/+page.svelte b/app/src/routes/(app)/neuigkeiten/+page.svelte
deleted file mode 100644
index 4ef9432..0000000
--- a/app/src/routes/(app)/neuigkeiten/+page.svelte
+++ /dev/null
@@ -1,476 +0,0 @@
-
-
-
Neuigkeiten — vereins.haus
-
-
-
Neuigkeiten
- {#if canPost()}
-
- {/if}
-
-
-{#if loading}
-
Laden…
-{:else if ladeError}
-
{ladeError}
-{:else if neuigkeiten.length === 0}
-
Noch keine Beiträge – schreib den ersten!
-{:else}
-
- {#each neuigkeiten as n (n.id)}
-
-
-
{autorName(n)[0]?.toUpperCase()}
-
- {autorName(n)}
- {zeitAgo(n.created)}
-
- {#if n.autor_id === userId()}
-
- {/if}
-
-
- {#if n.termin_id}
- 📅 {terminName(n.termin_id)}
- {/if}
-
- {#if n.text}
- {n.text}
- {/if}
-
- {#if n.medien?.length > 0}
-
- {#each n.medien as datei (datei)}
- {#if isVideo(datei)}
-
- {:else}
-
- {/if}
- {/each}
-
- {/if}
-
-
-
- {#if n.gruppe_ids?.length > 0}
-
- {n.gruppe_ids.length === 1
- ? (gruppen.find(g => g.id === n.gruppe_ids[0])?.name ?? '')
- : `${n.gruppe_ids.length} Gruppen`}
-
- {/if}
-
-
- {/each}
-
-{/if}
-
-
-{#if showForm}
-
-
-
Neuer Beitrag
-
-
-
-
-{/if}
-
-
-{#if lightboxUrl}
-
lightboxUrl = ''}
- onkeydown={(e) => e.key === 'Escape' && (lightboxUrl = '')}>
-

-
-{/if}
-
-
diff --git a/app/src/routes/(app)/orte/+page.svelte b/app/src/routes/(app)/orte/+page.svelte
deleted file mode 100644
index 71fcab8..0000000
--- a/app/src/routes/(app)/orte/+page.svelte
+++ /dev/null
@@ -1,345 +0,0 @@
-
-
-
Veranstaltungsorte — vereins.haus
-
-
-
-{#if loading}
-
Laden…
-{:else if orte.length === 0}
-
Noch keine Orte angelegt.
-{:else}
-
-{/if}
-
-
-{#if ausfaelle.length > 0}
-
Geplante Ausfälle
-
-{/if}
-
-
-{#if showOrtForm}
-
-
-
{editOrtId ? 'Ort bearbeiten' : 'Neuer Ort'}
-
-
-
-{/if}
-
-
-{#if showAusfallForm}
-
-
-
Ausfall eintragen
-
{ortName(aOrtId)}
-
-
-
-{/if}
-
-
diff --git a/app/src/routes/(app)/termine/+page.svelte b/app/src/routes/(app)/termine/+page.svelte
index e872980..4f1f5ed 100644
--- a/app/src/routes/(app)/termine/+page.svelte
+++ b/app/src/routes/(app)/termine/+page.svelte
@@ -1,716 +1,27 @@
Termine — vereins.haus
-
+
-{#if isAdmin() && offene.length > 0}
-
- ⚠ {offene.length} {offene.length === 1 ? 'Termin benötigt' : 'Termine benötigen'} eine Bestätigung
-
-{/if}
-
-{#if loading}
-
Laden…
-{:else if ansicht !== 'liste'}
-
-
-
-{:else if termine.length === 0}
-
Noch keine Termine geplant.
-{:else}
- {#if upcoming.length > 0}
-
- {#each upcoming as t (t.id)}
- -
-
- {new Date(t.beginn).toLocaleDateString('de-DE', { weekday: 'short' })}
- {new Date(t.beginn).getDate()}
- {new Date(t.beginn).toLocaleDateString('de-DE', { month: 'short' })}
-
-
-
-
- {t.titel}
- {#if t.serie_id}↻{/if}
-
-
- {formatZeit(t.beginn)}{t.ende ? ' – ' + formatZeit(t.ende) : ''}{t.ort ? ' · ' + t.ort : ''}
-
- {#if t.ort_id || t.ort}
-
{ortNameById(t.ort_id) || t.ort}
- {/if}
- {#if t.gruppe_ids?.length}
-
{gruppenLabel(t.gruppe_ids)}
- {/if}
- {#if ortAusfall(t)}
- {@const af = ortAusfall(t)!}
-
⚠ Ort gesperrt{af.grund ? ': ' + af.grund : ''}
- {/if}
-
- {#if t.durchfuehrender_id}
-
- {#if t.verfuegbarkeit && t.verfuegbarkeit !== 'offen'}
-
- ● {verfuegbarkeitConfig[t.verfuegbarkeit].label}
-
- {:else}
- ● Offen
- {/if}
- {#if isAdmin() && userName(t.durchfuehrender_id)}
- → {userName(t.durchfuehrender_id)}
- {/if}
-
-
- {#if istMeinTermin(t)}
-
-
-
-
-
- {/if}
- {/if}
-
-
- {#if isAdmin()}
-
-
-
-
- {/if}
-
- {/each}
-
- {/if}
-
- {#if vergangen.length > 0}
-
- Vergangene Termine ({vergangen.length})
-
- {#each vergangen as t (t.id)}
- -
-
- {new Date(t.beginn).toLocaleDateString('de-DE', { weekday: 'short' })}
- {new Date(t.beginn).getDate()}
- {new Date(t.beginn).toLocaleDateString('de-DE', { month: 'short' })}
-
-
- {t.titel}
- {formatDatum(t.beginn)}{t.ort ? ' · ' + t.ort : ''}
-
- {#if isAdmin()}
-
-
-
- {/if}
-
- {/each}
-
-
- {/if}
-{/if}
-
-
-{#if showForm && isAdmin()}
-
-
-
{editId ? 'Termin bearbeiten' : 'Neuer Termin'}
-
-
-
-{/if}
-
-
-{#if showDelete}
- {@const t = termine.find(x => x.id === showDelete)!}
-
-
-
Termin löschen?
- {#if t?.serie_id}
-
-
-
-
- {:else}
-
Diese Aktion kann nicht rückgängig gemacht werden.
- {/if}
-
-
-
-
-
-
-{/if}
+
Terminkalender — in Entwicklung
diff --git a/app/src/routes/(auth)/+layout.svelte b/app/src/routes/(auth)/+layout.svelte
index c91c827..b971aac 100644
--- a/app/src/routes/(auth)/+layout.svelte
+++ b/app/src/routes/(auth)/+layout.svelte
@@ -1,13 +1,12 @@
@@ -32,8 +31,8 @@
:global(body) {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
- color: var(--c-text);
- background: var(--c-bg-subtle);
+ color: #1e293b;
+ background: #f8fafc;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
diff --git a/app/src/routes/api/auth/login/+server.ts b/app/src/routes/api/auth/login/+server.ts
deleted file mode 100644
index 88156db..0000000
--- a/app/src/routes/api/auth/login/+server.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { json, error } from '@sveltejs/kit';
-import { getDb } from '$lib/server/db';
-import { checkPassword, signJwt } from '$lib/server/auth';
-
-export async function POST({ request }) {
- const { email, password } = await request.json();
- if (!email || !password) throw error(400, 'E-Mail und Passwort erforderlich');
-
- const db = getDb();
- const u = db.prepare('SELECT * FROM users WHERE email = ?').get(email.toLowerCase()) as any;
- if (!u || !(await checkPassword(password, u.password_hash))) throw error(401, 'Ungültige Zugangsdaten');
-
- const token = await signJwt({
- sub: u.id, verein_id: u.verein_id, rolle: u.rolle, name: u.name, email: u.email
- });
-
- return json({ token, id: u.id, verein_id: u.verein_id, rolle: u.rolle, name: u.name, email: u.email });
-}
diff --git a/app/src/routes/api/auth/me/+server.ts b/app/src/routes/api/auth/me/+server.ts
deleted file mode 100644
index 5071e53..0000000
--- a/app/src/routes/api/auth/me/+server.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { json } from '@sveltejs/kit';
-import { requireAuth } from '$lib/server/auth';
-import { getDb } from '$lib/server/db';
-
-export async function GET({ request }) {
- const u = await requireAuth(request);
- const db = getDb();
- const row = db.prepare('SELECT id, verein_id, email, name, rolle FROM users WHERE id = ?').get(u.sub) as any;
- if (!row) return new Response(null, { status: 401 });
- return json(row);
-}
diff --git a/app/src/routes/api/auth/register/+server.ts b/app/src/routes/api/auth/register/+server.ts
deleted file mode 100644
index b82508e..0000000
--- a/app/src/routes/api/auth/register/+server.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { json, error } from '@sveltejs/kit';
-import { getDb, newId } from '$lib/server/db';
-import { hashPassword, signJwt } from '$lib/server/auth';
-
-export async function POST({ request }) {
- const { vereinName, email, password, name } = await request.json();
- if (!vereinName || !email || !password) throw error(400, 'Pflichtfelder fehlen');
- if (password.length < 8) throw error(400, 'Passwort mindestens 8 Zeichen');
-
- const db = getDb();
- const existing = db.prepare('SELECT id FROM users WHERE email = ?').get(email.toLowerCase());
- if (existing) throw error(409, 'E-Mail bereits registriert');
-
- const vereinId = newId();
- const userId = newId();
- const hash = await hashPassword(password);
-
- db.prepare('INSERT INTO vereine (id, name) VALUES (?, ?)').run(vereinId, vereinName);
- db.prepare('INSERT INTO users (id, verein_id, email, password_hash, name, rolle) VALUES (?, ?, ?, ?, ?, NULL)')
- .run(userId, vereinId, email.toLowerCase(), hash, name || email.split('@')[0]);
-
- const token = await signJwt({ sub: userId, verein_id: vereinId, rolle: null, name: name || email.split('@')[0], email: email.toLowerCase() });
- return json({ token, id: userId, verein_id: vereinId, rolle: null, name: name || email.split('@')[0], email: email.toLowerCase() }, { status: 201 });
-}
diff --git a/app/src/routes/api/beitraege/+server.ts b/app/src/routes/api/beitraege/+server.ts
deleted file mode 100644
index e894c5d..0000000
--- a/app/src/routes/api/beitraege/+server.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { json, error } from '@sveltejs/kit';
-import { getDb, newId, rows, row } from '$lib/server/db';
-import { requireAuth } from '$lib/server/auth';
-
-export async function GET({ request }) {
- const u = await requireAuth(request);
- const db = getDb();
- const items = db.prepare(
- 'SELECT * FROM beitraege WHERE verein_id = ? ORDER BY name'
- ).all(u.verein_id);
- return json(rows(items as Record
[]));
-}
-
-export async function POST({ request }) {
- const u = await requireAuth(request);
- const db = getDb();
- const body = await request.json();
-
- if (!body.name || body.betrag == null) throw error(400, 'Name und Betrag sind Pflichtfelder');
-
- const id = newId();
-
- db.prepare(`
- INSERT INTO beitraege (id, verein_id, name, betrag, rhythmus, beschreibung)
- VALUES (?, ?, ?, ?, ?, ?)
- `).run(
- id,
- u.verein_id,
- body.name,
- body.betrag,
- body.rhythmus ?? 'jaehrlich',
- body.beschreibung ?? null
- );
-
- const beitrag = db.prepare('SELECT * FROM beitraege WHERE id = ?').get(id);
- return json(row(beitrag as Record), { status: 201 });
-}
diff --git a/app/src/routes/api/beitraege/[id]/+server.ts b/app/src/routes/api/beitraege/[id]/+server.ts
deleted file mode 100644
index 43b57da..0000000
--- a/app/src/routes/api/beitraege/[id]/+server.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { json, error } from '@sveltejs/kit';
-import { getDb, row } from '$lib/server/db';
-import { requireAuth } from '$lib/server/auth';
-
-export async function GET({ request, params }) {
- const u = await requireAuth(request);
- const db = getDb();
- const beitrag = db.prepare(
- 'SELECT * FROM beitraege WHERE id = ? AND verein_id = ?'
- ).get(params.id, u.verein_id);
- if (!beitrag) throw error(404, 'Beitrag nicht gefunden');
- return json(row(beitrag as Record));
-}
-
-export async function PUT({ request, params }) {
- const u = await requireAuth(request);
- const db = getDb();
-
- const existing = db.prepare(
- 'SELECT id FROM beitraege WHERE id = ? AND verein_id = ?'
- ).get(params.id, u.verein_id);
- if (!existing) throw error(404, 'Beitrag nicht gefunden');
-
- const body = await request.json();
-
- db.prepare(`
- UPDATE beitraege SET
- name = ?, betrag = ?, rhythmus = ?, beschreibung = ?,
- updated = strftime('%Y-%m-%dT%H:%M:%SZ','now')
- WHERE id = ? AND verein_id = ?
- `).run(
- body.name,
- body.betrag,
- body.rhythmus ?? 'jaehrlich',
- body.beschreibung ?? null,
- params.id,
- u.verein_id
- );
-
- const beitrag = db.prepare('SELECT * FROM beitraege WHERE id = ?').get(params.id);
- return json(row(beitrag as Record));
-}
-
-export async function DELETE({ request, params }) {
- const u = await requireAuth(request);
- const db = getDb();
- const result = db.prepare(
- 'DELETE FROM beitraege WHERE id = ? AND verein_id = ?'
- ).run(params.id, u.verein_id);
- if (result.changes === 0) throw error(404, 'Beitrag nicht gefunden');
- return new Response(null, { status: 204 });
-}
diff --git a/app/src/routes/api/einladungen/+server.ts b/app/src/routes/api/einladungen/+server.ts
deleted file mode 100644
index bfbcce3..0000000
--- a/app/src/routes/api/einladungen/+server.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { json, error } from '@sveltejs/kit';
-import { getDb, newId, row } from '$lib/server/db';
-import { requireAuth } from '$lib/server/auth';
-
-export async function POST({ request }) {
- const u = await requireAuth(request);
- const db = getDb();
- const body = await request.json();
-
- const id = newId();
-
- db.prepare(`
- INSERT INTO einladungen (id, verein_id, rolle)
- VALUES (?, ?, ?)
- `).run(
- id,
- u.verein_id,
- body.rolle ?? 'trainer'
- );
-
- const einladung = db.prepare('SELECT * FROM einladungen WHERE id = ?').get(id);
- return json(row(einladung as Record), { status: 201 });
-}
diff --git a/app/src/routes/api/einladungen/[token]/+server.ts b/app/src/routes/api/einladungen/[token]/+server.ts
deleted file mode 100644
index 9631d8c..0000000
--- a/app/src/routes/api/einladungen/[token]/+server.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import { json, error } from '@sveltejs/kit';
-import { getDb, newId, row } from '$lib/server/db';
-import { hashPassword, signJwt } from '$lib/server/auth';
-
-export async function GET({ params }) {
- const db = getDb();
-
- const einladung = db.prepare(`
- SELECT e.*, v.name as vereinName
- FROM einladungen e JOIN vereine v ON v.id = e.verein_id
- WHERE e.token = ? AND e.genutzt = 0
- `).get(params.token);
-
- if (!einladung) throw error(404, 'Einladung nicht gefunden oder bereits verwendet');
-
- return json(row(einladung as Record));
-}
-
-export async function POST({ request, params }) {
- const db = getDb();
- const body = await request.json();
-
- if (!body.email || !body.password || !body.name) throw error(400, 'E-Mail, Passwort und Name sind Pflichtfelder');
- if (body.password.length < 8) throw error(400, 'Passwort mindestens 8 Zeichen');
-
- const einladung = db.prepare(
- 'SELECT * FROM einladungen WHERE token = ? AND genutzt = 0'
- ).get(params.token) as Record | undefined;
-
- if (!einladung) throw error(404, 'Einladung nicht gefunden oder bereits verwendet');
-
- const verein_id = einladung.verein_id as string;
- const rolle = einladung.rolle as string;
-
- const existing = db.prepare('SELECT id FROM users WHERE email = ?').get(body.email.toLowerCase());
- if (existing) throw error(409, 'E-Mail bereits registriert');
-
- const userId = newId();
- const hash = await hashPassword(body.password);
-
- db.prepare(`
- INSERT INTO users (id, verein_id, email, password_hash, name, rolle)
- VALUES (?, ?, ?, ?, ?, ?)
- `).run(userId, verein_id, body.email.toLowerCase(), hash, body.name, rolle);
-
- db.prepare(
- `UPDATE einladungen SET genutzt = 1, updated = strftime('%Y-%m-%dT%H:%M:%SZ','now') WHERE token = ?`
- ).run(params.token);
-
- const token = await signJwt({
- sub: userId,
- verein_id,
- rolle,
- name: body.name,
- email: body.email.toLowerCase()
- });
-
- return json(
- { token, id: userId, verein_id, rolle, name: body.name, email: body.email.toLowerCase() },
- { status: 201 }
- );
-}
diff --git a/app/src/routes/api/einzuege/+server.ts b/app/src/routes/api/einzuege/+server.ts
deleted file mode 100644
index 9acc51e..0000000
--- a/app/src/routes/api/einzuege/+server.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { json, error } from '@sveltejs/kit';
-import { getDb, newId, rows } from '$lib/server/db';
-import { requireAuth } from '$lib/server/auth';
-
-export async function GET({ request }) {
- const u = await requireAuth(request);
- const db = getDb();
- const items = db.prepare(`
- SELECT e.*, m.vorname, m.nachname, b.name as beitrag_name
- FROM einzuege e
- JOIN mitglieder m ON m.id = e.mitglied_id
- JOIN beitraege b ON b.id = e.beitrag_id
- WHERE e.verein_id = ?
- ORDER BY e.faellig_am
- `).all(u.verein_id);
- return json(rows(items as Record[]));
-}
-
-export async function POST({ request }) {
- const u = await requireAuth(request);
- const db = getDb();
- const body = await request.json();
-
- const einzuege = Array.isArray(body) ? body : [body];
- if (einzuege.length === 0) throw error(400, 'Keine Einzüge angegeben');
-
- const insert = db.prepare(`
- INSERT INTO einzuege (id, verein_id, mitglied_id, beitrag_id, betrag, faellig_am, status)
- VALUES (?, ?, ?, ?, ?, ?, ?)
- `);
-
- const insertMany = db.transaction((items: typeof einzuege) => {
- for (const e of items) {
- insert.run(
- newId(),
- u.verein_id,
- e.mitglied_id,
- e.beitrag_id,
- e.betrag,
- e.faellig_am ?? null,
- e.status ?? 'ausstehend'
- );
- }
- });
-
- insertMany(einzuege);
-
- const created = db.prepare(`
- SELECT e.*, m.vorname, m.nachname, b.name as beitrag_name
- FROM einzuege e
- JOIN mitglieder m ON m.id = e.mitglied_id
- JOIN beitraege b ON b.id = e.beitrag_id
- WHERE e.verein_id = ?
- ORDER BY e.faellig_am
- `).all(u.verein_id);
-
- return json(rows(created as Record[]), { status: 201 });
-}
diff --git a/app/src/routes/api/files/[...path]/+server.ts b/app/src/routes/api/files/[...path]/+server.ts
deleted file mode 100644
index b886015..0000000
--- a/app/src/routes/api/files/[...path]/+server.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { readFileSync, existsSync } from 'fs';
-import { join } from 'path';
-
-const UPLOAD_DIR = process.env.UPLOAD_DIR || './data/uploads';
-
-export async function GET({ params }) {
- const filePath = join(UPLOAD_DIR, params.path);
- if (!existsSync(filePath)) return new Response(null, { status: 404 });
- const data = readFileSync(filePath);
- const ext = filePath.split('.').pop()?.toLowerCase() || '';
- const mime: Record = {
- jpg: 'image/jpeg',
- jpeg: 'image/jpeg',
- png: 'image/png',
- gif: 'image/gif',
- webp: 'image/webp',
- mp4: 'video/mp4',
- mov: 'video/quicktime'
- };
- return new Response(data, {
- headers: {
- 'Content-Type': mime[ext] || 'application/octet-stream',
- 'Cache-Control': 'public, max-age=31536000'
- }
- });
-}
diff --git a/app/src/routes/api/gruppen/+server.ts b/app/src/routes/api/gruppen/+server.ts
deleted file mode 100644
index 033387b..0000000
--- a/app/src/routes/api/gruppen/+server.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { json, error } from '@sveltejs/kit';
-import { getDb, newId, rows, row, toArr } from '$lib/server/db';
-import { requireAuth } from '$lib/server/auth';
-
-export async function GET({ request }) {
- const u = await requireAuth(request);
- const db = getDb();
- const items = db.prepare(
- 'SELECT * FROM gruppen WHERE verein_id = ? ORDER BY name'
- ).all(u.verein_id);
- return json(rows(items as Record[]));
-}
-
-export async function POST({ request }) {
- const u = await requireAuth(request);
- const db = getDb();
- const body = await request.json();
-
- if (!body.name) throw error(400, 'Name ist ein Pflichtfeld');
-
- const id = newId();
-
- db.prepare(`
- INSERT INTO gruppen (id, verein_id, name, beschreibung, trainer_ids)
- VALUES (?, ?, ?, ?, ?)
- `).run(
- id,
- u.verein_id,
- body.name,
- body.beschreibung ?? null,
- toArr(body.trainer_ids)
- );
-
- const gruppe = db.prepare('SELECT * FROM gruppen WHERE id = ?').get(id);
- return json(row(gruppe as Record), { status: 201 });
-}
diff --git a/app/src/routes/api/gruppen/[id]/+server.ts b/app/src/routes/api/gruppen/[id]/+server.ts
deleted file mode 100644
index 799fa9c..0000000
--- a/app/src/routes/api/gruppen/[id]/+server.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { json, error } from '@sveltejs/kit';
-import { getDb, row, toArr } from '$lib/server/db';
-import { requireAuth } from '$lib/server/auth';
-
-export async function GET({ request, params }) {
- const u = await requireAuth(request);
- const db = getDb();
- const gruppe = db.prepare(
- 'SELECT * FROM gruppen WHERE id = ? AND verein_id = ?'
- ).get(params.id, u.verein_id);
- if (!gruppe) throw error(404, 'Gruppe nicht gefunden');
- return json(row(gruppe as Record));
-}
-
-export async function PUT({ request, params }) {
- const u = await requireAuth(request);
- const db = getDb();
-
- const existing = db.prepare(
- 'SELECT id FROM gruppen WHERE id = ? AND verein_id = ?'
- ).get(params.id, u.verein_id);
- if (!existing) throw error(404, 'Gruppe nicht gefunden');
-
- const body = await request.json();
-
- db.prepare(`
- UPDATE gruppen SET
- name = ?, beschreibung = ?, trainer_ids = ?,
- updated = strftime('%Y-%m-%dT%H:%M:%SZ','now')
- WHERE id = ? AND verein_id = ?
- `).run(
- body.name,
- body.beschreibung ?? null,
- toArr(body.trainer_ids),
- params.id,
- u.verein_id
- );
-
- const gruppe = db.prepare('SELECT * FROM gruppen WHERE id = ?').get(params.id);
- return json(row(gruppe as Record));
-}
-
-export async function DELETE({ request, params }) {
- const u = await requireAuth(request);
- const db = getDb();
- const result = db.prepare(
- 'DELETE FROM gruppen WHERE id = ? AND verein_id = ?'
- ).run(params.id, u.verein_id);
- if (result.changes === 0) throw error(404, 'Gruppe nicht gefunden');
- return new Response(null, { status: 204 });
-}
diff --git a/app/src/routes/api/kalender/[vereinId]/+server.ts b/app/src/routes/api/kalender/[vereinId]/+server.ts
deleted file mode 100644
index 9ab614e..0000000
--- a/app/src/routes/api/kalender/[vereinId]/+server.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import ical from 'ical-generator';
-import { getDb } from '$lib/server/db';
-
-export async function GET({ params }) {
- const { vereinId } = params;
-
- const db = getDb();
-
- const verein = db.prepare('SELECT * FROM vereine WHERE id = ?').get(vereinId) as { name: string } | undefined;
- if (!verein) {
- return new Response('Verein nicht gefunden.', { status: 404 });
- }
-
- // Termine der nächsten 365 Tage laden
- const von = new Date().toISOString();
- const bis = new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString();
-
- const termine = db.prepare(
- `SELECT * FROM termine WHERE verein_id = ? AND beginn >= ? AND beginn <= ? ORDER BY beginn LIMIT 500`
- ).all(vereinId, von, bis) as { id: string; titel: string; beginn: string; ende: string | null; ort: string | null; beschreibung: string | null }[];
-
- // iCal-Kalender aufbauen
- const cal = ical({
- name: verein.name ?? 'vereins.haus',
- prodId: '//vereins.haus//Vereinskalender//DE',
- timezone: 'Europe/Berlin',
- ttl: 60 * 60, // Clients aktualisieren stündlich
- });
-
- for (const t of termine) {
- const start = new Date(t.beginn);
- const end = t.ende
- ? new Date(t.ende)
- : new Date(start.getTime() + 60 * 60 * 1000); // Default: 1 Stunde
-
- const ev = cal.createEvent({
- start,
- end,
- summary: t.titel,
- location: t.ort ?? undefined,
- description: t.beschreibung ?? undefined,
- });
- ev.uid(t.id + '@vereins.haus');
- }
-
- return new Response(cal.toString(), {
- headers: {
- 'Content-Type': 'text/calendar; charset=utf-8',
- 'Content-Disposition': `attachment; filename="${encodeURIComponent(verein.name ?? 'kalender')}.ics"`,
- 'Cache-Control': 'no-cache',
- },
- });
-}
diff --git a/app/src/routes/api/mitglieder/+server.ts b/app/src/routes/api/mitglieder/+server.ts
deleted file mode 100644
index 1037048..0000000
--- a/app/src/routes/api/mitglieder/+server.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import { json, error } from '@sveltejs/kit';
-import { getDb, newId, rows, row, toArr } from '$lib/server/db';
-import { requireAuth } from '$lib/server/auth';
-
-export async function GET({ request, url }) {
- const u = await requireAuth(request);
- const db = getDb();
- const status = url.searchParams.get('status');
-
- let items;
- if (status) {
- items = db.prepare(
- 'SELECT * FROM mitglieder WHERE verein_id = ? AND status = ? ORDER BY nachname, vorname'
- ).all(u.verein_id, status);
- } else {
- items = db.prepare(
- 'SELECT * FROM mitglieder WHERE verein_id = ? ORDER BY nachname, vorname'
- ).all(u.verein_id);
- }
-
- return json(rows(items as Record[]));
-}
-
-export async function POST({ request }) {
- const u = await requireAuth(request);
- const db = getDb();
- const body = await request.json();
-
- if (!body.vorname || !body.nachname) throw error(400, 'Vorname und Nachname sind Pflichtfelder');
-
- const id = newId();
-
- db.prepare(`
- INSERT INTO mitglieder (
- id, verein_id, vorname, nachname, email, telefon,
- geburtsdatum, eintrittsdatum, austrittsdatum,
- strasse, plz, ort, iban, bic,
- gruppe_ids, status, notizen,
- mandatsreferenz, mandatsdatum
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
- `).run(
- id,
- u.verein_id,
- body.vorname,
- body.nachname,
- body.email ?? null,
- body.telefon ?? null,
- body.geburtsdatum ?? null,
- body.eintrittsdatum ?? null,
- body.austrittsdatum ?? null,
- body.strasse ?? null,
- body.plz ?? null,
- body.ort ?? null,
- body.iban ?? null,
- body.bic ?? null,
- toArr(body.gruppe_ids),
- body.status ?? 'aktiv',
- body.notizen ?? null,
- body.mandatsreferenz ?? null,
- body.mandatsdatum ?? null
- );
-
- const mitglied = db.prepare('SELECT * FROM mitglieder WHERE id = ?').get(id);
- return json(row(mitglied as Record), { status: 201 });
-}
diff --git a/app/src/routes/api/mitglieder/[id]/+server.ts b/app/src/routes/api/mitglieder/[id]/+server.ts
deleted file mode 100644
index 769b33b..0000000
--- a/app/src/routes/api/mitglieder/[id]/+server.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import { json, error } from '@sveltejs/kit';
-import { getDb, row, toArr } from '$lib/server/db';
-import { requireAuth } from '$lib/server/auth';
-
-export async function GET({ request, params }) {
- const u = await requireAuth(request);
- const db = getDb();
- const mitglied = db.prepare(
- 'SELECT * FROM mitglieder WHERE id = ? AND verein_id = ?'
- ).get(params.id, u.verein_id);
- if (!mitglied) throw error(404, 'Mitglied nicht gefunden');
- return json(row(mitglied as Record));
-}
-
-export async function PUT({ request, params }) {
- const u = await requireAuth(request);
- const db = getDb();
-
- const existing = db.prepare(
- 'SELECT id FROM mitglieder WHERE id = ? AND verein_id = ?'
- ).get(params.id, u.verein_id);
- if (!existing) throw error(404, 'Mitglied nicht gefunden');
-
- const body = await request.json();
-
- db.prepare(`
- UPDATE mitglieder SET
- vorname = ?, nachname = ?, email = ?, telefon = ?,
- geburtsdatum = ?, eintrittsdatum = ?, austrittsdatum = ?,
- strasse = ?, plz = ?, ort = ?, iban = ?, bic = ?,
- gruppe_ids = ?, status = ?, notizen = ?,
- mandatsreferenz = ?, mandatsdatum = ?,
- updated = strftime('%Y-%m-%dT%H:%M:%SZ','now')
- WHERE id = ? AND verein_id = ?
- `).run(
- body.vorname,
- body.nachname,
- body.email ?? null,
- body.telefon ?? null,
- body.geburtsdatum ?? null,
- body.eintrittsdatum ?? null,
- body.austrittsdatum ?? null,
- body.strasse ?? null,
- body.plz ?? null,
- body.ort ?? null,
- body.iban ?? null,
- body.bic ?? null,
- toArr(body.gruppe_ids),
- body.status ?? 'aktiv',
- body.notizen ?? null,
- body.mandatsreferenz ?? null,
- body.mandatsdatum ?? null,
- params.id,
- u.verein_id
- );
-
- const mitglied = db.prepare('SELECT * FROM mitglieder WHERE id = ?').get(params.id);
- return json(row(mitglied as Record));
-}
-
-export async function DELETE({ request, params }) {
- const u = await requireAuth(request);
- const db = getDb();
- const result = db.prepare(
- 'DELETE FROM mitglieder WHERE id = ? AND verein_id = ?'
- ).run(params.id, u.verein_id);
- if (result.changes === 0) throw error(404, 'Mitglied nicht gefunden');
- return new Response(null, { status: 204 });
-}
diff --git a/app/src/routes/api/nachrichten/+server.ts b/app/src/routes/api/nachrichten/+server.ts
deleted file mode 100644
index f4e63a0..0000000
--- a/app/src/routes/api/nachrichten/+server.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { json, error } from '@sveltejs/kit';
-import { getDb, newId, rows, row, toArr } from '$lib/server/db';
-import { requireAuth } from '$lib/server/auth';
-
-export async function GET({ request }) {
- const u = await requireAuth(request);
- const db = getDb();
- const items = db.prepare(
- 'SELECT * FROM nachrichten WHERE verein_id = ? ORDER BY gesendet_am DESC, created DESC'
- ).all(u.verein_id);
- return json(rows(items as Record[]));
-}
-
-export async function POST({ request }) {
- const u = await requireAuth(request);
- const db = getDb();
- const body = await request.json();
-
- if (!body.betreff) throw error(400, 'Betreff ist ein Pflichtfeld');
-
- const id = newId();
- const gesendet_am = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
-
- db.prepare(`
- INSERT INTO nachrichten (id, verein_id, autor_id, betreff, text, gruppe_ids, gesendet_am)
- VALUES (?, ?, ?, ?, ?, ?, ?)
- `).run(
- id,
- u.verein_id,
- u.sub,
- body.betreff,
- body.text ?? '',
- toArr(body.gruppe_ids),
- gesendet_am
- );
-
- const nachricht = db.prepare('SELECT * FROM nachrichten WHERE id = ?').get(id);
- return json(row(nachricht as Record), { status: 201 });
-}
diff --git a/app/src/routes/api/nachrichten/[id]/+server.ts b/app/src/routes/api/nachrichten/[id]/+server.ts
deleted file mode 100644
index 40f1678..0000000
--- a/app/src/routes/api/nachrichten/[id]/+server.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { error } from '@sveltejs/kit';
-import { getDb } from '$lib/server/db';
-import { requireAuth } from '$lib/server/auth';
-
-export async function DELETE({ request, params }) {
- const u = await requireAuth(request);
- const db = getDb();
- const result = db.prepare(
- 'DELETE FROM nachrichten WHERE id = ? AND verein_id = ?'
- ).run(params.id, u.verein_id);
- if (result.changes === 0) throw error(404, 'Nachricht nicht gefunden');
- return new Response(null, { status: 204 });
-}
diff --git a/app/src/routes/api/neuigkeiten/+server.ts b/app/src/routes/api/neuigkeiten/+server.ts
deleted file mode 100644
index 9d00bc2..0000000
--- a/app/src/routes/api/neuigkeiten/+server.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import { json, error } from '@sveltejs/kit';
-import { getDb, newId, rows, row } from '$lib/server/db';
-import { requireAuth } from '$lib/server/auth';
-import { writeFileSync, mkdirSync } from 'fs';
-import { join } from 'path';
-
-const UPLOAD_DIR = process.env.UPLOAD_DIR || './data/uploads';
-
-export async function GET({ request }) {
- const u = await requireAuth(request);
- const db = getDb();
- const items = db.prepare(
- 'SELECT * FROM neuigkeiten WHERE verein_id = ? ORDER BY created DESC'
- ).all(u.verein_id);
- return json(rows(items as Record[]));
-}
-
-export async function POST({ request }) {
- const u = await requireAuth(request);
- const db = getDb();
-
- const formData = await request.formData();
-
- const text = formData.get('text') as string | null;
- const gruppeIdsRaw = formData.get('gruppe_ids') as string | null;
- const terminId = formData.get('termin_id') as string | null;
-
- let gruppe_ids: string[] = [];
- if (gruppeIdsRaw) {
- try { gruppe_ids = JSON.parse(gruppeIdsRaw); } catch { gruppe_ids = []; }
- }
-
- const id = newId();
- const uploadPath = join(UPLOAD_DIR, u.verein_id, id);
- const medien: string[] = [];
-
- const files = formData.getAll('medien') as File[];
- if (files.length > 0) {
- mkdirSync(uploadPath, { recursive: true });
- for (const file of files) {
- if (!(file instanceof File)) continue;
- const ext = file.name.split('.').pop() || 'bin';
- const filename = `${newId()}.${ext}`;
- const buffer = Buffer.from(await file.arrayBuffer());
- writeFileSync(join(uploadPath, filename), buffer);
- medien.push(filename);
- }
- }
-
- if (!text && medien.length === 0) throw error(400, 'Text oder Medien sind erforderlich');
-
- db.prepare(`
- INSERT INTO neuigkeiten (id, verein_id, autor_id, autor_name, text, medien, gruppe_ids, termin_id)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
- `).run(
- id,
- u.verein_id,
- u.sub,
- u.name,
- text ?? null,
- JSON.stringify(medien),
- JSON.stringify(gruppe_ids),
- terminId ?? null
- );
-
- const neuigkeit = db.prepare('SELECT * FROM neuigkeiten WHERE id = ?').get(id);
- return json(row(neuigkeit as Record), { status: 201 });
-}
diff --git a/app/src/routes/api/neuigkeiten/[id]/+server.ts b/app/src/routes/api/neuigkeiten/[id]/+server.ts
deleted file mode 100644
index c390a91..0000000
--- a/app/src/routes/api/neuigkeiten/[id]/+server.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { error } from '@sveltejs/kit';
-import { getDb, parseArr } from '$lib/server/db';
-import { requireAuth } from '$lib/server/auth';
-import { rmSync, existsSync } from 'fs';
-import { join } from 'path';
-
-const UPLOAD_DIR = process.env.UPLOAD_DIR || './data/uploads';
-
-export async function DELETE({ request, params }) {
- const u = await requireAuth(request);
- const db = getDb();
-
- const neuigkeit = db.prepare(
- 'SELECT * FROM neuigkeiten WHERE id = ? AND verein_id = ?'
- ).get(params.id, u.verein_id) as Record | undefined;
-
- if (!neuigkeit) throw error(404, 'Neuigkeit nicht gefunden');
-
- const uploadPath = join(UPLOAD_DIR, u.verein_id, params.id);
- if (existsSync(uploadPath)) {
- rmSync(uploadPath, { recursive: true, force: true });
- }
-
- db.prepare('DELETE FROM neuigkeiten WHERE id = ? AND verein_id = ?').run(params.id, u.verein_id);
-
- return new Response(null, { status: 204 });
-}
diff --git a/app/src/routes/api/ort-ausfaelle/+server.ts b/app/src/routes/api/ort-ausfaelle/+server.ts
deleted file mode 100644
index 1c7ec83..0000000
--- a/app/src/routes/api/ort-ausfaelle/+server.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { json, error } from '@sveltejs/kit';
-import { getDb, newId, rows, row } from '$lib/server/db';
-import { requireAuth } from '$lib/server/auth';
-
-export async function GET({ request, url }) {
- const u = await requireAuth(request);
- const db = getDb();
-
- const ort_id = url.searchParams.get('ort_id');
-
- let items;
- if (ort_id) {
- items = db.prepare(`
- SELECT a.* FROM ort_ausfaelle a
- JOIN veranstaltungsorte o ON o.id = a.ort_id
- WHERE a.ort_id = ? AND o.verein_id = ?
- ORDER BY a.von
- `).all(ort_id, u.verein_id);
- } else {
- items = db.prepare(`
- SELECT a.* FROM ort_ausfaelle a
- JOIN veranstaltungsorte o ON o.id = a.ort_id
- WHERE o.verein_id = ?
- ORDER BY a.von
- `).all(u.verein_id);
- }
-
- return json(rows(items as Record[]));
-}
-
-export async function POST({ request }) {
- const u = await requireAuth(request);
- const db = getDb();
- const body = await request.json();
-
- if (!body.ort_id || !body.von || !body.bis) throw error(400, 'ort_id, von und bis sind Pflichtfelder');
-
- const ort = db.prepare(
- 'SELECT id FROM veranstaltungsorte WHERE id = ? AND verein_id = ?'
- ).get(body.ort_id, u.verein_id);
- if (!ort) throw error(404, 'Ort nicht gefunden');
-
- const id = newId();
-
- db.prepare(`
- INSERT INTO ort_ausfaelle (id, ort_id, von, bis, grund)
- VALUES (?, ?, ?, ?, ?)
- `).run(
- id,
- body.ort_id,
- body.von,
- body.bis,
- body.grund ?? null
- );
-
- const ausfall = db.prepare('SELECT * FROM ort_ausfaelle WHERE id = ?').get(id);
- return json(row(ausfall as Record), { status: 201 });
-}
diff --git a/app/src/routes/api/ort-ausfaelle/[id]/+server.ts b/app/src/routes/api/ort-ausfaelle/[id]/+server.ts
deleted file mode 100644
index df7e2f4..0000000
--- a/app/src/routes/api/ort-ausfaelle/[id]/+server.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { error } from '@sveltejs/kit';
-import { getDb } from '$lib/server/db';
-import { requireAuth } from '$lib/server/auth';
-
-export async function DELETE({ request, params }) {
- const u = await requireAuth(request);
- const db = getDb();
-
- // Verify the ausfall belongs to an ort in the user's Verein
- const ausfall = db.prepare(`
- SELECT a.id FROM ort_ausfaelle a
- JOIN veranstaltungsorte o ON o.id = a.ort_id
- WHERE a.id = ? AND o.verein_id = ?
- `).get(params.id, u.verein_id);
-
- if (!ausfall) throw error(404, 'Ausfall nicht gefunden');
-
- db.prepare('DELETE FROM ort_ausfaelle WHERE id = ?').run(params.id);
-
- return new Response(null, { status: 204 });
-}
diff --git a/app/src/routes/api/orte/+server.ts b/app/src/routes/api/orte/+server.ts
deleted file mode 100644
index 73d7452..0000000
--- a/app/src/routes/api/orte/+server.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { json, error } from '@sveltejs/kit';
-import { getDb, newId, rows, row } from '$lib/server/db';
-import { requireAuth } from '$lib/server/auth';
-
-export async function GET({ request }) {
- const u = await requireAuth(request);
- const db = getDb();
- const items = db.prepare(
- 'SELECT * FROM veranstaltungsorte WHERE verein_id = ? ORDER BY name'
- ).all(u.verein_id);
- return json(rows(items as Record[]));
-}
-
-export async function POST({ request }) {
- const u = await requireAuth(request);
- const db = getDb();
- const body = await request.json();
-
- if (!body.name) throw error(400, 'Name ist ein Pflichtfeld');
-
- const id = newId();
-
- db.prepare(`
- INSERT INTO veranstaltungsorte (id, verein_id, name, adresse, typ, aktiv)
- VALUES (?, ?, ?, ?, ?, ?)
- `).run(
- id,
- u.verein_id,
- body.name,
- body.adresse ?? null,
- body.typ ?? 'sonstiges',
- body.aktiv !== false ? 1 : 0
- );
-
- const ort = db.prepare('SELECT * FROM veranstaltungsorte WHERE id = ?').get(id);
- return json(row(ort as Record), { status: 201 });
-}
diff --git a/app/src/routes/api/orte/[id]/+server.ts b/app/src/routes/api/orte/[id]/+server.ts
deleted file mode 100644
index 25d18b9..0000000
--- a/app/src/routes/api/orte/[id]/+server.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { json, error } from '@sveltejs/kit';
-import { getDb, row } from '$lib/server/db';
-import { requireAuth } from '$lib/server/auth';
-
-export async function GET({ request, params }) {
- const u = await requireAuth(request);
- const db = getDb();
- const ort = db.prepare(
- 'SELECT * FROM veranstaltungsorte WHERE id = ? AND verein_id = ?'
- ).get(params.id, u.verein_id);
- if (!ort) throw error(404, 'Ort nicht gefunden');
- return json(row(ort as Record));
-}
-
-export async function PUT({ request, params }) {
- const u = await requireAuth(request);
- const db = getDb();
-
- const existing = db.prepare(
- 'SELECT id FROM veranstaltungsorte WHERE id = ? AND verein_id = ?'
- ).get(params.id, u.verein_id);
- if (!existing) throw error(404, 'Ort nicht gefunden');
-
- const body = await request.json();
-
- db.prepare(`
- UPDATE veranstaltungsorte SET
- name = ?, adresse = ?, typ = ?, aktiv = ?,
- updated = strftime('%Y-%m-%dT%H:%M:%SZ','now')
- WHERE id = ? AND verein_id = ?
- `).run(
- body.name,
- body.adresse ?? null,
- body.typ ?? 'sonstiges',
- body.aktiv !== false ? 1 : 0,
- params.id,
- u.verein_id
- );
-
- const ort = db.prepare('SELECT * FROM veranstaltungsorte WHERE id = ?').get(params.id);
- return json(row(ort as Record));
-}
-
-export async function DELETE({ request, params }) {
- const u = await requireAuth(request);
- const db = getDb();
- const result = db.prepare(
- 'DELETE FROM veranstaltungsorte WHERE id = ? AND verein_id = ?'
- ).run(params.id, u.verein_id);
- if (result.changes === 0) throw error(404, 'Ort nicht gefunden');
- return new Response(null, { status: 204 });
-}
diff --git a/app/src/routes/api/push/key/+server.ts b/app/src/routes/api/push/key/+server.ts
deleted file mode 100644
index 7399d91..0000000
--- a/app/src/routes/api/push/key/+server.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { json } from '@sveltejs/kit';
-import { env } from '$env/dynamic/private';
-
-export async function GET() {
- return json({ publicKey: env.PUBLIC_VAPID_KEY ?? '' });
-}
diff --git a/app/src/routes/api/push/senden/+server.ts b/app/src/routes/api/push/senden/+server.ts
deleted file mode 100644
index 3e0575b..0000000
--- a/app/src/routes/api/push/senden/+server.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { json } from '@sveltejs/kit';
-import { env } from '$env/dynamic/private';
-import webpush from 'web-push';
-import { requireAuth } from '$lib/server/auth';
-import { getDb } from '$lib/server/db';
-
-export async function POST({ request }) {
- const authUser = await requireAuth(request).catch(() => null);
- if (!authUser) return json({ error: 'Nicht authentifiziert.' }, { status: 401 });
-
- const { titel, body, url = '/nachrichten' } = await request.json();
-
- if (!titel) return json({ error: 'Titel fehlt.' }, { status: 400 });
-
- const vapidPublic = env.PUBLIC_VAPID_KEY ?? '';
- const vapidPrivate = env.VAPID_PRIVATE_KEY ?? '';
- const vapidSubject = env.VAPID_SUBJECT ?? 'mailto:info@vereins.haus';
-
- if (!vapidPublic || !vapidPrivate) {
- return json({ error: 'VAPID-Keys nicht konfiguriert.' }, { status: 500 });
- }
-
- webpush.setVapidDetails(vapidSubject, vapidPublic, vapidPrivate);
-
- const db = getDb();
- const items = db.prepare(
- 'SELECT * FROM push_subscriptions WHERE verein_id = ?'
- ).all(authUser.verein_id) as { endpoint: string; p256dh: string; auth: string; id: string }[];
-
- if (!items.length) return json({ sent: 0 });
-
- const payload = JSON.stringify({ title: titel, body, url });
- let sent = 0;
-
- await Promise.allSettled(
- items.map(async (sub) => {
- try {
- await webpush.sendNotification(
- { endpoint: sub.endpoint, keys: { p256dh: sub.p256dh, auth: sub.auth } },
- payload,
- );
- sent++;
- } catch (err: unknown) {
- // 410 Gone = Subscription abgelaufen → löschen
- if ((err as { statusCode?: number }).statusCode === 410) {
- db.prepare('DELETE FROM push_subscriptions WHERE id = ?').run(sub.id);
- }
- }
- }),
- );
-
- return json({ sent });
-}
diff --git a/app/src/routes/api/push/subscribe/+server.ts b/app/src/routes/api/push/subscribe/+server.ts
deleted file mode 100644
index 34b10dc..0000000
--- a/app/src/routes/api/push/subscribe/+server.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { json } from '@sveltejs/kit';
-import { requireAuth } from '$lib/server/auth';
-import { getDb, newId } from '$lib/server/db';
-
-export async function POST({ request }) {
- const authUser = await requireAuth(request).catch(() => null);
- if (!authUser) return json({ error: 'Nicht authentifiziert.' }, { status: 401 });
-
- const { subscription } = await request.json();
-
- if (!subscription?.endpoint) {
- return json({ error: 'Ungültige Subscription.' }, { status: 400 });
- }
-
- const db = getDb();
-
- // Alte Subscriptions dieses Users löschen (Gerätewechsel)
- db.prepare('DELETE FROM push_subscriptions WHERE user_id = ?').run(authUser.sub);
-
- // Neue Subscription speichern
- db.prepare(`
- INSERT INTO push_subscriptions (id, user_id, verein_id, endpoint, p256dh, auth)
- VALUES (?, ?, ?, ?, ?, ?)
- `).run(
- newId(),
- authUser.sub,
- authUser.verein_id,
- subscription.endpoint,
- subscription.keys.p256dh,
- subscription.keys.auth,
- );
-
- return json({ success: true });
-}
-
-export async function DELETE({ request }) {
- const authUser = await requireAuth(request).catch(() => null);
- if (!authUser) return json({ success: true });
-
- const db = getDb();
- db.prepare('DELETE FROM push_subscriptions WHERE user_id = ?').run(authUser.sub);
-
- return json({ success: true });
-}
diff --git a/app/src/routes/api/reaktionen/+server.ts b/app/src/routes/api/reaktionen/+server.ts
deleted file mode 100644
index d48116b..0000000
--- a/app/src/routes/api/reaktionen/+server.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { json, error } from '@sveltejs/kit';
-import { getDb, newId, rows, row } from '$lib/server/db';
-import { requireAuth } from '$lib/server/auth';
-
-export async function GET({ request, url }) {
- const u = await requireAuth(request);
- const db = getDb();
-
- const beitrag_id = url.searchParams.get('beitrag_id');
-
- let items;
- if (beitrag_id) {
- items = db.prepare(`
- SELECT r.* FROM reaktionen r
- JOIN neuigkeiten n ON n.id = r.beitrag_id
- WHERE r.beitrag_id = ? AND n.verein_id = ?
- ORDER BY r.created
- `).all(beitrag_id, u.verein_id);
- } else {
- items = db.prepare(`
- SELECT r.* FROM reaktionen r
- JOIN neuigkeiten n ON n.id = r.beitrag_id
- WHERE n.verein_id = ?
- ORDER BY r.created
- `).all(u.verein_id);
- }
-
- return json(rows(items as Record[]));
-}
-
-export async function POST({ request }) {
- const u = await requireAuth(request);
- const db = getDb();
- const body = await request.json();
-
- if (!body.beitrag_id) throw error(400, 'beitrag_id ist erforderlich');
-
- const beitrag = db.prepare(
- 'SELECT id FROM neuigkeiten WHERE id = ? AND verein_id = ?'
- ).get(body.beitrag_id, u.verein_id);
- if (!beitrag) throw error(404, 'Beitrag nicht gefunden');
-
- const id = newId();
-
- try {
- db.prepare(`
- INSERT INTO reaktionen (id, beitrag_id, user_id)
- VALUES (?, ?, ?)
- `).run(id, body.beitrag_id, u.sub);
- } catch (e: unknown) {
- const msg = e instanceof Error ? e.message : String(e);
- if (msg.includes('UNIQUE')) throw error(409, 'Reaktion bereits vorhanden');
- throw e;
- }
-
- const reaktion = db.prepare('SELECT * FROM reaktionen WHERE id = ?').get(id);
- return json(row(reaktion as Record), { status: 201 });
-}
diff --git a/app/src/routes/api/reaktionen/[id]/+server.ts b/app/src/routes/api/reaktionen/[id]/+server.ts
deleted file mode 100644
index a2ed16d..0000000
--- a/app/src/routes/api/reaktionen/[id]/+server.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { error } from '@sveltejs/kit';
-import { getDb } from '$lib/server/db';
-import { requireAuth } from '$lib/server/auth';
-
-export async function DELETE({ request, params }) {
- const u = await requireAuth(request);
- const db = getDb();
- const result = db.prepare(
- 'DELETE FROM reaktionen WHERE id = ? AND user_id = ?'
- ).run(params.id, u.sub);
- if (result.changes === 0) throw error(404, 'Reaktion nicht gefunden oder keine Berechtigung');
- return new Response(null, { status: 204 });
-}
diff --git a/app/src/routes/api/termine/+server.ts b/app/src/routes/api/termine/+server.ts
deleted file mode 100644
index 86b5543..0000000
--- a/app/src/routes/api/termine/+server.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import { json, error } from '@sveltejs/kit';
-import { getDb, newId, rows, row, toArr } from '$lib/server/db';
-import { requireAuth } from '$lib/server/auth';
-
-export async function GET({ request, url }) {
- const u = await requireAuth(request);
- const db = getDb();
-
- const von = url.searchParams.get('von');
- const bis = url.searchParams.get('bis');
-
- let query = 'SELECT * FROM termine WHERE verein_id = ?';
- const params: unknown[] = [u.verein_id];
-
- if (von) {
- query += ' AND beginn >= ?';
- params.push(von);
- }
- if (bis) {
- query += ' AND beginn <= ?';
- params.push(bis);
- }
-
- query += ' ORDER BY beginn';
-
- const items = db.prepare(query).all(...params);
- return json(rows(items as Record[]));
-}
-
-export async function POST({ request }) {
- const u = await requireAuth(request);
- const db = getDb();
- const body = await request.json();
-
- if (!body.titel || !body.beginn) throw error(400, 'Titel und Beginn sind Pflichtfelder');
-
- const id = newId();
-
- db.prepare(`
- INSERT INTO termine (
- id, verein_id, titel, beschreibung, beginn, ende,
- ort, ort_id, gruppe_ids, durchfuehrender_id,
- verfuegbarkeit, rrule, serie_id
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
- `).run(
- id,
- u.verein_id,
- body.titel,
- body.beschreibung ?? null,
- body.beginn,
- body.ende ?? null,
- body.ort ?? null,
- body.ort_id ?? null,
- toArr(body.gruppe_ids),
- body.durchfuehrender_id ?? null,
- body.verfuegbarkeit ?? 'offen',
- body.rrule ?? null,
- body.serie_id ?? null
- );
-
- const termin = db.prepare('SELECT * FROM termine WHERE id = ?').get(id);
- return json(row(termin as Record), { status: 201 });
-}
diff --git a/app/src/routes/api/termine/[id]/+server.ts b/app/src/routes/api/termine/[id]/+server.ts
deleted file mode 100644
index 0af6d50..0000000
--- a/app/src/routes/api/termine/[id]/+server.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import { json, error } from '@sveltejs/kit';
-import { getDb, row, toArr } from '$lib/server/db';
-import { requireAuth } from '$lib/server/auth';
-
-export async function GET({ request, params }) {
- const u = await requireAuth(request);
- const db = getDb();
- const termin = db.prepare(
- 'SELECT * FROM termine WHERE id = ? AND verein_id = ?'
- ).get(params.id, u.verein_id);
- if (!termin) throw error(404, 'Termin nicht gefunden');
- return json(row(termin as Record));
-}
-
-export async function PUT({ request, params }) {
- const u = await requireAuth(request);
- const db = getDb();
-
- const existing = db.prepare(
- 'SELECT id FROM termine WHERE id = ? AND verein_id = ?'
- ).get(params.id, u.verein_id);
- if (!existing) throw error(404, 'Termin nicht gefunden');
-
- const body = await request.json();
-
- db.prepare(`
- UPDATE termine SET
- titel = ?, beschreibung = ?, beginn = ?, ende = ?,
- ort = ?, ort_id = ?, gruppe_ids = ?, durchfuehrender_id = ?,
- verfuegbarkeit = ?, rrule = ?, serie_id = ?,
- updated = strftime('%Y-%m-%dT%H:%M:%SZ','now')
- WHERE id = ? AND verein_id = ?
- `).run(
- body.titel,
- body.beschreibung ?? null,
- body.beginn,
- body.ende ?? null,
- body.ort ?? null,
- body.ort_id ?? null,
- toArr(body.gruppe_ids),
- body.durchfuehrender_id ?? null,
- body.verfuegbarkeit ?? 'offen',
- body.rrule ?? null,
- body.serie_id ?? null,
- params.id,
- u.verein_id
- );
-
- const termin = db.prepare('SELECT * FROM termine WHERE id = ?').get(params.id);
- return json(row(termin as Record));
-}
-
-export async function DELETE({ request, params, url }) {
- const u = await requireAuth(request);
- const db = getDb();
-
- const deleteSerie = url.searchParams.get('serie') === 'true';
-
- if (deleteSerie) {
- const termin = db.prepare(
- 'SELECT serie_id FROM termine WHERE id = ? AND verein_id = ?'
- ).get(params.id, u.verein_id) as { serie_id: string | null } | undefined;
-
- if (!termin) throw error(404, 'Termin nicht gefunden');
-
- if (termin.serie_id) {
- db.prepare(
- 'DELETE FROM termine WHERE serie_id = ? AND verein_id = ?'
- ).run(termin.serie_id, u.verein_id);
- } else {
- db.prepare(
- 'DELETE FROM termine WHERE id = ? AND verein_id = ?'
- ).run(params.id, u.verein_id);
- }
- } else {
- const result = db.prepare(
- 'DELETE FROM termine WHERE id = ? AND verein_id = ?'
- ).run(params.id, u.verein_id);
- if (result.changes === 0) throw error(404, 'Termin nicht gefunden');
- }
-
- return new Response(null, { status: 204 });
-}
diff --git a/app/src/routes/api/users/+server.ts b/app/src/routes/api/users/+server.ts
deleted file mode 100644
index 4f8e7d4..0000000
--- a/app/src/routes/api/users/+server.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { json } from '@sveltejs/kit';
-import { getDb, rows } from '$lib/server/db';
-import { requireAuth } from '$lib/server/auth';
-
-export async function GET({ request, url }) {
- const u = await requireAuth(request);
- const db = getDb();
- const rolle = url.searchParams.get('rolle');
-
- let query = 'SELECT id, verein_id, email, name, rolle, created FROM users WHERE verein_id = ?';
- const params: unknown[] = [u.verein_id];
-
- if (rolle) { query += ' AND rolle = ?'; params.push(rolle); }
- query += ' ORDER BY name';
-
- const users = db.prepare(query).all(...params);
- return json(users);
-}
diff --git a/app/src/routes/api/users/[id]/+server.ts b/app/src/routes/api/users/[id]/+server.ts
deleted file mode 100644
index 8a18995..0000000
--- a/app/src/routes/api/users/[id]/+server.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { json, error } from '@sveltejs/kit';
-import { getDb } from '$lib/server/db';
-import { requireAuth, hashPassword } from '$lib/server/auth';
-
-export async function GET({ request, params }) {
- const u = await requireAuth(request);
- const db = getDb();
- const row = db.prepare(
- 'SELECT id, verein_id, email, name, rolle, created FROM users WHERE id = ? AND verein_id = ?'
- ).get(params.id, u.verein_id);
- if (!row) throw error(404, 'User nicht gefunden');
- return json(row);
-}
-
-export async function PUT({ request, params }) {
- const u = await requireAuth(request);
- const db = getDb();
- const body = await request.json();
-
- const existing = db.prepare('SELECT id FROM users WHERE id = ? AND verein_id = ?').get(params.id, u.verein_id);
- if (!existing) throw error(404, 'User nicht gefunden');
-
- const fields: string[] = [];
- const vals: unknown[] = [];
-
- if (body.name !== undefined) { fields.push('name = ?'); vals.push(body.name); }
- if (body.email !== undefined) { fields.push('email = ?'); vals.push(body.email.toLowerCase()); }
- if (body.rolle !== undefined) { fields.push('rolle = ?'); vals.push(body.rolle || null); }
- if (body.password) { fields.push('password_hash = ?'); vals.push(await hashPassword(body.password)); }
- if (!fields.length) throw error(400, 'Keine Felder zum Aktualisieren');
-
- fields.push("updated = strftime('%Y-%m-%dT%H:%M:%SZ','now')");
- vals.push(params.id, u.verein_id);
-
- db.prepare(`UPDATE users SET ${fields.join(', ')} WHERE id = ? AND verein_id = ?`).run(...vals);
- const row = db.prepare('SELECT id, verein_id, email, name, rolle, created FROM users WHERE id = ?').get(params.id);
- return json(row);
-}
-
-export async function DELETE({ request, params }) {
- const u = await requireAuth(request);
- if (u.sub === params.id) throw error(400, 'Eigenen Account nicht löschbar');
- const db = getDb();
- const result = db.prepare('DELETE FROM users WHERE id = ? AND verein_id = ?').run(params.id, u.verein_id);
- if (result.changes === 0) throw error(404, 'User nicht gefunden');
- return new Response(null, { status: 204 });
-}
diff --git a/app/src/routes/api/vereine/+server.ts b/app/src/routes/api/vereine/+server.ts
deleted file mode 100644
index 242ab0d..0000000
--- a/app/src/routes/api/vereine/+server.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { json, error } from '@sveltejs/kit';
-import { getDb, row } from '$lib/server/db';
-import { requireAuth } from '$lib/server/auth';
-
-export async function GET({ request }) {
- const u = await requireAuth(request);
- const db = getDb();
- const verein = db.prepare('SELECT * FROM vereine WHERE id = ?').get(u.verein_id);
- if (!verein) throw error(404, 'Verein nicht gefunden');
- return json(row(verein as Record));
-}
-
-export async function PATCH({ request }) {
- const u = await requireAuth(request);
- const db = getDb();
- const body = await request.json();
-
- const allowed = [
- 'name', 'adresse', 'plz', 'ort', 'bundesland',
- 'email', 'telefon', 'website',
- 'glaeubigerid', 'iban', 'bic',
- 'dosb_mitglied'
- ];
-
- const fields = Object.keys(body).filter(k => allowed.includes(k));
- if (fields.length === 0) throw error(400, 'Keine gültigen Felder');
-
- const sets = fields.map(k => `${k} = ?`).join(', ');
- const vals = fields.map(k => {
- if (k === 'dosb_mitglied') return body[k] ? 1 : 0;
- return body[k];
- });
-
- db.prepare(`UPDATE vereine SET ${sets}, updated = strftime('%Y-%m-%dT%H:%M:%SZ','now') WHERE id = ?`)
- .run(...vals, u.verein_id);
-
- const verein = db.prepare('SELECT * FROM vereine WHERE id = ?').get(u.verein_id);
- return json(row(verein as Record));
-}
diff --git a/app/src/routes/favicon.ico/+server.ts b/app/src/routes/favicon.ico/+server.ts
deleted file mode 100644
index fbf5ba3..0000000
--- a/app/src/routes/favicon.ico/+server.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { redirect } from '@sveltejs/kit';
-
-export function GET() {
- throw redirect(301, '/favicon.svg');
-}
diff --git a/app/src/routes/invite/[token]/+page.svelte b/app/src/routes/invite/[token]/+page.svelte
deleted file mode 100644
index e1c36c7..0000000
--- a/app/src/routes/invite/[token]/+page.svelte
+++ /dev/null
@@ -1,173 +0,0 @@
-
-
-Einladung — vereins.haus
-
-
-
-

- vereins.haus
-
-
-
- {#if loading}
-
Prüfe Einladung…
-
- {:else if fehler}
-
✕
-
Ungültige Einladung
-
{fehler}
-
- {:else}
-
🎉
-
Du wurdest eingeladen!
-
- Du wirst als {einladung?.rolle === 'trainer' ? 'Trainer' : 'Admin'}
- zum Verein „{vereinName}" hinzugefügt.
-
-
-
- {/if}
-
-
-
-
diff --git a/app/src/routes/onboarding/+page.svelte b/app/src/routes/onboarding/+page.svelte
deleted file mode 100644
index de2c257..0000000
--- a/app/src/routes/onboarding/+page.svelte
+++ /dev/null
@@ -1,334 +0,0 @@
-
-
-Einrichtung — vereins.haus
-
-
-
-

- vereins.haus
-
-
-
-
-
- {#each [1, 2, 3] as s}
- = s}>
- {/each}
-
-
- {#if schritt === 1}
-
-
-
👋
-
Schön, dass du dabei bist!
-
- vereins.haus hilft deinem Vorstand, Mitglieder, Termine und
- Beiträge zu verwalten – übersichtlich, mobil und ohne Excel.
-
-
- Die Einrichtung dauert weniger als eine Minute.
-
-
-
-
- {:else if schritt === 2}
-
-
-
Dein Verein
-
Wie heißt dein Verein? Du kannst alle Angaben später noch ändern.
-
-
-
-
- {:else}
-
-
-
✓
-
Alles bereit!
-
- „{fertigName}" wurde eingerichtet. Du kannst jetzt loslegen.
-
-
-
- -
- 👤
-
- Mitglieder anlegen
- Namen, E-Mail und IBAN für den SEPA-Einzug
-
-
- -
- 💶
-
- Beitragsarten definieren
- Jahresbeitrag, Sonderumlage – mit SEPA-Export
-
-
- -
- 📅
-
- Termine eintragen
- Versammlung, Training, Ausflug
-
-
-
-
-
-
- {/if}
-
-
-
-
diff --git a/app/src/sw.ts b/app/src/sw.ts
deleted file mode 100644
index 3144a2a..0000000
--- a/app/src/sw.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-///
-import { cleanupOutdatedCaches, precacheAndRoute } from 'workbox-precaching';
-import { registerRoute } from 'workbox-routing';
-import { NetworkFirst } from 'workbox-strategies';
-
-declare const self: ServiceWorkerGlobalScope;
-
-cleanupOutdatedCaches();
-precacheAndRoute(self.__WB_MANIFEST);
-
-// PocketBase API: NetworkFirst (offline-fähig aus Cache)
-registerRoute(
- ({ url }) => url.hostname === 'api.vereins.haus',
- new NetworkFirst({
- cacheName: 'pocketbase-api',
- networkTimeoutSeconds: 5,
- }),
-);
-
-self.addEventListener('push', (event) => {
- const data = event.data?.json() ?? {};
- const title = data.title ?? 'vereins.haus';
- const options: NotificationOptions = {
- body: data.body ?? '',
- icon: '/icons/icon-192.png',
- badge: '/icons/icon-192.png',
- tag: data.tag ?? 'vereinshaus',
- data: { url: data.url ?? '/nachrichten' },
- };
- event.waitUntil(self.registration.showNotification(title, options));
-});
-
-self.addEventListener('notificationclick', (event) => {
- event.notification.close();
- const url = (event.notification.data as { url: string })?.url ?? '/';
- event.waitUntil(
- self.clients
- .matchAll({ type: 'window', includeUncontrolled: true })
- .then((list) => {
- const existing = list.find((c) => c.url.includes(url));
- if (existing) return existing.focus();
- return self.clients.openWindow(url);
- }),
- );
-});
diff --git a/app/static/apple-touch-icon.png b/app/static/apple-touch-icon.png
deleted file mode 100644
index cb64ca0..0000000
Binary files a/app/static/apple-touch-icon.png and /dev/null differ
diff --git a/app/static/favicon.svg b/app/static/favicon.svg
deleted file mode 100644
index 1b5e611..0000000
--- a/app/static/favicon.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-
diff --git a/app/static/icons/icon-192.png b/app/static/icons/icon-192.png
deleted file mode 100644
index 3a02f99..0000000
Binary files a/app/static/icons/icon-192.png and /dev/null differ
diff --git a/app/static/icons/icon-512.png b/app/static/icons/icon-512.png
deleted file mode 100644
index eb73fb8..0000000
Binary files a/app/static/icons/icon-512.png and /dev/null differ
diff --git a/app/static/icons/icon-512.svg b/app/static/icons/icon-512.svg
deleted file mode 100644
index 6412f9e..0000000
--- a/app/static/icons/icon-512.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-
diff --git a/app/static/logo.svg b/app/static/logo.svg
deleted file mode 100644
index 64f479b..0000000
--- a/app/static/logo.svg
+++ /dev/null
@@ -1,11 +0,0 @@
-
diff --git a/app/vite.config.ts b/app/vite.config.ts
index 41b6e6c..78f8947 100644
--- a/app/vite.config.ts
+++ b/app/vite.config.ts
@@ -6,28 +6,34 @@ export default defineConfig({
plugins: [
sveltekit(),
VitePWA({
- strategies: 'injectManifest',
- srcDir: 'src',
- filename: 'sw.ts',
registerType: 'autoUpdate',
manifest: {
name: 'vereins.haus',
short_name: 'vereins.haus',
description: 'Vereinsverwaltung die einfach funktioniert',
- theme_color: '#0F172A',
- background_color: '#0F172A',
+ theme_color: '#1e40af',
+ background_color: '#f8fafc',
display: 'standalone',
start_url: '/',
icons: [
+ { src: '/icons/icon-192.png', sizes: '192x192', type: 'image/png', purpose: 'any' },
{ src: '/icons/icon-512.png', sizes: '512x512', type: 'image/png', purpose: 'any maskable' },
- { src: '/icons/icon-192.png', sizes: '192x192', type: 'image/png', purpose: 'any maskable' },
- { src: '/apple-touch-icon.png', sizes: '180x180', type: 'image/png', purpose: 'any' },
- { src: '/favicon.svg', sizes: 'any', type: 'image/svg+xml', purpose: 'any' }
+ { src: '/icons/apple-touch-icon.png', sizes: '180x180', type: 'image/png' }
]
},
- injectManifest: {
+ workbox: {
globPatterns: ['**/*.{js,css,html,svg,png,ico}'],
- },
- }),
- ],
+ runtimeCaching: [
+ {
+ urlPattern: /^https:\/\/api\.vereins\.haus\/.*/i,
+ handler: 'NetworkFirst',
+ options: {
+ cacheName: 'pocketbase-api',
+ expiration: { maxEntries: 100, maxAgeSeconds: 60 * 60 * 24 }
+ }
+ }
+ ]
+ }
+ })
+ ]
});
diff --git a/docker-compose.staging.yml b/docker-compose.staging.yml
deleted file mode 100644
index 7e4208b..0000000
--- a/docker-compose.staging.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-version: "3.8"
-
-services:
- app-staging:
- build:
- context: ./app
- dockerfile: Dockerfile
- image: vereinshaus-staging-app
- container_name: vereinshaus-staging-app
- restart: unless-stopped
- volumes:
- - /volume1/docker/vereinshaus-staging/data:/data
- environment:
- - TZ=Europe/Berlin
- - HOST=0.0.0.0
- - PORT=3000
- - DB_PATH=/data/vereinshaus.db
- - UPLOAD_DIR=/data/uploads
- - JWT_SECRET=${JWT_SECRET:-staging-secret-change-me}
- - PUBLIC_VAPID_KEY=${PUBLIC_VAPID_KEY}
- - VAPID_PRIVATE_KEY=${VAPID_PRIVATE_KEY}
- - VAPID_SUBJECT=${VAPID_SUBJECT:-mailto:info@vereins.haus}
- - BREVO_KEY=${BREVO_KEY}
- - BREVO_SENDER=${BREVO_SENDER:-noreply@vereins.haus}
- networks:
- default: {}
- npm_bridge:
- ipv4_address: 172.25.0.13
-
-networks:
- npm_bridge:
- external: true
- name: nginx-proxy-manager_bridge_net
diff --git a/docker-compose.yml b/docker-compose.yml
index ee2a79a..bff1658 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,6 +1,21 @@
version: "3.8"
services:
+ pocketbase:
+ image: ghcr.io/muchobien/pocketbase:latest
+ container_name: vereinshaus-pocketbase
+ restart: unless-stopped
+ volumes:
+ - /volume1/docker/vereinshaus/pocketbase/data:/pb_data
+ - /volume1/docker/vereinshaus/pocketbase/storage:/pb_public
+ - /volume1/docker/vereinshaus/pocketbase/data/pb_hooks:/pb_hooks
+ environment:
+ - TZ=Europe/Berlin
+ - BREVO_KEY=${BREVO_KEY}
+ networks:
+ - default
+ - npm_bridge
+
app:
build:
context: ./app
@@ -8,24 +23,13 @@ services:
image: vereinshaus-app
container_name: vereinshaus-app
restart: unless-stopped
- volumes:
- - /volume1/docker/vereinshaus/data:/data
environment:
- TZ=Europe/Berlin
- HOST=0.0.0.0
- PORT=3000
- - DB_PATH=/data/vereinshaus.db
- - UPLOAD_DIR=/data/uploads
- - JWT_SECRET=${JWT_SECRET}
- - PUBLIC_VAPID_KEY=${PUBLIC_VAPID_KEY}
- - VAPID_PRIVATE_KEY=${VAPID_PRIVATE_KEY}
- - VAPID_SUBJECT=${VAPID_SUBJECT:-mailto:info@vereins.haus}
- - BREVO_KEY=${BREVO_KEY}
- - BREVO_SENDER=${BREVO_SENDER:-noreply@vereins.haus}
networks:
- default: {}
- npm_bridge:
- ipv4_address: 172.25.0.11
+ - default
+ - npm_bridge
networks:
npm_bridge:
diff --git a/pocketbase/pb_hooks/nachrichten.pb.js b/pocketbase/pb_hooks/nachrichten.pb.js
deleted file mode 100644
index 6201223..0000000
--- a/pocketbase/pb_hooks/nachrichten.pb.js
+++ /dev/null
@@ -1,86 +0,0 @@
-onRecordAfterCreateSuccess(function(e) {
- if (!e.record) return;
-
- var key = $os.getenv("BREVO_KEY");
- if (!key) {
- console.log("[nachrichten] BREVO_KEY nicht gesetzt – E-Mail übersprungen.");
- return;
- }
-
- var vereinId = e.record.getString("verein_id");
- var betreff = e.record.getString("betreff");
- var text = e.record.getString("text");
- var gruppeIds = e.record.getStringSlice("gruppe_ids");
-
- // Vereinsname für Absender
- var vereinName = "Ihr Verein";
- try {
- var verein = $app.findRecordById("vereine", vereinId);
- vereinName = verein.getString("name");
- } catch(err) {}
-
- // Mitglieder-Filter
- var filter = 'verein_id = "' + vereinId + '" && status = "aktiv" && email != ""';
- if (gruppeIds && gruppeIds.length > 0) {
- var gruppenParts = [];
- for (var i = 0; i < gruppeIds.length; i++) {
- gruppenParts.push('gruppe_ids ~ "' + gruppeIds[i] + '"');
- }
- filter += " && (" + gruppenParts.join(" || ") + ")";
- }
-
- var mitglieder;
- try {
- mitglieder = $app.findRecordsByFilter("mitglieder", filter, "nachname", 500, 0);
- } catch(err) {
- console.error("[nachrichten] Mitglieder laden fehlgeschlagen: " + String(err));
- return;
- }
-
- var empfaenger = [];
- for (var j = 0; j < mitglieder.length; j++) {
- var m = mitglieder[j];
- var email = m.getString("email");
- if (email) {
- empfaenger.push({
- email: email,
- name: m.getString("vorname") + " " + m.getString("nachname")
- });
- }
- }
-
- if (empfaenger.length === 0) {
- console.log("[nachrichten] Keine Empfänger mit E-Mail gefunden.");
- return;
- }
-
- var sender = $os.getenv("BREVO_SENDER") || "noreply@vereins.haus";
- var htmlContent = text
- ? text.replace(/\n/g, "
")
- : "(Kein Inhalt)
";
-
- // In 50er-Batches senden (Brevo-Limit)
- var BATCH = 50;
- for (var b = 0; b < empfaenger.length; b += BATCH) {
- var batch = empfaenger.slice(b, b + BATCH);
- var body = JSON.stringify({
- sender: { name: vereinName, email: sender },
- to: [batch[0]],
- bcc: batch.slice(1),
- subject: betreff,
- htmlContent: htmlContent
- });
-
- try {
- $http.send({
- url: "https://api.brevo.com/v3/smtp/email",
- method: "POST",
- headers: { "api-key": key, "Content-Type": "application/json" },
- body: body
- });
- console.log("[nachrichten] Batch " + (b / BATCH + 1) + " gesendet (" + batch.length + " Empfänger).");
- } catch(err) {
- console.error("[nachrichten] Brevo Fehler Batch " + (b / BATCH + 1) + ": " + String(err));
- }
- }
-}, "nachrichten");
diff --git a/pocketbase/pb_migrations/1779215839_created_vereine.js b/pocketbase/pb_migrations/1779215839_created_vereine.js
deleted file mode 100644
index b1f4095..0000000
--- a/pocketbase/pb_migrations/1779215839_created_vereine.js
+++ /dev/null
@@ -1,144 +0,0 @@
-///
-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);
-})
diff --git a/pocketbase/pb_migrations/1779215883_created_gruppen.js b/pocketbase/pb_migrations/1779215883_created_gruppen.js
deleted file mode 100644
index 858d446..0000000
--- a/pocketbase/pb_migrations/1779215883_created_gruppen.js
+++ /dev/null
@@ -1,67 +0,0 @@
-///
-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);
-})
diff --git a/pocketbase/pb_migrations/1779215883_updated_users.js b/pocketbase/pb_migrations/1779215883_updated_users.js
deleted file mode 100644
index d8f294a..0000000
--- a/pocketbase/pb_migrations/1779215883_updated_users.js
+++ /dev/null
@@ -1,29 +0,0 @@
-///
-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)
-})
diff --git a/pocketbase/pb_migrations/1779215901_created_beitraege.js b/pocketbase/pb_migrations/1779215901_created_beitraege.js
deleted file mode 100644
index 5ffa960..0000000
--- a/pocketbase/pb_migrations/1779215901_created_beitraege.js
+++ /dev/null
@@ -1,97 +0,0 @@
-///
-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);
-})
diff --git a/pocketbase/pb_migrations/1779215901_created_mitglieder.js b/pocketbase/pb_migrations/1779215901_created_mitglieder.js
deleted file mode 100644
index 6dad2e0..0000000
--- a/pocketbase/pb_migrations/1779215901_created_mitglieder.js
+++ /dev/null
@@ -1,139 +0,0 @@
-///
-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);
-})
diff --git a/pocketbase/pb_migrations/1779215948_created_einzuege.js b/pocketbase/pb_migrations/1779215948_created_einzuege.js
deleted file mode 100644
index 16c1ad5..0000000
--- a/pocketbase/pb_migrations/1779215948_created_einzuege.js
+++ /dev/null
@@ -1,123 +0,0 @@
-///
-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);
-})
diff --git a/pocketbase/pb_migrations/1779215948_created_nachrichten.js b/pocketbase/pb_migrations/1779215948_created_nachrichten.js
deleted file mode 100644
index 5343778..0000000
--- a/pocketbase/pb_migrations/1779215948_created_nachrichten.js
+++ /dev/null
@@ -1,105 +0,0 @@
-///
-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);
-})
diff --git a/pocketbase/pb_migrations/1779215948_created_push_subscriptions.js b/pocketbase/pb_migrations/1779215948_created_push_subscriptions.js
deleted file mode 100644
index c7998d0..0000000
--- a/pocketbase/pb_migrations/1779215948_created_push_subscriptions.js
+++ /dev/null
@@ -1,94 +0,0 @@
-///
-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);
-})
diff --git a/pocketbase/pb_migrations/1779215948_created_termine.js b/pocketbase/pb_migrations/1779215948_created_termine.js
deleted file mode 100644
index 6053dbe..0000000
--- a/pocketbase/pb_migrations/1779215948_created_termine.js
+++ /dev/null
@@ -1,120 +0,0 @@
-///
-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);
-})
diff --git a/pocketbase/pb_migrations/1779230000_align_schema.js b/pocketbase/pb_migrations/1779230000_align_schema.js
deleted file mode 100644
index 5499ebe..0000000
--- a/pocketbase/pb_migrations/1779230000_align_schema.js
+++ /dev/null
@@ -1,194 +0,0 @@
-///
-migrate((app) => {
-
- // vereine: +adresse, +dosb_mitglied; -stripe_customer_id
- {
- const c = app.findCollectionByNameOrId("pbc_3589557411")
- c.fields.removeById("text1888339527") // stripe_customer_id
- c.fields.addAt(2, new Field({
- "type": "text", "id": "text2001000001", "name": "adresse",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "autogeneratePattern": "", "min": 0, "max": 0, "pattern": ""
- }))
- c.fields.addAt(99, new Field({
- "type": "bool", "id": "bool2001000002", "name": "dosb_mitglied",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false
- }))
- app.save(c)
- }
-
- // gruppen: +beschreibung
- {
- const c = app.findCollectionByNameOrId("pbc_3099069179")
- c.fields.addAt(99, new Field({
- "type": "text", "id": "text2001000003", "name": "beschreibung",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "autogeneratePattern": "", "min": 0, "max": 0, "pattern": ""
- }))
- app.save(c)
- }
-
- // mitglieder: +telefon, geburtsdatum, eintrittsdatum, austrittsdatum, strasse, plz, ort, bic, notizen
- {
- const c = app.findCollectionByNameOrId("pbc_2707111162")
- c.fields.addAt(99, new Field({
- "type": "text", "id": "text2001000010", "name": "telefon",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "autogeneratePattern": "", "min": 0, "max": 0, "pattern": ""
- }))
- c.fields.addAt(99, new Field({
- "type": "date", "id": "date2001000011", "name": "geburtsdatum",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "min": "", "max": ""
- }))
- c.fields.addAt(99, new Field({
- "type": "date", "id": "date2001000012", "name": "eintrittsdatum",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "min": "", "max": ""
- }))
- c.fields.addAt(99, new Field({
- "type": "date", "id": "date2001000013", "name": "austrittsdatum",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "min": "", "max": ""
- }))
- c.fields.addAt(99, new Field({
- "type": "text", "id": "text2001000014", "name": "strasse",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "autogeneratePattern": "", "min": 0, "max": 0, "pattern": ""
- }))
- c.fields.addAt(99, new Field({
- "type": "text", "id": "text2001000015", "name": "plz",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "autogeneratePattern": "", "min": 0, "max": 0, "pattern": ""
- }))
- c.fields.addAt(99, new Field({
- "type": "text", "id": "text2001000016", "name": "ort",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "autogeneratePattern": "", "min": 0, "max": 0, "pattern": ""
- }))
- c.fields.addAt(99, new Field({
- "type": "text", "id": "text2001000017", "name": "bic",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "autogeneratePattern": "", "min": 0, "max": 0, "pattern": ""
- }))
- c.fields.addAt(99, new Field({
- "type": "text", "id": "text2001000018", "name": "notizen",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "autogeneratePattern": "", "min": 0, "max": 0, "pattern": ""
- }))
- app.save(c)
- }
-
- // beitraege: +beschreibung; rhythmus +halbjaehrlich (einmalig bleibt)
- {
- const c = app.findCollectionByNameOrId("pbc_3218207135")
- const rhythmus = c.fields.getById("select917011370")
- rhythmus.values = ["monatlich", "quartalsweise", "halbjaehrlich", "jaehrlich", "einmalig"]
- c.fields.addAt(99, new Field({
- "type": "text", "id": "text2001000020", "name": "beschreibung",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "autogeneratePattern": "", "min": 0, "max": 0, "pattern": ""
- }))
- app.save(c)
- }
-
- // einzuege: -stripe_payment_intent_id; status bezahlt→eingezogen
- {
- const c = app.findCollectionByNameOrId("pbc_659326735")
- c.fields.removeById("text4235393406") // stripe_payment_intent_id
- const status = c.fields.getById("select2063623452")
- status.values = ["ausstehend", "eingezogen", "fehlgeschlagen", "storniert"]
- app.save(c)
- }
-
- // termine: +beschreibung
- {
- const c = app.findCollectionByNameOrId("pbc_2279568741")
- c.fields.addAt(99, new Field({
- "type": "text", "id": "text2001000030", "name": "beschreibung",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "autogeneratePattern": "", "min": 0, "max": 0, "pattern": ""
- }))
- app.save(c)
- }
-
- // nachrichten: +autor_id (relation zu users)
- {
- const c = app.findCollectionByNameOrId("pbc_1415911511")
- c.fields.addAt(2, new Field({
- "type": "relation", "id": "relation2001000040", "name": "autor_id",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "cascadeDelete": false, "collectionId": "_pb_users_auth_", "maxSelect": 1, "minSelect": 0
- }))
- app.save(c)
- }
-
-}, (app) => {
-
- // vereine rollback
- {
- const c = app.findCollectionByNameOrId("pbc_3589557411")
- c.fields.removeById("text2001000001")
- c.fields.removeById("bool2001000002")
- c.fields.addAt(99, new Field({
- "type": "text", "id": "text1888339527", "name": "stripe_customer_id",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "autogeneratePattern": "", "min": 0, "max": 0, "pattern": ""
- }))
- app.save(c)
- }
-
- // gruppen rollback
- {
- const c = app.findCollectionByNameOrId("pbc_3099069179")
- c.fields.removeById("text2001000003")
- app.save(c)
- }
-
- // mitglieder rollback
- {
- const c = app.findCollectionByNameOrId("pbc_2707111162")
- for (const id of ["text2001000010","date2001000011","date2001000012","date2001000013",
- "text2001000014","text2001000015","text2001000016","text2001000017","text2001000018"]) {
- c.fields.removeById(id)
- }
- app.save(c)
- }
-
- // beitraege rollback
- {
- const c = app.findCollectionByNameOrId("pbc_3218207135")
- const rhythmus = c.fields.getById("select917011370")
- rhythmus.values = ["monatlich", "quartalsweise", "jaehrlich", "einmalig"]
- c.fields.removeById("text2001000020")
- app.save(c)
- }
-
- // einzuege rollback
- {
- const c = app.findCollectionByNameOrId("pbc_659326735")
- const status = c.fields.getById("select2063623452")
- status.values = ["ausstehend", "bezahlt", "fehlgeschlagen", "storniert"]
- c.fields.addAt(99, new Field({
- "type": "text", "id": "text4235393406", "name": "stripe_payment_intent_id",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "autogeneratePattern": "", "min": 0, "max": 0, "pattern": ""
- }))
- app.save(c)
- }
-
- // termine rollback
- {
- const c = app.findCollectionByNameOrId("pbc_2279568741")
- c.fields.removeById("text2001000030")
- app.save(c)
- }
-
- // nachrichten rollback
- {
- const c = app.findCollectionByNameOrId("pbc_1415911511")
- c.fields.removeById("relation2001000040")
- app.save(c)
- }
-
-})
diff --git a/pocketbase/pb_migrations/1779230100_sepa_fields.js b/pocketbase/pb_migrations/1779230100_sepa_fields.js
deleted file mode 100644
index f64ad90..0000000
--- a/pocketbase/pb_migrations/1779230100_sepa_fields.js
+++ /dev/null
@@ -1,58 +0,0 @@
-///
-migrate((app) => {
-
- // vereine: +glaeubigerid, +iban, +bic (Vereinskonto für SEPA-Einzug)
- {
- const c = app.findCollectionByNameOrId("pbc_3589557411")
- c.fields.addAt(99, new Field({
- "type": "text", "id": "text2001000050", "name": "glaeubigerid",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "autogeneratePattern": "", "min": 0, "max": 0, "pattern": ""
- }))
- c.fields.addAt(99, new Field({
- "type": "text", "id": "text2001000051", "name": "iban",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "autogeneratePattern": "", "min": 0, "max": 0, "pattern": ""
- }))
- c.fields.addAt(99, new Field({
- "type": "text", "id": "text2001000052", "name": "bic",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "autogeneratePattern": "", "min": 0, "max": 0, "pattern": ""
- }))
- app.save(c)
- }
-
- // mitglieder: +mandatsreferenz, +mandatsdatum (SEPA-Mandat des Mitglieds)
- {
- const c = app.findCollectionByNameOrId("pbc_2707111162")
- c.fields.addAt(99, new Field({
- "type": "text", "id": "text2001000053", "name": "mandatsreferenz",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "autogeneratePattern": "", "min": 0, "max": 0, "pattern": ""
- }))
- c.fields.addAt(99, new Field({
- "type": "date", "id": "date2001000054", "name": "mandatsdatum",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "min": "", "max": ""
- }))
- app.save(c)
- }
-
-}, (app) => {
-
- {
- const c = app.findCollectionByNameOrId("pbc_3589557411")
- c.fields.removeById("text2001000050")
- c.fields.removeById("text2001000051")
- c.fields.removeById("text2001000052")
- app.save(c)
- }
-
- {
- const c = app.findCollectionByNameOrId("pbc_2707111162")
- c.fields.removeById("text2001000053")
- c.fields.removeById("date2001000054")
- app.save(c)
- }
-
-})
diff --git a/pocketbase/pb_migrations/1779230200_push_subscriptions_vereinrule.js b/pocketbase/pb_migrations/1779230200_push_subscriptions_vereinrule.js
deleted file mode 100644
index 31c9122..0000000
--- a/pocketbase/pb_migrations/1779230200_push_subscriptions_vereinrule.js
+++ /dev/null
@@ -1,14 +0,0 @@
-///
-// Erlaubt allen Usern desselben Vereins, die Subscriptions ihrer Vereinsmitglieder zu lesen
-// (notwendig damit die /api/push/senden Route alle Geräte des Vereins erreicht)
-migrate((app) => {
- const c = app.findCollectionByNameOrId("pbc_1438754935") // push_subscriptions
- c.listRule = '@request.auth.verein_id = user_id.verein_id'
- c.viewRule = '@request.auth.id = user_id'
- app.save(c)
-}, (app) => {
- const c = app.findCollectionByNameOrId("pbc_1438754935")
- c.listRule = '@request.auth.id = user_id'
- c.viewRule = '@request.auth.id = user_id'
- app.save(c)
-})
diff --git a/pocketbase/pb_migrations/1779230300_vereine_create_rule.js b/pocketbase/pb_migrations/1779230300_vereine_create_rule.js
deleted file mode 100644
index 434db4f..0000000
--- a/pocketbase/pb_migrations/1779230300_vereine_create_rule.js
+++ /dev/null
@@ -1,11 +0,0 @@
-///
-// Erlaubt eingeloggten Nutzern, einen Verein anzulegen (Onboarding)
-migrate((app) => {
- const c = app.findCollectionByNameOrId("pbc_3589557411") // vereine
- c.createRule = "@request.auth.id != ''"
- app.save(c)
-}, (app) => {
- const c = app.findCollectionByNameOrId("pbc_3589557411")
- c.createRule = null
- app.save(c)
-})
diff --git a/pocketbase/pb_migrations/1779230400_verein_kontakt.js b/pocketbase/pb_migrations/1779230400_verein_kontakt.js
deleted file mode 100644
index 50ed13c..0000000
--- a/pocketbase/pb_migrations/1779230400_verein_kontakt.js
+++ /dev/null
@@ -1,26 +0,0 @@
-///
-migrate((app) => {
- const c = app.findCollectionByNameOrId("pbc_3589557411") // vereine
- c.fields.addAt(99, new Field({
- "type": "email", "id": "email2001000060", "name": "email",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "exceptDomains": null, "onlyDomains": null
- }))
- c.fields.addAt(99, new Field({
- "type": "text", "id": "text2001000061", "name": "telefon",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "autogeneratePattern": "", "min": 0, "max": 0, "pattern": ""
- }))
- c.fields.addAt(99, new Field({
- "type": "url", "id": "url2001000062", "name": "website",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "exceptDomains": null, "onlyDomains": null
- }))
- app.save(c)
-}, (app) => {
- const c = app.findCollectionByNameOrId("pbc_3589557411")
- c.fields.removeById("email2001000060")
- c.fields.removeById("text2001000061")
- c.fields.removeById("url2001000062")
- app.save(c)
-})
diff --git a/pocketbase/pb_migrations/1779230500_rollen.js b/pocketbase/pb_migrations/1779230500_rollen.js
deleted file mode 100644
index a998ab1..0000000
--- a/pocketbase/pb_migrations/1779230500_rollen.js
+++ /dev/null
@@ -1,92 +0,0 @@
-///
-migrate((app) => {
-
- // Users: +rolle, listRule für verein-weite Sichtbarkeit
- {
- const c = app.findCollectionByNameOrId("_pb_users_auth_")
- c.fields.addAt(99, new Field({
- "type": "select", "id": "select2001000070", "name": "rolle",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "maxSelect": 1, "values": ["admin", "trainer"]
- }))
- c.listRule = "@request.auth.verein_id = verein_id"
- app.save(c)
- }
-
- // Gruppen: +trainer_ids (welche User betreuen diese Gruppe)
- {
- const c = app.findCollectionByNameOrId("pbc_3099069179")
- c.fields.addAt(99, new Field({
- "type": "relation", "id": "relation2001000071", "name": "trainer_ids",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "cascadeDelete": false, "collectionId": "_pb_users_auth_",
- "maxSelect": 99, "minSelect": 0
- }))
- app.save(c)
- }
-
- // Einladungen-Collection
- {
- const c = new Collection({
- "createRule": "@request.auth.verein_id = verein_id",
- "deleteRule": "@request.auth.verein_id = verein_id",
- "listRule": "@request.auth.verein_id = verein_id",
- "viewRule": "",
- "updateRule": "@request.auth.verein_id = verein_id",
- "fields": [
- {
- "autogeneratePattern": "[a-z0-9]{15}", "id": "text3208210256",
- "max": 15, "min": 15, "name": "id", "pattern": "^[a-z0-9]+$",
- "primaryKey": true, "required": true, "system": true, "type": "text",
- "help": "", "hidden": false, "presentable": false
- },
- {
- "type": "relation", "id": "relation2001000072", "name": "verein_id",
- "help": "", "hidden": false, "presentable": false, "required": true, "system": false,
- "cascadeDelete": true, "collectionId": "pbc_3589557411", "maxSelect": 1, "minSelect": 0
- },
- {
- "type": "select", "id": "select2001000073", "name": "rolle",
- "help": "", "hidden": false, "presentable": false, "required": true, "system": false,
- "maxSelect": 1, "values": ["admin", "trainer"]
- },
- {
- "type": "text", "id": "text2001000074", "name": "token",
- "help": "", "hidden": false, "presentable": false, "required": true, "system": false,
- "autogeneratePattern": "", "min": 0, "max": 0, "pattern": ""
- },
- {
- "type": "bool", "id": "bool2001000075", "name": "genutzt",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false
- }
- ],
- "id": "pbc_einladungen",
- "indexes": ["CREATE UNIQUE INDEX idx_einladungen_token ON einladungen (token)"],
- "name": "einladungen",
- "system": false,
- "type": "base"
- })
- app.save(c)
- }
-
-}, (app) => {
-
- {
- const c = app.findCollectionByNameOrId("_pb_users_auth_")
- c.fields.removeById("select2001000070")
- c.listRule = ""
- app.save(c)
- }
-
- {
- const c = app.findCollectionByNameOrId("pbc_3099069179")
- c.fields.removeById("relation2001000071")
- app.save(c)
- }
-
- {
- const c = app.findCollectionByNameOrId("pbc_einladungen")
- app.delete(c)
- }
-
-})
diff --git a/pocketbase/pb_migrations/1779230600_termin_verfuegbarkeit.js b/pocketbase/pb_migrations/1779230600_termin_verfuegbarkeit.js
deleted file mode 100644
index 9f653e2..0000000
--- a/pocketbase/pb_migrations/1779230600_termin_verfuegbarkeit.js
+++ /dev/null
@@ -1,23 +0,0 @@
-///
-migrate((app) => {
- const c = app.findCollectionByNameOrId("pbc_2279568741") // termine
-
- c.fields.addAt(99, new Field({
- "type": "relation", "id": "relation2001000080", "name": "durchfuehrender_id",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "cascadeDelete": false, "collectionId": "_pb_users_auth_", "maxSelect": 1, "minSelect": 0
- }))
-
- c.fields.addAt(99, new Field({
- "type": "select", "id": "select2001000081", "name": "verfuegbarkeit",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "maxSelect": 1, "values": ["offen", "bestaetigt", "abgesagt", "vertretung_gesucht"]
- }))
-
- app.save(c)
-}, (app) => {
- const c = app.findCollectionByNameOrId("pbc_2279568741")
- c.fields.removeById("relation2001000080")
- c.fields.removeById("select2001000081")
- app.save(c)
-})
diff --git a/pocketbase/pb_migrations/1779230700_termine_serie.js b/pocketbase/pb_migrations/1779230700_termine_serie.js
deleted file mode 100644
index b44688a..0000000
--- a/pocketbase/pb_migrations/1779230700_termine_serie.js
+++ /dev/null
@@ -1,25 +0,0 @@
-///
-migrate((app) => {
- const c = app.findCollectionByNameOrId("pbc_2279568741") // termine
-
- // rrule – RFC-5545-Wiederholungsregel (nur RRULE-Teil ohne DTSTART)
- c.fields.addAt(99, new Field({
- "type": "text", "id": "text2001000090", "name": "rrule",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "autogeneratePattern": "", "min": 0, "max": 0, "pattern": ""
- }))
-
- // serie_id – gruppiert alle Termine einer Wiederholungsserie
- c.fields.addAt(99, new Field({
- "type": "text", "id": "text2001000091", "name": "serie_id",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "autogeneratePattern": "", "min": 0, "max": 0, "pattern": ""
- }))
-
- app.save(c)
-}, (app) => {
- const c = app.findCollectionByNameOrId("pbc_2279568741")
- c.fields.removeById("text2001000090")
- c.fields.removeById("text2001000091")
- app.save(c)
-})
diff --git a/pocketbase/pb_migrations/1779230800_veranstaltungsorte.js b/pocketbase/pb_migrations/1779230800_veranstaltungsorte.js
deleted file mode 100644
index 362a0eb..0000000
--- a/pocketbase/pb_migrations/1779230800_veranstaltungsorte.js
+++ /dev/null
@@ -1,117 +0,0 @@
-///
-migrate((app) => {
-
- // Veranstaltungsorte
- {
- const c = new Collection({
- "createRule": "@request.auth.verein_id = verein_id",
- "deleteRule": "@request.auth.verein_id = verein_id",
- "listRule": "@request.auth.verein_id = verein_id",
- "viewRule": "@request.auth.verein_id = verein_id",
- "updateRule": "@request.auth.verein_id = verein_id",
- "fields": [
- {
- "autogeneratePattern": "[a-z0-9]{15}", "id": "text3208210256",
- "max": 15, "min": 15, "name": "id", "pattern": "^[a-z0-9]+$",
- "primaryKey": true, "required": true, "system": true, "type": "text",
- "help": "", "hidden": false, "presentable": false
- },
- {
- "type": "relation", "id": "relation2001000100", "name": "verein_id",
- "help": "", "hidden": false, "presentable": false, "required": true, "system": false,
- "cascadeDelete": true, "collectionId": "pbc_3589557411", "maxSelect": 1, "minSelect": 0
- },
- {
- "type": "text", "id": "text2001000101", "name": "name",
- "help": "", "hidden": false, "presentable": true, "required": true, "system": false,
- "autogeneratePattern": "", "min": 0, "max": 0, "pattern": ""
- },
- {
- "type": "text", "id": "text2001000102", "name": "adresse",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "autogeneratePattern": "", "min": 0, "max": 0, "pattern": ""
- },
- {
- "type": "select", "id": "select2001000103", "name": "typ",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "maxSelect": 1, "values": ["halle", "platz", "gebaeude", "sonstiges"]
- },
- {
- "type": "bool", "id": "bool2001000104", "name": "aktiv",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false
- }
- ],
- "id": "pbc_veranstaltungsorte",
- "indexes": [],
- "name": "veranstaltungsorte",
- "system": false,
- "type": "base"
- })
- app.save(c)
- }
-
- // Ort-Ausfälle
- {
- const c = new Collection({
- "createRule": "@request.auth.verein_id = ort_id.verein_id",
- "deleteRule": "@request.auth.verein_id = ort_id.verein_id",
- "listRule": "@request.auth.verein_id = ort_id.verein_id",
- "viewRule": "@request.auth.verein_id = ort_id.verein_id",
- "updateRule": "@request.auth.verein_id = ort_id.verein_id",
- "fields": [
- {
- "autogeneratePattern": "[a-z0-9]{15}", "id": "text3208210256",
- "max": 15, "min": 15, "name": "id", "pattern": "^[a-z0-9]+$",
- "primaryKey": true, "required": true, "system": true, "type": "text",
- "help": "", "hidden": false, "presentable": false
- },
- {
- "type": "relation", "id": "relation2001000110", "name": "ort_id",
- "help": "", "hidden": false, "presentable": false, "required": true, "system": false,
- "cascadeDelete": true, "collectionId": "pbc_veranstaltungsorte", "maxSelect": 1, "minSelect": 0
- },
- {
- "type": "date", "id": "date2001000111", "name": "von",
- "help": "", "hidden": false, "presentable": false, "required": true, "system": false,
- "min": "", "max": ""
- },
- {
- "type": "date", "id": "date2001000112", "name": "bis",
- "help": "", "hidden": false, "presentable": false, "required": true, "system": false,
- "min": "", "max": ""
- },
- {
- "type": "text", "id": "text2001000113", "name": "grund",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "autogeneratePattern": "", "min": 0, "max": 0, "pattern": ""
- }
- ],
- "id": "pbc_ort_ausfaelle",
- "indexes": [],
- "name": "ort_ausfaelle",
- "system": false,
- "type": "base"
- })
- app.save(c)
- }
-
- // Termine: +ort_id (Relation zu veranstaltungsorte, optional)
- {
- const c = app.findCollectionByNameOrId("pbc_2279568741")
- c.fields.addAt(99, new Field({
- "type": "relation", "id": "relation2001000120", "name": "ort_id",
- "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
- "cascadeDelete": false, "collectionId": "pbc_veranstaltungsorte", "maxSelect": 1, "minSelect": 0
- }))
- app.save(c)
- }
-
-}, (app) => {
- {
- const c = app.findCollectionByNameOrId("pbc_2279568741")
- c.fields.removeById("relation2001000120")
- app.save(c)
- }
- try { app.delete(app.findCollectionByNameOrId("pbc_ort_ausfaelle")) } catch(_) {}
- try { app.delete(app.findCollectionByNameOrId("pbc_veranstaltungsorte")) } catch(_) {}
-})
diff --git a/pocketbase/pb_migrations/1779230900_neuigkeiten.js b/pocketbase/pb_migrations/1779230900_neuigkeiten.js
deleted file mode 100644
index 210d69f..0000000
--- a/pocketbase/pb_migrations/1779230900_neuigkeiten.js
+++ /dev/null
@@ -1,190 +0,0 @@
-///
-migrate((app) => {
-
- // neuigkeiten: new Collection() + app.save() – genau wie nachrichten/mitglieder.
- // importCollections() kompiliert Rules in PB v0.38 NICHT korrekt → HTTP 400.
- const neuigkeiten = new Collection({
- "id": "pbc_1779230901",
- "name": "neuigkeiten",
- "type": "base",
- "system": false,
- "listRule": "@request.auth.verein_id = verein_id",
- "viewRule": "@request.auth.verein_id = verein_id",
- "createRule": "@request.auth.verein_id = verein_id",
- "updateRule": "@request.auth.verein_id = verein_id && autor_id = @request.auth.id",
- "deleteRule": "@request.auth.verein_id = verein_id && autor_id = @request.auth.id",
- "indexes": [],
- "fields": [
- {
- "autogeneratePattern": "[a-z0-9]{15}",
- "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",
- "hidden": false,
- "id": "relation3100049688",
- "maxSelect": 1,
- "minSelect": 0,
- "name": "verein_id",
- "presentable": false,
- "required": true,
- "system": false,
- "type": "relation"
- },
- {
- "autogeneratePattern": "",
- "hidden": false,
- "id": "text2300079512",
- "max": 0,
- "min": 0,
- "name": "autor_id",
- "pattern": "",
- "presentable": false,
- "primaryKey": false,
- "required": true,
- "system": false,
- "type": "text"
- },
- {
- "autogeneratePattern": "",
- "hidden": false,
- "id": "text1546895374",
- "max": 0,
- "min": 0,
- "name": "autor_name",
- "pattern": "",
- "presentable": false,
- "primaryKey": false,
- "required": false,
- "system": false,
- "type": "text"
- },
- {
- "autogeneratePattern": "",
- "hidden": false,
- "id": "text1059453555",
- "max": 0,
- "min": 0,
- "name": "text",
- "pattern": "",
- "presentable": false,
- "primaryKey": false,
- "required": false,
- "system": false,
- "type": "text"
- },
- {
- "hidden": false,
- "id": "file5103562148",
- "maxSelect": 10,
- "maxSize": 15728640,
- "mimeTypes": ["image/jpeg","image/png","image/gif","image/webp","video/mp4","video/quicktime"],
- "name": "medien",
- "presentable": false,
- "required": false,
- "system": false,
- "type": "file"
- },
- {
- "cascadeDelete": false,
- "collectionId": "pbc_3099069179",
- "hidden": false,
- "id": "relation3177861730",
- "maxSelect": 99,
- "minSelect": 0,
- "name": "gruppe_ids",
- "presentable": false,
- "required": false,
- "system": false,
- "type": "relation"
- },
- {
- "cascadeDelete": false,
- "collectionId": "pbc_2279568741",
- "hidden": false,
- "id": "relation2143897900",
- "maxSelect": 1,
- "minSelect": 0,
- "name": "termin_id",
- "presentable": false,
- "required": false,
- "system": false,
- "type": "relation"
- }
- ]
- });
- app.save(neuigkeiten);
-
- const neuigkeitenId = app.findCollectionByNameOrId("neuigkeiten").id;
-
- const reaktionen = new Collection({
- "id": "pbc_1779230902",
- "name": "reaktionen",
- "type": "base",
- "system": false,
- "createRule": "@request.auth.id != ''",
- "deleteRule": "@request.auth.id = user_id",
- "listRule": "@request.auth.verein_id = beitrag_id.verein_id",
- "viewRule": "@request.auth.verein_id = beitrag_id.verein_id",
- "updateRule": null,
- "indexes": ["CREATE UNIQUE INDEX idx_reaktion_unique ON reaktionen (beitrag_id, user_id)"],
- "fields": [
- {
- "autogeneratePattern": "[a-z0-9]{15}",
- "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": neuigkeitenId,
- "hidden": false,
- "id": "relation4196188140",
- "maxSelect": 1,
- "minSelect": 0,
- "name": "beitrag_id",
- "presentable": false,
- "required": true,
- "system": false,
- "type": "relation"
- },
- {
- "cascadeDelete": true,
- "collectionId": "_pb_users_auth_",
- "hidden": false,
- "id": "relation3255232538",
- "maxSelect": 1,
- "minSelect": 0,
- "name": "user_id",
- "presentable": false,
- "required": true,
- "system": false,
- "type": "relation"
- }
- ]
- });
- app.save(reaktionen);
-
-}, (app) => {
- try { app.delete(app.findCollectionByNameOrId("reaktionen")) } catch(_) {}
- try { app.delete(app.findCollectionByNameOrId("neuigkeiten")) } catch(_) {}
-})
diff --git a/scripts/seed.js b/scripts/seed.js
deleted file mode 100644
index b3d4ac7..0000000
--- a/scripts/seed.js
+++ /dev/null
@@ -1,153 +0,0 @@
-#!/usr/bin/env node
-// Seed-Script für vereins.haus (SvelteKit + SQLite, kein PocketBase)
-// Verwendung: APP_URL=https://staging.vereins.haus node scripts/seed.js
-
-const BASE = process.env.APP_URL || 'http://localhost:3000';
-
-async function req(method, path, body, token) {
- const headers = { 'Content-Type': 'application/json' };
- if (token) headers['Authorization'] = `Bearer ${token}`;
- const res = await fetch(`${BASE}/api${path}`, {
- method, headers, body: body ? JSON.stringify(body) : undefined,
- });
- const json = await res.json().catch(() => ({}));
- if (!res.ok) throw new Error(`${method} ${path} → ${res.status}: ${JSON.stringify(json)}`);
- return json;
-}
-
-async function main() {
- console.log(`→ Seed gegen ${BASE}`);
-
- // 1. Verein + Admin registrieren
- console.log('→ Registrierung...');
- const auth = await req('POST', '/auth/register', {
- vereinName: 'TSV Musterstadt 1983 e.V.',
- email: 'vorstand@tsv-musterstadt.de',
- password: 'Test123456!',
- name: 'Max Vorstand',
- });
- const T = auth.token;
- const VID = auth.verein_id;
- console.log(` ✓ Verein ID: ${VID}`);
- console.log(` ✓ User: vorstand@tsv-musterstadt.de`);
-
- // 2. Verein-Details ergänzen
- await req('PATCH', '/vereine', {
- adresse: 'Musterstraße 1', plz: '12345', ort: 'Musterstadt',
- bundesland: 'Bayern', plan: 'starter', dosb_mitglied: true,
- email: 'info@tsv-musterstadt.de', telefon: '01234 56789',
- glaeubigerid: 'DE98ZZZ09999999999',
- iban: 'DE89370400440532013000', bic: 'COBADEFFXXX',
- }, T);
-
- // 3. Gruppen
- console.log('→ Gruppen...');
- const gruppen = {};
- for (const name of ['Vorstand', 'Aktive Mitglieder', 'Jugend U15', 'Senioren']) {
- const g = await req('POST', '/gruppen', { name }, T);
- gruppen[name] = g.id;
- }
- console.log(` ✓ ${Object.keys(gruppen).length} Gruppen`);
-
- // 4. Mitglieder
- console.log('→ Mitglieder (18)...');
- const mitgliederDaten = [
- { vorname: 'Max', nachname: 'Vorstand', email: 'vorstand@tsv-musterstadt.de', status: 'aktiv', gruppe_ids: [gruppen['Vorstand'], gruppen['Aktive Mitglieder']], eintrittsdatum: '2010-01-01' },
- { vorname: 'Anna', nachname: 'Schmidt', email: 'anna.schmidt@example.de', status: 'aktiv', gruppe_ids: [gruppen['Aktive Mitglieder']], eintrittsdatum: '2015-03-15', iban: 'DE89370400440532013001' },
- { vorname: 'Thomas', nachname: 'Müller', email: 'thomas.mueller@example.de', status: 'aktiv', gruppe_ids: [gruppen['Aktive Mitglieder'], gruppen['Senioren']], eintrittsdatum: '2012-06-01' },
- { vorname: 'Lisa', nachname: 'Weber', email: 'lisa.weber@example.de', status: 'aktiv', gruppe_ids: [gruppen['Jugend U15']], eintrittsdatum: '2022-09-01', geburtsdatum: '2010-04-12' },
- { vorname: 'Klaus', nachname: 'Fischer', email: 'klaus.fischer@example.de', status: 'aktiv', gruppe_ids: [gruppen['Senioren']], eintrittsdatum: '2008-01-15' },
- { vorname: 'Maria', nachname: 'Bauer', email: 'maria.bauer@example.de', status: 'aktiv', gruppe_ids: [gruppen['Aktive Mitglieder']], eintrittsdatum: '2019-02-20' },
- { vorname: 'Peter', nachname: 'Wagner', email: 'peter.wagner@example.de', status: 'aktiv', gruppe_ids: [gruppen['Aktive Mitglieder']], eintrittsdatum: '2017-05-10', iban: 'DE89370400440532013002' },
- { vorname: 'Julia', nachname: 'Becker', email: 'julia.becker@example.de', status: 'aktiv', gruppe_ids: [gruppen['Jugend U15']], eintrittsdatum: '2023-01-01', geburtsdatum: '2011-07-22' },
- { vorname: 'Stefan', nachname: 'Hoffmann', email: 'stefan.hoffmann@example.de', status: 'passiv', gruppe_ids: [], eintrittsdatum: '2005-03-01' },
- { vorname: 'Sandra', nachname: 'Koch', email: 'sandra.koch@example.de', status: 'aktiv', gruppe_ids: [gruppen['Senioren']], eintrittsdatum: '2014-11-15' },
- { vorname: 'Michael', nachname: 'Schäfer', email: 'michael.schaefer@example.de', status: 'aktiv', gruppe_ids: [gruppen['Aktive Mitglieder']], eintrittsdatum: '2020-04-01' },
- { vorname: 'Sabine', nachname: 'Zimmermann', email: 'sabine.zimm@example.de', status: 'aktiv', gruppe_ids: [gruppen['Aktive Mitglieder']], eintrittsdatum: '2018-08-20' },
- { vorname: 'Andreas', nachname: 'Braun', email: 'andreas.braun@example.de', status: 'aktiv', gruppe_ids: [gruppen['Senioren']], eintrittsdatum: '2011-01-01' },
- { vorname: 'Monika', nachname: 'Richter', email: 'monika.richter@example.de', status: 'passiv', gruppe_ids: [], eintrittsdatum: '2009-06-15' },
- { vorname: 'Tobias', nachname: 'Wolf', email: 'tobias.wolf@example.de', status: 'aktiv', gruppe_ids: [gruppen['Jugend U15']], eintrittsdatum: '2022-01-15', geburtsdatum: '2012-01-30' },
- { vorname: 'Eva', nachname: 'Krause', email: 'eva.krause@example.de', status: 'aktiv', gruppe_ids: [gruppen['Aktive Mitglieder']], eintrittsdatum: '2021-03-01' },
- { vorname: 'Markus', nachname: 'Schwarz', email: 'markus.schwarz@example.de', status: 'aktiv', gruppe_ids: [gruppen['Aktive Mitglieder'], gruppen['Vorstand']], eintrittsdatum: '2013-09-01' },
- { vorname: 'Nina', nachname: 'Lange', email: 'nina.lange@example.de', status: 'ausgetreten', gruppe_ids: [], eintrittsdatum: '2016-02-01', austrittsdatum: '2023-12-31' },
- ];
- for (const m of mitgliederDaten) await req('POST', '/mitglieder', m, T);
- console.log(` ✓ 18 Mitglieder`);
-
- // 5. Veranstaltungsorte
- console.log('→ Orte...');
- const orte = {};
- for (const o of [
- { name: 'Turnhalle Grundschule Muster', adresse: 'Schulweg 5, 12345 Musterstadt', typ: 'halle', aktiv: true },
- { name: 'Vereinsheim TSV', adresse: 'Musterstraße 3, 12345 Musterstadt', typ: 'gebaeude', aktiv: true },
- { name: 'Sportplatz West', adresse: 'Weststraße 10, 12345 Musterstadt', typ: 'platz', aktiv: true },
- ]) {
- const r = await req('POST', '/orte', o, T);
- orte[o.name] = r.id;
- }
- console.log(` ✓ 3 Orte`);
-
- // 6. Beitragsarten
- console.log('→ Beitragsarten...');
- for (const b of [
- { name: 'Jahresbeitrag Erwachsene', betrag: 120, rhythmus: 'jaehrlich', beschreibung: 'Normalbeitrag für erwachsene Mitglieder' },
- { name: 'Jahresbeitrag Jugend', betrag: 60, rhythmus: 'jaehrlich', beschreibung: 'Ermäßigter Beitrag bis 18 Jahre' },
- { name: 'Aufnahmegebühr', betrag: 25, rhythmus: 'einmalig', beschreibung: 'Einmalige Gebühr bei Eintritt' },
- { name: 'Monatsbeitrag Fitness', betrag: 15, rhythmus: 'monatlich', beschreibung: 'Zusatzbeitrag Fitnessraum' },
- ]) {
- await req('POST', '/beitraege', b, T);
- }
- console.log(` ✓ 4 Beitragsarten`);
-
- // 7. Termine
- console.log('→ Termine...');
- const now = new Date();
- const dt = (offsetDays, h = 18, m = 0) => {
- const d = new Date(now);
- d.setDate(d.getDate() + offsetDays);
- d.setHours(h, m, 0, 0);
- return d.toISOString().slice(0, 19);
- };
-
- const termine = [
- { titel: 'Vorstandssitzung', beginn: dt(3, 19), ort_id: orte['Vereinsheim TSV'], gruppe_ids: [gruppen['Vorstand']], verfuegbarkeit: 'bestaetigt' },
- { titel: 'Vereinsmeisterschaft', beginn: dt(14, 9), ort_id: orte['Sportplatz West'], gruppe_ids: [], beschreibung: 'Jährliche Meisterschaft', verfuegbarkeit: 'bestaetigt' },
- { titel: 'Jugendtraining', beginn: dt(2, 16), ort_id: orte['Turnhalle Grundschule Muster'], gruppe_ids: [gruppen['Jugend U15']], verfuegbarkeit: 'bestaetigt' },
- { titel: 'Mitgliederversammlung', beginn: dt(21, 19,30),ort_id: orte['Vereinsheim TSV'], gruppe_ids: [], beschreibung: 'Ordentliche Jahreshauptversammlung', verfuegbarkeit: 'offen' },
- { titel: 'Seniorensport', beginn: dt(7, 10), ort_id: orte['Turnhalle Grundschule Muster'], gruppe_ids: [gruppen['Senioren']], verfuegbarkeit: 'bestaetigt' },
- { titel: 'Sommerfest', beginn: dt(45, 14), ort_id: orte['Sportplatz West'], gruppe_ids: [], beschreibung: 'Großes Vereinssommerfest', verfuegbarkeit: 'offen' },
- ];
- // 8 Wochen Dienstags-Training
- let di = new Date(now);
- di.setDate(di.getDate() + ((2 - di.getDay() + 7) % 7 || 7));
- di.setHours(18, 0, 0, 0);
- for (let i = 0; i < 8; i++) {
- const b = new Date(di); b.setDate(b.getDate() + i * 7);
- termine.push({
- titel: 'Training Aktive', beginn: b.toISOString().slice(0, 19),
- ort_id: orte['Turnhalle Grundschule Muster'],
- gruppe_ids: [gruppen['Aktive Mitglieder']], verfuegbarkeit: 'offen',
- rrule: 'FREQ=WEEKLY;BYDAY=TU',
- });
- }
- for (const t of termine) await req('POST', '/termine', t, T);
- console.log(` ✓ ${termine.length} Termine`);
-
- // 8. Nachricht
- console.log('→ Nachricht...');
- await req('POST', '/nachrichten', {
- betreff: 'Willkommen in vereins.haus!',
- text: 'Hallo und herzlich willkommen! Dies ist eine Beispiel-Nachricht an alle Mitglieder.',
- gruppe_ids: [],
- }, T);
- console.log(` ✓ Nachricht erstellt`);
-
- console.log('');
- console.log('✓ Seed abgeschlossen!');
- console.log('');
- console.log(` Verein: TSV Musterstadt 1983 e.V.`);
- console.log(` Login: vorstand@tsv-musterstadt.de / Test123456!`);
- console.log(` App: ${BASE}`);
-}
-
-main().catch(e => { console.error('✗ Seed-Fehler:', e.message); process.exit(1); });