diff --git a/packages/nextjs/.env.example b/packages/nextjs/.env.example index ededfd7..6904792 100644 --- a/packages/nextjs/.env.example +++ b/packages/nextjs/.env.example @@ -18,3 +18,7 @@ FIREBASE_PROJECT_ID="buidlguidl-v3" # Setup api-key for admin ADMIN_API_KEY="admin-api-key" + +# For Grants-SRE grant completion sync +SRE_API_KEY= +SRE_API_URL= diff --git a/packages/nextjs/app/api/grants/[grantId]/review/route.tsx b/packages/nextjs/app/api/grants/[grantId]/review/route.tsx index 52ae0e6..4e2fde4 100644 --- a/packages/nextjs/app/api/grants/[grantId]/review/route.tsx +++ b/packages/nextjs/app/api/grants/[grantId]/review/route.tsx @@ -1,8 +1,10 @@ import { NextRequest, NextResponse } from "next/server"; import { EIP712TypedData } from "@safe-global/safe-core-sdk-types"; +import { waitUntil } from "@vercel/functions"; import { recoverTypedDataAddress } from "viem"; -import { reviewGrant } from "~~/services/database/grants"; +import { getGrantById, reviewGrant } from "~~/services/database/grants"; import { findUserByAddress } from "~~/services/database/users"; +import { extractBuildId, sendBuildToSRE } from "~~/services/sre"; import { EIP_712_DOMAIN, EIP_712_TYPES__REVIEW_GRANT, EIP_712_TYPES__REVIEW_GRANT_WITH_NOTE } from "~~/utils/eip712"; import { PROPOSAL_STATUS, ProposalStatusType } from "~~/utils/grants"; import { validateSafeSignature } from "~~/utils/safe-signature"; @@ -24,7 +26,6 @@ export async function POST(req: NextRequest, { params }: { params: { grantId: st // Validate action is valid const validActions = Object.values(PROPOSAL_STATUS); if (!validActions.includes(action)) { - console.error("Invalid action", action); return NextResponse.json({ error: "Invalid action" }, { status: 400 }); } @@ -84,7 +85,6 @@ export async function POST(req: NextRequest, { params }: { params: { grantId: st } if (!isValidSignature) { - console.error("Invalid signature", signer); return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } @@ -92,7 +92,6 @@ export async function POST(req: NextRequest, { params }: { params: { grantId: st const signerData = await findUserByAddress(signer); if (signerData.data?.role !== "admin") { - console.error("Unauthorized", signer); return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } @@ -105,9 +104,29 @@ export async function POST(req: NextRequest, { params }: { params: { grantId: st txChainId, }); } catch (error) { - console.error("Error approving grant", error); return NextResponse.json({ error: "Error approving grant" }, { status: 500 }); } + // Send build to SRE to mark it as bgGrant + if (action === PROPOSAL_STATUS.COMPLETED) { + // Use waitUntil for background processing + waitUntil( + (async () => { + try { + const grant = await getGrantById(grantId); + const buildId = extractBuildId(grant?.link); + if (buildId) { + await sendBuildToSRE(buildId); + } else { + console.warn("[SRE] Could not derive buildId from link – skipping SRE sync", grantId); + } + } catch (sreError) { + // Log SRE errors but don't fail the main operation + console.error("[SRE] Error notifying SpeedRunEthereum:", sreError); + } + })(), + ); + } + return NextResponse.json({ success: true }); } diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 141d485..159f0cb 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -21,6 +21,7 @@ "@safe-global/safe-core-sdk-types": "^5.0.3", "@uniswap/sdk-core": "^4.0.1", "@uniswap/v2-sdk": "^3.0.1", + "@vercel/functions": "^2.0.0", "blo": "^1.0.1", "daisyui": "4.5.0", "firebase-admin": "^11.11.1", diff --git a/packages/nextjs/services/sre.ts b/packages/nextjs/services/sre.ts new file mode 100644 index 0000000..c755eb0 --- /dev/null +++ b/packages/nextjs/services/sre.ts @@ -0,0 +1,49 @@ +export const extractBuildId = (link: string | undefined | null): string | null => { + if (!link) return null; + + try { + const url = new URL(link); + // Only try to extract the buildId from speedrunethereum.com + if (!url.hostname.endsWith("speedrunethereum.com")) { + return null; + } + + const parts = url.pathname.split("/").filter(Boolean); + const id = parts.pop(); + if (parts[parts.length - 1] !== "builds" || !id) { + return null; + } + return id; + } catch { + return null; + } +}; + +export const sendBuildToSRE = async (buildId: string): Promise => { + const apiUrl = process.env.SRE_API_URL; + const apiKey = process.env.SRE_API_KEY; + + if (!apiUrl || !apiKey) { + console.warn("[SRE] Missing SRE config (SRE_API_URL or SRE_API_KEY); skipping call"); + return; + } + if (!buildId) { + console.warn("[SRE] No buildId supplied, nothing to send to SRE"); + return; + } + + try { + const payload = { buildId, apiKey }; + const response = await fetch(apiUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + }); + + if (!response.ok) { + console.error("[SRE] SRE endpoint returned error status:", response.status); + } + } catch (error) { + console.error("[SRE] Failed to send build information to SRE", error); + } +}; diff --git a/yarn.lock b/yarn.lock index c3703de..1f36134 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2358,6 +2358,7 @@ __metadata: "@typescript-eslint/eslint-plugin": ^5.39.0 "@uniswap/sdk-core": ^4.0.1 "@uniswap/v2-sdk": ^3.0.1 + "@vercel/functions": ^2.0.0 autoprefixer: ^10.4.12 blo: ^1.0.1 daisyui: 4.5.0 @@ -3701,6 +3702,20 @@ __metadata: languageName: node linkType: hard +"@vercel/functions@npm:^2.0.0": + version: 2.2.8 + resolution: "@vercel/functions@npm:2.2.8" + dependencies: + "@vercel/oidc": 2.0.0 + peerDependencies: + "@aws-sdk/credential-provider-web-identity": "*" + peerDependenciesMeta: + "@aws-sdk/credential-provider-web-identity": + optional: true + checksum: 06e07f83205dc328018e9dcb6c41f3a71ac3ad2d58707748bda8877cf1877044ec2d959b6f50c008b0ef1310c168986023605180dfe108364926bbb15522ba46 + languageName: node + linkType: hard + "@vercel/gatsby-plugin-vercel-analytics@npm:1.0.11": version: 1.0.11 resolution: "@vercel/gatsby-plugin-vercel-analytics@npm:1.0.11" @@ -3798,6 +3813,13 @@ __metadata: languageName: node linkType: hard +"@vercel/oidc@npm:2.0.0": + version: 2.0.0 + resolution: "@vercel/oidc@npm:2.0.0" + checksum: cb93084e1996dd021fff0e3e88ecd7b33e6451a1646279b6d7309188d2bc3c4249031eb8e40af52e4e145065daee086b8d57cf785be4589a285b2a2643baa125 + languageName: node + linkType: hard + "@vercel/python@npm:4.0.2": version: 4.0.2 resolution: "@vercel/python@npm:4.0.2"