Skip to content

Commit

Permalink
Merge branch 'main' into ppr-19
Browse files Browse the repository at this point in the history
  • Loading branch information
devkiran committed Nov 27, 2024
2 parents e3cd16c + 6a95c3d commit 08979ef
Show file tree
Hide file tree
Showing 186 changed files with 8,241 additions and 3,414 deletions.
173 changes: 118 additions & 55 deletions apps/web/app/api/cron/import/csv/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,24 @@ import { sendCsvImportEmails } from "./utils";

export const dynamic = "force-dynamic";

// Type for mapper return value
type MapperResult =
| {
success: true;
data: {
domain: string;
key: string;
createdAt?: Date;
tags?: string[];
[key: string]: any;
};
}
| {
success?: false;
error: string;
link: { domain: string; key: string };
};

export async function POST(req: Request) {
try {
const body = await req.json();
Expand All @@ -34,37 +52,65 @@ export async function POST(req: Request) {

if (!id || !url) throw new Error("Missing ID or URL for the import file");

const mapper = (row: Record<string, string>) => {
const actualKey = normalizeString(mapping.link);
const linkUrl = actualKey ? getPrettyUrl(row[actualKey]) : "";
const mapper = (row: Record<string, string>): MapperResult => {
const getValueByNormalizedKey = (targetKey: string): string => {
const key = Object.keys(row).find(
(k) => normalizeString(k) === normalizeString(targetKey),
);
return key ? row[key].trim() : "";
};

return {
...Object.fromEntries(
Object.entries(mapping).map(([key, value]) => {
// Find the actual key for each mapped field
const csvKey = Object.keys(row).find(
(k) => normalizeString(k) === normalizeString(value),
);
return [key, csvKey ? row[csvKey] : undefined];
}),
),
domain: linkUrl.split("/")[0],
// domain.com/path/to/page => path/to/page
key: linkUrl.split("/").slice(1).join("/") || "_root",
createdAt: mapping.createdAt
? parseDateTime(row[mapping.createdAt])
: undefined,
tags: mapping.tags
? [
...new Set(
row[mapping.tags]
?.split(",")
// Validate required fields
const linkValue = getValueByNormalizedKey(mapping.link);
if (!linkValue) {
return {
error: "Missing link value",
link: { domain: "unknown", key: "unknown" },
};
}

const linkUrl = getPrettyUrl(linkValue);
if (!linkUrl) {
return {
error: "Invalid link format",
link: { domain: "unknown", key: "unknown" },
};
}

const domain = linkUrl.split("/")[0];
const key = linkUrl.split("/").slice(1).join("/") || "_root";

try {
return {
success: true,
data: {
...Object.fromEntries(
Object.entries(mapping).map(([key, value]) => [
key,
getValueByNormalizedKey(value),
]),
),
domain,
key,
createdAt: mapping.createdAt
? parseDateTime(getValueByNormalizedKey(mapping.createdAt)) ||
undefined
: undefined,
tags: mapping.tags
? getValueByNormalizedKey(mapping.tags)
.split(",")
.map((tag) => tag.trim())
.filter(Boolean),
),
]
: undefined,
};
.filter(Boolean)
.map((tag) => normalizeString(tag))
: undefined,
},
};
} catch (error) {
return {
error: error.message || "Error processing row",
link: { domain, key },
};
}
};

let cursor = parseInt(
Expand Down Expand Up @@ -117,14 +163,19 @@ export async function POST(req: Request) {
return;
}

// Find links that already exist in the workspace (we check matching of *both* domain and key below)
// Find links that already exist in the workspace
const alreadyCreatedLinks = await prisma.link.findMany({
where: {
domain: {
in: domains.map((domain) => domain.slug),
},
key: {
in: data.map((row) => mapper(row).key),
in: data.map((row) => {
const result = mapper(row);
return "success" in result && result.success
? result.data.key
: result.link.key;
}),
},
},
select: {
Expand All @@ -133,24 +184,31 @@ export async function POST(req: Request) {
},
});

// Find which links still need to be created
// Fix the linksToCreate typing and filtering
const linksToCreate = data
.map((row) => mapper(row))
.filter(
(link) =>
(result): result is Extract<MapperResult, { success: true }> =>
"success" in result &&
result.success === true &&
!alreadyCreatedLinks.some(
(l) => l.domain === link.domain && l.key === link.key,
) && link.key !== "_root",
);

(l) =>
l.domain === result.data.domain &&
l.key === result.data.key,
) &&
result.data.key !== "_root",
)
.map((result) => result.data);

// Fix the selectedTags extraction
const selectedTags = [
...new Set(
linksToCreate
.map(({ tags }) => tags)
.map(({ tags }) => tags || [])
.flat()
.filter(Boolean),
.filter((tag): tag is string => Boolean(tag)),
),
] as string[];
];

// Find tags that need to be added to the workspace
const tagsNotInWorkspace = selectedTags.filter(
Expand Down Expand Up @@ -218,22 +276,27 @@ export async function POST(req: Request) {

addedDomains.push(...domainsNotInWorkspace);

// Process all links
// Fix the processedLinks typing
type ProcessedLink = {
error: string | null;
link: ProcessedLinkProps;
};

const processedLinks = await Promise.all(
linksToCreate.map(({ createdAt, tags, ...link }) =>
processLink({
payload: {
...createLinkBodySchema.parse({
...link,
tagNames: tags || undefined,
}),
// 'createdAt' is not a valid field in createLinkBodySchema – but is valid for CSV imports
createdAt: createdAt?.toISOString(),
},
workspace: workspace as WorkspaceProps,
userId,
bulk: true,
}),
linksToCreate.map(
({ createdAt, tags, ...link }) =>
processLink({
payload: {
...createLinkBodySchema.parse({
...link,
tagNames: tags || undefined,
}),
createdAt: createdAt?.toISOString(),
},
workspace,
userId,
bulk: true,
}) as Promise<ProcessedLink>,
),
);

Expand Down
50 changes: 50 additions & 0 deletions apps/web/app/api/dashboards/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,62 @@
import { DubApiError } from "@/lib/api/errors";
import { withWorkspace } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import {
dashboardSchema,
updateDashboardBodySchema,
} from "@/lib/zod/schemas/dashboard";
import { waitUntil } from "@vercel/functions";
import { NextResponse } from "next/server";

const getDashboardOrThrow = async ({
id,
workspaceId,
}: {
id: string;
workspaceId: string;
}) => {
const dashboard = await prisma.dashboard.findUnique({
where: { id, projectId: workspaceId },
});

if (!dashboard) {
throw new DubApiError({
code: "not_found",
message: "Dashboard not found",
});
}

return dashboard;
};

// PATCH /api/dashboards/[id] – update a dashboard
export const PATCH = withWorkspace(
async ({ params, workspace, req }) => {
const { id } = params;
await getDashboardOrThrow({ id, workspaceId: workspace.id });

const body = await req.json();
const data = updateDashboardBodySchema.parse(body);

const dashboard = await prisma.dashboard.update({
where: {
id,
},
data,
});

return NextResponse.json(dashboardSchema.parse(dashboard));
},
{
requiredPermissions: ["links.write"],
},
);

// DELETE /api/dashboards/[id] – delete a dashboard
export const DELETE = withWorkspace(
async ({ params, workspace }) => {
const { id } = params;
await getDashboardOrThrow({ id, workspaceId: workspace.id });

const dashboard = await prisma.dashboard.delete({
where: {
Expand Down
6 changes: 1 addition & 5 deletions apps/web/app/api/links/bulk/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,11 +397,7 @@ export const DELETE = withWorkspace(
],
},
include: {
tags: {
select: {
id: true,
},
},
tags: true,
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,11 @@ export const GET = withPartner(async ({ partner, params, searchParams }) => {
}

const parsedParams = analyticsQuerySchema
.pick({
event: true,
start: true,
end: true,
interval: true,
groupBy: true,
timezone: true,
.omit({
domain: true,
key: true,
linkId: true,
externalId: true,
})
.parse(searchParams);

Expand Down
10 changes: 5 additions & 5 deletions apps/web/app/api/programs/[programId]/partners/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@ export const GET = withWorkspace(
skip: (page - 1) * pageSize,
take: pageSize,
orderBy:
sortBy === "earnings"
? {
sortBy === "createdAt"
? { [sortBy]: order }
: {
link: {
saleAmount: order,
[sortBy === "earnings" ? "saleAmount" : sortBy]: order,
},
}
: { [sortBy]: order },
},
});

const partners = programEnrollments.map((enrollment) => ({
Expand Down
4 changes: 2 additions & 2 deletions apps/web/app/api/qr/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export async function GET(req: NextRequest) {
try {
const params = getSearchParams(req.url);

let { url, logo, size, level, fgColor, bgColor, hideLogo, includeMargin } =
let { url, logo, size, level, fgColor, bgColor, margin, hideLogo } =
getQRCodeQuerySchema.parse(params);

await ratelimitOrThrow(req, "qr");
Expand Down Expand Up @@ -49,9 +49,9 @@ export async function GET(req: NextRequest) {
value: url,
size,
level,
includeMargin,
fgColor,
bgColor,
margin,
...(logo
? {
imageSettings: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ function InvitePartnerSheetContent({ setIsOpen }: InvitePartnerSheetProps) {
setIsOpen(false);
},
onError({ error }) {
toast.error(error.serverError?.serverError);
toast.error(error.serverError);
},
});

Expand Down
Loading

0 comments on commit 08979ef

Please sign in to comment.