feat(licenses): implement license deactivation and validation features

This commit is contained in:
Mauricio Siu
2025-03-23 19:40:54 -06:00
parent 4074942dbf
commit 1acd330462
5 changed files with 239 additions and 26 deletions

View File

@@ -1,15 +1,21 @@
import { zValidator } from "@hono/zod-validator";
import { Hono } from "hono";
import { z } from "zod";
import { activateLicense, validateLicense } from "../utils/license";
import {
activateLicense,
deactivateLicense,
validateLicense,
cleanLicense,
} from "../utils/license";
import { logger } from "../logger";
import { eq } from "drizzle-orm";
import { users } from "../schema";
import { eq, desc } from "drizzle-orm";
import { users, licenses } from "../schema";
import { db } from "../db";
import { transporter } from "../email";
import { nanoid } from "nanoid";
import { stripe } from "../stripe";
import type Stripe from "stripe";
import { getLicenseTypeFromPriceId } from "../utils";
const validateSchema = z.object({
licenseKey: z.string(),
serverIp: z.string(),
@@ -28,7 +34,7 @@ licenseRouter.post(
return c.json(result);
} catch (error) {
logger.error("Error validating license:", { error });
return c.json({ isValid: false, error: "Error validating license" }, 500);
return c.json({ success: false, error: "Error validating license" }, 500);
}
},
);
@@ -52,6 +58,43 @@ licenseRouter.post(
},
);
licenseRouter.post(
"/deactivate",
zValidator("json", validateSchema),
async (c) => {
const { licenseKey, serverIp } = c.req.valid("json");
try {
const license = await deactivateLicense(licenseKey, serverIp);
return c.json({ success: true, license });
} catch (error) {
logger.error("Error deactivating license:", error);
return c.json(
{ success: false, error: "Error deactivating license" },
500,
);
}
},
);
licenseRouter.post(
"/remove-server",
zValidator(
"json",
z.object({ licenseKey: z.string().min(1), serverIp: z.string().min(1) }),
),
async (c) => {
const { licenseKey, serverIp } = c.req.valid("json");
try {
const license = await cleanLicense(licenseKey, serverIp);
return c.json({ success: true, license });
} catch (error) {
logger.error("Error cleaning license:", error);
return c.json({ success: false, error: "Error cleaning license" }, 500);
}
},
);
// router.post("/resend-license", zValidator("json", resendSchema), async (c) => {
// const { licenseKey } = c.req.valid("json");
@@ -178,6 +221,7 @@ licenseRouter.get(
with: {
licenses: true,
},
orderBy: desc(licenses.createdAt),
});
if (!user) {
@@ -202,11 +246,16 @@ licenseRouter.get(
(suscription) => suscription.id === license.stripeSubscriptionId,
);
const { type } = getLicenseTypeFromPriceId(
suscription?.items.data[0].price.id || "",
);
return {
license: license,
stripeSuscription: {
quantity: suscription?.items.data[0].quantity,
billingType: suscription?.items.data[0].price.recurring?.interval,
type: type,
},
};
});

View File

@@ -69,7 +69,7 @@ export const validateLicense = async (licenseKey: string, serverIp: string) => {
});
if (!license) {
return { isValid: false, error: "License not found" };
return { success: false, error: "License not found" };
}
const suscription = await stripe.subscriptions.retrieve(
@@ -80,7 +80,7 @@ export const validateLicense = async (licenseKey: string, serverIp: string) => {
if (currentServerQuantity >= serversQuantity) {
return {
isValid: false,
success: false,
error:
"You have reached the maximum number of servers, please upgrade your license to add more servers",
};
@@ -88,13 +88,17 @@ export const validateLicense = async (licenseKey: string, serverIp: string) => {
if (suscription.status !== "active") {
return {
isValid: false,
success: false,
error: `License is ${getLicenseStatus(suscription)}`,
};
}
if (license.serverIps && !license.serverIps.includes(serverIp)) {
return { isValid: false, error: "Invalid server IP" };
return {
success: false,
error:
"This server is not authorized to use this license, please remove the current license from the UI, and activate a new one",
};
}
await db
@@ -102,7 +106,7 @@ export const validateLicense = async (licenseKey: string, serverIp: string) => {
.set({ lastVerifiedAt: new Date() })
.where(eq(licenses.id, license.id));
return { isValid: true, license };
return { success: true, license };
};
export const activateLicense = async (licenseKey: string, serverIp: string) => {
@@ -130,10 +134,8 @@ export const activateLicense = async (licenseKey: string, serverIp: string) => {
);
}
console.log("License", license.serverIps?.includes(serverIp));
if (license.serverIps && !license.serverIps.includes(serverIp)) {
throw new Error("License is already activated on a different server");
if (license.serverIps?.includes(serverIp)) {
return license;
}
// Activate the license with the server IP
@@ -150,6 +152,45 @@ export const activateLicense = async (licenseKey: string, serverIp: string) => {
return updatedLicense[0];
};
export const deactivateLicense = async (
licenseKey: string,
serverIp: string,
) => {
const license = await db.query.licenses.findFirst({
where: eq(licenses.licenseKey, licenseKey),
});
if (!license) {
throw new Error("License not found");
}
const updatedLicense = await db
.update(licenses)
.set({ serverIps: license.serverIps?.filter((ip) => ip !== serverIp) })
.where(eq(licenses.id, license.id))
.returning();
return updatedLicense[0];
};
export const cleanLicense = async (licenseKey: string, serverIp: string) => {
const license = await db.query.licenses.findFirst({
where: eq(licenses.licenseKey, licenseKey),
});
if (!license) {
throw new Error("License not found");
}
const updatedLicense = await db
.update(licenses)
.set({ serverIps: license.serverIps?.filter((ip) => ip !== serverIp) })
.where(eq(licenses.id, license.id))
.returning();
return updatedLicense[0];
};
export const getLicenseStatus = async (license: Stripe.Subscription) => {
if (license.status === "active") {
return "active";