This commit is contained in:
Jun-te kim 2025-08-22 18:35:56 +00:00
parent c802931b5b
commit a2b7c643d9
3 changed files with 297 additions and 0 deletions

109
.env.local Normal file
View file

@ -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

View file

@ -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 }
);
}
}

View file

@ -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<string | null>(null);
const [jsonUrl, setJsonUrl] = useState<string | null>(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 (
<div className="px-6 py-4 border rounded-md shadow bg-white space-y-2">
<p>
<span className="font-semibold">Document ID:</span> {latestReport.id}
</p>
<p>
<span className="font-semibold">Uploaded:</span>{" "}
{new Date(latestReport.s3FileUploadTimestamp).toLocaleString()}
</p>
<p>
<span className="font-semibold">PDF:</span>{" "}
{pdfUrl ? (
<a
href={pdfUrl}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 underline"
>
View Report
</a>
) : (
<span className="text-gray-500">Not available</span>
)}
</p>
<p>
<span className="font-semibold">JSON:</span>{" "}
{jsonUrl ? (
<a
href={jsonUrl}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 underline"
>
View JSON
</a>
) : (
<span className="text-gray-500">Not available</span>
)}
</p>
</div>
);
}