import slugify from "@calcom/lib/slugify"; import prisma from "@calcom/prisma"; import { TRPCError } from "@trpc/server"; import type { TrpcSessionUser } from "../../../trpc"; import type { ZEditAttributeSchema } from "./edit.schema"; import { getOptionsWithValidContains } from "./utils"; type GetOptions = { ctx: { user: NonNullable; }; input: ZEditAttributeSchema; }; const editAttributesHandler = async ({ input, ctx }: GetOptions) => { const org = ctx.user.organization; if (!org.id) { throw new TRPCError({ code: "UNAUTHORIZED", message: "You need to be apart of an organization to use this feature", }); } // If an option is removed, it is to be removed from contains of corresponding group as well if any const options = getOptionsWithValidContains(input.options); const foundAttribute = await prisma.attribute.findUnique({ where: { id: input.attributeId, teamId: org.id, }, select: { id: true, }, }); if (!foundAttribute) { throw new TRPCError({ code: "NOT_FOUND", message: "Attribute not found", }); } const attributes = await prisma.attribute.update({ where: { id: input.attributeId, }, data: { name: input.name, type: input.type, teamId: org.id, isLocked: input.isLocked, isWeightsEnabled: input.isWeightsEnabled, }, select: { id: true, }, }); // Check all ids of options passed in are owned by the attribute and organization await validateOptionsBelongToAttribute(options, attributes.id); await prisma.$transaction(async (tx) => { const updateOptions = options.filter((option) => option.id !== undefined && option.id !== ""); const updatedOptionsIds = updateOptions.map((option) => option.id!); // We need to delete all options that are not present in this UpdateOptions.id (as they have been deleted) await tx.attributeOption.deleteMany({ where: { attributeId: attributes.id, NOT: [ { id: { in: updatedOptionsIds, }, }, ], }, }); const createOptions = options.filter((option) => option.id === undefined || option.id === ""); const updatePromises = updateOptions.map(async (option) => { return tx.attributeOption.update({ where: { id: option.id, }, data: { value: option.value, slug: slugify(option.value), isGroup: option.isGroup, contains: option.contains, }, }); }); // Create new options where Ids are not present in the options array const createPromises = createOptions.map(async (option) => { return tx.attributeOption.create({ data: { attributeId: attributes.id, value: option.value, isGroup: option.isGroup, contains: option.contains, slug: slugify(option.value), }, }); }); await Promise.all(updatePromises); await Promise.all(createPromises); }); return attributes; }; async function validateOptionsBelongToAttribute( options: ZEditAttributeSchema["options"], attributeId: string ) { // We have to use ! here to make sure typescript knows that the id is not undefined const optionsWithId = options .filter((option) => option.id !== undefined && option.id !== "") .map((option) => option.id!); // Check all ids of options passed in are owned by the attribute const optionsWithIdOwnedByAttribute = await prisma.attributeOption.findMany({ where: { id: { in: optionsWithId, }, attributeId: attributeId, }, select: { id: true, }, }); if (optionsWithIdOwnedByAttribute.length !== optionsWithId.length) { throw new TRPCError({ code: "UNAUTHORIZED", message: "You can't edit options that are not owned by the attribute", }); } } export default editAttributesHandler;