* mv * wip * wip * wip * wip * plural * fix: resolve TypeScript type errors for attribute refactoring - Add TransformedAttributeOption type to handle transformed contains field - Update imports to use routing-forms Attribute type where needed - Fix getValueOfAttributeOption to use TransformedAttributeOption type - Update AttributeOptionValueWithType to use TransformedAttributeOption - Fix pre-existing lint warnings (any -> unknown, proper type casting) Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix * cleanup * cleanup * fix * fix * fix * fix * fix * fix: add explicit type annotation to reduce callback in getAttributes.ts Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: use RouterOutputs from @calcom/trpc/react for consistent type inference - Update AppPage.tsx to use RouterOutputs from @calcom/trpc/react instead of inferRouterOutputs<AppRouter> - Update MultiDisconnectIntegration.tsx to use the same RouterOutputs type source - Add eslint-disable comments for pre-existing warnings (useEffect deps, img elements) Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: explicitly type attribute property in AttributeOptionAssignment The attribute property from Pick<AttributeOption, 'attribute'> was typed as 'unknown' because Prisma relations don't have explicit types when picked. This explicitly defines the attribute shape with id, type, and isLocked properties that are used in assignValueToUser.ts. Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: remove unused assignedUsers from AttributeOptionAssignment Pick The assignedUsers property was not being used in the code but was required by the Pick type, causing a type mismatch when the actual data from the database query didn't include it. Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: update import path for AttributeOptionAssignment and BulkAttributeAssigner types The types.d.ts file was moved from packages/lib/service/attribute/ to packages/app-store/routing-forms/types/. Updated the import path in utils.ts to point to the new location. Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
233 lines
5.9 KiB
TypeScript
233 lines
5.9 KiB
TypeScript
import { getWhereClauseForAttributeOptionsManagedByCalcom } from "@calcom/features/attributes/lib/utils";
|
|
import slugify from "@calcom/lib/slugify";
|
|
import prisma from "@calcom/prisma";
|
|
|
|
import { TRPCError } from "@trpc/server";
|
|
|
|
import type { TrpcSessionUser } from "../../../types";
|
|
import type { ZAssignUserToAttribute } from "./assignUserToAttribute.schema";
|
|
|
|
type GetOptions = {
|
|
ctx: {
|
|
user: NonNullable<TrpcSessionUser>;
|
|
};
|
|
input: ZAssignUserToAttribute;
|
|
};
|
|
|
|
const assignUserToAttributeHandler = 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",
|
|
});
|
|
}
|
|
|
|
// Ensure this organization can access these attributes and attribute options
|
|
const attributes = await prisma.attribute.findMany({
|
|
where: {
|
|
id: {
|
|
in: input.attributes.map((attribute) => attribute.id),
|
|
},
|
|
teamId: org.id,
|
|
},
|
|
select: {
|
|
name: true,
|
|
id: true,
|
|
type: true,
|
|
options: true,
|
|
isLocked: true,
|
|
},
|
|
});
|
|
|
|
if (attributes.length !== input.attributes.length) {
|
|
throw new TRPCError({
|
|
code: "UNAUTHORIZED",
|
|
message: "You do not have access to these attributes",
|
|
});
|
|
}
|
|
|
|
const arrayOfAttributeOptionIds = attributes.flatMap(
|
|
(attribute) => attribute.options?.map((option) => option.id) || []
|
|
);
|
|
|
|
const attributeOptionIds = Array.from(new Set(arrayOfAttributeOptionIds));
|
|
|
|
const attributeOptions = await prisma.attributeOption.findMany({
|
|
where: {
|
|
id: {
|
|
in: attributeOptionIds,
|
|
},
|
|
attribute: {
|
|
teamId: org.id,
|
|
},
|
|
},
|
|
select: {
|
|
id: true,
|
|
value: true,
|
|
slug: true,
|
|
},
|
|
});
|
|
|
|
if (attributeOptions.length !== attributeOptionIds.length) {
|
|
throw new TRPCError({
|
|
code: "UNAUTHORIZED",
|
|
message: "You do not have access to these attribute options",
|
|
});
|
|
}
|
|
|
|
const membership = await prisma.membership.findUnique({
|
|
where: {
|
|
userId_teamId: {
|
|
userId: input.userId,
|
|
teamId: org.id,
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!membership) {
|
|
throw new TRPCError({
|
|
code: "UNAUTHORIZED",
|
|
message: "This user is not apart of your organization",
|
|
});
|
|
}
|
|
|
|
// const promises: Promise<{ id: string }>[] = [];
|
|
|
|
const unlockedAttributesInInput = input.attributes.filter((attribute) => {
|
|
const attributeFromDb = attributes.find((a) => a.id === attribute.id);
|
|
return !attributeFromDb?.isLocked;
|
|
});
|
|
|
|
const lockedAttributesFromDb = attributes.filter((attribute) => attribute.isLocked);
|
|
|
|
unlockedAttributesInInput.map(async (attribute) => {
|
|
// TEXT, NUMBER
|
|
if (attribute.value && !attribute.options) {
|
|
const valueAsString = String(attribute.value);
|
|
|
|
// Check if it is already the value
|
|
const existingAttributeOption = await prisma.attributeToUser.findFirst({
|
|
where: {
|
|
memberId: membership.id,
|
|
attributeOption: {
|
|
attribute: {
|
|
id: attribute.id,
|
|
},
|
|
},
|
|
},
|
|
select: {
|
|
id: true,
|
|
attributeOption: {
|
|
select: {
|
|
id: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (existingAttributeOption) {
|
|
// Update the value if it already exists
|
|
await prisma.attributeOption.update({
|
|
where: {
|
|
id: existingAttributeOption.attributeOption.id,
|
|
},
|
|
data: {
|
|
value: valueAsString,
|
|
slug: slugify(valueAsString),
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
|
|
await prisma.attributeOption.create({
|
|
data: {
|
|
value: valueAsString,
|
|
slug: slugify(valueAsString),
|
|
attribute: {
|
|
connect: {
|
|
id: attribute.id,
|
|
},
|
|
},
|
|
assignedUsers: {
|
|
create: {
|
|
memberId: membership.id,
|
|
},
|
|
},
|
|
},
|
|
select: {
|
|
id: true,
|
|
},
|
|
});
|
|
} else if (!attribute.value && attribute.options) {
|
|
const options = attribute.options;
|
|
|
|
// Delete all users attributes for this attribute that are not in the options list
|
|
await prisma.attributeToUser.deleteMany({
|
|
where: {
|
|
attributeOption: {
|
|
attribute: {
|
|
id: attribute.id,
|
|
},
|
|
},
|
|
memberId: membership.id,
|
|
...getWhereClauseForAttributeOptionsManagedByCalcom(),
|
|
NOT: {
|
|
id: {
|
|
in: options.map((option) => option.value),
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
options?.map(async (option) => {
|
|
// Assign the attribute option to the user
|
|
await prisma.attributeToUser.upsert({
|
|
where: {
|
|
memberId_attributeOptionId: {
|
|
memberId: membership.id,
|
|
attributeOptionId: option.value,
|
|
},
|
|
},
|
|
create: {
|
|
memberId: membership.id,
|
|
attributeOptionId: option.value,
|
|
weight: option.weight,
|
|
},
|
|
update: {}, // No update needed if it already exists
|
|
select: {
|
|
id: true,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
// Delete the attribute from the user
|
|
if (!attribute.value && !attribute.options) {
|
|
await prisma.attributeToUser.deleteMany({
|
|
where: {
|
|
memberId: membership.id,
|
|
...getWhereClauseForAttributeOptionsManagedByCalcom(),
|
|
attributeOption: {
|
|
attribute: {
|
|
id: attribute.id,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
});
|
|
|
|
return {
|
|
success: true,
|
|
message: lockedAttributesFromDb.length
|
|
? `Attributes assigned successfully. Locked attributes ${lockedAttributesFromDb
|
|
.map((attribute) => attribute.name)
|
|
.join(", ")} were not assigned.`
|
|
: "Attributes assigned successfully.",
|
|
};
|
|
};
|
|
|
|
export default assignUserToAttributeHandler;
|