recovering from merge missing files, adding Hubspot sync capabilities

This commit is contained in:
Khalim Conn-Kowlessar 2026-04-18 10:46:21 +00:00
parent 9a13e0bf3f
commit 6056214039
7 changed files with 174 additions and 1 deletions

View file

@ -10,6 +10,7 @@ import { and, eq, inArray, sql } from "drizzle-orm";
import { z } from "zod";
import { getServerSession } from "next-auth";
import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { syncMeasureApprovalsToHubSpot } from "@/app/lib/hubspot/dealSync";
async function getRequestingUserId(email: string): Promise<bigint | null> {
const rows = await db
@ -204,6 +205,31 @@ export async function POST(
});
}
const affectedDealIds = [...new Set(body.changes.map((c) => c.hubspotDealId))];
for (const dealId of affectedDealIds) {
const approvalRows = await db
.select({
measureName: dealMeasureApprovals.measureName,
approvedByEmail: user.email,
})
.from(dealMeasureApprovals)
.leftJoin(user, eq(user.id, dealMeasureApprovals.approvedBy))
.where(
and(
eq(dealMeasureApprovals.hubspotDealId, dealId),
eq(dealMeasureApprovals.isApproved, true),
),
);
void syncMeasureApprovalsToHubSpot({
hubspotDealId: dealId,
approvedMeasures: approvalRows.map((r) => ({
measureName: r.measureName,
approvedByEmail: r.approvedByEmail ?? "unknown",
})),
});
}
return NextResponse.json({ success: true });
} catch (err) {
console.error("POST /approvals error:", err);

View file

@ -7,6 +7,7 @@ import { and, eq, desc } from "drizzle-orm";
import { z } from "zod";
import { getServerSession } from "next-auth";
import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { syncRemovalRequestToHubSpot } from "@/app/lib/hubspot/dealSync";
const WRITE_ROLES = ["creator", "admin", "write"] as const;
@ -204,6 +205,13 @@ export async function POST(
})
.returning();
void syncRemovalRequestToHubSpot({
hubspotDealId,
status: "pending",
reason,
requestedByEmail: requestingUser.email,
});
return NextResponse.json({ success: true, id: String(inserted.id) });
} catch (err) {
console.error("[removal-requests POST]", err);
@ -258,8 +266,15 @@ export async function PATCH(
try {
const target = await db
.select({ id: propertyRemovalRequests.id, status: propertyRemovalRequests.status })
.select({
id: propertyRemovalRequests.id,
status: propertyRemovalRequests.status,
hubspotDealId: propertyRemovalRequests.hubspotDealId,
reason: propertyRemovalRequests.reason,
requestedByEmail: user.email,
})
.from(propertyRemovalRequests)
.innerJoin(user, eq(user.id, propertyRemovalRequests.requestedBy))
.where(eq(propertyRemovalRequests.id, BigInt(requestId)))
.limit(1);
@ -283,6 +298,14 @@ export async function PATCH(
})
.where(eq(propertyRemovalRequests.id, BigInt(requestId)));
void syncRemovalRequestToHubSpot({
hubspotDealId: target[0].hubspotDealId,
status: action,
reason: target[0].reason,
requestedByEmail: target[0].requestedByEmail,
reviewedByEmail: requestingUser.email,
});
return NextResponse.json({ success: true });
} catch (err) {
console.error("[removal-requests PATCH]", err);

View file

@ -6,6 +6,7 @@ import { eq, inArray } from "drizzle-orm";
import { z } from "zod";
import { getServerSession } from "next-auth";
import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { syncContractorDocUploadToHubSpot } from "@/app/lib/hubspot/dealSync";
// POST — record a contractor install document in uploaded_files (fileType optional — can be classified later)
export async function POST(req: NextRequest) {
@ -56,6 +57,15 @@ export async function POST(req: NextRequest) {
})
.returning({ id: uploadedFiles.id });
if (body.hubspotDealId) {
void syncContractorDocUploadToHubSpot({
hubspotDealId: body.hubspotDealId,
fileType: body.fileType ?? null,
measureName: body.measureName ?? null,
uploadedByEmail: session.user.email,
});
}
return NextResponse.json({ id: inserted.id.toString() }, { status: 201 });
} catch (err) {
console.error("POST /upload/contractor-install error:", err);

View file

@ -0,0 +1,14 @@
import { Client } from "@hubspot/api-client";
let _client: Client | null = null;
export function getHubSpotClient(): Client {
if (!_client) {
const accessToken = process.env.HUBSPOT_API_KEY;
if (!accessToken) {
throw new Error("HUBSPOT_API_KEY environment variable is not set");
}
_client = new Client({ accessToken });
}
return _client;
}

View file

@ -0,0 +1,96 @@
import { getHubSpotClient } from "./client";
export async function syncRemovalRequestToHubSpot(params: {
hubspotDealId: string;
status: "pending" | "approved" | "declined";
reason: string;
requestedByEmail: string;
reviewedByEmail?: string | null;
}): Promise<void> {
try {
const client = getHubSpotClient();
const statusLabel =
params.status === "pending"
? "Removal Request In Progress"
: params.status === "approved"
? "Removed From Project"
: "";
let log = `Requested by: ${params.requestedByEmail}\nReason: ${params.reason}`;
if (params.reviewedByEmail) {
const action = params.status === "approved" ? "Approved" : "Declined";
log += `\n${action} by: ${params.reviewedByEmail}`;
}
await client.crm.deals.basicApi.update(params.hubspotDealId, {
properties: {
project_removal_status: statusLabel,
project_removal_request_log: log,
},
});
} catch (err) {
console.error("[HubSpot] syncRemovalRequestToHubSpot failed", {
dealId: params.hubspotDealId,
error: err,
});
}
}
export async function syncContractorDocUploadToHubSpot(params: {
hubspotDealId: string;
fileType: string | null;
measureName: string | null;
uploadedByEmail: string;
}): Promise<void> {
try {
const client = getHubSpotClient();
const log = [
`File type: ${params.fileType ?? "unclassified"}`,
`Measure: ${params.measureName ?? "N/A"}`,
`Uploaded by: ${params.uploadedByEmail}`,
].join("\n");
await client.crm.deals.basicApi.update(params.hubspotDealId, {
properties: {
contractor_document_upload_log: log,
},
});
} catch (err) {
console.error("[HubSpot] syncContractorDocUploadToHubSpot failed", {
dealId: params.hubspotDealId,
error: err,
});
}
}
export async function syncMeasureApprovalsToHubSpot(params: {
hubspotDealId: string;
approvedMeasures: Array<{ measureName: string; approvedByEmail: string }>;
}): Promise<void> {
try {
const client = getHubSpotClient();
const log =
params.approvedMeasures.length === 0
? "No measures currently approved"
: [
"Approved measures:",
...params.approvedMeasures.map(
(m) => `- ${m.measureName} (approved by ${m.approvedByEmail})`,
),
].join("\n");
await client.crm.deals.basicApi.update(params.hubspotDealId, {
properties: {
client_measures_approval_log: log,
},
});
} catch (err) {
console.error("[HubSpot] syncMeasureApprovalsToHubSpot failed", {
dealId: params.hubspotDealId,
error: err,
});
}
}

View file

@ -1,4 +1,5 @@
import { UsersPermissionsCard } from "../UsersPermissionsCard";
import { CapabilitiesCard } from "../CapabilitiesCard";
export default async function UserAccessPage(props: {
params: Promise<{ slug: string }>;
@ -8,6 +9,7 @@ export default async function UserAccessPage(props: {
return (
<div>
<UsersPermissionsCard portfolioId={slug} />
<CapabilitiesCard portfolioId={slug} />
</div>
);
}

View file

@ -217,6 +217,8 @@ export default function LiveTracker({
data={currentProject?.allDeals ?? []}
onOpenDrawer={handleOpenDrawer}
docStatusMap={docStatusMap}
portfolioId={portfolioId}
userCapability={userCapability}
/>
</div>
</TabsContent>