fix: preview file name and content type (#1678)

This commit is contained in:
Anshuman Pandey
2023-11-24 21:53:49 +05:30
committed by GitHub
parent 8414f3b030
commit 4102dbd0e4
12 changed files with 42 additions and 105 deletions

View File

@@ -52,10 +52,10 @@ export const handleFileUpload = async (
requestHeaders = {
"X-File-Type": file.type,
"X-File-Name": file.name,
"X-File-Name": encodeURIComponent(file.name),
"X-Environment-ID": environmentId ?? "",
"X-Signature": signature,
"X-Timestamp": timestamp,
"X-Timestamp": String(timestamp),
"X-UUID": uuid,
};
}

View File

@@ -110,6 +110,7 @@ export default function SurveyEditor({
product={product}
environment={environment}
previewType={localSurvey.type === "web" ? "modal" : "fullwidth"}
onFileUpload={async (file) => file.name}
/>
</aside>
</div>

View File

@@ -17,6 +17,7 @@ import {
} from "@heroicons/react/24/solid";
import { Variants, motion } from "framer-motion";
import { useEffect, useRef, useState } from "react";
import { TUploadFileConfig } from "@formbricks/types/storage";
type TPreviewType = "modal" | "fullwidth" | "email";
@@ -27,6 +28,7 @@ interface PreviewSurveyProps {
previewType?: TPreviewType;
product: TProduct;
environment: TEnvironment;
onFileUpload: (file: File, config?: TUploadFileConfig) => Promise<string>;
}
let surveyNameTemp;
@@ -64,6 +66,7 @@ export default function PreviewSurvey({
previewType,
product,
environment,
onFileUpload,
}: PreviewSurveyProps) {
const [isModalOpen, setIsModalOpen] = useState(true);
const [isFullScreenPreview, setIsFullScreenPreview] = useState(false);
@@ -207,7 +210,7 @@ export default function PreviewSurvey({
isBrandingEnabled={product.linkSurveyBranding}
onActiveQuestionChange={setActiveQuestionId}
isRedirectDisabled={true}
onFileUpload={async () => ""}
onFileUpload={onFileUpload}
/>
</Modal>
) : (
@@ -222,7 +225,7 @@ export default function PreviewSurvey({
activeQuestionId={activeQuestionId || undefined}
isBrandingEnabled={product.linkSurveyBranding}
onActiveQuestionChange={setActiveQuestionId}
onFileUpload={async () => ""}
onFileUpload={onFileUpload}
/>
</div>
</div>
@@ -279,7 +282,7 @@ export default function PreviewSurvey({
isBrandingEnabled={product.linkSurveyBranding}
onActiveQuestionChange={setActiveQuestionId}
isRedirectDisabled={true}
onFileUpload={async () => ""}
onFileUpload={onFileUpload}
/>
</Modal>
) : (
@@ -293,7 +296,7 @@ export default function PreviewSurvey({
isBrandingEnabled={product.linkSurveyBranding}
onActiveQuestionChange={setActiveQuestionId}
isRedirectDisabled={true}
onFileUpload={async () => ""}
onFileUpload={onFileUpload}
/>
</div>
</div>

View File

@@ -77,6 +77,7 @@ export default function TemplateContainerWithPreview({
product={product}
environment={environment}
setActiveQuestionId={setActiveQuestionId}
onFileUpload={async (file) => file.name}
/>
</div>
)}

View File

@@ -38,7 +38,7 @@ export async function POST(req: NextRequest, context: Context): Promise<NextResp
const headersList = headers();
const fileType = headersList.get("X-File-Type");
const fileName = headersList.get("X-File-Name");
const encodedFileName = headersList.get("X-File-Name");
const surveyId = headersList.get("X-Survey-ID");
const signedSignature = headersList.get("X-Signature");
@@ -49,7 +49,7 @@ export async function POST(req: NextRequest, context: Context): Promise<NextResp
return responses.badRequestResponse("contentType is required");
}
if (!fileName) {
if (!encodedFileName) {
return responses.badRequestResponse("fileName is required");
}
@@ -79,6 +79,8 @@ export async function POST(req: NextRequest, context: Context): Promise<NextResp
return responses.notFoundResponse("TeamByEnvironmentId", environmentId);
}
const fileName = decodeURIComponent(encodedFileName);
// validate signature
const validated = validateLocalSignedUrl(

View File

@@ -17,7 +17,7 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
const headersList = headers();
const fileType = headersList.get("X-File-Type");
const fileName = headersList.get("X-File-Name");
const encodedFileName = headersList.get("X-File-Name");
const environmentId = headersList.get("X-Environment-ID");
const signedSignature = headersList.get("X-Signature");
@@ -28,7 +28,7 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
return responses.badRequestResponse("fileType is required");
}
if (!fileName) {
if (!encodedFileName) {
return responses.badRequestResponse("fileName is required");
}
@@ -60,6 +60,8 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
return responses.unauthorizedResponse();
}
const fileName = decodeURIComponent(encodedFileName);
// validate signature
const validated = validateLocalSignedUrl(

View File

@@ -15,7 +15,7 @@ const getFile = async (environmentId: string, accessType: string, fileName: stri
return new Response(fileBuffer, {
headers: {
"Content-Type": metaData.contentType,
"Content-Disposition": "inline",
"Content-Disposition": "attachment",
},
});
} catch (err) {

View File

@@ -22,7 +22,9 @@ export async function GET(
);
}
const { environmentId, accessType, fileName } = params;
const { environmentId, accessType, fileName: fileNameOG } = params;
const fileName = decodeURIComponent(fileNameOG);
if (accessType === "public") {
return await getFile(environmentId, accessType, fileName);
@@ -42,7 +44,8 @@ export async function GET(
return responses.unauthorizedResponse();
}
return await getFile(environmentId, accessType, fileName);
const file = await getFile(environmentId, accessType, fileName);
return file;
}
export async function DELETE(_: NextRequest, { params }: { params: { fileName: string } }) {

View File

@@ -51,10 +51,10 @@ export class StorageAPI {
requestHeaders = {
"X-File-Type": file.type,
"X-File-Name": file.name,
"X-File-Name": encodeURIComponent(file.name),
"X-Survey-ID": surveyId ?? "",
"X-Signature": signature,
"X-Timestamp": timestamp,
"X-Timestamp": String(timestamp),
"X-UUID": uuid,
};
}

View File

@@ -1,77 +0,0 @@
export const uploadFile = async (
file: File | Blob,
allowedFileExtensions: string[] | undefined,
surveyId: string | undefined,
environmentId: string | undefined
) => {
if (!(file instanceof Blob) || !(file instanceof File)) {
throw new Error(`Invalid file type. Expected Blob or File, but received ${typeof file}`);
}
const payload = {
fileName: file.name,
fileType: file.type,
allowedFileExtensions: allowedFileExtensions,
surveyId: surveyId,
};
const response = await fetch(`/api/v1/client/${environmentId}/storage`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`Upload failed with status: ${response.status}`);
}
const json = await response.json();
const { data } = json;
const { signedUrl, fileUrl, signingData, presignedFields } = data;
let requestHeaders: Record<string, string> = {};
if (signingData) {
const { signature, timestamp, uuid } = signingData;
requestHeaders = {
fileType: file.type,
fileName: file.name,
surveyId: surveyId ?? "",
signature,
timestamp,
uuid,
};
}
const formData = new FormData();
if (presignedFields) {
Object.keys(presignedFields).forEach((key) => {
formData.append(key, presignedFields[key]);
});
}
// Add the actual file to be uploaded
formData.append("file", file);
const uploadResponse = await fetch(signedUrl, {
method: "POST",
...(signingData ? { headers: requestHeaders } : {}),
body: formData,
});
if (!uploadResponse.ok) {
const uploadJson = await uploadResponse.json();
console.log(uploadJson);
throw new Error(`${uploadJson.message}`);
}
return {
uploaded: true,
url: fileUrl,
};
};

View File

@@ -54,12 +54,12 @@ const uploadFile = async (
const { signature, timestamp, uuid } = signingData;
requestHeaders = {
fileType: file.type,
fileName: file.name,
environmentId: environmentId ?? "",
signature,
timestamp,
uuid,
"X-File-Type": file.type,
"X-File-Name": encodeURIComponent(file.name),
"X-Environment-ID": environmentId ?? "",
"X-Signature": signature,
"X-Timestamp": String(timestamp),
"X-UUID": uuid,
};
}

View File

@@ -18,13 +18,15 @@ export const PictureSelectionResponse = ({ choices, selected }: PictureSelection
<div className="my-1 flex flex-wrap gap-x-5 gap-y-4">
{selected.map((id) => (
<div className="relative h-32 w-56">
<Image
src={choiceImageMapping[id]}
alt={choiceImageMapping[id].split("/").pop() || "Image"}
fill
style={{ objectFit: "cover" }}
className="rounded-lg"
/>
{choiceImageMapping[id] && (
<Image
src={choiceImageMapping[id]}
alt={choiceImageMapping[id].split("/").pop() || "Image"}
fill
style={{ objectFit: "cover" }}
className="rounded-lg"
/>
)}
</div>
))}
</div>