Skip to content

Commit 077a366

Browse files
committed
chore(vercel): Rewrite logic to use neverthrow instead of try-catch
1 parent fa2295d commit 077a366

5 files changed

+312
-259
lines changed

apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx

Lines changed: 45 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {
33
LoaderFunctionArgs,
44
} from "@remix-run/node";
55
import { json, redirect } from "@remix-run/node";
6+
import { fromPromise } from "neverthrow";
67
import { Form, useActionData, useNavigation } from "@remix-run/react";
78
import { typedjson, useTypedLoaderData } from "remix-typedjson";
89
import { z } from "zod";
@@ -145,60 +146,67 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
145146
return json({ error: "Vercel integration not found" }, { status: 404 });
146147
}
147148

148-
try {
149-
// First, attempt to uninstall the integration from Vercel side
150-
const uninstallResult = await VercelIntegrationRepository.uninstallVercelIntegration(vercelIntegration);
149+
const uninstallActionResult = await fromPromise(
150+
(async () => {
151+
// First, attempt to uninstall the integration from Vercel side
152+
const uninstallResult = await VercelIntegrationRepository.uninstallVercelIntegration(vercelIntegration);
151153

152-
// Then soft-delete the integration and all connected projects in a transaction
153-
await $transaction(prisma, async (tx) => {
154-
// Soft-delete all connected projects
155-
await tx.organizationProjectIntegration.updateMany({
156-
where: {
157-
organizationIntegrationId: vercelIntegration.id,
158-
deletedAt: null,
159-
},
160-
data: { deletedAt: new Date() },
161-
});
154+
// Then soft-delete the integration and all connected projects in a transaction
155+
await $transaction(prisma, async (tx) => {
156+
// Soft-delete all connected projects
157+
await tx.organizationProjectIntegration.updateMany({
158+
where: {
159+
organizationIntegrationId: vercelIntegration.id,
160+
deletedAt: null,
161+
},
162+
data: { deletedAt: new Date() },
163+
});
162164

163-
// Soft-delete the integration record
164-
await tx.organizationIntegration.update({
165-
where: { id: vercelIntegration.id },
166-
data: { deletedAt: new Date() },
165+
// Soft-delete the integration record
166+
await tx.organizationIntegration.update({
167+
where: { id: vercelIntegration.id },
168+
data: { deletedAt: new Date() },
169+
});
167170
});
168-
});
169171

170-
if (uninstallResult.authInvalid) {
171-
logger.warn("Vercel integration uninstalled with auth error - token invalid", {
172-
organizationId: organization.id,
173-
organizationSlug,
174-
userId,
175-
integrationId: vercelIntegration.id,
176-
});
177-
} else {
178-
logger.info("Vercel integration uninstalled successfully", {
179-
organizationId: organization.id,
180-
organizationSlug,
181-
userId,
182-
integrationId: vercelIntegration.id,
183-
});
184-
}
172+
return uninstallResult;
173+
})(),
174+
(error) => error
175+
);
185176

186-
// Redirect back to organization settings
187-
return redirect(`/orgs/${organizationSlug}/settings`);
188-
} catch (error) {
177+
if (uninstallActionResult.isErr()) {
189178
logger.error("Failed to uninstall Vercel integration", {
190179
organizationId: organization.id,
191180
organizationSlug,
192181
userId,
193182
integrationId: vercelIntegration.id,
194-
error: error instanceof Error ? error.message : String(error),
183+
error: uninstallActionResult.error instanceof Error ? uninstallActionResult.error.message : String(uninstallActionResult.error),
195184
});
196185

197186
return json(
198187
{ error: "Failed to uninstall Vercel integration. Please try again." },
199188
{ status: 500 }
200189
);
201190
}
191+
192+
if (uninstallActionResult.value.authInvalid) {
193+
logger.warn("Vercel integration uninstalled with auth error - token invalid", {
194+
organizationId: organization.id,
195+
organizationSlug,
196+
userId,
197+
integrationId: vercelIntegration.id,
198+
});
199+
} else {
200+
logger.info("Vercel integration uninstalled successfully", {
201+
organizationId: organization.id,
202+
organizationSlug,
203+
userId,
204+
integrationId: vercelIntegration.id,
205+
});
206+
}
207+
208+
// Redirect back to organization settings
209+
return redirect(`/orgs/${organizationSlug}/settings`);
202210
};
203211

204212
export default function VercelIntegrationPage() {
Lines changed: 81 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
22
import { json } from "@remix-run/server-runtime";
3+
import { fromPromise } from "neverthrow";
34
import { z } from "zod";
45
import { prisma } from "~/db.server";
56
import { apiCors } from "~/utils/apiCors";
@@ -47,79 +48,46 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
4748

4849
const { organizationSlug, projectParam } = parsedParams.data;
4950

50-
try {
51-
// Find the project, verifying org membership
52-
const project = await prisma.project.findFirst({
53-
where: {
54-
slug: projectParam,
55-
organization: {
56-
slug: organizationSlug,
57-
members: {
58-
some: {
59-
userId: authenticationResult.userId,
51+
const result = await fromPromise(
52+
(async () => {
53+
// Find the project, verifying org membership
54+
const project = await prisma.project.findFirst({
55+
where: {
56+
slug: projectParam,
57+
organization: {
58+
slug: organizationSlug,
59+
members: {
60+
some: {
61+
userId: authenticationResult.userId,
62+
},
6063
},
6164
},
65+
deletedAt: null,
6266
},
63-
deletedAt: null,
64-
},
65-
select: {
66-
id: true,
67-
name: true,
68-
slug: true,
69-
organizationId: true,
70-
},
71-
});
67+
select: {
68+
id: true,
69+
name: true,
70+
slug: true,
71+
organizationId: true,
72+
},
73+
});
7274

73-
if (!project) {
74-
return apiCors(
75-
request,
76-
json({ error: "Project not found" }, { status: 404 })
77-
);
78-
}
79-
80-
// Get Vercel integration for the project
81-
const vercelService = new VercelIntegrationService();
82-
const integration = await vercelService.getVercelProjectIntegration(project.id);
83-
84-
if (!integration) {
85-
return apiCors(
86-
request,
87-
json({
88-
connected: false,
89-
vercelProject: null,
90-
config: null,
91-
syncEnvVarsMapping: null,
92-
})
93-
);
94-
}
95-
96-
const { parsedIntegrationData } = integration;
75+
if (!project) {
76+
return { type: "not_found" as const };
77+
}
9778

98-
return apiCors(
99-
request,
100-
json({
101-
connected: true,
102-
vercelProject: {
103-
id: parsedIntegrationData.vercelProjectId,
104-
name: parsedIntegrationData.vercelProjectName,
105-
teamId: parsedIntegrationData.vercelTeamId,
106-
},
107-
config: {
108-
atomicBuilds: parsedIntegrationData.config.atomicBuilds,
109-
pullEnvVarsBeforeBuild: parsedIntegrationData.config.pullEnvVarsBeforeBuild,
110-
vercelStagingEnvironment: parsedIntegrationData.config.vercelStagingEnvironment,
111-
},
112-
syncEnvVarsMapping: parsedIntegrationData.syncEnvVarsMapping,
113-
triggerProject: {
114-
id: project.id,
115-
name: project.name,
116-
slug: project.slug,
117-
},
118-
})
119-
);
120-
} catch (error) {
79+
// Get Vercel integration for the project
80+
const vercelService = new VercelIntegrationService();
81+
const integration = await vercelService.getVercelProjectIntegration(project.id);
82+
83+
return { type: "success" as const, project, integration };
84+
})(),
85+
(error) => error
86+
);
87+
88+
if (result.isErr()) {
12189
logger.error("Failed to fetch Vercel projects", {
122-
error,
90+
error: result.error,
12391
organizationSlug,
12492
projectParam,
12593
});
@@ -129,5 +97,51 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
12997
json({ error: "Internal server error" }, { status: 500 })
13098
);
13199
}
100+
101+
if (result.value.type === "not_found") {
102+
return apiCors(
103+
request,
104+
json({ error: "Project not found" }, { status: 404 })
105+
);
106+
}
107+
108+
const { project, integration } = result.value;
109+
110+
if (!integration) {
111+
return apiCors(
112+
request,
113+
json({
114+
connected: false,
115+
vercelProject: null,
116+
config: null,
117+
syncEnvVarsMapping: null,
118+
})
119+
);
120+
}
121+
122+
const { parsedIntegrationData } = integration;
123+
124+
return apiCors(
125+
request,
126+
json({
127+
connected: true,
128+
vercelProject: {
129+
id: parsedIntegrationData.vercelProjectId,
130+
name: parsedIntegrationData.vercelProjectName,
131+
teamId: parsedIntegrationData.vercelTeamId,
132+
},
133+
config: {
134+
atomicBuilds: parsedIntegrationData.config.atomicBuilds,
135+
pullEnvVarsBeforeBuild: parsedIntegrationData.config.pullEnvVarsBeforeBuild,
136+
vercelStagingEnvironment: parsedIntegrationData.config.vercelStagingEnvironment,
137+
},
138+
syncEnvVarsMapping: parsedIntegrationData.syncEnvVarsMapping,
139+
triggerProject: {
140+
id: project.id,
141+
name: project.name,
142+
slug: project.slug,
143+
},
144+
})
145+
);
132146
}
133147

0 commit comments

Comments
 (0)