diff --git a/apps/api/v1/pages/api/me/_get.ts b/apps/api/v1/pages/api/me/_get.ts index 637ebc1bb7..d7887a15da 100644 --- a/apps/api/v1/pages/api/me/_get.ts +++ b/apps/api/v1/pages/api/me/_get.ts @@ -7,7 +7,12 @@ import { schemaUserReadPublic } from "~/lib/validations/user"; async function handler({ userId }: NextApiRequest) { const data = await prisma.user.findUniqueOrThrow({ where: { id: userId } }); - return { user: schemaUserReadPublic.parse(data) }; + return { + user: schemaUserReadPublic.parse({ + ...data, + avatar: data.avatarUrl, + }), + }; } export default defaultResponder(handler); diff --git a/apps/api/v1/pages/api/users/[userId]/_get.ts b/apps/api/v1/pages/api/users/[userId]/_get.ts index 43dba1e4c1..19b9a6c293 100644 --- a/apps/api/v1/pages/api/users/[userId]/_get.ts +++ b/apps/api/v1/pages/api/users/[userId]/_get.ts @@ -45,7 +45,10 @@ export async function getHandler(req: NextApiRequest) { if (!isSystemWideAdmin && query.userId !== req.userId) throw new HttpError({ statusCode: 403, message: "Forbidden" }); const data = await prisma.user.findUnique({ where: { id: query.userId } }); - const user = schemaUserReadPublic.parse(data); + const user = schemaUserReadPublic.parse({ + ...data, + avatar: data?.avatarUrl, + }); return { user }; } diff --git a/apps/api/v1/pages/api/users/[userId]/_patch.ts b/apps/api/v1/pages/api/users/[userId]/_patch.ts index a69aa29f0e..cd7ecceaee 100644 --- a/apps/api/v1/pages/api/users/[userId]/_patch.ts +++ b/apps/api/v1/pages/api/users/[userId]/_patch.ts @@ -2,7 +2,9 @@ import type { NextApiRequest } from "next"; import { HttpError } from "@calcom/lib/http-error"; import { defaultResponder } from "@calcom/lib/server"; +import { uploadAvatar } from "@calcom/lib/server/avatar"; import prisma from "@calcom/prisma"; +import type { Prisma } from "@calcom/prisma/client"; import { schemaQueryUserId } from "~/lib/validations/shared/queryUserId"; import { schemaUserEditBodyParams, schemaUserReadPublic } from "~/lib/validations/user"; @@ -101,7 +103,8 @@ export async function patchHandler(req: NextApiRequest) { if (!isSystemWideAdmin && query.userId !== req.userId) throw new HttpError({ statusCode: 403, message: "Forbidden" }); - const body = await schemaUserEditBodyParams.parseAsync(req.body); + const { avatar, ...body }: { avatar?: string | undefined } & Prisma.UserUpdateInput = + await schemaUserEditBodyParams.parseAsync(req.body); // disable role or branding changes unless admin. if (!isSystemWideAdmin) { if (body.role) body.role = undefined; @@ -119,6 +122,14 @@ export async function patchHandler(req: NextApiRequest) { message: "Bad request: Invalid default schedule id", }); } + + if (avatar) { + body.avatarUrl = await uploadAvatar({ + userId: query.userId, + avatar: await (await import("@calcom/lib/server/resizeBase64Image")).resizeBase64Image(avatar), + }); + } + const data = await prisma.user.update({ where: { id: query.userId }, data: body, diff --git a/apps/web/components/apps/installation/AccountsStepCard.tsx b/apps/web/components/apps/installation/AccountsStepCard.tsx index 2cb735c544..1c67f70de8 100644 --- a/apps/web/components/apps/installation/AccountsStepCard.tsx +++ b/apps/web/components/apps/installation/AccountsStepCard.tsx @@ -53,7 +53,7 @@ const AccountSelector: FC = ({ ); }; -export type PersonalAccountProps = Pick & { alreadyInstalled: boolean }; +export type PersonalAccountProps = Pick & { alreadyInstalled: boolean }; export type TeamsProp = (Pick & { alreadyInstalled: boolean; @@ -74,7 +74,7 @@ export const AccountsStepCard: FC = ({ teams, personalAcco
onSelect()} diff --git a/apps/web/pages/apps/installation/[[...step]].tsx b/apps/web/pages/apps/installation/[[...step]].tsx index f739a0be91..bc3b280a51 100644 --- a/apps/web/pages/apps/installation/[[...step]].tsx +++ b/apps/web/pages/apps/installation/[[...step]].tsx @@ -474,7 +474,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => const personalAccount = { id: user.id, name: user.name, - avatar: user.avatarUrl, + avatarUrl: user.avatarUrl, alreadyInstalled: appInstalls.some((install) => !Boolean(install.teamId) && install.userId === user.id), }; diff --git a/packages/features/auth/signup/utils/prefillAvatar.ts b/packages/features/auth/signup/utils/prefillAvatar.ts index 082f9c1059..e94f29e4be 100644 --- a/packages/features/auth/signup/utils/prefillAvatar.ts +++ b/packages/features/auth/signup/utils/prefillAvatar.ts @@ -1,9 +1,9 @@ import type { Prisma } from "@prisma/client"; import fetch from "node-fetch"; +import { uploadAvatar } from "@calcom/lib/server/avatar"; import { resizeBase64Image } from "@calcom/lib/server/resizeBase64Image"; import prisma from "@calcom/prisma"; -import { uploadAvatar } from "@calcom/trpc/server/routers/loggedInViewer/updateProfile.handler"; interface IPrefillAvatar { email: string; diff --git a/packages/lib/server/uploadLogo.ts b/packages/lib/server/avatar.ts similarity index 55% rename from packages/lib/server/uploadLogo.ts rename to packages/lib/server/avatar.ts index ed1b7904e0..48b2cdd463 100644 --- a/packages/lib/server/uploadLogo.ts +++ b/packages/lib/server/avatar.ts @@ -2,6 +2,32 @@ import { v4 as uuidv4 } from "uuid"; import { prisma } from "@calcom/prisma"; +export const uploadAvatar = async ({ userId, avatar: data }: { userId: number; avatar: string }) => { + const objectKey = uuidv4(); + + await prisma.avatar.upsert({ + where: { + teamId_userId_isBanner: { + teamId: 0, + userId, + isBanner: false, + }, + }, + create: { + userId: userId, + data, + objectKey, + isBanner: false, + }, + update: { + data, + objectKey, + }, + }); + + return `/api/avatar/${objectKey}.png`; +}; + export const uploadLogo = async ({ teamId, logo: data, diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index f8175099cd..b0a3e60d78 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -237,7 +237,6 @@ model User { emailVerified DateTime? password UserPassword? bio String? - avatar String? avatarUrl String? timeZone String @default("Europe/London") travelSchedules TravelSchedule[] @@ -275,7 +274,6 @@ model User { darkBrandColor String? // the location where the events will end up destinationCalendar DestinationCalendar? - away Boolean @default(false) // participate in dynamic group booking or not allowDynamicBooking Boolean? @default(true) @@ -361,7 +359,6 @@ model Team { name String /// @zod.min(1) slug String? - logo String? logoUrl String? calVideoLogo String? appLogo String? diff --git a/packages/trpc/server/routers/loggedInViewer/updateProfile.handler.ts b/packages/trpc/server/routers/loggedInViewer/updateProfile.handler.ts index 2220d6dff9..20351a9a75 100644 --- a/packages/trpc/server/routers/loggedInViewer/updateProfile.handler.ts +++ b/packages/trpc/server/routers/loggedInViewer/updateProfile.handler.ts @@ -2,7 +2,6 @@ import { Prisma } from "@prisma/client"; // eslint-disable-next-line no-restricted-imports import { keyBy } from "lodash"; import type { GetServerSidePropsContext, NextApiResponse } from "next"; -import { v4 as uuidv4 } from "uuid"; import stripe from "@calcom/app-store/stripepayment/lib/server"; import { getPremiumMonthlyPlanPriceId } from "@calcom/app-store/stripepayment/lib/utils"; @@ -12,6 +11,7 @@ import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata"; import { HttpError } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; import { getTranslation } from "@calcom/lib/server"; +import { uploadAvatar } from "@calcom/lib/server/avatar"; import { checkUsername } from "@calcom/lib/server/checkUsername"; import { updateNewTeamMemberEventTypes } from "@calcom/lib/server/queries"; import { resizeBase64Image } from "@calcom/lib/server/resizeBase64Image"; @@ -36,32 +36,6 @@ type UpdateProfileOptions = { input: TUpdateProfileInputSchema; }; -export const uploadAvatar = async ({ userId, avatar: data }: { userId: number; avatar: string }) => { - const objectKey = uuidv4(); - - await prisma.avatar.upsert({ - where: { - teamId_userId_isBanner: { - teamId: 0, - userId, - isBanner: false, - }, - }, - create: { - userId: userId, - data, - objectKey, - isBanner: false, - }, - update: { - data, - objectKey, - }, - }); - - return `/api/avatar/${objectKey}.png`; -}; - export const updateProfileHandler = async ({ ctx, input }: UpdateProfileOptions) => { const { user } = ctx; const userMetadata = handleUserMetadata({ ctx, input }); diff --git a/packages/trpc/server/routers/loggedInViewer/workflowOrder.handler.ts b/packages/trpc/server/routers/loggedInViewer/workflowOrder.handler.ts index 4bf2516fc9..ce5093e389 100644 --- a/packages/trpc/server/routers/loggedInViewer/workflowOrder.handler.ts +++ b/packages/trpc/server/routers/loggedInViewer/workflowOrder.handler.ts @@ -1,7 +1,7 @@ import type { TFormSchema } from "@calcom/app-store/routing-forms/trpc/forms.schema"; import { hasFilter } from "@calcom/features/filters/lib/hasFilter"; import { prisma } from "@calcom/prisma"; -import type { Prisma } from "@calcom/prisma/client"; +import { Prisma } from "@calcom/prisma/client"; import { entries } from "@calcom/prisma/zod-utils"; import type { TrpcSessionUser } from "@calcom/trpc/server/trpc"; @@ -19,34 +19,36 @@ type RoutingFormOrderOptions = { export const workflowOrderHandler = async ({ ctx, input }: RoutingFormOrderOptions) => { const { user } = ctx; - const includedFields = { - activeOn: { - select: { - eventType: { - select: { - id: true, - title: true, - parentId: true, - _count: { - select: { - children: true, + const { include: includedFields } = Prisma.validator()({ + include: { + activeOn: { + select: { + eventType: { + select: { + id: true, + title: true, + parentId: true, + _count: { + select: { + children: true, + }, }, }, }, }, }, - }, - steps: true, - team: { - select: { - id: true, - slug: true, - name: true, - members: true, - logo: true, + steps: true, + team: { + select: { + id: true, + slug: true, + name: true, + members: true, + logoUrl: true, + }, }, }, - }; + }); const allWorkflows = await prisma.workflow.findMany({ where: { diff --git a/packages/trpc/server/routers/viewer/organizations/update.handler.ts b/packages/trpc/server/routers/viewer/organizations/update.handler.ts index 801aa2a708..db279b8965 100644 --- a/packages/trpc/server/routers/viewer/organizations/update.handler.ts +++ b/packages/trpc/server/routers/viewer/organizations/update.handler.ts @@ -2,9 +2,9 @@ import type { Prisma } from "@prisma/client"; import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants"; import { getMetadataHelpers } from "@calcom/lib/getMetadataHelpers"; +import { uploadLogo } from "@calcom/lib/server/avatar"; import { isOrganisationAdmin } from "@calcom/lib/server/queries/organisations"; import { resizeBase64Image } from "@calcom/lib/server/resizeBase64Image"; -import { uploadLogo } from "@calcom/lib/server/uploadLogo"; import { closeComUpdateTeam } from "@calcom/lib/sync/SyncServiceManager"; import type { PrismaClient } from "@calcom/prisma"; import { prisma } from "@calcom/prisma"; diff --git a/packages/trpc/server/routers/viewer/organizations/updateUser.handler.ts b/packages/trpc/server/routers/viewer/organizations/updateUser.handler.ts index 531bee5af2..7e3d29e01d 100644 --- a/packages/trpc/server/routers/viewer/organizations/updateUser.handler.ts +++ b/packages/trpc/server/routers/viewer/organizations/updateUser.handler.ts @@ -1,12 +1,12 @@ import type { Prisma, PrismaPromise, User, Membership, Profile } from "@prisma/client"; import { ensureOrganizationIsReviewed } from "@calcom/ee/organizations/lib/ensureOrganizationIsReviewed"; +import { uploadAvatar } from "@calcom/lib/server/avatar"; import { checkRegularUsername } from "@calcom/lib/server/checkRegularUsername"; import { isOrganisationAdmin, isOrganisationOwner } from "@calcom/lib/server/queries/organisations"; import { resizeBase64Image } from "@calcom/lib/server/resizeBase64Image"; import { prisma } from "@calcom/prisma"; import { MembershipRole } from "@calcom/prisma/enums"; -import { uploadAvatar } from "@calcom/trpc/server/routers/loggedInViewer/updateProfile.handler"; import type { TrpcSessionUser } from "@calcom/trpc/server/trpc"; import { TRPCError } from "@trpc/server"; diff --git a/packages/trpc/server/routers/viewer/teams/create.handler.ts b/packages/trpc/server/routers/viewer/teams/create.handler.ts index 07879d7803..267a42b678 100644 --- a/packages/trpc/server/routers/viewer/teams/create.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/create.handler.ts @@ -1,8 +1,8 @@ import { generateTeamCheckoutSession } from "@calcom/features/ee/teams/lib/payments"; import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants"; +import { uploadLogo } from "@calcom/lib/server/avatar"; import { ProfileRepository } from "@calcom/lib/server/repository/profile"; import { resizeBase64Image } from "@calcom/lib/server/resizeBase64Image"; -import { uploadLogo } from "@calcom/lib/server/uploadLogo"; import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager"; import { prisma } from "@calcom/prisma"; import { MembershipRole } from "@calcom/prisma/enums"; diff --git a/packages/trpc/server/routers/viewer/teams/update.handler.ts b/packages/trpc/server/routers/viewer/teams/update.handler.ts index 38b54d33f8..e6b1ec9e5d 100644 --- a/packages/trpc/server/routers/viewer/teams/update.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/update.handler.ts @@ -2,8 +2,8 @@ import type { Prisma } from "@prisma/client"; import { getOrgFullOrigin } from "@calcom/ee/organizations/lib/orgDomains"; import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants"; +import { uploadLogo } from "@calcom/lib/server/avatar"; import { isTeamAdmin } from "@calcom/lib/server/queries/teams"; -import { uploadLogo } from "@calcom/lib/server/uploadLogo"; import { closeComUpdateTeam } from "@calcom/lib/sync/SyncServiceManager"; import { prisma } from "@calcom/prisma"; import { RedirectType } from "@calcom/prisma/enums"; diff --git a/packages/trpc/server/routers/viewer/workflows/filteredList.handler.tsx b/packages/trpc/server/routers/viewer/workflows/filteredList.handler.tsx index ef117f3241..862bbf65b3 100644 --- a/packages/trpc/server/routers/viewer/workflows/filteredList.handler.tsx +++ b/packages/trpc/server/routers/viewer/workflows/filteredList.handler.tsx @@ -1,7 +1,7 @@ import type { WorkflowType } from "@calcom/ee/workflows/components/WorkflowListPage"; import { hasFilter } from "@calcom/features/filters/lib/hasFilter"; import type { PrismaClient } from "@calcom/prisma"; -import type { Prisma } from "@calcom/prisma/client"; +import { Prisma } from "@calcom/prisma/client"; import { MembershipRole } from "@calcom/prisma/client"; import type { TrpcSessionUser } from "@calcom/trpc/server/trpc"; @@ -15,34 +15,36 @@ type FilteredListOptions = { input: TFilteredListInputSchema; }; -const includedFields = { - activeOn: { - select: { - eventType: { - select: { - id: true, - title: true, - parentId: true, - _count: { - select: { - children: true, +const { include: includedFields } = Prisma.validator()({ + include: { + activeOn: { + select: { + eventType: { + select: { + id: true, + title: true, + parentId: true, + _count: { + select: { + children: true, + }, }, }, }, }, }, - }, - steps: true, - team: { - select: { - id: true, - slug: true, - name: true, - members: true, - logo: true, + steps: true, + team: { + select: { + id: true, + slug: true, + name: true, + members: true, + logoUrl: true, + }, }, }, -}; +}); export const filteredListHandler = async ({ ctx, input }: FilteredListOptions) => { const { prisma, user } = ctx;