chore(k8s): Wabenhain auf Build sha-ed17d14 (Bienenpatenschaft), Einrichtungsskript
Docker Image bauen und veroeffentlichen / typecheck (push) Successful in 2m21s
Docker Image bauen und veroeffentlichen / lint (push) Successful in 2m27s
Docker Image bauen und veroeffentlichen / test (push) Successful in 3m19s
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 7m44s
Docker Image bauen und veroeffentlichen / typecheck (push) Successful in 2m21s
Docker Image bauen und veroeffentlichen / lint (push) Successful in 2m27s
Docker Image bauen und veroeffentlichen / test (push) Successful in 3m19s
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 7m44s
Ausgerollt am 2026-10-16, Migrationen und Seed beim Start gelaufen. scripts/sponsorship-bootstrap.ts verknuepft den Paket-Honig und legt die Abo-Angebote inaktiv samt Stripe-Preis an. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
e3b0eb7b05
commit
f786601309
2 files changed
+133
-1
No files matched your search
@@ -86,7 +86,7 @@ spec:
|
||||
# und hier nachziehen. Tag plus Digest: der Tag ist lesbar, der
|
||||
# Digest macht das Image unveraenderlich (Tags lassen sich in der
|
||||
# Registry ueberschreiben).
|
||||
image: git.encry.de/nick.rosenloecher/wabenhain:sha-0d254db@sha256:2a81f6a0ccfa27139517558aea2b61c9c258122e15ecce00effd9abec9b36d51
|
||||
image: git.encry.de/nick.rosenloecher/wabenhain:sha-ed17d14@sha256:9f3c66f603aaa3d2c7e921b654130cc755b0f84119bd498f9a1a22768e6da6c9
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Ersteinrichtung der Bienenpatenschaft in einer bestehenden Installation.
|
||||
*
|
||||
* Wiederholbar: legt nur an, was fehlt, und aendert nie ein Angebot, das der
|
||||
* Inhaber schon bearbeitet hat (Felder werden nur gesetzt, wenn sie leer sind).
|
||||
*
|
||||
* - verknuepft die physischen Einmal-Pakete mit einem Honigprodukt,
|
||||
* - legt die beiden Abo-Angebote INAKTIV an (Testpreise, im Admin pruefen),
|
||||
* - legt fuer jedes Abo-Angebot Stripe-Produkt, Monatspreis und
|
||||
* Traegerprodukt an (syncOfferStripePrice).
|
||||
*
|
||||
* Aufruf (Werkzeug-Image, Umgebung wie die App):
|
||||
* tsx scripts/sponsorship-bootstrap.ts --honey=<produkt-slug> (Probelauf)
|
||||
* tsx scripts/sponsorship-bootstrap.ts --honey=<produkt-slug> --apply (ausfuehren)
|
||||
*/
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { syncOfferStripePrice } from "@/lib/sponsorship-subscription/stripe-prices";
|
||||
|
||||
interface AboTemplate {
|
||||
slug: string;
|
||||
name: string;
|
||||
nameEn: string;
|
||||
description: string;
|
||||
descriptionEn: string;
|
||||
features: string[];
|
||||
featuresEn: string[];
|
||||
priceCents: number;
|
||||
honeyJars: number;
|
||||
honeyShareCentsPerMonth: number;
|
||||
cycle: "VIERTELJAEHRLICH" | "ERNTE";
|
||||
maxActiveSubscriptions: number;
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
// Testpreise — vor der Aktivierung kaufmaennisch pruefen (Spec, "Geschaeftliche
|
||||
// Konfiguration"). Honiganteil je Monat = Glaeser je Lieferung × 7 € ÷ Monate
|
||||
// je Lieferung, zum Honig-Steuersatz.
|
||||
const ABO_TEMPLATES: AboTemplate[] = [
|
||||
{
|
||||
slug: "abo-vierteljaehrlich",
|
||||
name: "Patenschaft im Abo, vierteljährlich",
|
||||
nameEn: "Sponsorship subscription, quarterly",
|
||||
description: "Monatlich zahlen, alle drei bezahlten Monate zwei Gläser Wabenhain-Honig. Versand inklusive, monatlich kündbar.",
|
||||
descriptionEn: "Pay monthly, two jars of Wabenhain honey after every three paid months. Shipping included, cancel monthly.",
|
||||
features: ["Symbolische Namensgebung mit Urkunde", "2 × 500 g Honig nach jeweils 3 bezahlten Monaten", "Versand inklusive", "Monatlich kündbar"],
|
||||
featuresEn: ["Symbolic naming with certificate", "2 × 500 g honey after every 3 paid months", "Shipping included", "Cancel monthly"],
|
||||
priceCents: 1290,
|
||||
honeyJars: 2,
|
||||
honeyShareCentsPerMonth: 467,
|
||||
cycle: "VIERTELJAEHRLICH",
|
||||
maxActiveSubscriptions: 20,
|
||||
sortOrder: 10,
|
||||
},
|
||||
{
|
||||
slug: "abo-ernte",
|
||||
name: "Patenschaft im Abo, nach der Ernte",
|
||||
nameEn: "Sponsorship subscription, after the harvest",
|
||||
description: "Monatlich zahlen, Honig frisch nach der Ernte: drei Gläser, sobald seit der letzten Lieferung sechs Monate bezahlt sind. Versand inklusive, monatlich kündbar.",
|
||||
descriptionEn: "Pay monthly, honey fresh after the harvest: three jars once six months have been paid since the last delivery. Shipping included, cancel monthly.",
|
||||
features: ["Symbolische Namensgebung mit Urkunde", "3 × 500 g Honig im Erntefenster", "Versand inklusive", "Monatlich kündbar"],
|
||||
featuresEn: ["Symbolic naming with certificate", "3 × 500 g honey in the harvest window", "Shipping included", "Cancel monthly"],
|
||||
priceCents: 990,
|
||||
honeyJars: 3,
|
||||
honeyShareCentsPerMonth: 350,
|
||||
cycle: "ERNTE",
|
||||
maxActiveSubscriptions: 10,
|
||||
sortOrder: 11,
|
||||
},
|
||||
];
|
||||
|
||||
function arg(name: string): string | undefined {
|
||||
const hit = process.argv.find((value) => value.startsWith(`--${name}=`));
|
||||
return hit ? hit.slice(name.length + 3) : undefined;
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const apply = process.argv.includes("--apply");
|
||||
const honeySlug = arg("honey");
|
||||
if (!honeySlug) throw new Error("--honey=<produkt-slug> fehlt.");
|
||||
|
||||
const honey = await prisma.product.findUnique({ where: { slug: honeySlug }, select: { id: true, name: true, category: true, isActive: true } });
|
||||
if (!honey || honey.category !== "HONIG" || !honey.isActive) {
|
||||
throw new Error(`Honigprodukt "${honeySlug}" nicht gefunden, nicht HONIG oder nicht aktiv.`);
|
||||
}
|
||||
console.log(`${apply ? "AUSFUEHREN" : "PROBELAUF"} — Honig: ${honey.name}`);
|
||||
|
||||
const physical = await prisma.sponsorshipOffer.findMany({
|
||||
where: { kind: "EINMALIG", isPhysical: true, honeyProductId: null },
|
||||
select: { id: true, slug: true },
|
||||
});
|
||||
for (const offer of physical) {
|
||||
console.log(` Paket ${offer.slug}: Honig verknuepfen`);
|
||||
if (apply) await prisma.sponsorshipOffer.update({ where: { id: offer.id }, data: { honeyProductId: honey.id } });
|
||||
}
|
||||
|
||||
for (const template of ABO_TEMPLATES) {
|
||||
const existing = await prisma.sponsorshipOffer.findUnique({ where: { slug: template.slug }, select: { id: true, stripePriceId: true } });
|
||||
if (existing) {
|
||||
console.log(` Abo ${template.slug}: vorhanden${existing.stripePriceId ? "" : ", Stripe-Preis fehlt"}`);
|
||||
if (apply && !existing.stripePriceId) await syncOfferStripePrice(existing.id);
|
||||
continue;
|
||||
}
|
||||
console.log(` Abo ${template.slug}: anlegen (inaktiv, ${(template.priceCents / 100).toFixed(2)} €/Monat) + Stripe-Preis`);
|
||||
if (!apply) continue;
|
||||
const created = await prisma.sponsorshipOffer.create({
|
||||
data: {
|
||||
...template,
|
||||
kind: "ABO",
|
||||
isActive: false,
|
||||
isPhysical: true,
|
||||
honeyProductId: honey.id,
|
||||
medalTier: "ABO",
|
||||
},
|
||||
});
|
||||
await syncOfferStripePrice(created.id);
|
||||
}
|
||||
|
||||
const offers = await prisma.sponsorshipOffer.findMany({
|
||||
orderBy: { sortOrder: "asc" },
|
||||
select: { slug: true, kind: true, isActive: true, priceCents: true, honeyProductId: true, stripePriceId: true, productId: true },
|
||||
});
|
||||
console.table(offers);
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((error: unknown) => {
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
Reference in new issue
Block a user