Skip to content

Commit 6451c59

Browse files
committed
final changes
1 parent a7d67d8 commit 6451c59

2 files changed

Lines changed: 197 additions & 0 deletions

File tree

apps/web/app/(ee)/api/cron/payouts/aggregate-due-commissions/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ async function handler(req: Request) {
3636

3737
const partnerGroupsByHoldingPeriod = await prisma.partnerGroup.groupBy({
3838
by: ["holdingPeriodDays"],
39+
...(programId ? { where: { programId } } : {}),
3940
_count: {
4041
id: true,
4142
},
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
import { anthropic } from "@ai-sdk/anthropic";
2+
import { prisma } from "@dub/prisma";
3+
import { Category } from "@dub/prisma/client";
4+
import FireCrawlApp from "@mendable/firecrawl-js";
5+
import { generateObject } from "ai";
6+
import "dotenv-flow/config";
7+
import { z } from "zod";
8+
9+
const CategoryEnum = z.nativeEnum(Category);
10+
11+
// AI response schema
12+
const categorizationSchema = z.object({
13+
categories: z.array(CategoryEnum).min(1).max(3),
14+
reasoning: z.string(),
15+
});
16+
17+
// Result interface
18+
interface ProgramResult {
19+
programId: string;
20+
programName: string;
21+
categories: Category[];
22+
url?: string;
23+
error?: string;
24+
}
25+
26+
if (!process.env.FIRECRAWL_API_KEY)
27+
throw new Error("FIRECRAWL_API_KEY is not set");
28+
29+
// Initialize FireCrawl
30+
const firecrawl = new FireCrawlApp({
31+
apiKey: process.env.FIRECRAWL_API_KEY,
32+
});
33+
34+
// Function to scrape website content
35+
async function scrapeWebsite(url: string) {
36+
try {
37+
const scrapeResult = await firecrawl.scrapeUrl(url, {
38+
formats: ["markdown"],
39+
onlyMainContent: true,
40+
parsePDF: false,
41+
maxAge: 14400000, // 4 hours cache
42+
excludeTags: ["img"],
43+
});
44+
45+
if (!scrapeResult.success) {
46+
throw new Error(scrapeResult.error || "Failed to scrape");
47+
}
48+
49+
return {
50+
content: scrapeResult.markdown || "",
51+
title: scrapeResult.metadata?.title || "",
52+
description: scrapeResult.metadata?.description || "",
53+
};
54+
} catch (error) {
55+
console.error(`Error scraping ${url}:`, error);
56+
return null;
57+
}
58+
}
59+
60+
// Function to categorize content using AI
61+
async function categorizeProgram(
62+
programName: string,
63+
url: string,
64+
content: string,
65+
title: string,
66+
description: string,
67+
) {
68+
try {
69+
const prompt = `Analyze this website and categorize it into 1-3 most relevant categories.
70+
71+
IMPORTANT: You must select categories from this EXACT list (case-sensitive):
72+
- Artificial_Intelligence
73+
- Development
74+
- Design
75+
- Productivity
76+
- Finance
77+
- Marketing
78+
- Ecommerce
79+
- Security
80+
- Education
81+
- Health
82+
- Consumer
83+
84+
Category descriptions:
85+
- Artificial_Intelligence: AI/ML tools, chatbots, automation, machine learning platforms
86+
- Development: Code tools, APIs, developer platforms, programming resources
87+
- Design: Design tools, UI/UX, creative software, graphics
88+
- Productivity: Task management, collaboration, workflow tools, organization
89+
- Finance: Financial services, payments, accounting, investment, banking
90+
- Marketing: Marketing tools, analytics, advertising, social media, SEO
91+
- Ecommerce: Online stores, commerce platforms, marketplaces, retail
92+
- Security: Cybersecurity, privacy, protection tools, data security
93+
- Education: Learning platforms, courses, educational content, training
94+
- Health: Healthcare, fitness, wellness apps, medical services
95+
- Consumer: General consumer products/services that don't fit other categories
96+
97+
CRITICAL: Only use the exact category names listed above. DO NOT create new categories or modify existing ones. Do not select "Entrepreneurship" or "Business" as a category.
98+
99+
Website information:
100+
Name: ${programName}
101+
Website URL: ${url}
102+
Page Title: ${title}
103+
Meta Description: ${description}
104+
Website Content Preview: ${content.slice(0, 300)}...`;
105+
106+
const { object } = await generateObject({
107+
model: anthropic("claude-sonnet-4-20250514"),
108+
schema: categorizationSchema,
109+
prompt,
110+
});
111+
112+
return object.categories;
113+
} catch (error) {
114+
// console.error(`Error categorizing ${programName}:`, error);
115+
116+
// If it's a validation error (invalid enum values), return empty array
117+
if (
118+
error?.name === "AI_NoObjectGeneratedError" ||
119+
error?.cause?.name === "AI_TypeValidationError"
120+
) {
121+
console.log(
122+
` Invalid categories returned for ${programName}, skipping categorization`,
123+
);
124+
return []; // Return empty array for invalid categories
125+
}
126+
127+
return []; // Return empty array for any other errors too
128+
}
129+
}
130+
131+
// Main processing function
132+
async function main() {
133+
const program = await prisma.program.findUniqueOrThrow({
134+
where: {
135+
slug: "",
136+
url: {
137+
not: null,
138+
},
139+
},
140+
select: {
141+
id: true,
142+
name: true,
143+
url: true,
144+
},
145+
});
146+
147+
// Scrape website
148+
console.log(`Scraping: ${program.url}...`);
149+
150+
const scraped = await scrapeWebsite(program.url!); // already filtered above
151+
152+
if (!scraped) {
153+
throw new Error("Failed to scrape website");
154+
}
155+
156+
console.log(`Description: ${scraped.description}`);
157+
158+
// Categorize with AI
159+
console.log(`Analyzing content...`);
160+
const categories = await categorizeProgram(
161+
program.name,
162+
program.url!, // already filtered above
163+
scraped.content,
164+
scraped.title,
165+
scraped.description,
166+
);
167+
168+
console.log(
169+
`Categories: ${categories.length > 0 ? categories.join(", ") : "None (invalid/failed categorization)"}`,
170+
);
171+
172+
const res = await prisma.program.update({
173+
where: { id: program.id },
174+
data: {
175+
addedToMarketplaceAt: new Date(),
176+
description: scraped.description,
177+
categories: {
178+
deleteMany: {},
179+
create: categories.map((category) => ({ category })),
180+
},
181+
},
182+
});
183+
184+
console.log(
185+
`Added ${res.name} to the marketplace with description: ${res.description}`,
186+
);
187+
}
188+
189+
main()
190+
.catch((error) => {
191+
console.error("Script failed:", error);
192+
process.exit(1);
193+
})
194+
.finally(async () => {
195+
await prisma.$disconnect();
196+
});

0 commit comments

Comments
 (0)