Files
linkwarden/apps/web/lib/api/controllers/dashboard/updateDashboardLayout.ts
2025-06-11 11:55:47 -04:00

189 lines
4.6 KiB
TypeScript

import {
UpdateDashboardLayoutSchema,
UpdateDashboardLayoutSchemaType,
} from "@linkwarden/lib/schemaValidation";
import { prisma } from "@linkwarden/prisma";
import { DashboardSection } from "@linkwarden/prisma/client";
import getDashboardData from "./getDashboardDataV2";
export default async function updateDashboardLayout(
userId: number,
body: UpdateDashboardLayoutSchemaType[]
) {
const dataValidation = UpdateDashboardLayoutSchema.safeParse(body);
if (!dataValidation.success) {
console.log(dataValidation.error.issues[0].message);
return {
response: `Error: ${
dataValidation.error.issues[0].message
} [${dataValidation.error.issues[0].path.join(", ")}]`,
status: 400,
};
}
const data = dataValidation.data;
let collectionIds = data
.map((section) => section.collectionId)
.filter((id) => id !== undefined);
if (collectionIds && collectionIds.length > 0) {
collectionIds = (
await prisma.collection.findMany({
where: {
id: {
in: collectionIds as number[],
},
OR: [{ ownerId: userId }, { members: { some: { userId } } }],
},
})
).map((collection) => collection.id);
}
let parsedData = data.filter((section) =>
section.collectionId ? collectionIds.includes(section.collectionId) : true
);
const existingSections = await prisma.dashboardSection.findMany({
where: {
userId: userId,
},
});
const existingSectionsMap = new Map(
existingSections.map((section) => [
`${section.type}-${section.collectionId || "default"}`,
section,
])
);
const sectionsToCreate: Pick<
DashboardSection,
"userId" | "type" | "collectionId" | "order"
>[] = [];
const sectionsToUpdate: Array<{
id: number;
newOrder: number;
oldOrder: number;
}> = [];
const sectionsToDelete: Array<{
id: number;
order: number;
}> = [];
for (const section of parsedData) {
const key = `${section.type}-${section.collectionId || "default"}`;
const existingSection = existingSectionsMap.get(key);
if (existingSection && !section.enabled) {
sectionsToDelete.push({
id: existingSection.id,
order: existingSection.order,
});
} else if (existingSection && section.enabled) {
if (existingSection.order !== section.order) {
sectionsToUpdate.push({
id: existingSection.id,
newOrder: section.order!,
oldOrder: existingSection.order,
});
}
} else if (section.enabled) {
sectionsToCreate.push({
userId: userId,
type: section.type,
collectionId: section.collectionId || null,
order: section.order!,
});
}
}
await prisma.$transaction(async (tx) => {
for (const sectionToDelete of sectionsToDelete) {
await tx.dashboardSection.delete({
where: {
id: sectionToDelete.id,
},
});
// Decrement order for all sections with order greater than deleted section
await tx.dashboardSection.updateMany({
where: {
userId,
order: {
gt: sectionToDelete.order,
},
},
data: {
order: {
decrement: 1,
},
},
});
}
// Handle order updates - need to be careful about conflicts
for (const sectionToUpdate of sectionsToUpdate) {
const { id, newOrder, oldOrder } = sectionToUpdate;
if (newOrder > oldOrder) {
// Moving down: decrement order for sections between oldOrder and newOrder
await tx.dashboardSection.updateMany({
where: {
userId,
order: {
gt: oldOrder,
lte: newOrder,
},
id: {
not: id,
},
},
data: {
order: {
decrement: 1,
},
},
});
} else if (newOrder < oldOrder) {
await tx.dashboardSection.updateMany({
where: {
userId,
order: {
gte: newOrder,
lt: oldOrder,
},
id: {
not: id,
},
},
data: {
order: {
increment: 1,
},
},
});
}
await tx.dashboardSection.update({
where: { id },
data: { order: newOrder },
});
}
if (sectionsToCreate.length > 0) {
await tx.dashboardSection.createMany({
data: sectionsToCreate,
});
}
});
const getData = await getDashboardData(userId);
return {
status: 200,
response: getData,
};
}