diff --git a/app/actions/bulk-screen.ts b/app/actions/bulk-screen.ts new file mode 100644 index 0000000..ee98f92 --- /dev/null +++ b/app/actions/bulk-screen.ts @@ -0,0 +1,117 @@ +"use server"; + +import { Deal } from "@prisma/client"; +import { redisClient } from "@/lib/redis"; +import { auth } from "@/auth"; + +// Copied from worker code +interface Submission { + id: string; + userId: string; + name: string; + screenerContent: string; + brokerage: string; + firstName: string; + lastName: string; + linkedinUrl: string; + workPhone: string; + dealCaption: string; + dealType: string; + revenue: number; + ebitda: number; + ebitdaMargin: number; + industry: string; + sourceWebsite: string; + companyLocation: string; +} + +export default async function BulkScreenDeals( + deals: Deal[], + screenerContent: string, // could be screener id/name instead? +): Promise<{ status: number; message: string }> { + // don't think this is necessary + const userSession = await auth(); + + if (!userSession) { + return { + message: "Unauthorized", + status: 400, + }; + } + + if (deals.length === 0) { + return { + status: 400, + message: "No deals were selected", + }; + } + + // if (!screenerId || !screenerContent || !screenerName) { + // console.log( + // "screener information is not present inside screen all function", + // ); + + // return NextResponse.json({ message: "Invalid screener" }, { status: 400 }); + // } + + // console.log("inside api route"); + // console.log(dealListings); + // console.log(screenerId); + + // TODO: is this even necessary? + try { + console.log("connecting to redis"); + // if (!redisClient.isOpen) { + // await redisClient.connect(); + // } + console.log("connected to redis"); + } catch (error) { + console.error("Error connecting to Redis:", error); + return { + message: "Internal Server Error", + status: 500, + }; + } + + try { + console.log("sending all deals to AI screener"); + + deals.forEach(async (dealListing: any) => { + const dealListingWithUserId = { + ...dealListing, + userId: userSession.user.id, + }; + + // typing doesn't actually do anything here + const submission: Submission = { + ...dealListingWithUserId, + screenerContent, + } + + await redisClient.lpush( + "dealListings", + JSON.stringify(submission), + ); + }); + + // publish the message that a new screening call request was made + await redisClient.publish( + "new_screen_call", + JSON.stringify({ + userId: userSession.user.id, + }), + ); + } catch (error) { + console.error("Error pushing to Redis:", error); + + return { + message: "Error pushing to Redis", + status: 500, + }; + } + + return { + message: "Products successfully pushed on to the backend", + status: 200 + } +} diff --git a/app/actions/bulk-upload-deal.ts b/app/actions/bulk-upload-deal.ts index 0e7ebb2..c4a644f 100644 --- a/app/actions/bulk-upload-deal.ts +++ b/app/actions/bulk-upload-deal.ts @@ -2,7 +2,7 @@ import { TransformedDeal } from "../types"; import prismaDB from "@/lib/prisma"; -import { DealType } from "@prisma/client"; +import { Deal, DealType } from "@prisma/client"; import { auth } from "@/auth"; import { rateLimit } from "@/lib/redis"; import { headers } from "next/headers"; @@ -14,15 +14,16 @@ import { headers } from "next/headers"; * Each deal is added to the "manual-deals" collection with a timestamp. * * @param {TransformedDeal[]} deals - An array of deals conforming to the `TransformedDeal` type. - * @returns {Promise<{ type: string; message: string; failedDeals?: TransformedDeal[] }>} + * @returns {Promise<{ status: number; message: string; dbDeals?: Deal[] }>} * Returns an object indicating success or failure and lists any deals that failed to upload. */ -const BulkUploadDealsToDB = async (deals: TransformedDeal[]) => { +const BulkUploadDealsToDB = async (deals: TransformedDeal[]): Promise<{ status: number; message: string; dbDeals?: Deal[]; }> => { const userSession = await auth(); if (!userSession) { return { - error: "unauthorized user", + status: 401, + message: "Unauthorized user", }; } @@ -38,20 +39,22 @@ const BulkUploadDealsToDB = async (deals: TransformedDeal[]) => { console.log("Rate limit excedded for bulk upload db"); return { - error: "Too many requests", + status: 429, + message: "Too many requests", }; } - if (!Array.isArray(deals) || deals.length === 0) { + if (deals.length === 0) { return { - error: "No deals or a valid array provided for bulk upload.", + status: 400, + message: "No deals provided for bulk upload.", }; } console.log("deals received", deals); - + let dbDeals: Deal[] = []; try { - await prismaDB.deal.createMany({ + dbDeals = await prismaDB.deal.createManyAndReturn({ data: deals.map((deal) => ({ title: deal.dealCaption || null, // Title is optional in schema, use null as fallback dealCaption: deal.dealCaption || "", // Required in schema, use empty string as fallback @@ -73,13 +76,15 @@ const BulkUploadDealsToDB = async (deals: TransformedDeal[]) => { }); return { - success: `${deals.length} deals uploaded successfully.`, + status: 200, + message: "Bulk upload successful", + dbDeals, }; } catch (error) { - console.error("Bulk upload error:", error); - + console.error("Bulk upload message:", error); return { - error: "Bulk upload failed due to a server error.", + status: 500, + message: "Bulk upload failed due to a server error.", }; } }; diff --git a/components/Dialogs/bulk-import-dialog.tsx b/components/Dialogs/bulk-import-dialog.tsx index 832dd2a..44b2123 100644 --- a/components/Dialogs/bulk-import-dialog.tsx +++ b/components/Dialogs/bulk-import-dialog.tsx @@ -23,6 +23,11 @@ import { ScrollArea } from "../ui/scroll-area"; import { TransformedDeal } from "@/app/types"; import { useToast } from "@/hooks/use-toast"; import BulkUploadDealsToDB from "@/app/actions/bulk-upload-deal"; +import BulkScreenDeals from "@/app/actions/bulk-screen"; + +import useSWR from "swr"; +import { DealScreenersGET } from "@/app/types"; +import { fetcher } from "@/lib/utils"; type SheetDeal = { Brokerage: string; @@ -52,6 +57,15 @@ export function BulkImportDialog() { const [uploading, setUploading] = useState(false); const [uploadComplete, setUploadComplete] = useState(false); + const { + data: screeners, + error: screenerFetchError, + isLoading, + } = useSWR(`/api/deal-screeners`, fetcher); + const [selectedScreenerId, setSelectedScreenerId] = React.useState< + string | null + >(null); + const expectedHeaders = [ "Brokerage", "First Name", @@ -153,29 +167,72 @@ export function BulkImportDialog() { const formattedDeals = transformDeals(deals); console.log("formattedDeals", formattedDeals); - const response = await BulkUploadDealsToDB(formattedDeals); + const uploadResponse = await BulkUploadDealsToDB(formattedDeals); - if (response.error) { - setError(response.error); + if (uploadResponse.status != 200) { + setError(uploadResponse.message); toast({ title: "Error uploading deals", variant: "destructive", - description: response.error, + description: uploadResponse.message, }); - } else { + + setUploading(false); + setUploadComplete(true); + return; + } + console.log(selectedScreenerId) + if (selectedScreenerId === null) { setSuccess("Deals uploaded successfully"); setDeals([]); setFile(null); - toast({ title: "Deals uploaded successfully" }); + + setUploading(false); + setUploadComplete(true); + return; } + const screenerContent = screeners?.find( + (screener) => screener.id === selectedScreenerId, + )?.content; + + const screenResponse = await BulkScreenDeals( + uploadResponse.dbDeals!, + screenerContent!, + ); + if (screenResponse.status != 200) { + setError(screenResponse.message); + toast({ + title: "Error screening deals", + variant: "destructive", + description: screenResponse.message, + }); + + setUploading(false); + setUploadComplete(true); + return; + } + + setSuccess("Deals uploaded successfully"); + setDeals([]); + setFile(null); + + // toasting doesn't appear to work + toast({ title: "Deals uploaded successfully" }); + setUploading(false); setUploadComplete(true); }; return ( <> - + { + setSuccess(null); + setIsOpen(open); + }} + >