Skip to content

Commit aefa043

Browse files
Merge pull request #24 from nyxCore-Systems/feat/foerderer-donate-page
feat(foerderer): 2-column sponsor page with logos + :::donate::: directive
2 parents 067a818 + cdb82b7 commit aefa043

23 files changed

Lines changed: 453 additions & 28 deletions
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: Seed Förderer page
2+
3+
# One-off, manually-triggered content seed. Runs prisma/seed-foerderer-page.ts
4+
# inside the migrator container ON the server, where the compose env_file (.env)
5+
# provides the live DATABASE_URL (self-hosted eventschau Postgres). The script is
6+
# idempotent — safe to re-run. Requires the source already rsynced to the server
7+
# by the normal deploy (push to main), so run AFTER a deploy completes.
8+
#
9+
# Reuses the deploy SSH secrets: DEPLOY_SSH_KEY / DEPLOY_HOST / DEPLOY_USER / DEPLOY_PORT.
10+
11+
on:
12+
workflow_dispatch:
13+
14+
concurrency:
15+
group: seed-eventschau-production
16+
cancel-in-progress: false
17+
18+
env:
19+
APP_DIR: /opt/e-ventschau
20+
MIGRATE_SERVICE: eventschau-migrate
21+
22+
jobs:
23+
seed:
24+
name: Seed Förderer page (migrator container)
25+
runs-on: ubuntu-latest
26+
environment: production
27+
steps:
28+
- name: Configure SSH
29+
env:
30+
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
31+
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
32+
DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }}
33+
run: |
34+
set -euo pipefail
35+
install -m 700 -d ~/.ssh
36+
printf '%s\n' "$DEPLOY_SSH_KEY" > ~/.ssh/id_deploy
37+
chmod 600 ~/.ssh/id_deploy
38+
ssh-keyscan -p "${DEPLOY_PORT:-22}" -H "${DEPLOY_HOST:-nyxcore.cloud}" >> ~/.ssh/known_hosts 2>/dev/null
39+
40+
- name: Run seed in migrator container
41+
env:
42+
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
43+
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
44+
DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }}
45+
run: |
46+
set -euo pipefail
47+
ssh -i ~/.ssh/id_deploy -p "${DEPLOY_PORT:-22}" \
48+
"${DEPLOY_USER:-root}@${DEPLOY_HOST:-nyxcore.cloud}" \
49+
APP_DIR="$APP_DIR" MIGRATE_SVC="$MIGRATE_SERVICE" \
50+
'bash -seuo pipefail' <<'REMOTE'
51+
cd "$APP_DIR"
52+
docker compose --profile migrate run --rm --build "$MIGRATE_SVC" \
53+
npx tsx prisma/seed-foerderer-page.ts
54+
REMOTE

