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
|
# und hier nachziehen. Tag plus Digest: der Tag ist lesbar, der
|
||||||
# Digest macht das Image unveraenderlich (Tags lassen sich in der
|
# Digest macht das Image unveraenderlich (Tags lassen sich in der
|
||||||
# Registry ueberschreiben).
|
# 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
|
imagePullPolicy: IfNotPresent
|
||||||
securityContext:
|
securityContext:
|
||||||
allowPrivilegeEscalation: false
|
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