* chore: Upgrade prisma to 6.7.0 * Build fixes * type fixes Signed-off-by: Omar López <zomars@me.com> * Update schema.prisma * Patching * Revert "Update schema.prisma" This reverts commit 47d8618bf89ef4d007b30084df766f17281e21a1. * Revert "Patching" This reverts commit a1d2e3040e71690a44d4324db95d73b4d68c6adb. * Revert schema changes Signed-off-by: Omar López <zomars@me.com> * WIP Signed-off-by: Omar López <zomars@me.com> * Update getPublicEvent.ts * Update imports Signed-off-by: Omar López <zomars@me.com> * Update gitignore Signed-off-by: Omar López <zomars@me.com> * update remaining imports Signed-off-by: Omar López <zomars@me.com> * Delete .cursor/config.json * Discard changes to packages/features/eventtypes/lib/getPublicEvent.ts * Update _get.ts * Update user.ts * Update .gitignore * update * Update WorkflowStepContainer.tsx * Update next-auth-custom-adapter.ts * Update getPublicEvent.ts * Update workflow.ts * Update next-auth-custom-adapter.ts * Update next-auth-options.ts * Update bookingScenario.ts * fix missing imports * upgrades prismock Signed-off-by: Omar López <zomars@me.com> * patches prismock Signed-off-by: Omar López <zomars@me.com> * Update reschedule.test.ts * Update prisma.ts * patch prismock Signed-off-by: Omar López <zomars@me.com> * fix enums imports Signed-off-by: Omar López <zomars@me.com> * Revert "Update prisma.ts" This reverts commit 64edcf8db54171ff4456c209d563b5d431d99619. * Revert "patch prismock" This reverts commit e95819113dc9d88e7130947aa120cd42710977c8. * fix patch * Fix test that overrun the boundary, it shouldn't test too much * Move prisma import to changeSMSLockState * Bring back broken test without illegal imports * Merge with main and fix filter hosts by same round robin host * Fixed buildDryRunBooking fn tests * Fix and move ooo create or update handler test * Fix packages/features/eventtypes/lib/isCurrentlyAvailable.test.ts * Fix packages/trpc/server/routers/viewer/organizations/listMembers.handler.test.ts * Mock @calcom/prisma * Fix: verify-email.test.ts * fix: Moved WebhookService test and fixed default import mock * Fix: Added missing prisma mock, handleNewBooking uses that of course * We're not testing createContext here * fix: Prisma mock fix for listMembers.test.ts * More fixes to broken testcases * Forgot to remove borked test * Prevent the need to mock a lot of dependencies by moving out buildBaseWhereCondition to its own file * Temporarily skip getCalendarEvents, needs a rewrite * Fix: turns out you can access protected in testcases * fix further mocks * Added packages/features/insights/server/buildBaseWhereCondition.ts, types * Always great to have a mock and then not use it * And one less again. * fix: confirm.handler.test, didn't mock prisma * fix: Address minor nit by @eunjae & fix ImpersonationProvider test * Updated isPrismaAvailableCheck that doesn't crash on import * fix: Get Prisma directly from the client, it usually involves the Validator and does not need 'local' inclusion * Add zod-prisma-types without the generator enabled (commented out) * Uncomment and see what happens * Change method of import as imports did not work in Input Schemas * Remove custom 'zod' booking model, it does not belong with Prisma * Fix all other global Model imports * Rewrite most schema includes AND remove barrel file * Add bookingCreateBodySchema to features/bookings * Flurry of type fixes for compatibility with new zod gen * Refactor out the custom prisma type createEventTypeInput * Work around nullable eventTypeLocations * HandlePayment type fix * More fixes, final fix remaining is CompleteEventType * Should fix a bunch more booking related type errors * Missed one * Some props missing from BookingCreateBodySchema * Fix location type in handleChildrenEventTypes * Little bit hacky imo but it works * Final type error \o/ * Forgot to include Prisma * Do not include zod-utils in booker/types * Oops, was already including Booker/types * Fix membership type, also disallow updating createdAt/updatedAt, make part of patch/post * Fix api v1 type errors * Fix EventTypeDescription typings * Remove getParserWithGeneric, use userBodySchema with UserSchema * use centralized timeZoneSchema * Implement feedback by @zomars * Couple of WIP pushes * Fix tests * Type fixes in `handleChildrenEventTypes` test * Try and parse metadata before use * Change zod-prisma-types configuration for optimal performance * Fix prisma validator error in `prisma/selects/credential` * Disable seperate relations model, hits a bug * Import absolute - this makes rollup work in @platform/libraries * Attempt at removing resolutions override * Refactor using `Prisma.validator` to `satisfies` * Build atoms using @calcom/prisma/client * Build atoms using @calcom/prisma/client * fixes * Update eventTypeSelect.ts * Adjust `eventTypeMetaDataSchemaWithUntypedApps` from `unknown` to `record(any)` * `EventTypeDescription` rely on `descriptionAsSafeHTML` instead of `description` * Add `seatsPerTimeSlot` to event type public select * Fix typing in `users-public-view` getServerSide props * Add missing `schedulingType` to prop * chore: bump platform libraries * Function return type is illegal, not sure how this passed eslint (#21567) * Merged with main * Update updateTokenObject.ts * Update handleResponse.ts * Update index.ts * Update handleChildrenEventTypes.ts * Update booking-idempotency-key.ts * Update WebhookService.test.ts * Update events.test.ts * Update queued-response.test.ts * Update events.test.ts * Update getRoutedUrl.test.ts * fix: type checks Signed-off-by: Omar López <zomars@me.com> * fixes Signed-off-by: Omar López <zomars@me.com> * chore: bump platform libraries * Update yarn.lock * more fixes Signed-off-by: Omar López <zomars@me.com> * fixes Signed-off-by: Omar López <zomars@me.com> * biuld fixes * chore: bump platform libraries * Update conferencing.repository.ts * Update conferencing.repository.ts * Update getCalendarsEvents.test.ts * Update vite.config.js * chore: bump platform libraries * Update users.ts * Discard changes to docs/api-reference/v2/openapi.json * Update vite.config.ts * updated platform libraries * Update get.handler.test.ts * Update get.handler.test.ts * Update schema.prisma * Discard changes to docs/api-reference/v2/openapi.json * Update next-auth-custom-adapter.ts * Update team.ts * Flurry of type fixes * Fix majority of insight related type errors * Type fixes for unlink of account * Make user nullable again * Fixed a bunch of unit tests and one type error * Attempted mock fix * Attempted fix for Attribute type * Ensure default import becomes prisma, but not direct usage * Import default as prisma in prisma.module * Add attributeOption to attribute type * Fix calcom/prisma mock * Refactor Prisma client imports to @calcom/prisma/client Updated all imports from '@prisma/client' to '@calcom/prisma/client' across tests and repository files for consistency and to use the correct Prisma client package. This change improves maintainability and ensures the correct client is referenced throughout the codebase. * Undo removal of max-warnings=0 to get main to merge * Remove unit tests for e2e fixtures, provide new prisma mock * Mock @calcom/prisma in event manager * Mock @calcom/prisma in event manager * Add correct format even with --no-verify * Mock prisma in CalendarManager * Add mock for permission-check.service * Better injection in PrismaApiKeyRepository imports * More mock fixes :) * Fix listMembers.handler.test * Fix User import * Appropriately adjust all types to be imported as types, there were a lot of types imported as normal deps * Why was this a thing? * Strictly speaking; Not using prismock anymore * Ditched patch file for prismock * Fix output.service.ts platform type imports, need concrete for plainToClass * Better typing and tests for unlinkConnectedAccount.handler * Small type fix * Disable calendar cache tests as they are dependent on prismock * chore: bump platform lib * getRoutedUrl test remove of unused import * Extract select to external const on getEventTypesFromDB * Direct select of userSelect from selects/user * fix type error from merging 23653 * Fixed integration tests by removing hardcoded values that were possible due to mocking, but as its now directly hitting the db no longer * fix: vite config atoms prisma client type location * revert: example app prisma client * revert: example app prisma client * bump platform libs * fix: use class instead of type for DI of PlatformBookingsService * update platform libs * remove unused variable * chore: generate prisma client for api v2 * fix: api v2 e2e * fix: atoms e2e * fix: atoms e2e * fix: atoms e2e * fix: api v2 e2e * fix: tsconfig apiv2 enums * publish libraries * Simplify check for existence teamId --------- Signed-off-by: Omar López <zomars@me.com> Co-authored-by: Alex van Andel <me@alexvanandel.com> Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com> Co-authored-by: supalarry <laurisskraucis@gmail.com> Co-authored-by: cal.com <morgan@cal.com> Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> Co-authored-by: Benny Joo <sldisek783@gmail.com>
747 lines
18 KiB
TypeScript
747 lines
18 KiB
TypeScript
import { v4 as uuidv4 } from "uuid";
|
|
|
|
import { whereClauseForOrgWithSlugOrRequestedSlug } from "@calcom/ee/organizations/lib/orgDomains";
|
|
import { getOrgUsernameFromEmail } from "@calcom/features/auth/signup/utils/getOrgUsernameFromEmail";
|
|
import { DATABASE_CHUNK_SIZE } from "@calcom/lib/constants";
|
|
import { safeStringify } from "@calcom/lib/safeStringify";
|
|
import prisma from "@calcom/prisma";
|
|
import type { User as PrismaUser } from "@calcom/prisma/client";
|
|
import type { Prisma } from "@calcom/prisma/client";
|
|
import type { Team } from "@calcom/prisma/client";
|
|
import { userMetadata } from "@calcom/prisma/zod-utils";
|
|
import type { UpId, UserAsPersonalProfile, UserProfile } from "@calcom/types/UserProfile";
|
|
|
|
import logger from "../../logger";
|
|
import { getParsedTeam } from "./teamUtils";
|
|
|
|
const userSelect = {
|
|
name: true,
|
|
avatarUrl: true,
|
|
username: true,
|
|
id: true,
|
|
email: true,
|
|
locale: true,
|
|
defaultScheduleId: true,
|
|
startTime: true,
|
|
endTime: true,
|
|
bufferTime: true,
|
|
isPlatformManaged: true,
|
|
} satisfies Prisma.UserSelect;
|
|
|
|
const membershipSelect = {
|
|
id: true,
|
|
teamId: true,
|
|
userId: true,
|
|
accepted: true,
|
|
role: true,
|
|
disableImpersonation: true,
|
|
} satisfies Prisma.MembershipSelect;
|
|
|
|
const log = logger.getSubLogger({ prefix: ["repository/profile"] });
|
|
const organizationSettingsSelect = {
|
|
allowSEOIndexing: true,
|
|
orgProfileRedirectsToVerifiedDomain: true,
|
|
} satisfies Prisma.OrganizationSettingsSelect;
|
|
const organizationSelect = {
|
|
id: true,
|
|
slug: true,
|
|
name: true,
|
|
metadata: true,
|
|
logoUrl: true,
|
|
bannerUrl: true,
|
|
isPlatform: true,
|
|
hideBranding: true,
|
|
};
|
|
const organizationWithSettingsSelect = {
|
|
...organizationSelect,
|
|
organizationSettings: {
|
|
select: organizationSettingsSelect,
|
|
},
|
|
};
|
|
|
|
export enum LookupTarget {
|
|
User,
|
|
Profile,
|
|
}
|
|
|
|
export class ProfileRepository {
|
|
static generateProfileUid() {
|
|
return uuidv4();
|
|
}
|
|
|
|
// This is a minimal replication of UserRepository.findById only selecting the data we need here to prevent circular dependency
|
|
private static async findUserByid({ id }: { id: number }) {
|
|
const user = await prisma.user.findUnique({
|
|
where: {
|
|
id,
|
|
},
|
|
select: {
|
|
id: true,
|
|
username: true,
|
|
name: true,
|
|
avatarUrl: true,
|
|
startTime: true,
|
|
endTime: true,
|
|
bufferTime: true,
|
|
metadata: true,
|
|
},
|
|
});
|
|
|
|
if (!user) {
|
|
return null;
|
|
}
|
|
return {
|
|
...user,
|
|
metadata: userMetadata.parse(user.metadata),
|
|
};
|
|
}
|
|
|
|
private static getInheritedDataFromUser({
|
|
user,
|
|
}: {
|
|
user: Pick<PrismaUser, "name" | "avatarUrl" | "startTime" | "endTime" | "bufferTime">;
|
|
}) {
|
|
return {
|
|
name: user.name,
|
|
avatarUrl: user.avatarUrl,
|
|
startTime: user.startTime,
|
|
endTime: user.endTime,
|
|
bufferTime: user.bufferTime,
|
|
};
|
|
}
|
|
|
|
static getLookupTarget(upId: UpId) {
|
|
if (upId.startsWith("usr-")) {
|
|
return {
|
|
type: LookupTarget.User,
|
|
id: parseInt(upId.replace("usr-", "")),
|
|
} as const;
|
|
}
|
|
return {
|
|
type: LookupTarget.Profile,
|
|
id: parseInt(upId),
|
|
} as const;
|
|
}
|
|
|
|
private static async _create({
|
|
userId,
|
|
organizationId,
|
|
username,
|
|
email,
|
|
movedFromUserId,
|
|
}: {
|
|
userId: number;
|
|
organizationId: number;
|
|
username: string | null;
|
|
email: string;
|
|
movedFromUserId?: number;
|
|
}) {
|
|
log.debug("_create", safeStringify({ userId, organizationId, username, email }));
|
|
return prisma.profile.create({
|
|
data: {
|
|
uid: ProfileRepository.generateProfileUid(),
|
|
user: {
|
|
connect: {
|
|
id: userId,
|
|
},
|
|
},
|
|
organization: {
|
|
connect: {
|
|
id: organizationId,
|
|
},
|
|
},
|
|
...(movedFromUserId
|
|
? {
|
|
movedFromUser: {
|
|
connect: {
|
|
id: movedFromUserId,
|
|
},
|
|
},
|
|
}
|
|
: null),
|
|
|
|
username: username || email.split("@")[0],
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Accepts `email` as a source to derive username from when username is null
|
|
* @returns
|
|
*/
|
|
static create({
|
|
userId,
|
|
organizationId,
|
|
username,
|
|
email,
|
|
}: {
|
|
userId: number;
|
|
organizationId: number;
|
|
username: string | null;
|
|
email: string;
|
|
}) {
|
|
return ProfileRepository._create({ userId, organizationId, username, email });
|
|
}
|
|
|
|
static async upsert({
|
|
create,
|
|
update,
|
|
updateWhere,
|
|
}: {
|
|
create: {
|
|
userId: number;
|
|
organizationId: number;
|
|
username: string | null;
|
|
email: string;
|
|
};
|
|
update: {
|
|
username: string | null;
|
|
email: string;
|
|
};
|
|
updateWhere: {
|
|
userId: number;
|
|
organizationId: number;
|
|
};
|
|
}) {
|
|
return prisma.profile.upsert({
|
|
create: {
|
|
uid: ProfileRepository.generateProfileUid(),
|
|
user: {
|
|
connect: {
|
|
id: create.userId,
|
|
},
|
|
},
|
|
organization: {
|
|
connect: {
|
|
id: create.organizationId,
|
|
},
|
|
},
|
|
username: create.username || create.email.split("@")[0],
|
|
},
|
|
update: {
|
|
username: update.username || update.email.split("@")[0],
|
|
},
|
|
where: {
|
|
userId_organizationId: {
|
|
userId: updateWhere.userId,
|
|
organizationId: updateWhere.organizationId,
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
static async createForExistingUser({
|
|
userId,
|
|
organizationId,
|
|
username,
|
|
email,
|
|
movedFromUserId,
|
|
}: {
|
|
userId: number;
|
|
organizationId: number;
|
|
username: string | null;
|
|
email: string;
|
|
movedFromUserId: number;
|
|
}) {
|
|
return await ProfileRepository._create({
|
|
userId,
|
|
organizationId,
|
|
username,
|
|
email: email,
|
|
movedFromUserId,
|
|
});
|
|
}
|
|
|
|
static async createManyForExistingUsers({
|
|
users,
|
|
organizationId,
|
|
orgAutoAcceptEmail,
|
|
}: {
|
|
users: { id: number; username: string | null; email: string }[];
|
|
organizationId: number;
|
|
orgAutoAcceptEmail: string;
|
|
}) {
|
|
await prisma.profile.createMany({
|
|
data: users.map((user) => ({
|
|
uid: ProfileRepository.generateProfileUid(),
|
|
userId: user.id,
|
|
organizationId,
|
|
username: user?.username || getOrgUsernameFromEmail(user.email, orgAutoAcceptEmail),
|
|
})),
|
|
skipDuplicates: true,
|
|
});
|
|
|
|
// Populate the movedFromUser
|
|
const createdProfiles = await prisma.profile.findMany({
|
|
where: {
|
|
userId: {
|
|
in: users.map((user) => user.id),
|
|
},
|
|
organizationId,
|
|
},
|
|
});
|
|
|
|
for (let i = 0; i < createdProfiles.length; i += DATABASE_CHUNK_SIZE) {
|
|
const profilesBatch = createdProfiles.slice(i, i + DATABASE_CHUNK_SIZE);
|
|
await Promise.allSettled([
|
|
profilesBatch.map((profile) => {
|
|
prisma.user.update({
|
|
where: {
|
|
id: profile.userId,
|
|
},
|
|
data: {
|
|
movedToProfile: {
|
|
connect: { id: profile.id },
|
|
},
|
|
},
|
|
});
|
|
}),
|
|
]);
|
|
}
|
|
}
|
|
|
|
static async createManyPromise({
|
|
users,
|
|
organizationId,
|
|
orgAutoAcceptEmail,
|
|
}: {
|
|
users: { id: number; username: string | null; email: string }[];
|
|
organizationId: number;
|
|
orgAutoAcceptEmail: string;
|
|
}) {
|
|
return await prisma.profile.createMany({
|
|
data: users.map((user) => ({
|
|
uid: ProfileRepository.generateProfileUid(),
|
|
userId: user.id,
|
|
organizationId,
|
|
username: user?.username || getOrgUsernameFromEmail(user.email, orgAutoAcceptEmail),
|
|
})),
|
|
skipDuplicates: true,
|
|
});
|
|
}
|
|
|
|
static createMany({
|
|
users,
|
|
organizationId,
|
|
orgAutoAcceptEmail,
|
|
}: {
|
|
users: { id: number; username: string | null; email: string }[];
|
|
organizationId: number;
|
|
orgAutoAcceptEmail: string;
|
|
}) {
|
|
return prisma.profile.createMany({
|
|
data: users.map((user) => ({
|
|
uid: ProfileRepository.generateProfileUid(),
|
|
userId: user.id,
|
|
organizationId,
|
|
username: user?.username || getOrgUsernameFromEmail(user.email, orgAutoAcceptEmail),
|
|
})),
|
|
skipDuplicates: true,
|
|
});
|
|
}
|
|
|
|
static delete({ userId, organizationId }: { userId: number; organizationId: number }) {
|
|
// Even though there can be just one profile matching a userId and organizationId, we are using deleteMany as it won't error if the profile doesn't exist
|
|
return prisma.profile.deleteMany({
|
|
where: { userId, organizationId },
|
|
});
|
|
}
|
|
|
|
static deleteMany({ userIds }: { userIds: number[] }) {
|
|
// Even though there can be just one profile matching a userId and organizationId, we are using deleteMany as it won't error if the profile doesn't exist
|
|
return prisma.profile.deleteMany({
|
|
where: { userId: { in: userIds } },
|
|
});
|
|
}
|
|
|
|
static async findByUserIdAndOrgId({
|
|
userId,
|
|
organizationId,
|
|
}: {
|
|
userId: number;
|
|
organizationId: number | null;
|
|
}) {
|
|
if (!organizationId) {
|
|
return null;
|
|
}
|
|
const profile = await prisma.profile.findUnique({
|
|
where: {
|
|
userId_organizationId: {
|
|
userId,
|
|
organizationId,
|
|
},
|
|
},
|
|
include: {
|
|
organization: {
|
|
select: organizationSelect,
|
|
},
|
|
user: {
|
|
select: userSelect,
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!profile) {
|
|
return null;
|
|
}
|
|
|
|
const organization = getParsedTeam(profile.organization);
|
|
return normalizeProfile({
|
|
...profile,
|
|
organization: {
|
|
...organization,
|
|
requestedSlug: organization.metadata?.requestedSlug ?? null,
|
|
metadata: organization.metadata,
|
|
},
|
|
});
|
|
}
|
|
|
|
static async findByOrgIdAndUsername({
|
|
organizationId,
|
|
username,
|
|
}: {
|
|
organizationId: number;
|
|
username: string;
|
|
}) {
|
|
const profile = await prisma.profile.findUnique({
|
|
where: {
|
|
username_organizationId: {
|
|
username,
|
|
organizationId,
|
|
},
|
|
},
|
|
include: {
|
|
organization: {
|
|
select: organizationSelect,
|
|
},
|
|
user: {
|
|
select: userSelect,
|
|
},
|
|
},
|
|
});
|
|
return profile;
|
|
}
|
|
|
|
static async findByUpId(upId: string) {
|
|
const lookupTarget = ProfileRepository.getLookupTarget(upId);
|
|
log.debug("findByUpId", safeStringify({ upId, lookupTarget }));
|
|
if (lookupTarget.type === LookupTarget.User) {
|
|
const user = await this.findUserByid({ id: lookupTarget.id });
|
|
if (!user) {
|
|
return null;
|
|
}
|
|
return {
|
|
username: user.username,
|
|
upId: `usr-${user.id}`,
|
|
id: null,
|
|
organizationId: null,
|
|
organization: null,
|
|
...ProfileRepository.getInheritedDataFromUser({ user }),
|
|
};
|
|
}
|
|
|
|
const profile = await ProfileRepository.findById(lookupTarget.id);
|
|
if (!profile) {
|
|
return null;
|
|
}
|
|
const user = profile.user;
|
|
if (profile.organization?.isPlatform && !user.isPlatformManaged) {
|
|
return {
|
|
...this.buildPersonalProfileFromUser({ user }),
|
|
...ProfileRepository.getInheritedDataFromUser({ user }),
|
|
};
|
|
}
|
|
return {
|
|
...profile,
|
|
...ProfileRepository.getInheritedDataFromUser({ user }),
|
|
};
|
|
}
|
|
|
|
static async findById(id: number | null) {
|
|
if (!id) {
|
|
return null;
|
|
}
|
|
|
|
const profile = await prisma.profile.findUnique({
|
|
where: {
|
|
id,
|
|
},
|
|
include: {
|
|
user: {
|
|
select: userSelect,
|
|
},
|
|
movedFromUser: {
|
|
select: {
|
|
id: true,
|
|
},
|
|
},
|
|
organization: {
|
|
select: {
|
|
id: true,
|
|
logoUrl: true,
|
|
name: true,
|
|
slug: true,
|
|
metadata: true,
|
|
bannerUrl: true,
|
|
isPrivate: true,
|
|
isPlatform: true,
|
|
hideBranding: true,
|
|
organizationSettings: {
|
|
select: {
|
|
lockEventTypeCreationForUsers: true,
|
|
allowSEOIndexing: true,
|
|
},
|
|
},
|
|
members: {
|
|
distinct: ["role"],
|
|
select: membershipSelect,
|
|
where: {
|
|
accepted: true,
|
|
// Filter out memberships that are not owned by the user
|
|
user: { profiles: { some: { id } } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!profile) {
|
|
return null;
|
|
}
|
|
|
|
return normalizeProfile(profile);
|
|
}
|
|
|
|
static async findManyByOrgSlugOrRequestedSlug({
|
|
usernames,
|
|
orgSlug,
|
|
}: {
|
|
usernames: string[];
|
|
orgSlug: string;
|
|
}) {
|
|
logger.debug("findManyByOrgSlugOrRequestedSlug", safeStringify({ usernames, orgSlug }));
|
|
const profiles = await prisma.profile.findMany({
|
|
where: {
|
|
username: {
|
|
in: usernames,
|
|
},
|
|
organization: whereClauseForOrgWithSlugOrRequestedSlug(orgSlug),
|
|
},
|
|
include: {
|
|
user: {
|
|
select: userSelect,
|
|
},
|
|
organization: {
|
|
select: organizationWithSettingsSelect,
|
|
},
|
|
},
|
|
});
|
|
|
|
return profiles.map(normalizeProfile);
|
|
}
|
|
|
|
static async findAllProfilesForUserIncludingMovedUser(user: {
|
|
id: number;
|
|
username: string | null;
|
|
}): Promise<UserProfile[]> {
|
|
const profiles = await ProfileRepository.findManyForUser(user);
|
|
// User isn't member of any organization. Also, he has no user profile. We build the profile from user table
|
|
if (!profiles.length) {
|
|
return [
|
|
ProfileRepository.buildPersonalProfileFromUser({
|
|
user,
|
|
}),
|
|
];
|
|
}
|
|
|
|
return profiles;
|
|
}
|
|
|
|
static async findManyForUsers(userIds: number[]) {
|
|
const profiles = await prisma.profile.findMany({
|
|
where: {
|
|
userId: {
|
|
in: userIds,
|
|
},
|
|
},
|
|
include: {
|
|
organization: {
|
|
select: organizationSelect,
|
|
},
|
|
},
|
|
});
|
|
|
|
return profiles.map((profile) => {
|
|
const parsedOrganization = getParsedTeam(profile.organization);
|
|
|
|
return normalizeProfile({
|
|
username: profile.username,
|
|
id: profile.id,
|
|
userId: profile.userId,
|
|
uid: profile.uid,
|
|
name: parsedOrganization.name,
|
|
organizationId: profile.organizationId,
|
|
organization: {
|
|
...parsedOrganization,
|
|
requestedSlug: parsedOrganization.metadata?.requestedSlug ?? null,
|
|
metadata: parsedOrganization.metadata,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
static async findManyForUser(user: { id: number }) {
|
|
const profiles = (
|
|
await prisma.profile.findMany({
|
|
where: {
|
|
userId: user.id,
|
|
},
|
|
include: {
|
|
organization: {
|
|
select: organizationSelect,
|
|
},
|
|
},
|
|
})
|
|
)
|
|
.map((profile) => {
|
|
return {
|
|
...profile,
|
|
organization: getParsedTeam(profile.organization),
|
|
};
|
|
})
|
|
.map((profile) => {
|
|
return normalizeProfile({
|
|
username: profile.username,
|
|
id: profile.id,
|
|
userId: profile.userId,
|
|
uid: profile.uid,
|
|
name: profile.organization.name,
|
|
organizationId: profile.organizationId,
|
|
organization: {
|
|
...profile.organization,
|
|
requestedSlug: profile.organization.metadata?.requestedSlug ?? null,
|
|
metadata: profile.organization.metadata,
|
|
},
|
|
});
|
|
});
|
|
return profiles;
|
|
}
|
|
|
|
static async findManyForOrg({ organizationId }: { organizationId: number }) {
|
|
return await prisma.profile.findMany({
|
|
where: {
|
|
organizationId,
|
|
},
|
|
include: {
|
|
user: {
|
|
select: userSelect,
|
|
},
|
|
organization: {
|
|
select: organizationSelect,
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
static async findByUserIdAndProfileId({ userId, profileId }: { userId: number; profileId: number }) {
|
|
const profile = await prisma.profile.findUnique({
|
|
where: {
|
|
userId,
|
|
id: profileId,
|
|
},
|
|
include: {
|
|
organization: {
|
|
select: organizationSelect,
|
|
},
|
|
user: {
|
|
select: userSelect,
|
|
},
|
|
},
|
|
});
|
|
if (!profile) {
|
|
return profile;
|
|
}
|
|
return normalizeProfile(profile);
|
|
}
|
|
|
|
static async findByUserIdAndOrgSlug({
|
|
userId,
|
|
organizationId,
|
|
}: {
|
|
userId: number;
|
|
organizationId: number;
|
|
}) {
|
|
const profile = await prisma.profile.findUnique({
|
|
where: {
|
|
userId_organizationId: {
|
|
userId,
|
|
organizationId,
|
|
},
|
|
},
|
|
include: {
|
|
organization: {
|
|
select: organizationSelect,
|
|
},
|
|
},
|
|
});
|
|
return profile;
|
|
}
|
|
|
|
/**
|
|
* Personal profile should come from Profile table only
|
|
*/
|
|
static buildPersonalProfileFromUser({
|
|
user,
|
|
}: {
|
|
user: { username: string | null; id: number };
|
|
}): UserAsPersonalProfile {
|
|
return {
|
|
id: null,
|
|
upId: `usr-${user.id}`,
|
|
username: user.username,
|
|
organizationId: null,
|
|
organization: null,
|
|
};
|
|
}
|
|
|
|
static _getPrismaWhereForProfilesOfOrg({ orgSlug }: { orgSlug: string | null }) {
|
|
return {
|
|
profiles: {
|
|
...(orgSlug
|
|
? {
|
|
some: {
|
|
organization: {
|
|
slug: orgSlug,
|
|
},
|
|
},
|
|
}
|
|
: // If it's not orgSlug we want to ensure that no profile is there. Having a profile means that the user is a member of some organization.
|
|
{
|
|
none: {},
|
|
}),
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
export const normalizeProfile = <
|
|
T extends {
|
|
id: number;
|
|
organization: Pick<Team, keyof typeof organizationSelect>;
|
|
createdAt?: Date;
|
|
updatedAt?: Date;
|
|
}
|
|
>(
|
|
profile: T
|
|
) => {
|
|
return {
|
|
...profile,
|
|
upId: profile.id.toString(),
|
|
organization: getParsedTeam(profile.organization),
|
|
// Make these ↓ props ISO strings so that they can be returned from getServerSideProps as is without any issues
|
|
...(profile.createdAt ? { createdAt: profile.createdAt.toISOString() } : null),
|
|
...(profile.updatedAt ? { updatedAt: profile.updatedAt.toISOString() } : null),
|
|
};
|
|
};
|