From a2b7c643d9da8d7c953940f74495fc636c926ae6 Mon Sep 17 00:00:00 2001 From: Jun-te kim Date: Fri, 22 Aug 2025 18:35:56 +0000 Subject: [PATCH] force --- .env.local | 109 ++++++++++++++++++ src/app/api/upload/presign-get/route.ts | 89 ++++++++++++++ .../condition/ConditionReport.tsx | 99 ++++++++++++++++ 3 files changed, 297 insertions(+) create mode 100644 .env.local create mode 100644 src/app/api/upload/presign-get/route.ts create mode 100644 src/app/portfolio/[slug]/building-passport/[propertyId]/condition/ConditionReport.tsx diff --git a/.env.local b/.env.local new file mode 100644 index 0000000..77ca296 --- /dev/null +++ b/.env.local @@ -0,0 +1,109 @@ +NEXTAUTH_SECRET=df425f28-06ab-47c2-bb78-7e604387d463 +NEXTAUTH_URL=http://localhost:3000 + + +GOOGLE_CLIENT_ID=232063354367-ustovlgtk3cmtvohvd6tdlejnj1qjjj0.apps.googleusercontent.com + +GOOGLE_CLIENT_SECRET=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 + +EPC_AUTH_TOKEN=a2Nvbm5rb3dsZXNzYXJAZ21haWwuY29tOjY5MGJiMWM0NmIyOGI5ZDUxYzAxMzQzYzNiZGNlZGJjZDNmODQwMzA= + + + +AZURE_AD_B2C_TENANT_NAME=DomnaApp + +AZURE_AD_B2C_CLIENT_ID=f0a1f977-ddc4-4037-b129-a310008ee934 + +AZURE_AD_B2C_CLIENT_SECRET=6uh8Q~dmZNqQy3ZxM_Ce33fVSeW24K27R~pYYduD + +AZURE_AD_B2C_PRIMARY_USER_FLOW=B2C_1_signupsignin + + + +AZURE_AD_CLIENT_ID=069e75ee-ba54-45ff-ba77-a06f29c0e21c + +AZURE_AD_CLIENT_SECRET=x6D8Q~f2roqrnoP1YuomSGN5CvU0HPtIWqqPPaYW + +AZURE_AD_TENANT_ID=4a85e8bb-8b7f-4bbd-adc2-1448bb6a9810 + + + +DB_HOST=terraform-20230705170609686900000001.cdgzupxvdyp0.eu-west-2.rds.amazonaws.com + +DB_PORT=5432 + +DB_NAME=DevAssessmentModelDB + +DB_USERNAME=DevAddessmentModelDB + +DB_PASSWORD=!}-A=3D%(2Awy[Qx + + + +URL=http://localhost:3000 + +PRESIGN_AWS_ACCESS_KEY=AKIAU5A36PPNMR2G7ZQO + +PRESIGN_AWS_SECRET_KEY=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 + +RETROFIT_PLAN_INPUT_BUCKET_NAME=retrofit-plan-inputs-dev + +PRESIGN_AWS_REGION=eu-west-2 + + + +DUE_CONSIDERATIONS_BUCKET=retrofit-due-considerations-dev + +DUE_CONSIDERATIONS_AWS_ACCESS_KEY=AKIAU5A36PPNPNFWLJOY + +DUE_CONSIDERATIONS_AWS_SECRET_KEY=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 + +DUE_CONSIDERATIONS_AWS_REGION=eu-west-2 + + + +ECO_SPREADSHEET_BUCKET=retrofit-eco-spreadsheet-dev +ECO_SPREADSHEET_AWS_ACCESS_KEY=AKIAU5A36PPNPTFDQGOJ +ECO_SPREADSHEET_AWS_SECRET_KEY=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 +ECO_SPREADSHEET_AWS_REGION=eu-west-2 + +# Please change using "Jun-te" aws role atm +SQS_AWS_REGION=eu-west-2 +SQS_AWS_ACCESS_KEY_ID=AKIAU5A36PPNK7RXX52V +SQS_AWS_SECRET_ACCESS_KEY=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 + + +RETROFIT_ENERGY_ASSESSMENTS_BUCKET=retrofit-energy-assessments-dev + +RETROFIT_ENERGY_ASSESSMENTS_AWS_ACCESS_KEY=AKIAU5A36PPNJMZZ3KRW + +RETROFIT_ENERGY_ASSESSMENTS_AWS_SECRET=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 + + + +FASTAPI_API_KEY=4QPwbB6hEdUloDVtbBJCUTfGBdBgWwpeavWQ7t5Z + +FASTAPI_API_URL=https://api.dev.hestia.homes + + + +DUE_CONSIDERATIONS_API_URL=https://api.dev.hestia.homes + +ECO_SPREADSHEET_API_URL=https://api.dev.hestia.homes + + + + + +DOCUMENTS_DATABASE_URL=postgresql://postgres:makingwarmhomes@terraform-20250331175522503500000002.cdgzupxvdyp0.eu-west-2.rds.amazonaws.com:5432/surveyDB + +DOCUMENTS_DB_HOST=terraform-20250331175522503500000002.cdgzupxvdyp0.eu-west-2.rds.amazonaws.com + +DOCUMENTS_DB_PORT=5432 + +DOCUMENTS_DB_NAME=surveyDB + +DOCUMENTS_DB_USERNAME=postgres + +DOCUMENTS_DB_PASSWORD=makingwarmhomes + diff --git a/src/app/api/upload/presign-get/route.ts b/src/app/api/upload/presign-get/route.ts new file mode 100644 index 0000000..b5ba486 --- /dev/null +++ b/src/app/api/upload/presign-get/route.ts @@ -0,0 +1,89 @@ +// app/api/presign-get/route.ts +import { NextRequest, NextResponse } from "next/server"; +import { z } from "zod"; +import { + createS3Client, + presignGetUrl, + parseS3Url, +} from "@/app/utils/s3"; + +// avoid caching the route itself +export const dynamic = "force-dynamic"; + +const Schema = z.object({ + // Either a raw S3 key (e.g. "documents/....pdf") OR a full S3 URL + path: z.string().min(1, "path is required"), + expiresInSeconds: z.coerce.number().int().positive().default(300), + contentType: z.string().optional(), // e.g. application/pdf + contentDisposition: z.string().optional(), // e.g. "inline" or 'attachment; filename="file.pdf"' +}); + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + const parsed = Schema.parse({ + path: searchParams.get("path"), + expiresInSeconds: searchParams.get("expiresInSeconds") ?? undefined, + contentType: searchParams.get("contentType") ?? undefined, + contentDisposition: searchParams.get("contentDisposition") ?? undefined, + }); + + // Default bucket used when `path` is a raw key (not a full URL) + const defaultBucket = process.env.RETROFIT_ENERGY_ASSESSMENTS_BUCKET; + if (!defaultBucket) { + return NextResponse.json( + { msg: "RETROFIT_ENERGY_ASSESSMENTS_BUCKET is not set" }, + { status: 400 } + ); + } + + // Build S3 client using Retrofit creds + const s3 = createS3Client({ + region: process.env.PRESIGN_AWS_REGION, + accessKeyId: process.env.RETROFIT_ENERGY_ASSESSMENTS_AWS_ACCESS_KEY, + secretAccessKey: process.env.RETROFIT_ENERGY_ASSESSMENTS_AWS_SECRET, + }); + + // Accept either a raw key or a full S3 URL + let bucket = defaultBucket; + let key = parsed.path; + + if ( + parsed.path.startsWith("http://") || + parsed.path.startsWith("https://") || + parsed.path.startsWith("s3://") + ) { + const fromUrl = parseS3Url(parsed.path); + bucket = fromUrl.bucket; + key = fromUrl.key; + } + + const url = await presignGetUrl(s3, { + bucket, + key, + expiresInSeconds: parsed.expiresInSeconds, + responseContentType: parsed.contentType, + responseContentDisposition: parsed.contentDisposition ?? "inline", + }); + + return new NextResponse(JSON.stringify({ url }), { + status: 200, + headers: { + "content-type": "application/json", + "cache-control": "no-store, no-cache, must-revalidate", + }, + }); + } catch (err) { + console.error(err); + if (err instanceof z.ZodError) { + return NextResponse.json( + { msg: "Invalid input", issues: err.issues }, + { status: 400 } + ); + } + return NextResponse.json( + { msg: "Internal server error" }, + { status: 500 } + ); + } +} diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/condition/ConditionReport.tsx b/src/app/portfolio/[slug]/building-passport/[propertyId]/condition/ConditionReport.tsx new file mode 100644 index 0000000..5dc116a --- /dev/null +++ b/src/app/portfolio/[slug]/building-passport/[propertyId]/condition/ConditionReport.tsx @@ -0,0 +1,99 @@ +"use client"; +import { useEffect, useState } from "react"; + +type Report = { + id: string; + s3FileUri?: string; + s3JsonUri?: string; + s3FileUploadTimestamp: string; +}; + +export default function ConditionReport({ latestReport }: { latestReport: Report }) { + const [pdfUrl, setPdfUrl] = useState(null); + const [jsonUrl, setJsonUrl] = useState(null); + + useEffect(() => { + let cancelled = false; + + async function fetchPresigned() { + if (!latestReport) return; + + if (latestReport.s3FileUri) { + const res = await fetch( + `/api/upload/presign-get?` + + new URLSearchParams({ + path: latestReport.s3FileUri, + contentType: "application/pdf", + contentDisposition: "inline", + expiresInSeconds: "300", + }), + { cache: "no-store" } + ); + const data = await res.json(); + if (!cancelled) setPdfUrl(data.url ?? null); + } + + if (latestReport.s3JsonUri) { + const res = await fetch( + `/api/upload/presign-get?` + + new URLSearchParams({ + path: latestReport.s3JsonUri, + contentType: "application/json", + contentDisposition: "inline", + expiresInSeconds: "300", + }), + { cache: "no-store" } + ); + const data = await res.json(); + if (!cancelled) setJsonUrl(data.url ?? null); + } + } + + fetchPresigned(); + return () => { + cancelled = true; + }; + }, [latestReport]); + + return ( +
+

+ Document ID: {latestReport.id} +

+

+ Uploaded:{" "} + {new Date(latestReport.s3FileUploadTimestamp).toLocaleString()} +

+

+ PDF:{" "} + {pdfUrl ? ( + + View Report + + ) : ( + Not available + )} +

+

+ JSON:{" "} + {jsonUrl ? ( + + View JSON + + ) : ( + Not available + )} +

+
+ ); +}