feat(licenses): implement license management system with email notifications

- Added database schema for licenses, including types and statuses.
- Implemented license creation, validation, and activation functionalities.
- Integrated email templates for sending license keys and resend requests.
- Updated package dependencies and configuration for PostgreSQL integration.
- Introduced migration and truncation scripts for database management.
- Enhanced API endpoints for license operations with error handling and validation.
This commit is contained in:
Mauricio Siu
2025-03-19 00:36:35 -06:00
parent 9d047164ee
commit 4b6db35f16
19 changed files with 5071 additions and 82 deletions

View File

@@ -0,0 +1,119 @@
import { randomBytes } from "node:crypto";
import { db } from "../db";
import { licenses } from "../schema";
import { eq } from "drizzle-orm";
export const generateLicenseKey = () => {
return randomBytes(32).toString("hex");
};
export const createLicense = async ({
customerId,
productId,
type,
billingType,
email,
}: {
customerId: string;
productId: string;
type: "basic" | "premium" | "business";
billingType: "monthly" | "annual";
email: string;
}) => {
const licenseKey = generateLicenseKey();
const expiresAt = new Date();
expiresAt.setMonth(
expiresAt.getMonth() + (billingType === "annual" ? 12 : 1),
);
const license = await db
.insert(licenses)
.values({
customerId,
productId,
licenseKey,
type,
billingType,
expiresAt,
email,
})
.returning();
return license[0];
};
export const validateLicense = async (
licenseKey: string,
serverIp?: string,
) => {
const license = await db.query.licenses.findFirst({
where: eq(licenses.licenseKey, licenseKey),
});
if (!license) {
return { isValid: false, error: "License not found" };
}
if (license.status !== "active") {
return { isValid: false, error: "License is not active" };
}
if (new Date() > license.expiresAt) {
await db
.update(licenses)
.set({ status: "expired" })
.where(eq(licenses.id, license.id));
return { isValid: false, error: "License has expired" };
}
if (license.serverIp && serverIp && license.serverIp !== serverIp) {
return { isValid: false, error: "Invalid server IP" };
}
// Update last verified timestamp
await db
.update(licenses)
.set({ lastVerifiedAt: new Date() })
.where(eq(licenses.id, license.id));
return { isValid: true, license };
};
export const activateLicense = 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");
}
if (license.status !== "active") {
throw new Error("License is not active");
}
if (new Date() > license.expiresAt) {
await db
.update(licenses)
.set({ status: "expired" })
.where(eq(licenses.id, license.id));
throw new Error("License has expired");
}
if (license.serverIp && license.serverIp !== serverIp) {
throw new Error("License is already activated on a different server");
}
// Activate the license with the server IP
const updatedLicense = await db
.update(licenses)
.set({
serverIp,
activatedAt: new Date(),
lastVerifiedAt: new Date(),
})
.where(eq(licenses.id, license.id))
.returning();
return updatedLicense[0];
};