From afaf55b100370e50f52559222ac78299f8e897db Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Thu, 23 Jan 2025 15:21:52 -0500 Subject: [PATCH 1/5] Add server utility function to fetch a PDF of an existing form submission from Formio --- app/server/app/utilities/formio.js | 98 ++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/app/server/app/utilities/formio.js b/app/server/app/utilities/formio.js index d07d4755..863d5cb1 100644 --- a/app/server/app/utilities/formio.js +++ b/app/server/app/utilities/formio.js @@ -3,6 +3,7 @@ const ObjectId = require("mongodb").ObjectId; // --- const { axiosFormio, + formioProjectUrl, formUrl, submissionPeriodOpen, formioCSBMetadata, @@ -1090,6 +1091,101 @@ function downloadS3FileMetadata({ rebateYear, req, res }) { }); } +/** + * @param {Object} param + * @param {RebateYear} param.rebateYear + * @param {express.Request} param.req + * @param {express.Response} param.res + */ +function fetchSubmissionPDF({ rebateYear, req, res }) { + const { bapComboKeys } = req; + const { mail } = req.user; + const { formType, mongoId } = req.params; + + // NOTE: included to support EPA API scan + if (mongoId === formioExampleMongoId) { + return res.json(""); + } + + /** NOTE: verifyMongoObjectId */ + if (!ObjectId.isValid(mongoId)) { + const errorStatus = 400; + const errorMessage = `MongoDB ObjectId validation error for: '${mongoId}'.`; + return res.status(errorStatus).json({ message: errorMessage }); + } + + const comboKeyFieldName = getComboKeyFieldName({ rebateYear }); + + const formioFormUrl = formUrl[rebateYear][formType]; + + if (!formioFormUrl) { + const errorStatus = 400; + const errorMessage = `Formio form URL does not exist for ${rebateYear} ${formType.toUpperCase()}.`; + return res.status(errorStatus).json({ message: errorMessage }); + } + + axiosFormio(req) + .get(`${formioFormUrl}/submission/${mongoId}`) + .then((axiosRes) => axiosRes.data) + .then((submission) => { + const projectId = submission.project; + const formId = submission.form; + const comboKey = submission.data?.[comboKeyFieldName]; + + if (!bapComboKeys.includes(comboKey)) { + const logMessage = + `User with email '${mail}' attempted to download a PDF of ` + + `${rebateYear} ${formType.toUpperCase()} submission '${mongoId}' ` + + `that they do not have access to.`; + log({ level: "warn", message: logMessage, req }); + + const errorStatus = 401; + const errorMessage = `Unauthorized.`; + return res.status(errorStatus).json({ message: errorMessage }); + } + + const headers = { + "x-allow": `GET:/project/${projectId}/form/${formId}/submission/${mongoId}/download`, + "x-expire": 3600, + }; + + axiosFormio(req) + .get(`${formioProjectUrl}/token`, { headers }) + .then((axiosRes) => axiosRes.data) + .then((json) => { + const url = `${formioProjectUrl}/form/${formId}/submission/${mongoId}/download?token=${json.key}`; + + axiosFormio(req) + .get(url, { responseType: "arraybuffer" }) + .then((axiosRes) => axiosRes.data) + .then((fileData) => { + const base64String = Buffer.from(fileData).toString("base64"); + res.attachment(`${mongoId}.pdf`); + res.type("application/pdf"); + res.send(base64String); + }) + .catch((error) => { + // NOTE: error is logged in axiosFormio response interceptor + const errorStatus = error.response?.status || 500; + const errorMessage = `Error getting Formio submission PDF.`; + return res.status(errorStatus).json({ message: errorMessage }); + }); + }) + .catch((error) => { + // NOTE: error is logged in axiosFormio response interceptor + const errorStatus = error.response?.status || 500; + const errorMessage = `Error getting Formio download token.`; + return res.status(errorStatus).json({ message: errorMessage }); + }); + }) + .catch((error) => { + // NOTE: error is logged in axiosFormio response interceptor + const errorStatus = error.response?.status || 500; + const errorMessage = `Error getting Formio ${rebateYear} ${formType.toUpperCase()} form submission '${mongoId}'.`; + return res.status(errorStatus).json({ message: errorMessage }); + }); +} + /** * @param {Object} param * @param {RebateYear} param.rebateYear @@ -2222,6 +2318,8 @@ module.exports = { uploadS3FileMetadata, downloadS3FileMetadata, // + fetchSubmissionPDF, + // fetchFRFSubmissions, createFRFSubmission, fetchFRFSubmission, From 2443f0c05aaf01794fc4ec21ac652e2e190c3df9 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Thu, 23 Jan 2025 15:30:18 -0500 Subject: [PATCH 2/5] Add custom hook to fetch a PDF of a form submission from Formio --- app/client/src/utilities.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/app/client/src/utilities.ts b/app/client/src/utilities.ts index df4324ac..c4f348f0 100644 --- a/app/client/src/utilities.ts +++ b/app/client/src/utilities.ts @@ -10,6 +10,7 @@ import { useSearchParams } from "react-router-dom"; // --- import { type RebateYear, + type FormType, type Content, type UserData, type ConfigData, @@ -187,6 +188,32 @@ export function useBapSamData() { return queryClient.getQueryData(["bap/sam"]); } +/** Custom hook to fetch a PDF of a form submission from Formio. */ +export function useSubmissionPDFQuery(options: { + rebateYear: RebateYear; + formType: FormType; + mongoId: string | undefined; +}) { + const { rebateYear, formType, mongoId } = options; + + return useQuery({ + queryKey: [`formio/${rebateYear}/${formType}-pdf`, { id: mongoId }], + queryFn: () => { + const url = `${serverUrl}/api/formio/${rebateYear}/pdf/${formType}/${mongoId}`; + return getData(url); + }, + onSuccess: (res) => { + const link = document.createElement("a"); + link.setAttribute("href", `data:application/pdf;base64,${res}`); + link.setAttribute("download", `${mongoId}.pdf`); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }, + enabled: false, + }); +} + /** Custom hook to fetch Change Request form submissions from Formio. */ export function useChangeRequestsQuery( rebateYear: Year, From bfb4826f8d0385d075a3a0304d4959b7508d8800 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Thu, 23 Jan 2025 15:36:15 -0500 Subject: [PATCH 3/5] Update server app's 2022, 2023, and 2024 Formio API routes to include routes for fetching a PDF of a form submission --- app/server/app/routes/formio2022.js | 7 +++++++ app/server/app/routes/formio2023.js | 7 +++++++ app/server/app/routes/formio2024.js | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/app/server/app/routes/formio2022.js b/app/server/app/routes/formio2022.js index 2316b65c..0504c5cf 100644 --- a/app/server/app/routes/formio2022.js +++ b/app/server/app/routes/formio2022.js @@ -9,6 +9,8 @@ const { uploadS3FileMetadata, downloadS3FileMetadata, // + fetchSubmissionPDF, + // fetchFRFSubmissions, createFRFSubmission, fetchFRFSubmission, @@ -49,6 +51,11 @@ router.post( }, ); +// --- get a PDF of a 2022 form submission from Formio +router.get("/pdf/:formType/:mongoId", fetchBapComboKeys, (req, res) => { + fetchSubmissionPDF({ rebateYear, req, res }); +}); + // --- get user's 2022 FRF submissions from Formio router.get("/frf-submissions", fetchBapComboKeys, (req, res) => { fetchFRFSubmissions({ rebateYear, req, res }); diff --git a/app/server/app/routes/formio2023.js b/app/server/app/routes/formio2023.js index f2500134..06b2e184 100644 --- a/app/server/app/routes/formio2023.js +++ b/app/server/app/routes/formio2023.js @@ -11,6 +11,8 @@ const { uploadS3FileMetadata, downloadS3FileMetadata, // + fetchSubmissionPDF, + // fetchFRFSubmissions, createFRFSubmission, fetchFRFSubmission, @@ -61,6 +63,11 @@ router.post( }, ); +// --- get a PDF of a 2023 form submission from Formio +router.get("/pdf/:formType/:mongoId", fetchBapComboKeys, (req, res) => { + fetchSubmissionPDF({ rebateYear, req, res }); +}); + // --- get user's 2023 FRF submissions from Formio router.get("/frf-submissions", fetchBapComboKeys, (req, res) => { fetchFRFSubmissions({ rebateYear, req, res }); diff --git a/app/server/app/routes/formio2024.js b/app/server/app/routes/formio2024.js index 60a32f76..e61df562 100644 --- a/app/server/app/routes/formio2024.js +++ b/app/server/app/routes/formio2024.js @@ -11,6 +11,8 @@ const { uploadS3FileMetadata, downloadS3FileMetadata, // + fetchSubmissionPDF, + // fetchFRFSubmissions, createFRFSubmission, fetchFRFSubmission, @@ -61,6 +63,11 @@ router.post( }, ); +// --- get a PDF of a 2024 form submission from Formio +router.get("/pdf/:formType/:mongoId", fetchBapComboKeys, (req, res) => { + fetchSubmissionPDF({ rebateYear, req, res }); +}); + // --- get user's 2024 FRF submissions from Formio router.get("/frf-submissions", fetchBapComboKeys, (req, res) => { fetchFRFSubmissions({ rebateYear, req, res }); From 1680023493ad2d6932144fdaf9cc6dbb9924f7ca Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Thu, 23 Jan 2025 16:32:51 -0500 Subject: [PATCH 4/5] Update all forms to use the useSubmissionPDFQuery() custom hook and include a button for downloading a PDF of the submission --- app/client/src/routes/crf2022.tsx | 34 ++++++++++++++++++++++++++++++- app/client/src/routes/frf2022.tsx | 32 ++++++++++++++++++++++++++++- app/client/src/routes/frf2023.tsx | 32 ++++++++++++++++++++++++++++- app/client/src/routes/frf2024.tsx | 32 ++++++++++++++++++++++++++++- app/client/src/routes/prf2022.tsx | 34 ++++++++++++++++++++++++++++++- app/client/src/routes/prf2023.tsx | 34 ++++++++++++++++++++++++++++++- app/client/src/routes/prf2024.tsx | 34 ++++++++++++++++++++++++++++++- 7 files changed, 225 insertions(+), 7 deletions(-) diff --git a/app/client/src/routes/crf2022.tsx b/app/client/src/routes/crf2022.tsx index 10a5be3a..7bd63b93 100644 --- a/app/client/src/routes/crf2022.tsx +++ b/app/client/src/routes/crf2022.tsx @@ -19,6 +19,7 @@ import { useContentData, useConfigData, useBapSamData, + useSubmissionPDFQuery, useSubmissionsQueries, useSubmissions, submissionNeedsEdits, @@ -27,7 +28,7 @@ import { entityHasDebtSubjectToOffset, getUserInfo, } from "@/utilities"; -import { Loading } from "@/components/loading"; +import { Loading, LoadingButtonIcon } from "@/components/loading"; import { Message } from "@/components/message"; import { MarkdownContent } from "@/components/markdownContent"; import { useNotificationsActions } from "@/contexts/notifications"; @@ -40,6 +41,7 @@ function useFormioSubmissionQueryAndMutation(rebateId: string | undefined) { useEffect(() => { queryClient.resetQueries({ queryKey: ["formio/2022/crf-submission"] }); + queryClient.resetQueries({ queryKey: ["formio/2022/crf-pdf"] }); }, [queryClient]); const url = `${serverUrl}/api/formio/2022/crf-submission/${rebateId}`; @@ -129,6 +131,12 @@ function CloseOutRequestForm(props: { email: string }) { const { query, mutation } = useFormioSubmissionQueryAndMutation(rebateId); const { userAccess, formSchema, submission } = query.data ?? {}; + const pdfQuery = useSubmissionPDFQuery({ + rebateYear: "2022", + formType: "crf", + mongoId: submission?._id || "", + }); + /** * Stores when data is being posted to the server, so a loading overlay can * be rendered over the form, preventing the user from losing input data when @@ -243,6 +251,30 @@ function CloseOutRequestForm(props: { email: string }) { + {submission?._id && ( +

