Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
155 changes: 23 additions & 132 deletions apps/web/app/api/user/brands/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,144 +361,35 @@ export const POST = withSession(async ({ req, session }) => {
);
}
} else if (advertiserId === "5") {
interface Campaign {
CampaignId: string;
AdvertiserName: string;
CampaignUrl: string;
}

interface ApiResponse {
"@total": string;
"@numpages": string;
"@page": string;
"@nextpageuri": string;
Campaigns: Campaign[];
}

// Partnerize-specific processing
try {
if (!accountId || !encryptedApiKey) {
return NextResponse.json(
{ error: "Missing Impact.com credentials." },
{ status: 400 },
);
}

const apiKey = decrypt(encryptedApiKey);
const baseUrl = `https://api.impact.com/Mediapartners/${accountId}/Campaigns`;
const headers = {
Accept: "application/json",
Authorization: `Basic ${Buffer.from(`${accountId}:${apiKey}`).toString("base64")}`,
};
const params = new URLSearchParams({
InsertionOrderStatus: "Active",
PageSize: "100", // Maximum allowed page size
});

let allCampaigns: Campaign[] = [];
let nextPageUri = `${baseUrl}?${params}`;

while (nextPageUri) {
const response = await fetch(nextPageUri, { headers });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const data = (await response.json()) as ApiResponse;
allCampaigns = allCampaigns.concat(data.Campaigns);

nextPageUri = data["@nextpageuri"]
? `https://api.impact.com${data["@nextpageuri"]}`
: "";
}

// Process campaigns and update database
const processedBrands = await Promise.all(
allCampaigns.map(async (campaign) => {
const brandName = campaign.AdvertiserName;
const brandId = campaign.CampaignId;
const advertiserUrl = campaign.CampaignUrl;
const modifiedUrl = extractBaseUrlUpdated(advertiserUrl);

if (modifiedUrl) {
const brand = await prisma.brand.findFirst({
where: {
url: modifiedUrl,
advertisers: {
some: {
brandIdAtAdvertiser: brandId,
advertiserId,
},
},
},
include: {
advertisers: {
where: { advertiserId: advertiserId },
},
userBrandRelationships: {
where: { userId: session.user.id, advertiserId },
},
},
});

if (!brand) {
const newBrand = await prisma.brand.create({
data: {
name: brandName,
url: modifiedUrl,
advertisers: {
create: {
brandIdAtAdvertiser: brandId,
advertiserId,
},
},
},
include: {
advertisers: {
where: { advertiserId: advertiserId },
},
},
});

const userBrandRelationship =
await prisma.userBrandRelationship.create({
data: {
userId: session.user.id,
brandId: newBrand.id,
advertiserId,
userAdvertiserRelationId: relationship.id,
brandAdvertiserRelationId: newBrand.advertisers[0].id,
},
});

return { brand: newBrand, userBrandRelationship };
}

if (brand) {
let userBrandRelationship = brand.userBrandRelationships[0];
if (!userBrandRelationship) {
userBrandRelationship =
await prisma.userBrandRelationship.create({
data: {
userId: session.user.id,
brandId: brand.id,
advertiserId,
userAdvertiserRelationId: relationship.id,
brandAdvertiserRelationId: brand.advertisers[0].id,
},
});
}
const partnerizeUrl = "https://api.partnerize.com/v1/affiliate/link";

return { brand, userBrandRelationship };
}
}
return null;
const response = await fetch(partnerizeUrl, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: processedUrl,
campaign_id: accountId,
}),
);
});

const validBrands = processedBrands.filter(Boolean);
return NextResponse.json(validBrands);
if (response.ok) {
const data = await response.json();
return NextResponse.json({ affiliateUrl: data.affiliate_link_url });
} else {
const errorData = await response.json();
return NextResponse.json(
{ error: `Partnerize API error: ${errorData.message}` },
{ status: response.status },
);
}
} catch (error) {
console.error("Error processing Impact.com advertiser:", error);
console.error("Error processing Partnerize advertiser:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 },
Expand Down
50 changes: 50 additions & 0 deletions apps/web/lib/api/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,56 @@ export async function processLink({
} catch (error) {
console.error("Error generating affiliate link:", error);
}
} else if (advertiserId === "5") {
const userAdvertiserRelation =
userBrandRelationship.userAdvertiserRelation;

const apiKey = decrypt(userAdvertiserRelation.encryptedApiKey || "");
const accountId = userAdvertiserRelation.accountId || "";

if (!apiKey || !accountId) {
return {
link: payload,
error: "Missing credentials for Partnerize affiliate program.",
code: "unprocessable_entity",
};
}

const partnerizeUrl = "https://api.partnerize.com/v1/affiliate/link";

const headers = {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
};

const data = {
url: processedUrl,
campaign_id: accountId,
};

try {
const response = await fetch(partnerizeUrl, {
method: "POST",
headers,
body: JSON.stringify(data),
});

if (response.ok) {
const responseJson = await response.json();
const affiliateUrl = responseJson.affiliate_link_url || "";
clickUrl = affiliateUrl;
} else {
const errorData = await response.json();
console.error(`Partnerize API error: ${errorData.message}`);
}
} catch (error) {
console.error("Error generating Partnerize affiliate link:", error);
}
}
}
} catch (error) {
console.error("Error generating affiliate link:", error);
}
} else if (advertiserId === "5") {
// Impact.com

Expand Down
59 changes: 59 additions & 0 deletions apps/web/ui/modals/add-edit-network-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,65 @@ function AddEditNetworkModal({
</div>
</div>
</>
) : advertiserId === "5" ? ( // Partnerize
<>
<div>
<div className="flex items-center justify-between">
<label htmlFor={`partnerizeApiKey-${randomIdx}`} className="block text-sm font-medium text-gray-700">
API Key
</label>
<a
href="https://docs.partnerize.com"
target="_blank"
rel="noopener noreferrer"
className="text-gray-400 hover:text-gray-500"
>
<HelpCircle className="h-4 w-4" />
</a>
</div>
<div className="relative mt-1 flex rounded-md shadow-sm">
<input
name="apiKey"
id={`partnerizeApiKey-${randomIdx}`}
placeholder="Enter your Partnerize API Key"
value={partialApiKey ?? ""}
required
autoComplete="off"
onChange={(e) => handleInputChange("partialApiKey", e.target.value)}
className="border-gray-300 text-gray-900 placeholder-gray-300 focus:border-gray-500 focus:ring-gray-500 block w-full rounded-md focus:outline-none sm:text-sm"
aria-invalid="true"
/>
</div>
</div>
<div>
<div className="flex items-center justify-between">
<label htmlFor={`partnerizeAccountId-${randomIdx}`} className="block text-sm font-medium text-gray-700">
Account ID
</label>
<a
href="https://docs.partnerize.com"
target="_blank"
rel="noopener noreferrer"
className="text-gray-400 hover:text-gray-500"
>
<HelpCircle className="h-4 w-4" />
</a>
</div>
<div className="relative mt-1 flex rounded-md shadow-sm">
<input
name="accountId"
id={`partnerizeAccountId-${randomIdx}`}
placeholder="Enter your Partnerize Account ID"
value={accountId ?? ""}
required
autoComplete="off"
onChange={(e) => handleInputChange("accountId", e.target.value)}
className="border-gray-300 text-gray-900 placeholder-gray-300 focus:border-gray-500 focus:ring-gray-500 block w-full rounded-md focus:outline-none sm:text-sm"
aria-invalid="true"
/>
</div>
</div>
</>
) : null}
</div>

Expand Down