mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-19 22:25:22 +02:00
feat(licenses): enhance license validation and loading state management
- Added loading state management in the EnablePaidFeatures component to improve user experience during license validation. - Updated validateLicense function to return detailed error messages for better feedback. - Modified user API to return validation results instead of a boolean, enhancing error handling. - Removed unused SQL files and updated package.json scripts for better development workflow.
This commit is contained in:
@@ -16,6 +16,7 @@ import { useState, useEffect } from "react";
|
||||
|
||||
export const EnablePaidFeatures = () => {
|
||||
const { data, refetch } = api.user.get.useQuery();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { mutateAsync: validateLicense } =
|
||||
api.user.validateLicense.useMutation();
|
||||
const { mutateAsync: update } = api.user.update.useMutation();
|
||||
@@ -32,6 +33,7 @@ export const EnablePaidFeatures = () => {
|
||||
toast.error("Please enter a license key");
|
||||
return;
|
||||
}
|
||||
setIsLoading(true);
|
||||
await validateLicense({
|
||||
licenseKey,
|
||||
})
|
||||
@@ -40,7 +42,12 @@ export const EnablePaidFeatures = () => {
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
toast.error("Error validating license");
|
||||
toast.error("Error validating license", {
|
||||
description: e.message,
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -102,8 +109,12 @@ export const EnablePaidFeatures = () => {
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
<Button onClick={handleValidateLicense} variant="secondary">
|
||||
Validate
|
||||
<Button
|
||||
onClick={handleValidateLicense}
|
||||
variant="secondary"
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? "Validating..." : "Validate"}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -148,21 +148,21 @@ export const userRouter = createTRPCRouter({
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const owner = await findUserById(ctx.user.ownerId);
|
||||
const isValid = await validateLicense(
|
||||
const result = await validateLicense(
|
||||
input.licenseKey,
|
||||
owner?.serverIp || "",
|
||||
);
|
||||
if (!isValid) {
|
||||
if (!result.isValid) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "Invalid license key",
|
||||
message: result.error,
|
||||
});
|
||||
}
|
||||
|
||||
await updateUser(ctx.user.id, {
|
||||
licenseKey: input.licenseKey,
|
||||
});
|
||||
return isValid;
|
||||
return result;
|
||||
}),
|
||||
getUserByToken: publicProcedure
|
||||
.input(apiFindOneToken)
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
const licensesUrl = process.env.LICENSES_URL || "http://localhost:4002";
|
||||
|
||||
export const validateLicense = async (
|
||||
licenseKey: string,
|
||||
serverIp: string,
|
||||
): Promise<boolean> => {
|
||||
export const validateLicense = async (licenseKey: string, serverIp: string) => {
|
||||
const response = await fetch(`${licensesUrl}/api/validate`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
@@ -17,5 +14,5 @@ export const validateLicense = async (
|
||||
console.log("Validation errors:", data.error.issues);
|
||||
}
|
||||
|
||||
return response.ok;
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
CREATE TYPE "public"."billing_type" AS ENUM('monthly', 'annual');--> statement-breakpoint
|
||||
CREATE TYPE "public"."license_status" AS ENUM('active', 'expired', 'cancelled', 'payment_pending');--> statement-breakpoint
|
||||
CREATE TYPE "public"."license_type" AS ENUM('basic', 'premium', 'business');--> statement-breakpoint
|
||||
CREATE TABLE "licenses" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"product_id" text NOT NULL,
|
||||
"license_key" text NOT NULL,
|
||||
"status" "license_status" DEFAULT 'active' NOT NULL,
|
||||
"type" "license_type" NOT NULL,
|
||||
"billing_type" "billing_type" NOT NULL,
|
||||
"server_ip" text,
|
||||
"activated_at" timestamp,
|
||||
"last_verified_at" timestamp,
|
||||
"expires_at" timestamp NOT NULL,
|
||||
"stripeCustomerId" text NOT NULL,
|
||||
"stripeSubscriptionId" text NOT NULL,
|
||||
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" timestamp DEFAULT CURRENT_TIMESTAMP,
|
||||
"metadata" text,
|
||||
"email" text NOT NULL,
|
||||
CONSTRAINT "licenses_license_key_unique" UNIQUE("license_key")
|
||||
);
|
||||
@@ -1,171 +0,0 @@
|
||||
{
|
||||
"id": "5a996744-b11f-4f1a-b4b0-91f6bf5c2bed",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.licenses": {
|
||||
"name": "licenses",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"product_id": {
|
||||
"name": "product_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"license_key": {
|
||||
"name": "license_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "license_status",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'active'"
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "license_type",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"billing_type": {
|
||||
"name": "billing_type",
|
||||
"type": "billing_type",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"server_ip": {
|
||||
"name": "server_ip",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"activated_at": {
|
||||
"name": "activated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"last_verified_at": {
|
||||
"name": "last_verified_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"stripeCustomerId": {
|
||||
"name": "stripeCustomerId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"stripeSubscriptionId": {
|
||||
"name": "stripeSubscriptionId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "CURRENT_TIMESTAMP"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "CURRENT_TIMESTAMP"
|
||||
},
|
||||
"metadata": {
|
||||
"name": "metadata",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"licenses_license_key_unique": {
|
||||
"name": "licenses_license_key_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"license_key"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"public.billing_type": {
|
||||
"name": "billing_type",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"monthly",
|
||||
"annual"
|
||||
]
|
||||
},
|
||||
"public.license_status": {
|
||||
"name": "license_status",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"active",
|
||||
"expired",
|
||||
"cancelled",
|
||||
"payment_pending"
|
||||
]
|
||||
},
|
||||
"public.license_type": {
|
||||
"name": "license_type",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"basic",
|
||||
"premium",
|
||||
"business"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,5 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1742369437742,
|
||||
"tag": "0000_noisy_epoch",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
"entries": []
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
"typecheck": "tsc --noEmit",
|
||||
"generate": "drizzle-kit generate",
|
||||
"drop": "drizzle-kit drop",
|
||||
"push": "drizzle-kit push",
|
||||
"migrate": "tsx ./migrate.ts",
|
||||
"truncate": "tsx ./truncate.ts",
|
||||
"reset:all": "tsx ./truncate.ts && tsx ./migrate.ts",
|
||||
|
||||
@@ -58,13 +58,12 @@ router.get("/health", async (c) => {
|
||||
router.post("/validate", zValidator("json", validateSchema), async (c) => {
|
||||
const { licenseKey, serverIp } = c.req.valid("json");
|
||||
|
||||
console.log("Validating license", licenseKey, serverIp);
|
||||
|
||||
try {
|
||||
const result = await validateLicense(licenseKey, serverIp);
|
||||
console.log("Result", result);
|
||||
return c.json(result);
|
||||
} catch (error) {
|
||||
logger.error("Error validating license:", error);
|
||||
logger.error("Error validating license:", { error });
|
||||
return c.json({ isValid: false, error: "Error validating license" }, 500);
|
||||
}
|
||||
});
|
||||
@@ -125,12 +124,7 @@ router.post("/resend-license", zValidator("json", resendSchema), async (c) => {
|
||||
customerName: license.email,
|
||||
}),
|
||||
);
|
||||
// await transporter.sendMail({
|
||||
// from: fromAddress,
|
||||
// to: toAddresses.join(", "),
|
||||
// subject,
|
||||
// html: htmlContent,
|
||||
// });
|
||||
|
||||
await transporter.sendMail({
|
||||
from: process.env.SMTP_FROM_ADDRESS,
|
||||
to: license.email,
|
||||
@@ -199,7 +193,7 @@ router.post("/stripe/webhook", async (c) => {
|
||||
billingType,
|
||||
email: session.customer_details?.email!,
|
||||
stripeCustomerId: customerResponse.id,
|
||||
stripeSubscriptionId: session.id,
|
||||
stripeSubscriptionId: session.subscription as string,
|
||||
});
|
||||
|
||||
console.log("License created", license);
|
||||
|
||||
@@ -60,6 +60,8 @@ export const validateLicense = async (
|
||||
license.stripeSubscriptionId,
|
||||
);
|
||||
|
||||
console.log("Suscription", suscription);
|
||||
|
||||
if (suscription.status !== "active") {
|
||||
return {
|
||||
isValid: false,
|
||||
|
||||
Reference in New Issue
Block a user