Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions app/actions/bulk-screen.ts
Original file line number Diff line number Diff line change
@@ -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
}
}
31 changes: 18 additions & 13 deletions app/actions/bulk-upload-deal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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",
};
}

Expand All @@ -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
Expand All @@ -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.",
};
}
};
Expand Down
97 changes: 90 additions & 7 deletions components/Dialogs/bulk-import-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -52,6 +57,15 @@ export function BulkImportDialog() {
const [uploading, setUploading] = useState(false);
const [uploadComplete, setUploadComplete] = useState(false);

const {
data: screeners,
error: screenerFetchError,
isLoading,
} = useSWR<DealScreenersGET>(`/api/deal-screeners`, fetcher);
const [selectedScreenerId, setSelectedScreenerId] = React.useState<
string | null
>(null);

const expectedHeaders = [
"Brokerage",
"First Name",
Expand Down Expand Up @@ -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 (
<>
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<Dialog
open={isOpen}
onOpenChange={(open) => {
setSuccess(null);
setIsOpen(open);
}}
>
<DialogTrigger asChild>
<Button className="w-full">
<Upload className="mr-2 h-4 w-4" />
Expand Down Expand Up @@ -231,6 +288,32 @@ export function BulkImportDialog() {
</div>
)}
</div>
<div>
<label
className="mb-2 block text-sm font-medium"
htmlFor="screener-select"
>
Select Screener
</label>
<select
id="screener-select"
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-primary"
value={selectedScreenerId ?? ""}
onChange={(e) =>
setSelectedScreenerId(
e.target.value === "" ? null : e.target.value,
)
}
>
<option value="">No Screener</option>
{screeners &&
screeners.map((screener) => (
<option key={screener.id} value={screener.id}>
{screener.name}
</option>
))}
</select>
</div>
</ScrollArea>
<Button
onClick={handleUpload}
Expand Down