perf: remove trpc basecamp3 router (#23785)
* remove trpc basecamp3 router * update * define business logic in appstore package * cleaner * cleaner code * refactor * use zod * cleaner * update
This commit is contained in:
@@ -1,4 +0,0 @@
|
||||
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||
import appBasecamp3 from "@calcom/trpc/server/routers/apps/basecamp3/_router";
|
||||
|
||||
export default createNextApiHandler(appBasecamp3);
|
||||
+41
-24
@@ -1,47 +1,54 @@
|
||||
import type { NextApiRequest } from "next";
|
||||
import { z } from "zod";
|
||||
|
||||
import getAppKeysFromSlug from "@calcom/app-store/_utils/getAppKeysFromSlug";
|
||||
import { refreshAccessToken } from "@calcom/app-store/basecamp3/lib/helpers";
|
||||
import type { BasecampToken } from "@calcom/app-store/basecamp3/lib/types";
|
||||
import type { PrismaClient } from "@calcom/prisma/client";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultHandler } from "@calcom/lib/server/defaultHandler";
|
||||
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import type { TProjectMutationInputSchema } from "./projectMutation.schema";
|
||||
|
||||
interface ProjectMutationHandlerOptions {
|
||||
ctx: {
|
||||
prisma: PrismaClient;
|
||||
user: NonNullable<TrpcSessionUser>;
|
||||
};
|
||||
input: TProjectMutationInputSchema;
|
||||
}
|
||||
|
||||
interface IDock {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
export const projectMutationHandler = async ({ ctx, input }: ProjectMutationHandlerOptions) => {
|
||||
const { user_agent } = await getAppKeysFromSlug("basecamp3");
|
||||
const { user, prisma } = ctx;
|
||||
|
||||
const { projectId } = input;
|
||||
const ZProjectMutationInputSchema = z.object({ projectId: z.string() });
|
||||
|
||||
async function handler(req: NextApiRequest) {
|
||||
const userId = req.session?.user?.id;
|
||||
if (!userId) {
|
||||
throw new HttpError({ statusCode: 401, message: "Unauthorized" });
|
||||
}
|
||||
|
||||
const parsed = ZProjectMutationInputSchema.safeParse(req.body ?? {});
|
||||
if (!parsed.success) {
|
||||
throw new HttpError({
|
||||
statusCode: 400,
|
||||
message: "Invalid request body",
|
||||
});
|
||||
}
|
||||
const { projectId } = parsed.data;
|
||||
|
||||
const { user_agent } = await getAppKeysFromSlug("basecamp3");
|
||||
|
||||
const credential = await prisma.credential.findFirst({
|
||||
where: {
|
||||
userId: user?.id,
|
||||
},
|
||||
where: { userId },
|
||||
select: credentialForCalendarServiceSelect,
|
||||
});
|
||||
|
||||
if (!credential) {
|
||||
throw new TRPCError({ code: "FORBIDDEN", message: "No credential found for user" });
|
||||
throw new HttpError({ statusCode: 403, message: "No credential found for user" });
|
||||
}
|
||||
|
||||
let credentialKey = credential.key as BasecampToken;
|
||||
|
||||
if (credentialKey.expires_at < Date.now()) {
|
||||
credentialKey = (await refreshAccessToken(credential)) as BasecampToken;
|
||||
}
|
||||
// get schedule id
|
||||
|
||||
const basecampUserId = credentialKey.account.id;
|
||||
const scheduleResponse = await fetch(
|
||||
`https://3.basecampapi.com/${basecampUserId}/projects/${projectId}.json`,
|
||||
@@ -52,12 +59,22 @@ export const projectMutationHandler = async ({ ctx, input }: ProjectMutationHand
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!scheduleResponse.ok) {
|
||||
throw new HttpError({ statusCode: 400, message: "Failed to fetch project details" });
|
||||
}
|
||||
|
||||
const scheduleJson = await scheduleResponse.json();
|
||||
const scheduleId = scheduleJson.dock.find((dock: IDock) => dock.name === "schedule").id;
|
||||
|
||||
await prisma.credential.update({
|
||||
where: { id: credential.id },
|
||||
data: { key: { ...credentialKey, projectId: Number(projectId), scheduleId } },
|
||||
});
|
||||
|
||||
return { message: "Updated project successfully" };
|
||||
};
|
||||
}
|
||||
|
||||
export default defaultHandler({
|
||||
POST: Promise.resolve({ default: defaultResponder(handler) }),
|
||||
});
|
||||
+28
-19
@@ -1,34 +1,35 @@
|
||||
import type { NextApiRequest } from "next";
|
||||
|
||||
import getAppKeysFromSlug from "@calcom/app-store/_utils/getAppKeysFromSlug";
|
||||
import { refreshAccessToken } from "@calcom/app-store/basecamp3/lib/helpers";
|
||||
import type { BasecampToken } from "@calcom/app-store/basecamp3/lib/types";
|
||||
import type { PrismaClient } from "@calcom/prisma/client";
|
||||
import { defaultHandler } from "@calcom/lib/server/defaultHandler";
|
||||
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
async function handler(req: NextApiRequest) {
|
||||
const userId = req.session?.user?.id;
|
||||
if (!userId) {
|
||||
throw new HttpError({ statusCode: 401, message: "Unauthorized" });
|
||||
}
|
||||
|
||||
interface ProjectsHandlerOptions {
|
||||
ctx: {
|
||||
prisma: PrismaClient;
|
||||
user: NonNullable<TrpcSessionUser>;
|
||||
};
|
||||
}
|
||||
|
||||
export const projectHandler = async ({ ctx }: ProjectsHandlerOptions) => {
|
||||
const { user_agent } = await getAppKeysFromSlug("basecamp3");
|
||||
const { user, prisma } = ctx;
|
||||
|
||||
const credential = await prisma.credential.findFirst({
|
||||
where: {
|
||||
userId: user?.id,
|
||||
},
|
||||
where: { userId },
|
||||
select: credentialForCalendarServiceSelect,
|
||||
});
|
||||
|
||||
if (!credential) {
|
||||
throw new TRPCError({ code: "FORBIDDEN", message: "No credential found for user" });
|
||||
throw new HttpError({ statusCode: 403, message: "No credential found for user" });
|
||||
}
|
||||
|
||||
let credentialKey = credential.key as BasecampToken;
|
||||
|
||||
if (!credentialKey.account) {
|
||||
return;
|
||||
return { currentProject: null, projects: [] };
|
||||
}
|
||||
|
||||
if (credentialKey.expires_at < Date.now()) {
|
||||
@@ -36,10 +37,18 @@ export const projectHandler = async ({ ctx }: ProjectsHandlerOptions) => {
|
||||
}
|
||||
|
||||
const url = `${credentialKey.account.href}/projects.json`;
|
||||
|
||||
const resp = await fetch(url, {
|
||||
headers: { "User-Agent": user_agent as string, Authorization: `Bearer ${credentialKey.access_token}` },
|
||||
});
|
||||
|
||||
if (!resp.ok) {
|
||||
throw new HttpError({ statusCode: 400, message: "Failed to fetch Basecamp projects" });
|
||||
}
|
||||
|
||||
const projects = await resp.json();
|
||||
return { currentProject: credentialKey.projectId, projects };
|
||||
};
|
||||
}
|
||||
|
||||
export default defaultHandler({
|
||||
GET: Promise.resolve({ default: defaultResponder(handler) }),
|
||||
});
|
||||
@@ -1,34 +1,47 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
import type { EventTypeAppSettingsComponent } from "@calcom/app-store/types";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Select } from "@calcom/ui/components/form";
|
||||
|
||||
const EventTypeAppSettingsInterface: EventTypeAppSettingsComponent = ({}) => {
|
||||
const [projects, setProjects] = useState();
|
||||
const [selectedProject, setSelectedProject] = useState<undefined | { label: string; value: string }>();
|
||||
const { data } = trpc.viewer.appBasecamp3.projects.useQuery();
|
||||
const setProject = trpc.viewer.appBasecamp3.projectMutation.useMutation();
|
||||
type Basecamp3Project = { label: string; value: string };
|
||||
|
||||
useEffect(
|
||||
function refactorMeWithoutEffect() {
|
||||
setSelectedProject({
|
||||
value: data?.projects.currentProject,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
label: data?.projects?.find((project: any) => project.id === data?.currentProject)?.name,
|
||||
});
|
||||
setProjects(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data?.projects?.map((project: any) => {
|
||||
return {
|
||||
value: project.id,
|
||||
label: project.name,
|
||||
};
|
||||
const EventTypeAppSettingsInterface: EventTypeAppSettingsComponent = () => {
|
||||
const [projects, setProjects] = useState<Basecamp3Project[] | undefined>();
|
||||
const [selectedProject, setSelectedProject] = useState<undefined | { label: string; value: string }>();
|
||||
|
||||
useEffect(() => {
|
||||
async function loadProjects() {
|
||||
const res = await fetch("/api/integrations/basecamp3/projects");
|
||||
if (!res.ok) return;
|
||||
const json = await res.json();
|
||||
const currentProjectId = json?.currentProject;
|
||||
const options: Basecamp3Project[] = json?.projects?.map(
|
||||
(project: { id: number | string; name: string }) => ({
|
||||
value: String(project.id),
|
||||
label: project.name,
|
||||
})
|
||||
);
|
||||
},
|
||||
[data]
|
||||
);
|
||||
if (!options) return;
|
||||
setProjects(options);
|
||||
if (currentProjectId) {
|
||||
const match = options.find((project: Basecamp3Project) => project.value === String(currentProjectId));
|
||||
if (match) setSelectedProject(match);
|
||||
}
|
||||
}
|
||||
|
||||
loadProjects();
|
||||
}, []);
|
||||
|
||||
async function handleProjectChange(project: { label: string; value: string } | null) {
|
||||
if (!project) return;
|
||||
|
||||
await fetch("/api/integrations/basecamp3/projectMutation", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ projectId: project.value }),
|
||||
});
|
||||
setSelectedProject(project);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-2 text-sm">
|
||||
@@ -41,12 +54,7 @@ const EventTypeAppSettingsInterface: EventTypeAppSettingsComponent = ({}) => {
|
||||
options={projects}
|
||||
isLoading={!projects}
|
||||
className="md:min-w-[120px]"
|
||||
onChange={(project) => {
|
||||
if (project) {
|
||||
setProject.mutate({ projectId: project?.value.toString() });
|
||||
setSelectedProject(project);
|
||||
}
|
||||
}}
|
||||
onChange={handleProjectChange}
|
||||
value={selectedProject}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import authedProcedure from "@calcom/trpc/server/procedures/authedProcedure";
|
||||
import { router } from "@calcom/trpc/server/trpc";
|
||||
|
||||
import { ZProjectMutationInputSchema } from "./projectMutation.schema";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const UNSTABLE_HANDLER_CACHE: any = {};
|
||||
|
||||
const appBasecamp3 = router({
|
||||
projects: authedProcedure.query(async ({ ctx }) => {
|
||||
if (!UNSTABLE_HANDLER_CACHE.projects) {
|
||||
UNSTABLE_HANDLER_CACHE.projects = await import("./projects.handler").then((mod) => mod.projectHandler);
|
||||
}
|
||||
return UNSTABLE_HANDLER_CACHE.projects({ ctx });
|
||||
}),
|
||||
projectMutation: authedProcedure.input(ZProjectMutationInputSchema).mutation(async ({ ctx, input }) => {
|
||||
if (!UNSTABLE_HANDLER_CACHE.projectMutation) {
|
||||
UNSTABLE_HANDLER_CACHE.projectMutation = await import("./projectMutation.handler").then(
|
||||
(mod) => mod.projectMutationHandler
|
||||
);
|
||||
}
|
||||
return UNSTABLE_HANDLER_CACHE.projectMutation({ ctx, input });
|
||||
}),
|
||||
});
|
||||
|
||||
export default appBasecamp3;
|
||||
@@ -1,7 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const ZProjectMutationInputSchema = z.object({
|
||||
projectId: z.string(),
|
||||
});
|
||||
|
||||
export type TProjectMutationInputSchema = z.infer<typeof ZProjectMutationInputSchema>;
|
||||
@@ -3,7 +3,6 @@ import { featureFlagRouter } from "@calcom/features/flags/server/router";
|
||||
import { insightsRouter } from "@calcom/features/insights/server/trpc-router";
|
||||
|
||||
import { router, mergeRouters } from "../../trpc";
|
||||
import app_Basecamp3 from "../apps/basecamp3/_router";
|
||||
import app_RoutingForms from "../apps/routing-forms/_router";
|
||||
import { loggedInViewerRouter } from "../loggedInViewer/_router";
|
||||
import { publicViewerRouter } from "../publicViewer/_router";
|
||||
@@ -79,7 +78,6 @@ export const viewerRouter = router({
|
||||
// NOTE: Add all app related routes in the bottom till the problem described in @calcom/app-store/trpc-routers.ts is solved.
|
||||
// After that there would just one merge call here for all the apps.
|
||||
appRoutingForms: app_RoutingForms,
|
||||
appBasecamp3: app_Basecamp3,
|
||||
features: featureFlagRouter,
|
||||
users: userAdminRouter,
|
||||
oAuth: oAuthRouter,
|
||||
|
||||
Reference in New Issue
Block a user