Implement Omada device management and enhance payment processing

- Added new omada-actions.ts file to handle fetching and updating device groups in Omada.
- Updated authMiddleware to include new payment routes.
- Enhanced createPayment function to add devices to a group upon successful payment verification.
- Improved payment verification process to include device management.
- Refactored PaymentsTable and DevicesToPay components for better UI and state handling.
- Removed unused hasSession function from auth-guard.ts for cleaner code.
This commit is contained in:
i701 2024-12-13 11:24:13 +05:00
parent 36f22c0614
commit e9d81c089a
7 changed files with 222 additions and 25 deletions

181
actions/omada-actions.ts Normal file
View File

@ -0,0 +1,181 @@
interface IpAddress {
ip: string;
mask: number;
}
interface Ipv6Address {
ip: string;
prefix: number;
}
interface MacAddress {
ruleId?: number;
name: string;
macAddress: string;
}
interface GroupProfile {
groupId: string;
site?: string;
name: string;
buildIn?: boolean;
ipList?: IpAddress[];
ipv6List?: Ipv6Address[];
macAddressList?: MacAddress[];
count: number;
type: number;
resource: number;
}
interface OmadaResponse {
errorCode: number;
msg: string;
result: {
data: GroupProfile[];
};
}
async function fetchOmadaGroupProfiles(
omadacId: string,
siteId: string,
): Promise<OmadaResponse> {
if (!omadacId || !siteId) {
throw new Error("omadacId and siteId are required parameters");
}
const timestamp: number = Date.now();
const baseUrl: string = "https://omada.sarlink.link";
const url: string = `${baseUrl}/${omadacId}/api/v2/sites/${siteId}/setting/profiles/groups?_t=${timestamp}`;
const headers: HeadersInit = {
accept: "application/json, text/plain, */*",
"accept-language": "en-US,en;q=0.9",
cookie: "TPOMADA_SESSIONID=f30bf82348784089ba90740e59e4aa99",
"csrf-token": "fedc6283e9604372b402f82fdaeba0db",
priority: "u=1, i",
referer: baseUrl,
refresh: "manual",
"sec-ch-ua": '"Brave";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Linux"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"sec-gpc": "1",
"user-agent":
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
"x-requested-with": "XMLHttpRequest",
};
try {
const response: Response = await fetch(url, {
method: "GET",
headers: headers,
credentials: "include",
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: OmadaResponse = await response.json();
return data;
} catch (error) {
console.error("Error fetching Omada group profiles:", error);
throw error instanceof Error ? error : new Error("Unknown error occurred");
}
}
export { fetchOmadaGroupProfiles, type MacAddress };
export async function addDevicesToGroup({
omadacId,
siteId,
groupId,
newDevices,
}: {
omadacId?: string;
siteId?: string;
groupId?: string;
newDevices: MacAddress[];
}): Promise<void> {
if (!omadacId || !siteId || !groupId) {
throw new Error("omadacId, siteId, and groupId are required parameters");
}
try {
// Fetch the existing group profiles
const groupProfiles: OmadaResponse = await fetchOmadaGroupProfiles(
omadacId,
siteId,
);
// Find the group profile with the specified groupId
const groupProfile: GroupProfile | undefined =
groupProfiles.result.data.find((profile) => profile.groupId === groupId);
if (!groupProfile) {
throw new Error(`Group with ID ${groupId} not found`);
}
// Create a new array with the existing and new devices
const updatedMacAddressList: MacAddress[] = [
...(groupProfile.macAddressList || []),
...newDevices,
];
// Prepare the request payload
const requestBody = {
name: groupProfile.name,
type: groupProfile.type,
resource: groupProfile.resource,
ipList: groupProfile.ipList,
ipv6List: groupProfile.ipv6List,
macAddressList: updatedMacAddressList,
portList: null,
countryList: null,
portType: null,
portMaskList: null,
domainNamePort: null,
};
const timestamp: number = Date.now();
const baseUrl: string = "https://omada.sarlink.link";
const url: string = `${baseUrl}/${omadacId}/api/v2/sites/${siteId}/setting/profiles/groups/2/${groupId}?_t=${timestamp}`;
const headers: HeadersInit = {
accept: "application/json, text/plain, */*",
"accept-language": "en-US,en;q=0.9",
"content-type": "application/json;charset=UTF-8",
cookie: "TPOMADA_SESSIONID=f30bf82348784089ba90740e59e4aa99",
"csrf-token": "fedc6283e9604372b402f82fdaeba0db",
origin: baseUrl,
priority: "u=1, i",
referer: baseUrl,
refresh: "manual",
"sec-ch-ua": '"Brave";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Linux"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"sec-gpc": "1",
"user-agent":
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
"x-requested-with": "XMLHttpRequest",
};
const response: Response = await fetch(url, {
method: "PATCH",
headers: headers,
body: JSON.stringify(requestBody),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
} catch (error) {
console.error("Error adding devices to group:", error);
throw error instanceof Error ? error : new Error("Unknown error occurred");
}
}

View File

@ -3,6 +3,7 @@
import prisma from "@/lib/db";
import type { PaymentType } from "@/lib/types";
import { revalidatePath } from "next/cache";
import { addDevicesToGroup } from "./omada-actions";
export async function createPayment(data: PaymentType) {
console.log("data", data);
@ -40,6 +41,9 @@ export async function verifyPayment(data: VerifyPaymentType) {
where: {
id: data.paymentId,
},
include: {
devices: true,
},
});
const response = await fetch(
"https://verifypaymentsapi.baraveli.dev/verify-payment",
@ -53,19 +57,38 @@ export async function verifyPayment(data: VerifyPaymentType) {
);
const json = await response.json();
console.log(json);
const newDevices = payment?.devices.map((d) => {
return {
name: d.name,
macAddress: d.mac,
};
});
if (json.success === true) {
await prisma.payment.update({
where: {
id: payment?.id,
},
data: {
paid: true,
},
});
await Promise.all([
prisma.payment.update({
where: {
id: payment?.id,
},
data: {
paid: true,
},
}),
addDevicesToGroup({
groupId: process.env.OMADA_GROUP_ID,
omadacId: process.env.OMADA_CID,
siteId: process.env.OMADA_SITE_ID,
newDevices: newDevices || [],
}),
]);
}
revalidatePath("/payment[paymentId]");
return json;
} catch (error) {
console.error(error);
}
}
export async function addDevicesToOmada() {
console.log("hi");
}

View File

@ -1,7 +1,7 @@
import DevicesToPay from "@/components/devices-to-pay";
import { auth } from "@/lib/auth";
import { hasSession } from "@/lib/auth-guard";
import prisma from "@/lib/db";
import { cn } from "@/lib/utils";
import { headers } from "next/headers";
import React from "react";
export default async function PaymentPage({
@ -26,12 +26,14 @@ export default async function PaymentPage({
devices: true,
},
});
await hasSession();
const formula = await prisma.billFormula.findFirst();
return (
<div>
<div className="flex justify-between items-center border-b-2 text-gray-500 text-2xl font-bold title-bg py-4 px-2 mb-4">
<h3>Payment</h3>
<span className={cn("text-sm border px-4 py-2 rounded-md uppercase font-semibold", payment?.paid ? "text-green-500 bg-green-500/20" : "text-yellow-500 bg-yellow-500/20")}>
{payment?.paid ? "Paid" : "Pending"}
</span>
</div>
<div

View File

@ -42,7 +42,7 @@ export default function DevicesToPay({
return (
<div className="w-full">
<div className="p-2 flex flex-col gap-2">
<h3 className="title-bg my-1 font-semibold text-lg">
<h3 className="title-bg my-1 p-2 font-semibold text-lg">
{!payment?.paid ? "Devices to pay" : "Devices Paid"}
</h3>
<div className="flex flex-col gap-2">
@ -71,7 +71,7 @@ export default function DevicesToPay({
accountNo="90101400028321000"
/>
{payment?.paid ? (
<Button size={"lg"} variant={"secondary"} disabled className="text-green-400 bg-green-800">Payment Verified</Button>
<Button size={"lg"} variant={"secondary"} disabled className="dark:text-green-200 text-green-900 bg-green-500/20 uppercase font-semibold">Payment Verified</Button>
) : (
<Button
disabled={verifying}

View File

@ -11,6 +11,7 @@ import {
import prisma from "@/lib/db";
import Link from "next/link";
import { cn } from "@/lib/utils";
import { Calendar } from "lucide-react";
import Pagination from "./pagination";
import { Badge } from "./ui/badge";
@ -96,7 +97,7 @@ export async function PaymentsTable({
{payments.map((payment) => (
<TableRow key={payment.id}>
<TableCell>
<div className="flex flex-col items-start title-bg border rounded p-2">
<div className={cn("flex flex-col items-start title-bg border rounded p-2", payment?.paid ? "bg-green-500/10 border-dashed border-green=500" : "bg-yellow-500/10 border-dashed border-yellow-500 dark:border-yellow-500/50")}>
<div className="flex items-center gap-2">
<Calendar size={16} opacity={0.5} />
<span className="text-muted-foreground">
@ -114,7 +115,7 @@ export async function PaymentsTable({
View Details
</Button>
</Link>
<Badge className="p-2" variant={payment.paid ? "outline" : "secondary"}>
<Badge className={cn(payment?.paid ? "text-green-500 bg-green-500/20" : "text-yellow-500 bg-yellow-500/20")} variant={payment.paid ? "outline" : "secondary"}>
{payment.paid ? "Paid" : "Unpaid"}
</Badge>
</div>

View File

@ -12,13 +12,3 @@ export async function AdminAuthGuard() {
}
return true;
}
export async function hasSession() {
const session = await auth.api.getSession({
headers: await headers(),
});
if (!session) {
return redirect("/login");
}
return true;
}

View File

@ -21,5 +21,5 @@ export default async function authMiddleware(request: NextRequest) {
}
export const config = {
matcher: ["/devices", "/"],
matcher: ["/devices", "/", "/payments", "/payments/:paymentId"],
};