diff --git a/apps/licenses/src/migrate.ts b/apps/licenses/migrate.ts similarity index 100% rename from apps/licenses/src/migrate.ts rename to apps/licenses/migrate.ts diff --git a/apps/licenses/package.json b/apps/licenses/package.json index ea768aeb..83213975 100644 --- a/apps/licenses/package.json +++ b/apps/licenses/package.json @@ -9,9 +9,9 @@ "typecheck": "tsc --noEmit", "generate": "drizzle-kit generate", "drop": "drizzle-kit drop", - "migrate": "tsx src/migrate.ts", - "truncate": "tsx src/truncate.ts", - "reset:all": "tsx src/truncate.ts && tsx src/migrate.ts", + "migrate": "tsx migrate.ts", + "truncate": "tsx truncate.ts", + "reset:all": "tsx truncate.ts && tsx migrate.ts", "studio": "drizzle-kit studio" }, "dependencies": { diff --git a/apps/licenses/src/email/index.ts b/apps/licenses/src/email/index.ts new file mode 100644 index 00000000..8aad6b3d --- /dev/null +++ b/apps/licenses/src/email/index.ts @@ -0,0 +1,11 @@ +import { createTransport } from "nodemailer"; + +export const transporter = createTransport({ + host: process.env.SMTP_HOST, + port: Number(process.env.SMTP_PORT), + secure: true, + auth: { + user: process.env.SMTP_USER, + pass: process.env.SMTP_PASS, + }, +}); diff --git a/apps/licenses/src/index.ts b/apps/licenses/src/index.ts index df15d962..0638674b 100644 --- a/apps/licenses/src/index.ts +++ b/apps/licenses/src/index.ts @@ -4,9 +4,7 @@ import { cors } from "hono/cors"; import { z } from "zod"; import { zValidator } from "@hono/zod-validator"; import { logger } from "./logger"; -import Stripe from "stripe"; import { render } from "@react-email/render"; -import { createTransport } from "nodemailer"; import { LicenseEmail } from "../templates/emails/license-email"; import { ResendLicenseEmail } from "../templates/emails/resend-license-email"; import { @@ -18,24 +16,14 @@ import { db } from "./db"; import { eq } from "drizzle-orm"; import { licenses } from "./schema"; import "dotenv/config"; +import { getLicenseFeatures, getLicenseTypeFromPriceId } from "./utils"; +import { transporter } from "./email"; +import type Stripe from "stripe"; +import { stripe } from "./stripe"; const app = new Hono(); app.use("/*", cors()); -const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { - apiVersion: "2024-09-30.acacia", -}); - -const transporter = createTransport({ - host: process.env.SMTP_HOST, - port: Number(process.env.SMTP_PORT), - secure: true, - auth: { - user: process.env.SMTP_USER, - pass: process.env.SMTP_PASS, - }, -}); - const validateSchema = z.object({ licenseKey: z.string(), serverIp: z.string(), @@ -45,7 +33,6 @@ const resendSchema = z.object({ licenseKey: z.string(), }); -// Endpoint para validar una licencia app.post("/validate", zValidator("json", validateSchema), async (c) => { const { licenseKey, serverIp } = c.req.valid("json"); @@ -58,7 +45,6 @@ app.post("/validate", zValidator("json", validateSchema), async (c) => { } }); -// Endpoint para activar una licencia app.post("/activate", zValidator("json", validateSchema), async (c) => { const { licenseKey, serverIp } = c.req.valid("json"); @@ -74,7 +60,6 @@ app.post("/activate", zValidator("json", validateSchema), async (c) => { } }); -// Endpoint para reenviar una licencia por email app.post("/resend-license", zValidator("json", resendSchema), async (c) => { const { licenseKey } = c.req.valid("json"); @@ -113,7 +98,6 @@ app.post("/resend-license", zValidator("json", resendSchema), async (c) => { } }); -// Webhook de Stripe app.post("/stripe/webhook", async (c) => { const sig = c.req.header("stripe-signature"); const body = await c.req.json(); @@ -136,7 +120,6 @@ app.post("/stripe/webhook", async (c) => { case "checkout.session.completed": { const session = event.data.object as Stripe.Checkout.Session; - // Obtener el customer const customerResponse = await stripe.customers.retrieve( session.customer as string, ); @@ -145,7 +128,6 @@ app.post("/stripe/webhook", async (c) => { throw new Error("Customer was deleted"); } - // Obtener el precio y determinar el tipo de licencia y facturación const lineItems = await stripe.checkout.sessions.listLineItems( session.id, ); @@ -153,7 +135,6 @@ app.post("/stripe/webhook", async (c) => { const { type, billingType } = getLicenseTypeFromPriceId(priceId!); - // Crear la licencia const license = await createLicense({ customerId: customerResponse.id, productId: session.id, @@ -162,7 +143,6 @@ app.post("/stripe/webhook", async (c) => { email: session.customer_details?.email!, }); - // Enviar el email con la licencia const features = getLicenseFeatures(type); const emailHtml = await render( LicenseEmail({ @@ -183,7 +163,6 @@ app.post("/stripe/webhook", async (c) => { break; } - // Puedes agregar más casos según necesites } return c.json({ received: true }); @@ -196,77 +175,6 @@ app.post("/stripe/webhook", async (c) => { } }); -// Función auxiliar para obtener las características según el tipo de licencia -function getLicenseFeatures(type: string): string[] { - const baseFeatures = [ - "Unlimited deployments", - "Basic monitoring", - "Email support", - ]; - - const premiumFeatures = [ - ...baseFeatures, - "Priority support", - "Advanced monitoring", - "Custom domains", - "Team collaboration", - ]; - - const businessFeatures = [ - ...premiumFeatures, - "24/7 support", - "Custom integrations", - "SLA guarantees", - "Dedicated account manager", - ]; - - switch (type) { - case "basic": - return baseFeatures; - case "premium": - return premiumFeatures; - case "business": - return businessFeatures; - default: - return baseFeatures; - } -} - -// Función auxiliar para determinar el tipo de licencia según el price ID -function getLicenseTypeFromPriceId(priceId: string): { - type: "basic" | "premium" | "business"; - billingType: "monthly" | "annual"; -} { - const priceMap = { - [process.env.SELF_HOSTED_BASIC_PRICE_MONTHLY_ID!]: { - type: "basic", - billingType: "monthly", - }, - [process.env.SELF_HOSTED_BASIC_PRICE_ANNUAL_ID!]: { - type: "basic", - billingType: "annual", - }, - [process.env.SELF_HOSTED_PREMIUM_PRICE_MONTHLY_ID!]: { - type: "premium", - billingType: "monthly", - }, - [process.env.SELF_HOSTED_PREMIUM_PRICE_ANNUAL_ID!]: { - type: "premium", - billingType: "annual", - }, - [process.env.SELF_HOSTED_BUSINESS_PRICE_MONTHLY_ID!]: { - type: "business", - billingType: "monthly", - }, - [process.env.SELF_HOSTED_BUSINESS_PRICE_ANNUAL_ID!]: { - type: "business", - billingType: "annual", - }, - } as const; - - return priceMap[priceId] || { type: "basic", billingType: "monthly" }; -} - const port = process.env.PORT || 4000; console.log(`Server is running on port ${port}`); diff --git a/apps/licenses/src/stripe/index.ts b/apps/licenses/src/stripe/index.ts new file mode 100644 index 00000000..e75f322d --- /dev/null +++ b/apps/licenses/src/stripe/index.ts @@ -0,0 +1,5 @@ +import Stripe from "stripe"; + +export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { + apiVersion: "2024-09-30.acacia", +}); diff --git a/apps/licenses/src/utils.ts b/apps/licenses/src/utils.ts index 3f3c9698..60c65756 100644 --- a/apps/licenses/src/utils.ts +++ b/apps/licenses/src/utils.ts @@ -1,82 +1,70 @@ -import { - deployRemoteApplication, - deployRemoteCompose, - deployRemotePreviewApplication, - rebuildRemoteApplication, - rebuildRemoteCompose, - updateApplicationStatus, - updateCompose, - updatePreviewDeployment, -} from "@dokploy/server"; -import type { DeployJob } from "./schema"; +export const getLicenseFeatures = (type: string): string[] => { + const baseFeatures = [ + "Unlimited deployments", + "Basic monitoring", + "Email support", + ]; -export const deploy = async (job: DeployJob) => { - try { - if (job.applicationType === "application") { - await updateApplicationStatus(job.applicationId, "running"); - if (job.server) { - if (job.type === "redeploy") { - await rebuildRemoteApplication({ - applicationId: job.applicationId, - titleLog: job.titleLog, - descriptionLog: job.descriptionLog, - }); - } else if (job.type === "deploy") { - await deployRemoteApplication({ - applicationId: job.applicationId, - titleLog: job.titleLog, - descriptionLog: job.descriptionLog, - }); - } - } - } else if (job.applicationType === "compose") { - await updateCompose(job.composeId, { - composeStatus: "running", - }); + const premiumFeatures = [ + ...baseFeatures, + "Priority support", + "Advanced monitoring", + "Custom domains", + "Team collaboration", + ]; - if (job.server) { - if (job.type === "redeploy") { - await rebuildRemoteCompose({ - composeId: job.composeId, - titleLog: job.titleLog, - descriptionLog: job.descriptionLog, - }); - } else if (job.type === "deploy") { - await deployRemoteCompose({ - composeId: job.composeId, - titleLog: job.titleLog, - descriptionLog: job.descriptionLog, - }); - } - } - } else if (job.applicationType === "application-preview") { - await updatePreviewDeployment(job.previewDeploymentId, { - previewStatus: "running", - }); - if (job.server) { - if (job.type === "deploy") { - await deployRemotePreviewApplication({ - applicationId: job.applicationId, - titleLog: job.titleLog, - descriptionLog: job.descriptionLog, - previewDeploymentId: job.previewDeploymentId, - }); - } - } - } - } catch (_) { - if (job.applicationType === "application") { - await updateApplicationStatus(job.applicationId, "error"); - } else if (job.applicationType === "compose") { - await updateCompose(job.composeId, { - composeStatus: "error", - }); - } else if (job.applicationType === "application-preview") { - await updatePreviewDeployment(job.previewDeploymentId, { - previewStatus: "error", - }); - } + const businessFeatures = [ + ...premiumFeatures, + "24/7 support", + "Custom integrations", + "SLA guarantees", + "Dedicated account manager", + ]; + + switch (type) { + case "basic": + return baseFeatures; + case "premium": + return premiumFeatures; + case "business": + return businessFeatures; + default: + return baseFeatures; } - - return true; +}; + +export const getLicenseTypeFromPriceId = ( + priceId: string, +): { + type: "basic" | "premium" | "business"; + billingType: "monthly" | "annual"; +} => { + const priceMap = { + [process.env.SELF_HOSTED_BASIC_PRICE_MONTHLY_ID!]: { + type: "basic", + billingType: "monthly", + }, + [process.env.SELF_HOSTED_BASIC_PRICE_ANNUAL_ID!]: { + type: "basic", + billingType: "annual", + }, + [process.env.SELF_HOSTED_PREMIUM_PRICE_MONTHLY_ID!]: { + type: "premium", + billingType: "monthly", + }, + [process.env.SELF_HOSTED_PREMIUM_PRICE_ANNUAL_ID!]: { + type: "premium", + billingType: "annual", + }, + [process.env.SELF_HOSTED_BUSINESS_PRICE_MONTHLY_ID!]: { + type: "business", + billingType: "monthly", + }, + [process.env.SELF_HOSTED_BUSINESS_PRICE_ANNUAL_ID!]: { + type: "business", + billingType: "annual", + }, + } as const; + + return priceMap[priceId] || { type: "basic", billingType: "monthly" }; }; diff --git a/apps/licenses/src/utils/license.ts b/apps/licenses/src/utils/license.ts index ccd7b87b..2f87e173 100644 --- a/apps/licenses/src/utils/license.ts +++ b/apps/licenses/src/utils/license.ts @@ -20,7 +20,7 @@ export const createLicense = async ({ billingType: "monthly" | "annual"; email: string; }) => { - const licenseKey = generateLicenseKey(); + const licenseKey = `dokploy-${generateLicenseKey()}`; const expiresAt = new Date(); expiresAt.setMonth( expiresAt.getMonth() + (billingType === "annual" ? 12 : 1), diff --git a/apps/licenses/src/truncate.ts b/apps/licenses/truncate.ts similarity index 100% rename from apps/licenses/src/truncate.ts rename to apps/licenses/truncate.ts diff --git a/apps/licenses/tsconfig.json b/apps/licenses/tsconfig.json index 61c0c966..c893ec51 100644 --- a/apps/licenses/tsconfig.json +++ b/apps/licenses/tsconfig.json @@ -16,6 +16,6 @@ "@dokploy/server/*": ["../../packages/server/src/*"] } }, - "include": ["src/**/*"], + "include": ["src/**/*", "truncate.ts"], "exclude": ["node_modules", "dist"] }