+ +

+ )} + {}}>
diff --git a/app/client/src/routes/frf2022.tsx b/app/client/src/routes/frf2022.tsx index a0f9c72e..cfbaa797 100644 --- a/app/client/src/routes/frf2022.tsx +++ b/app/client/src/routes/frf2022.tsx @@ -19,6 +19,7 @@ import { useContentData, useConfigData, useBapSamData, + useSubmissionPDFQuery, useSubmissionsQueries, useSubmissions, submissionNeedsEdits, @@ -27,7 +28,7 @@ import { entityHasDebtSubjectToOffset, getUserInfo, } from "@/utilities"; -import { Loading } from "@/components/loading"; +import { Loading, LoadingButtonIcon } from "@/components/loading"; import { Message } from "@/components/message"; import { MarkdownContent } from "@/components/markdownContent"; import { useDialogActions } from "@/contexts/dialog"; @@ -41,6 +42,7 @@ function useFormioSubmissionQueryAndMutation(mongoId: string | undefined) { useEffect(() => { queryClient.resetQueries({ queryKey: ["formio/2022/frf-submission"] }); + queryClient.resetQueries({ queryKey: ["formio/2022/frf-pdf"] }); }, [queryClient]); const url = `${serverUrl}/api/formio/2022/frf-submission/${mongoId}`; @@ -142,6 +144,12 @@ function FundingRequestForm(props: { email: string }) { const { query, mutation } = useFormioSubmissionQueryAndMutation(mongoId); const { userAccess, formSchema, submission } = query.data ?? {}; + const pdfQuery = useSubmissionPDFQuery({ + rebateYear: "2022", + formType: "frf", + mongoId, + }); + /** * Stores when data is being posted to the server, so a loading overlay can * be rendered over the form, preventing the user from losing input data when @@ -391,6 +399,28 @@ function FundingRequestForm(props: { email: string }) { )} +

+ +

+ {}}>
diff --git a/app/client/src/routes/frf2023.tsx b/app/client/src/routes/frf2023.tsx index 9683e325..3c475b5b 100644 --- a/app/client/src/routes/frf2023.tsx +++ b/app/client/src/routes/frf2023.tsx @@ -19,6 +19,7 @@ import { useContentData, useConfigData, useBapSamData, + useSubmissionPDFQuery, useSubmissionsQueries, useSubmissions, submissionNeedsEdits, @@ -27,7 +28,7 @@ import { entityHasDebtSubjectToOffset, getUserInfo, } from "@/utilities"; -import { Loading } from "@/components/loading"; +import { Loading, LoadingButtonIcon } from "@/components/loading"; import { Message } from "@/components/message"; import { MarkdownContent } from "@/components/markdownContent"; import { useDialogActions } from "@/contexts/dialog"; @@ -41,6 +42,7 @@ function useFormioSubmissionQueryAndMutation(mongoId: string | undefined) { useEffect(() => { queryClient.resetQueries({ queryKey: ["formio/2023/frf-submission"] }); + queryClient.resetQueries({ queryKey: ["formio/2023/frf-pdf"] }); }, [queryClient]); const url = `${serverUrl}/api/formio/2023/frf-submission/${mongoId}`; @@ -128,6 +130,12 @@ function FundingRequestForm(props: { email: string }) { const { query, mutation } = useFormioSubmissionQueryAndMutation(mongoId); const { userAccess, formSchema, submission } = query.data ?? {}; + const pdfQuery = useSubmissionPDFQuery({ + rebateYear: "2023", + formType: "frf", + mongoId, + }); + /** * Stores when data is being posted to the server, so a loading overlay can * be rendered over the form, preventing the user from losing input data when @@ -377,6 +385,28 @@ function FundingRequestForm(props: { email: string }) { )} +

+ +

+ {}}>
diff --git a/app/client/src/routes/frf2024.tsx b/app/client/src/routes/frf2024.tsx index c34d8425..dedac67c 100644 --- a/app/client/src/routes/frf2024.tsx +++ b/app/client/src/routes/frf2024.tsx @@ -19,6 +19,7 @@ import { useContentData, useConfigData, useBapSamData, + useSubmissionPDFQuery, useSubmissionsQueries, useSubmissions, submissionNeedsEdits, @@ -27,7 +28,7 @@ import { entityHasDebtSubjectToOffset, getUserInfo, } from "@/utilities"; -import { Loading } from "@/components/loading"; +import { Loading, LoadingButtonIcon } from "@/components/loading"; import { Message } from "@/components/message"; import { MarkdownContent } from "@/components/markdownContent"; import { useDialogActions } from "@/contexts/dialog"; @@ -41,6 +42,7 @@ function useFormioSubmissionQueryAndMutation(mongoId: string | undefined) { useEffect(() => { queryClient.resetQueries({ queryKey: ["formio/2024/frf-submission"] }); + queryClient.resetQueries({ queryKey: ["formio/2024/frf-pdf"] }); }, [queryClient]); const url = `${serverUrl}/api/formio/2024/frf-submission/${mongoId}`; @@ -128,6 +130,12 @@ function FundingRequestForm(props: { email: string }) { const { query, mutation } = useFormioSubmissionQueryAndMutation(mongoId); const { userAccess, formSchema, submission } = query.data ?? {}; + const pdfQuery = useSubmissionPDFQuery({ + rebateYear: "2024", + formType: "frf", + mongoId, + }); + /** * Stores when data is being posted to the server, so a loading overlay can * be rendered over the form, preventing the user from losing input data when @@ -377,6 +385,28 @@ function FundingRequestForm(props: { email: string }) { )} +

+ +

+ {}}>
diff --git a/app/client/src/routes/prf2022.tsx b/app/client/src/routes/prf2022.tsx index 05921b2c..1bda0afa 100644 --- a/app/client/src/routes/prf2022.tsx +++ b/app/client/src/routes/prf2022.tsx @@ -19,6 +19,7 @@ import { useContentData, useConfigData, useBapSamData, + useSubmissionPDFQuery, useSubmissionsQueries, useSubmissions, submissionNeedsEdits, @@ -27,7 +28,7 @@ import { entityHasDebtSubjectToOffset, getUserInfo, } from "@/utilities"; -import { Loading } from "@/components/loading"; +import { Loading, LoadingButtonIcon } from "@/components/loading"; import { Message } from "@/components/message"; import { MarkdownContent } from "@/components/markdownContent"; import { useNotificationsActions } from "@/contexts/notifications"; @@ -40,6 +41,7 @@ function useFormioSubmissionQueryAndMutation(rebateId: string | undefined) { useEffect(() => { queryClient.resetQueries({ queryKey: ["formio/2022/prf-submission"] }); + queryClient.resetQueries({ queryKey: ["formio/2022/prf-pdf"] }); }, [queryClient]); const url = `${serverUrl}/api/formio/2022/prf-submission/${rebateId}`; @@ -129,6 +131,12 @@ function PaymentRequestForm(props: { email: string }) { const { query, mutation } = useFormioSubmissionQueryAndMutation(rebateId); const { userAccess, formSchema, submission } = query.data ?? {}; + const pdfQuery = useSubmissionPDFQuery({ + rebateYear: "2022", + formType: "prf", + mongoId: submission?._id || "", + }); + /** * Stores when data is being posted to the server, so a loading overlay can * be rendered over the form, preventing the user from losing input data when @@ -264,6 +272,30 @@ function PaymentRequestForm(props: { email: string }) { + {submission?._id && ( +

+ +

+ )} + {}}>
diff --git a/app/client/src/routes/prf2023.tsx b/app/client/src/routes/prf2023.tsx index a8a34267..1ec178ec 100644 --- a/app/client/src/routes/prf2023.tsx +++ b/app/client/src/routes/prf2023.tsx @@ -19,6 +19,7 @@ import { useContentData, useConfigData, useBapSamData, + useSubmissionPDFQuery, useSubmissionsQueries, useSubmissions, submissionNeedsEdits, @@ -27,7 +28,7 @@ import { entityHasDebtSubjectToOffset, getUserInfo, } from "@/utilities"; -import { Loading } from "@/components/loading"; +import { Loading, LoadingButtonIcon } from "@/components/loading"; import { Message } from "@/components/message"; import { MarkdownContent } from "@/components/markdownContent"; import { useNotificationsActions } from "@/contexts/notifications"; @@ -40,6 +41,7 @@ function useFormioSubmissionQueryAndMutation(rebateId: string | undefined) { useEffect(() => { queryClient.resetQueries({ queryKey: ["formio/2023/prf-submission"] }); + queryClient.resetQueries({ queryKey: ["formio/2023/prf-pdf"] }); }, [queryClient]); const url = `${serverUrl}/api/formio/2023/prf-submission/${rebateId}`; @@ -129,6 +131,12 @@ function PaymentRequestForm(props: { email: string }) { const { query, mutation } = useFormioSubmissionQueryAndMutation(rebateId); const { userAccess, formSchema, submission } = query.data ?? {}; + const pdfQuery = useSubmissionPDFQuery({ + rebateYear: "2023", + formType: "prf", + mongoId: submission?._id || "", + }); + /** * Stores when data is being posted to the server, so a loading overlay can * be rendered over the form, preventing the user from losing input data when @@ -262,6 +270,30 @@ function PaymentRequestForm(props: { email: string }) { + {submission?._id && ( +

+ +

+ )} + {}}>
diff --git a/app/client/src/routes/prf2024.tsx b/app/client/src/routes/prf2024.tsx index 52e9e251..1c32bea9 100644 --- a/app/client/src/routes/prf2024.tsx +++ b/app/client/src/routes/prf2024.tsx @@ -19,6 +19,7 @@ import { useContentData, useConfigData, useBapSamData, + useSubmissionPDFQuery, useSubmissionsQueries, useSubmissions, submissionNeedsEdits, @@ -27,7 +28,7 @@ import { entityHasDebtSubjectToOffset, getUserInfo, } from "@/utilities"; -import { Loading } from "@/components/loading"; +import { Loading, LoadingButtonIcon } from "@/components/loading"; import { Message } from "@/components/message"; import { MarkdownContent } from "@/components/markdownContent"; import { useNotificationsActions } from "@/contexts/notifications"; @@ -40,6 +41,7 @@ function useFormioSubmissionQueryAndMutation(rebateId: string | undefined) { useEffect(() => { queryClient.resetQueries({ queryKey: ["formio/2024/prf-submission"] }); + queryClient.resetQueries({ queryKey: ["formio/2024/prf-pdf"] }); }, [queryClient]); const url = `${serverUrl}/api/formio/2024/prf-submission/${rebateId}`; @@ -129,6 +131,12 @@ function PaymentRequestForm(props: { email: string }) { const { query, mutation } = useFormioSubmissionQueryAndMutation(rebateId); const { userAccess, formSchema, submission } = query.data ?? {}; + const pdfQuery = useSubmissionPDFQuery({ + rebateYear: "2024", + formType: "prf", + mongoId: submission?._id || "", + }); + /** * Stores when data is being posted to the server, so a loading overlay can * be rendered over the form, preventing the user from losing input data when @@ -262,6 +270,30 @@ function PaymentRequestForm(props: { email: string }) { + {submission?._id && ( +

+ +

+ )} + {}}>
From f5abb29fc94330cd08b70473df79f2dd50d3e322 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Thu, 23 Jan 2025 17:35:51 -0500 Subject: [PATCH 5/5] Update OpenAPI docs to include new Formio PDF submission routes --- docs/csb-openapi.json | 63 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/docs/csb-openapi.json b/docs/csb-openapi.json index be0eff94..d0b924eb 100644 --- a/docs/csb-openapi.json +++ b/docs/csb-openapi.json @@ -373,6 +373,27 @@ } } }, + "/api/formio/2022/pdf/{formType}/{mongoId}": { + "get": { + "summary": "Download a PDF of a 2022 form submission.", + "parameters": [ + { + "$ref": "#/components/parameters/formType" + }, + { + "$ref": "#/components/parameters/mongoId" + } + ], + "responses": { + "200": { + "description": "OK" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + } + }, "/api/formio/2022/frf-submissions": { "get": { "summary": "Get user's 2022 FRF submissions from Formio.", @@ -744,6 +765,27 @@ } } }, + "/api/formio/2023/pdf/{formType}/{mongoId}": { + "get": { + "summary": "Download a PDF of a 2023 form submission.", + "parameters": [ + { + "$ref": "#/components/parameters/formType" + }, + { + "$ref": "#/components/parameters/mongoId" + } + ], + "responses": { + "200": { + "description": "OK" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + } + }, "/api/formio/2023/frf-submissions": { "get": { "summary": "Get user's 2023 FRF submissions from Formio.", @@ -1108,6 +1150,27 @@ } } }, + "/api/formio/2024/pdf/{formType}/{mongoId}": { + "get": { + "summary": "Download a PDF of a 2024 form submission.", + "parameters": [ + { + "$ref": "#/components/parameters/formType" + }, + { + "$ref": "#/components/parameters/mongoId" + } + ], + "responses": { + "200": { + "description": "OK" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + } + }, "/api/formio/2024/frf-submissions": { "get": { "summary": "Get user's 2024 FRF submissions from Formio.",