feat: turbostarter boilerplate

Production-ready Next.js boilerplate with:
- Runtime env validation (fail-fast on missing vars)
- Feature-gated config (S3, Stripe, email, OAuth)
- Docker + Coolify deployment pipeline
- PostgreSQL + pgvector, MinIO S3, Better Auth
- TypeScript strict mode (no ignoreBuildErrors)
- i18n (en/es), AI modules, billing, monitoring

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-02-02 17:29:12 +00:00
commit 3527e732d4
1618 changed files with 338230 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
import { getSubscription } from "@lemonsqueezy/lemonsqueezy.js";
import { HttpStatusCode } from "@turbostarter/shared/constants";
import { logger } from "@turbostarter/shared/logger";
import { HttpException } from "@turbostarter/shared/utils";
import { config } from "../../config";
import { getCustomerByCustomerId, updateCustomer } from "../../lib/customer";
import { toBillingStatus } from "./mappers/to-billing-status";
export const subscriptionStatusChangeHandler = async ({
id,
}: {
id: string;
}) => {
const { data } = await getSubscription(id);
const subscription = data?.data;
if (!subscription) {
throw new HttpException(HttpStatusCode.NOT_FOUND, {
code: "billing:error.subscriptionNotFound",
});
}
const customer = await getCustomerByCustomerId(
subscription.attributes.customer_id.toString(),
);
if (!customer) {
throw new HttpException(HttpStatusCode.NOT_FOUND, {
code: "billing:error.customerNotFound",
});
}
const priceId = subscription.attributes.variant_id.toString();
const plan = config.plans.find((p) => p.prices.find((x) => x.id === priceId));
await updateCustomer(customer.userId, {
status: toBillingStatus(subscription.attributes.status),
...(plan && { plan: plan.id }),
});
logger.info(
`✅ Subscription status changed for user ${customer.userId} to ${subscription.attributes.status}`,
);
};