Files
calendar/packages/features/hashedLink/lib/repository/HashedLinkRepository.ts
T
Hariom BalharaGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>Morgan
82951cd04e chore: [Booking Flow Refactor - 5] Move post booking things to separate service BookingEventHandlerService starting with HashedLink usage handling (#24025)
* chore: Move post booking stuff to separate service

* refactor: improve ISP compliance in BookingEventHandlerService

- Refactor updatePrivateLinkUsage to only accept hashedLink parameter instead of full payload
- Remove shouldProcess function and inline isDryRun check for better clarity
- Addresses feedback from PR #24025

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* define only what is used

* Define hashed-link-service as well in api-v2

* fix: export HashedLinkService through platform-libraries

- Add HashedLinkService to platform-libraries/private-links.ts
- Update API V2 to import from platform library instead of direct path
- Fixes MODULE_NOT_FOUND error in API V2 build

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2025-10-23 15:05:55 +03:00

263 lines
5.6 KiB
TypeScript

import type { PrismaClient } from "@calcom/prisma";
import { prisma } from "@calcom/prisma";
import type { Prisma } from "@calcom/prisma/client";
export type HashedLinkInputType = {
link: string;
expiresAt?: Date | null;
maxUsageCount?: number | null;
};
export const hashedLinkSelect = {
id: true,
link: true,
eventTypeId: true,
expiresAt: true,
maxUsageCount: true,
usageCount: true,
eventType: {
select: {
userId: true,
teamId: true,
users: {
select: {
username: true,
profiles: {
select: {
id: true,
organizationId: true,
username: true,
},
},
},
},
team: {
select: {
id: true,
slug: true,
hideBranding: true,
parent: {
select: {
hideBranding: true,
},
},
members: {
select: {
user: {
select: {
timeZone: true,
},
},
},
take: 1,
},
},
},
hosts: {
select: {
user: {
select: {
timeZone: true,
},
},
},
},
profile: {
select: {
user: {
select: {
timeZone: true,
},
},
},
},
owner: {
select: {
timeZone: true,
},
},
},
},
} satisfies Prisma.HashedLinkSelect;
export class HashedLinkRepository {
constructor(private readonly prismaClient: PrismaClient) {}
static create() {
return new HashedLinkRepository(prisma);
}
async deleteLinks(eventTypeId: number, linksToDelete: string[]) {
if (linksToDelete.length === 0) return;
return await this.prismaClient.hashedLink.deleteMany({
where: {
eventTypeId,
link: { in: linksToDelete },
},
});
}
async createLink(
eventTypeId: number,
linkData: { link: string; expiresAt: Date | null; maxUsageCount?: number | null }
) {
const data: Prisma.HashedLinkCreateManyInput = {
eventTypeId,
link: linkData.link,
expiresAt: linkData.expiresAt,
};
if (linkData.maxUsageCount && Number.isFinite(linkData.maxUsageCount)) {
data.maxUsageCount = linkData.maxUsageCount;
}
return await this.prismaClient.hashedLink.create({ data });
}
async updateLink(
eventTypeId: number,
linkData: { link: string; expiresAt: Date | null; maxUsageCount?: number | null }
) {
const updateData: Prisma.HashedLinkUpdateManyMutationInput = {
expiresAt: linkData.expiresAt,
};
if (typeof linkData.maxUsageCount === "number" && linkData.maxUsageCount !== null) {
updateData.maxUsageCount = linkData.maxUsageCount;
}
return await this.prismaClient.hashedLink.updateMany({
where: {
eventTypeId,
link: linkData.link,
},
data: updateData,
});
}
async findLinksByEventTypeId(eventTypeId: number) {
return await this.prismaClient.hashedLink.findMany({
where: {
eventTypeId,
},
select: {
link: true,
expiresAt: true,
maxUsageCount: true,
usageCount: true,
},
});
}
async findLinkWithEventTypeDetails(linkId: string) {
return await this.prismaClient.hashedLink.findUnique({
where: {
link: linkId,
},
select: {
id: true,
link: true,
expiresAt: true,
maxUsageCount: true,
usageCount: true,
eventTypeId: true,
eventType: {
select: {
teamId: true,
userId: true,
},
},
},
});
}
async findLinkWithDetails(linkId: string) {
return await this.prismaClient.hashedLink.findUnique({
where: {
link: linkId,
},
select: hashedLinkSelect,
});
}
async findLinksWithEventTypeDetails(linkIds: string[]) {
return await this.prismaClient.hashedLink.findMany({
where: {
link: {
in: linkIds,
},
},
select: {
id: true,
link: true,
expiresAt: true,
maxUsageCount: true,
usageCount: true,
eventTypeId: true,
eventType: {
select: {
teamId: true,
userId: true,
},
},
},
});
}
async findLinkWithValidationData(linkId: string) {
return await this.prismaClient.hashedLink.findUnique({
where: {
link: linkId,
},
select: {
id: true,
expiresAt: true,
maxUsageCount: true,
usageCount: true,
eventType: {
select: {
userId: true,
teamId: true,
hosts: {
select: {
user: {
select: {
timeZone: true,
},
},
},
},
profile: {
select: {
user: {
select: {
timeZone: true,
},
},
},
},
owner: {
select: {
timeZone: true,
},
},
},
},
},
});
}
async incrementUsage(linkId: number, maxUsageCount: number) {
return await this.prismaClient.hashedLink.update({
where: {
id: linkId,
usageCount: { lt: maxUsageCount },
},
data: {
usageCount: { increment: 1 },
},
});
}
}