- Proxy-Sperre für /sign-up, INVITE_REQUIRED, set-invite/consume-invite,
invites-Router, Admin-Invite-Seite und -Prozeduren, Startcodes in
pets.create entfernt — Registrierung ist offen
- /join?code=… leitet auf /r/<code> um, /invite auf /einladen; alte geteilte
Links laufen nicht ins Leere
- InviteCode-Tabelle bleibt nur als Historie (Kontolöschung räumt sie weiter auf)
- Datenschutzerklärung 4b (Empfehlungsprogramm, Herkunftsangabe, pf_ref-Cookie)
und Nutzungsbedingungen 3b (Programmbedingungen, Bestandsschutz, Missbrauch)
- CLAUDE.md: Abschnitt "Referral program" statt "Invite system"
Build, Typecheck und alle Tests grün.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replaces the (never-launched) ad business direction with a freiwilliger
Beitrag/Community-Abo model: owners can support PawFeed via Stripe with
a one-time payment or a monthly SEPA/card subscription (2 EUR minimum),
declared as Sponsoring/Community-Abo, not a tax-deductible donation. The
Sponsor badge (only perk, no feature unlocks) is Owner-scoped and shows
on every post of every one of their pets, plus their profile.
- Schema: Owner.stripeCustomerId/sponsorSince/sponsorPeriodEnd/
sponsorSubscriptionId/sponsorSubscriptionStatus, new
SponsorContribution model (payment history)
- src/lib/stripe.ts (singleton client, mux.ts pattern) + src/lib/sponsor.ts
(isActiveSponsor: one-time givers keep the badge permanently, subscribers
through sponsorPeriodEnd even after cancelling -- no mid-period cliff)
- src/trpc/routers/sponsor.ts: getStatus, createCheckoutSession (SEPA +
card for subscriptions, card for one-time, price_data since amounts are
user-chosen), cancelSubscription (cancel_at_period_end, no hard stop)
- src/app/api/webhooks/stripe: checkout.session.completed, invoice.paid,
customer.subscription.updated/deleted (mux webhook route pattern)
- src/app/(app)/unterstuetzen: amount chips (3/5/10 EUR) prefilling an
editable field, one-time/subscription toggle, status + contribution
history, in-app cancel button (no Stripe-hosted portal, per requirement)
- Badge surfaced in feed/explore postInclude + PostCard, and on the pet
profile page next to the existing isVerified badge
- Nutzungsbedingungen Sec. 12 (draft, needs sign-off before launch) +
Stripe added to the Datenschutzerklaerung's Auftragsverarbeiter list
- src/__tests__/sponsor.test.ts (7 tests), Stripe client fully mocked
STRIPE_WEBHOOK_SECRET intentionally not yet configured -- the webhook
endpoint can only be registered in the Stripe dashboard once this is
deployed and reachable at https://pawfeed.org/api/webhooks/stripe.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Thin nodemailer wrapper reading SMTP config from env vars. Uses a
provider-hosted mailbox (Strato, same as the domain host) rather than
a self-hosted relay from the NAS's residential/dynamic IP, which would
get spam-flagged by most receiving servers regardless of DKIM/SPF.
Foundation for the GDPR Art. 15 data-export feature (Plan B: expiring
download link + email notification) — no caller wired up yet. Verified
end-to-end with a real test send before committing.
Server, edge, and client instrumentation wired up via SENTRY_DSN /
NEXT_PUBLIC_SENTRY_DSN. Fully inert without a DSN configured (verified
with a clean local build + full test suite) — safe to ship ahead of
actually having a Sentry project. Source-map upload is opt-in via
SENTRY_AUTH_TOKEN (kept out of the Docker build-arg chain since build
args land in image layer history; only the public DSN is a build arg).
- consume-invite: check-then-act race on single-use invite redemption —
two concurrent redemptions could both pass the pre-check. Now guarded
with an atomic updateMany(where: {code, usedById: null, revoked: false}).
- schema.prisma: added missing index on Report.targetPostId (used by
admin.ts's groupBy/filter).
- RepostCard.tsx: reposter link used a raw <a> (full page reload) instead
of next/link; the original poster's header had no link at all.
- Sidebar.tsx/MobileNav.tsx: removed the duplicated "set active pet on
first load" effect — ActivePetInitializer already does this centrally
and is mounted alongside both in the app layout.
- prisma.ts: removed the dead Vercel/Prisma Accelerate code path
(PRISMA_ACCELERATE_URL is never set — this is a self-hosted Docker
deployment) and the now-unused @prisma/extension-accelerate dependency.
- .env.example: corrected the NEXT_PUBLIC_APP_URL comment (Mux cors_origin
is intentionally "*", not wired to it) and documented CRON_SECRET,
which was missing despite both cron routes requiring it.
- reactions.ts: removed hasReacted/getCount — dead code with no frontend
callers; getCount also duplicated the denormalized Post.reactionCount.
.env.example was missing MUX_TOKEN_ID/MUX_TOKEN_SECRET/MUX_WEBHOOK_SECRET/
NEXT_PUBLIC_APP_URL even though they're required fields per docker/.env.
AvatarUpload's docblock still described the pre-migration R2 upload path;
storage has been Supabase-only since the R2 removal.
Local .env/.env.local consolidation (not tracked by git) done alongside
this: merged the split DB/Supabase/Clerk/Mux config into .env.local and
removed the dead, never-filled R2 placeholder vars from .env.