prisma/seed-foerderer-page.ts

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/**
2+
* One-off, idempotent content seed for the "Unsere Förderer und Sponsoren"
3+
* (/foerderer) page and the "Ihre Spende" (/unterstuetzung/spende) page.
4+
*
5+
* Runs in the migrator container on the server (live DATABASE_URL from compose
6+
* env_file). Self-contained — NO imports from src/ (the migrator image only
7+
* copies prisma/ + node_modules), so the page markdown is embedded here.
8+
*
9+
* docker compose --profile migrate run --rm --build eventschau-migrate \
10+
* npx tsx prisma/seed-foerderer-page.ts
11+
*
12+
* The markdown below MUST stay in sync with src/content/foerderer.md and
13+
* src/content/spende.md (the canonical re-seed sources). Set DRY_RUN=1 to log
14+
* the planned markdown without writing.
15+
*
16+
* Overwrites page.content and resets contentJson/editorMode so the new markdown
17+
* is authoritative in both the public renderer and the admin editor.
18+
*/
19+
import { PrismaClient, Prisma } from '@prisma/client'
20+
21+
const prisma = new PrismaClient()
22+
const DRY = process.env.DRY_RUN === '1'
23+
const TENANT_SLUG = process.env.TENANT_SLUG || 'e-ventschau'
24+
25+
type Entry = { name: string; url?: string | null; logo?: string | null }
26+
27+
// Förderer (Hauptunterstützer)
28+
const FOERDERER: Entry[] = [
29+
{
30+
name: 'Sparkassenstiftung Lüneburg',
31+
url: 'http://www.sparkassenstiftung-lueneburg.de/aktuell.html',
32+
logo: '/foerderer-sparkassenstiftung.png',
33+
},
34+
]
35+
36+
// Sponsoren. `logo` is a self-hosted path under /public (see public/foerderer/*)
37+
// or null when no logo was found — then only name + link are shown.
38+
const SPONSORS: Entry[] = [
39+
{ name: 'Fips', url: 'https://www.fips-laden.de/', logo: '/foerderer/fips.svg' },
40+
{ name: 'GFA Lüneburg', url: 'http://www.gfa-lueneburg.de', logo: '/foerderer/gfa-lueneburg.png' },
41+
{ name: 'BWC Lüneburg', url: 'http://www.bwc-lueneburg.de', logo: '/foerderer/bwc-lueneburg.png' },
42+
{ name: 'Lübelner Mühle', url: null, logo: null },
43+
{ name: 'Haustechnik Will', url: 'http://www.haustechnik-will.de/', logo: null },
44+
{ name: 'CONTIGO Lüneburg', url: 'http://www.lueneburg.contigo.de', logo: '/foerderer/contigo.png' },
45+
{ name: 'Butenschön Design', url: 'http://www.ackermann-leder.de/', logo: null },
46+
{ name: 'Event Manufaktur', url: 'http://www.naturdirekt.de', logo: null },
47+
{ name: 'Stadtlichter', url: 'http://www.stadtlichter.com', logo: null },
48+
{ name: 'Zimmerei Jacob', url: 'https://zimmerei-jacob.de/', logo: '/foerderer/zimmerei-jacob.png' },
49+
{ name: 'SONIC SOUND', url: 'https://sonicsound.business.site', logo: null },
50+
{ name: 'Delinat', url: 'https://www.delinat.com/', logo: '/foerderer/delinat.svg' },
51+
{ name: 'Biomarkt Vitalis', url: 'http://www.biomarkt-vitalis.de/', logo: null },
52+
{ name: 'Lünestrom', url: 'http://www.evdbag.de', logo: null },
53+
{ name: 'PROTONES Veranstaltungstechnik', url: 'http://www.protones-veranstaltungstechnik.de/', logo: null },
54+
{ name: 'Autohaus Kurt Niehoff', url: 'http://autohaus-niehoff.de/', logo: '/foerderer/autohaus-niehoff.png' },
55+
{ name: 'PROFI MUSIK', url: 'http://www.profimusik.de/', logo: '/foerderer/profimusik.png' },
56+
{ name: 'VGH Michael Steisgerski', url: null, logo: null },
57+
]
58+
59+
function cell(e: Entry): string {
60+
const parts: string[] = []
61+
if (e.logo) parts.push(`![${e.name}|180](${e.logo})`)
62+
parts.push(e.url ? `**[${e.name}](${e.url})**` : `**${e.name}**`)
63+
return parts.join('\n\n')
64+
}
65+
66+
function columns2(entries: Entry[]): string {
67+
return `:::columns-2\n${entries.map(cell).join('\n---\n')}\n:::`
68+
}
69+
70+
const FOERDERER_CONTENT = `# Unsere Förderer und Sponsoren
71+
72+
e-Ventschau ist ein Benefiz-Open-Air – möglich nur durch die Unterstützung von Förderern, Sponsoren und vielen Spender:innen. Ihnen allen gilt unser herzlicher Dank. Jeder Beitrag fließt in den guten Zweck.
73+
74+
Sie möchten das Festival unterstützen? Jede Spende hilft:
75+
76+
:::donate
77+
:::
78+
79+
## Unsere Förderer
80+
81+
${columns2(FOERDERER)}
82+
83+
## Unsere Sponsoren
84+
85+
${columns2(SPONSORS)}
86+
`
87+
88+
const SPENDE_CONTENT = `# Unterstützen Sie e-Ventschau
89+
90+
e-Ventschau lebt von Menschen, die mit anpacken – und von finanzieller Unterstützung. Jeder überschüssige Euro fließt in den Benefiz-Zweck.
91+
92+
:::donate
93+
:::
94+
95+
## Volontäre
96+
e-Ventschau ist darauf angewiesen, dass Menschen bereit sind, ihr Knowhow und ihre Arbeitskraft zur Vorbereitung und Durchführung des Festivals einzubringen. Freiwilligenarbeit im Sinne von selbstbestimmtem Zupacken.
97+
98+
## Sponsoren
99+
Sponsoring- und Präsentationsmöglichkeiten rund um das Benefiz-Open-Air e-Ventschau. Sponsoren erleben Image-Transfer und helfen, das Fest in der regionalen Kulturlandschaft zu etablieren.
100+
101+
## Förderer / Patenschaften
102+
Jeder kann ein Förderer sein und die gute Sache mit einer finanziellen Zuwendung unterstützen.
103+
104+
Alle bisherigen Förderer und Sponsoren finden Sie auf der Seite [Unsere Förderer](/foerderer).
105+
`
106+
107+
const PAGES = [
108+
{
109+
slug: 'foerderer',
110+
path: '/foerderer',
111+
title: 'Unsere Förderer und Sponsoren',
112+
description: 'Die Unterstützer und Sponsoren des Benefiz-Open-Air-Festivals e-Ventschau.',
113+
content: FOERDERER_CONTENT,
114+
},
115+
{
116+
slug: 'spende',
117+
path: '/unterstuetzung/spende',
118+
title: 'Ihre Spende',
119+
description: 'Unterstützen Sie e-Ventschau durch Spenden, Sponsoring oder freiwilliges Engagement.',
120+
content: SPENDE_CONTENT,
121+
},
122+
]
123+
124+
async function main() {
125+
const tenant = await prisma.tenant.findUnique({ where: { slug: TENANT_SLUG } })
126+
if (!tenant) throw new Error(`Tenant '${TENANT_SLUG}' not found`)
127+
console.log(`Tenant: ${tenant.slug} (${tenant.id})${DRY ? ' [DRY RUN]' : ''}\n`)
128+
129+
for (const p of PAGES) {
130+
if (DRY) {
131+
console.log(`--- ${p.slug} (${p.path}) ---\n${p.content}\n`)
132+
continue
133+
}
134+
await prisma.page.upsert({
135+
where: { tenantId_slug: { tenantId: tenant.id, slug: p.slug } },
136+
update: {
137+
title: p.title,
138+
path: p.path,
139+
content: p.content,
140+
contentJson: Prisma.DbNull,
141+
editorMode: 'markdown',
142+
metaTitle: p.title,
143+
metaDescription: p.description,
144+
isPublished: true,
145+
},
146+
create: {
147+
tenantId: tenant.id,
148+
slug: p.slug,
149+
path: p.path,
150+
title: p.title,
151+
content: p.content,
152+
editorMode: 'markdown',
153+
metaTitle: p.title,
154+
metaDescription: p.description,
155+
isPublished: true,
156+
},
157+
})
158+
console.log(`✓ ${p.slug}: updated (${p.content.length} chars)`)
159+
}
160+
161+
console.log(`\n=== ${DRY ? 'DRY RUN — ' : ''}done: ${PAGES.length} page(s) ===`)
162+
}
163+
164+
main()
165+
.then(() => prisma.$disconnect())
166+
.then(() => process.exit(0))
167+
.catch(async (e) => {
168+
console.error(e)
169+
await prisma.$disconnect()
170+
process.exit(1)
171+
})
6.52 KB
Loading

public/foerderer/bwc-lueneburg.png

11.6 KB
Loading

public/foerderer/contigo.png

12.8 KB
Loading

public/foerderer/delinat.svg

Lines changed: 25 additions & 0 deletions
Loading

public/foerderer/fips.svg

Lines changed: 35 additions & 0 deletions
Loading

public/foerderer/gfa-lueneburg.png

12.6 KB
Loading

public/foerderer/profimusik.png

6.75 KB
Loading
18.9 KB
Loading

0 commit comments

Comments
 (0)