From f23ad2a300a9978e7ee61a2f1ae6061085230d41 Mon Sep 17 00:00:00 2001 From: rdbsolutions Date: Thu, 7 Jul 2022 17:06:56 +0100 Subject: [PATCH 1/6] upload fies to storage account for appeal --- .env.local | 20 +- actions/azurestorage.js | 196 +++++++++++++++ actions/index.js | 107 +++++++-- components/elements/index.js | 226 +++++++++--------- components/myportal/uploadFile.js | 150 +++++++++++- components/newappeal/buildCheckfield.js | 8 +- components/newappeal/buildchecksection.js | 19 +- components/newappeal/buildfield.js | 6 + components/newappeal/buildrow.js | 3 + components/newappeal/buildsection.js | 29 ++- components/newappeal/enforcementnotice.js | 24 -- package.json | 7 +- .../api/endpoint/getawaitingsubmission_api.js | 2 +- pages/api/endpoint/getbasicsearch_api.js | 2 +- pages/api/endpoint/getlpa_api.js | 2 + pages/api/file/deleteblob.js | 32 +++ pages/api/file/downloadblob.js | 84 +++++++ pages/api/file/getbloblist.js | 29 +++ pages/api/file/upload.js | 36 +++ pages/api/middleware/middleware.js | 16 ++ pages/myportal/[appealtypes].js | 11 +- pages/myportal/index.js | 2 +- pages/newappeal/[appealtypes].js | 5 + pages/storage.js | 104 ++++++++ public/assets/images/documenttypes/jpg.png | Bin 0 -> 964 bytes public/assets/images/documenttypes/png.png | Bin 0 -> 1017 bytes store/appealType/action.js | 9 + store/appealType/reducer.js | 6 + 28 files changed, 956 insertions(+), 179 deletions(-) create mode 100644 actions/azurestorage.js delete mode 100644 components/newappeal/enforcementnotice.js create mode 100644 pages/api/file/deleteblob.js create mode 100644 pages/api/file/downloadblob.js create mode 100644 pages/api/file/getbloblist.js create mode 100644 pages/api/file/upload.js create mode 100644 pages/api/middleware/middleware.js create mode 100644 pages/storage.js create mode 100644 public/assets/images/documenttypes/jpg.png create mode 100644 public/assets/images/documenttypes/png.png diff --git a/.env.local b/.env.local index 48877f7e..a086b470 100644 --- a/.env.local +++ b/.env.local @@ -1,5 +1,3 @@ - - ACCESS_TOKEN_ENDPOINT = https://login.microsoftonline.com/ //ESNR Tenant TENANT = a8d860de-d5e1-45af-8376-d93f02e6b502 @@ -45,10 +43,10 @@ RELAYPATH = "dev-pedw-hc" GOOGLE_TAG_MANAGER = GTM-T78CBC3 -SHOWLOGIN = "false" -SHOWREPRESENTATIONS = "false" -SHOWFILEUPLOAD = "false" -SHOWMAPS = "false" +SHOWLOGIN = "true" +SHOWREPRESENTATIONS = false +SHOWFILEUPLOAD = "true" +SHOWMAPS = "true" //BASIC_AUTH_CREDENTIALS = pinswg:password|pinswg2:password2 @@ -89,3 +87,13 @@ NEXTAUTH_URL=http://localhost:3000 NEXTAUTH_URL_INTERNAL=http://localhost:3000 DATABASE_URL=mysql://nextAuthAdmin:!Sky9fish1972@77.68.17.157:3306/nextauthDB?synchronise=true SECRET=SuperSecret + + +//Azure Storage Account + +AZURE_CLIENT_ID = $CLIENT_ID +AZURE_TENANT_ID = $TENANT +AZURE_CLIENT_SECRET = $CLIENT_SECRET + +AZURE_PEDW_STORAGE_ENDPOINT = https://pedwdev.blob.core.windows.net +AZURE_PEDW_CONTAINER = "pedwapplications" \ No newline at end of file diff --git a/actions/azurestorage.js b/actions/azurestorage.js new file mode 100644 index 00000000..a6efb105 --- /dev/null +++ b/actions/azurestorage.js @@ -0,0 +1,196 @@ +import { v4 as uuidv4 } from "uuid"; +import { BlobServiceClient, ContainerClient } from "@azure/storage-blob"; +import { + DefaultAzureCredential, + InteractiveBrowserCredential, + EnvironmentCredential, + ClientSecretCredential, +} from "@azure/identity"; +import { hashAPIPath } from "."; + +const STORAGE_PATH = process.env.AZURE_PEDW_STORAGE_ENDPOINT; +const STORAGE_CONTAINER = process.env.AZURE_PEDW_CONTAINER; + +export const createContainer = async (containerName) => { + const creds = new DefaultAzureCredential(); + + containerName = containerName.toLowerCase(); + + // console.log( + // "container name:", + // containerName, + // STORAGE_PATH + "/" + containerName + // ); + + const containerClient = new ContainerClient( + `${STORAGE_PATH}/${containerName}`, + creds + ); + + const blobServiceClient = new BlobServiceClient(`${STORAGE_PATH}`, creds); + + const createContainerResponse = await containerClient.createIfNotExists(); + // console.log( + // `Created container ${containerName} successfully`, + // createContainerResponse.requestId + // ); + + // console.log("Containers:"); + // for await (const container of blobServiceClient.listContainers()) { + // console.log(`- ${container.name}`); + // } + + return containerName; +}; + +export const getContainers = async () => { + const creds = new DefaultAzureCredential(); + const blobServiceClient = new BlobServiceClient(`${STORAGE_PATH}`, creds); + + console.log("Containers:"); + for await (const container of blobServiceClient.listContainers()) { + console.log(`- ${container.name}`); + } +}; + +export const getBlobs = async (containerName) => { + const creds = new DefaultAzureCredential(); + + const containerClient = new ContainerClient( + `${STORAGE_PATH}/${containerName}`, + creds + ); + + containerClient.createIfNotExists(); + + const blobObj = []; + for await (const blob of containerClient.listBlobsFlat({ + prefix: "files/", + })) { + let blobDocumentType = blob.name + .split("/")[1] + .slice(0, blob.name.split("/")[1].indexOf("_")); + + blobObj.push({ + "name": blob.name.split("/")[1], + "path": blob.name, + "documentType": blobDocumentType, + "versionId": blob.versionId, + "isCurrentVersion": blob.isCurrentVersion, + "contentLength": blob.properties.contentLength, + "contentType": blob.contentType, + "hashedfilepath": hashAPIPath( + "/api/file/downloadblob?container=" + + containerName.toLowerCase() + + "&blobname=" + + blob.name.split("/")[1] + ), + }); + } + + return blobObj; +}; + +export const createBlob = async (formContent, containerName) => { + const creds = new DefaultAzureCredential(); + + const containerClient = new ContainerClient( + `${STORAGE_PATH}/${containerName}`, + creds + ); + + const content = JSON.stringify(formContent); + const blobName = + formContent.pinswg_name + "_" + new Date().getTime() + ".json"; + const blockBlobClient = containerClient.getBlockBlobClient(blobName); + + const uploadBlobResponse = await blockBlobClient.upload( + content, + Buffer.byteLength(content) + ); + + // console.log("Blobs:"); + // for await (const blob of containerClient.listBlobsByHierarchy("/")) { + // console.log(`- ${blob.name}`); + // } + + return formContent.pinswg_name; +}; + +export const deleteBlob = async (containerName, blobName) => { + const creds = new DefaultAzureCredential(); + + const options = { + deleteSnapshots: "include", // or 'only' + }; + + const containerClient = new ContainerClient( + `${STORAGE_PATH}/${containerName.toLowerCase()}`, + creds + ); + + const blockBlobClient = containerClient.getBlockBlobClient(blobName); + + await blockBlobClient.deleteIfExists(options); + + console.log(`deleted blob ${blobName}`); + + return { "deleted": blobName }; +}; + +export const uploadFile = async (formContent, containerName, foldername) => { + const creds = new DefaultAzureCredential(); + + const containerClient = new ContainerClient( + `${STORAGE_PATH}/${containerName}`, + creds + ); + + const files = formContent; + + //console.log(...formContent); + //console.log("files...", files, files.length); + + for (const prop in files) { + //console.log(`files[${prop}] = ${files[prop][0].size}`); + const blobName = "files/" + files[prop][0].fieldName; + const blockBlobClient = containerClient.getBlockBlobClient(blobName); + const uploadBlobResponse = await blockBlobClient.uploadFile( + files[prop][0].path, + files[prop].size + ); + console.log( + `Uploaded block blob ${files[prop][0].fieldName} successfully`, + uploadBlobResponse.requestId + ); + } +}; + +export const downloadFile = async (containerName, blobName) => { + //console.log.apply(containerName, blobName); + const creds = new DefaultAzureCredential(); + + const containerClient = new ContainerClient( + `${STORAGE_PATH}/${containerName}`, + creds + ); + + const blobClient = containerClient.getBlobClient("files/" + blobName); + const downloadedBlob = await blobClient.download(0); + + const downloaded = await streamToBuffer(downloadedBlob.readableStreamBody); + return downloaded; +}; + +const streamToBuffer = async (readableStream) => { + return new Promise((resolve, reject) => { + const chunks = []; + readableStream.on("data", (data) => { + chunks.push(data instanceof Buffer ? data : Buffer.from(data)); + }); + readableStream.on("end", () => { + resolve(Buffer.concat(chunks)); + }); + readableStream.on("error", reject); + }); +}; diff --git a/actions/index.js b/actions/index.js index a57b8827..4f40fb86 100644 --- a/actions/index.js +++ b/actions/index.js @@ -3,7 +3,7 @@ import https from "https"; import CryptoJS from "crypto-js"; import HmacSHA256 from "crypto-js/hmac-sha256"; import { getFormCollection } from "../components/utils"; - +import { downloadFile } from "./azurestorage"; let BASE_URL; const port = parseInt(process.env.PORT, 10) || 3000; @@ -24,10 +24,6 @@ const WEBAPI_URL = process.env.RELAY_ROOT || "https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/"; -const agent = new https.Agent({ - rejectUnauthorized: false, -}); - export const getToken = () => { return axios .post( @@ -125,7 +121,6 @@ export const hashAPIPath = (queryPath) => { CryptoJS.enc.Hex.parse(WORDKEY) ); hashlink = hashlink.toString(CryptoJS.enc.Hex); - return (queryPath.indexOf("?") > -1 ? "&hash=" : "?hash=") + hashlink; }; @@ -1029,35 +1024,95 @@ export const createAccount = (formValues) => { }); }; -export const uploadFiles = (formValues) => { - var data = formValues; - var queryUrl = "/api/uploads"; - var config = { +export const uploadFiles = async (formValues, filesObj) => { + let files = filesObj; + + console.log(formValues); + var formData = new FormData(); + formData.append("appealData", JSON.stringify(formValues)); + + // files.forEach((file) => formData.append("files", file)); + + for (let i = 0; i < files.length; i++) { + for (let j = 0; j < files[i].length; j++) { + console.log(files[i][j].name); + formData.append(files[i][j].name, files[i][j]); + } + } + + console.log(formData); + + var queryUrl = "/api/file/upload"; + + const config = { method: "post", url: queryUrl, - data: data, + data: formData, + headers: { "content-type": "multipart/form-data" }, }; - // const config = { - // method: "post", - // url: queryUrl, - // data: data, - // headers: { "content-type": "multipart/form-data" }, - // onUploadProgress: (event) => { - // //console.log( - // `Current progress:`, - // Math.round((event.loaded * 100) / event.total) - // ); - // }, - // }; - //console.log(config); - return axios(config) + try { + const res = await axios(config); + return res.data; + } catch (error) { + console.log("this serror", error); + } +}; + +export const getFilesFromBlob = (containerName) => { + return axios + .get( + BASE_URL + + "/api/file/getbloblist?container=" + + containerName.toLowerCase() + ) .then((res) => { - //console.log("posted ", res.data); + console.log("//////////----", res.data); return res.data; }) .catch((error) => { console.log("this serror", error); }); }; + +export const deleteBlob = async (containerName, blobName) => { + try { + const res = await axios.get( + BASE_URL + + "/api/file/deleteblob?container=" + + containerName.toLowerCase() + + "&blobname=" + + blobName + ); + return res.data; + } catch (error) { + console.log("this serror", error); + } +}; + +export const downloadBlob = (containerName, blobName) => { + return axios + .get( + BASE_URL + + "/api/file/downloadblob?container=" + + containerName.toLowerCase() + + "&blobname=" + + blobName, + { responseType: "blob" } + ) + .then((response) => { + console.log("Downloaded blob content:www"); + + res.setHeader( + "content-disposition", + "attachment; filename=" + blobName + ); + console.log("has downloaded"); + + return res.status(200).send(response.data); + }) + .catch((error) => { + console.log("this serror", error); + }); +}; diff --git a/components/elements/index.js b/components/elements/index.js index fc653a3c..238c2e20 100644 --- a/components/elements/index.js +++ b/components/elements/index.js @@ -14,6 +14,14 @@ import FileUpload from "../newappeal/fileupload"; import Dropzone, { useDropzone } from "react-dropzone"; import _ from "lodash"; import { connect, useDispatch } from "react-redux"; +import { + getFilesFromBlob, + deleteBlob, + downloadBlob, + hashAPIPath, +} from "../../actions"; +import { setFilesForAppeal } from "../../store/appealType/action"; +import Link from "next/link"; import { addYears, @@ -1246,6 +1254,8 @@ export function FileUploadField(props) { ticketnumber={props.ticketnumber} documentTypeCode={props.documentTypeCode} hint={props.hint} + fileList={props.fileList} + setFilesForAppeal={props.setFilesForAppeal} /> ); @@ -1277,104 +1287,6 @@ const rejectStyle = { borderColor: "#ff1744", }; -// const RenderFileUpload2 = ({ -// id, -// className, -// rows, -// datafieldname, -// name, -// label, -// input, -// errorMsg, -// meta: { touched, errorStr }, -// ...custom -// }) => { -// const [files, setFiles] = useState([]); - -// const onDrop = useCallback((acceptedFiles, fileRejections, event) => { -// setFiles( -// acceptedFiles.map((file) => -// Object.assign(file, { -// preview: URL.createObjectURL(file), -// }) -// ) -// ); -// console.log(acceptedFiles); -// event.target[acceptedFiles]; -// console.log(event.target.files); -// }, []); - -// const onChange = useCallback((acceptedFiles, fileRejections, event) => { -// setFiles( -// acceptedFiles.map((file) => -// Object.assign(file, { -// preview: URL.createObjectURL(file), -// }) -// ) -// ); -// event.target.files[acceptedFiles]; -// console.log(event.target.files); -// }, []); - -// const { -// getRootProps, -// getInputProps, -// isDragActive, -// isDragAccept, -// isDragReject, -// } = useDropzone({ -// onDrop, -// onChange, -// }); - -// const style = useMemo( -// () => ({ -// ...baseStyle, -// ...(isDragActive ? activeStyle : {}), -// ...(isDragAccept ? acceptStyle : {}), -// ...(isDragReject ? rejectStyle : {}), -// }), -// [isDragActive, isDragReject, isDragAccept] -// ); - -// const thumbs = files.map((file) => ( -//
-// {file.name} -{" "} -// -// {file.name} ({file.size / 1024}kb) -// -//
-// )); - -// // clean up -// useEffect( -// () => () => { -// files.forEach((file) => URL.revokeObjectURL(file.preview)); -// }, -// [files] -// ); - -// const onChangeFile = (e) => { -// const { -// input: { onChange }, -// } = props; -// onChange(e.target.files[0]); -// }; -// return ( -//
-//
-//
-//
-// -//
Drag and drop your images here.
-//
-//
{" "} -// -// {touched && errorStr && {errorStr}} -//
-// ); -// }; - const RenderFileUpload = (field) => { const files = field.input.value; let { t } = useTranslation(); @@ -1437,11 +1349,54 @@ const RenderFileUpload = (field) => { case "image/png": return URL.createObjectURL(fileObj); break; + default: + return "/assets/images/documenttypes/txt.png"; + break; + } + }; + + const getThumbnailIconByExtension = (blobName) => { + let fileType = blobName.slice(blobName.lastIndexOf(".") + 1); + + switch (fileType) { + case "html": + return "/assets/images/documenttypes/html.png"; + break; + case "txt": + return "/assets/images/documenttypes/txt.png"; + break; + case "doc": + return "/assets/images/documenttypes/doc.png"; + break; + case "pdf": + return "/assets/images/documenttypes/pdf.png"; + break; + case "docx": + return "/assets/images/documenttypes/docx.png"; + break; + case "csv": + return "/assets/images/documenttypes/csv.png"; + break; + case "xlsx": + return "/assets/images/documenttypes/xlsx.png"; + break; + + case "zip": + return "/assets/images/documenttypes/zip.png"; + break; + case "jpeg": + case "jpg": + return "/assets/images/documenttypes/jpg.png"; + case "png": + return "/assets/images/documenttypes/png.png"; + break; + default: + return "/assets/images/documenttypes/txt.png"; + break; } }; const getDocumentType = (docCode) => { - console.log(docCode); switch (docCode) { case "000001": return "000001"; @@ -1493,6 +1448,25 @@ const RenderFileUpload = (field) => { return "000000"; } }; + + const filelistObj = field.fileList || {}; + + const blobList = jsonpath.query( + filelistObj, + "$..[?(@.documentType=='" + field.documentTypeCode + "')]" + ); + + const deleteThisBlob = async (containerName, blobName) => { + console.log(containerName, blobName); + deleteBlob(containerName, blobName) + .then((data) => data) + .then(() => { + getFilesFromBlob(containerName).then((newfilelist) => + field.setFilesForAppeal(newfilelist) + ); + }); + }; + return ( <> { // } onDrop={(filesToUpload, e) => { - console.log(field.ticketnumber, field.documentTypeCode); const renamedAcceptedFiles = filesToUpload.map( (file) => new File( [file], - `${field.ticketnumber}_${getDocumentType( - field.documentTypeCode - )}_${file.name}`, + `${getDocumentType(field.documentTypeCode)}_${ + file.name + }`, { type: file.type, } @@ -1519,14 +1492,14 @@ const RenderFileUpload = (field) => { > {({ getRootProps, getInputProps }) => ( <> - {field.hint != false && ( -
{t(field.hint)}
+ {/* {field.hint != false && ( +
ss{t(field.hint)}
)} -
+
*/}
{" "} - +
Drag and drop your files here.
@@ -1556,6 +1529,45 @@ const RenderFileUpload = (field) => { ))} )} + {blobList.map((blob, i) => ( +
+
+ { + deleteThisBlob(field.ticketnumber, blob.name); + }} + title="Remove this file" + > + − + +
+ {blob.name} + + + {blob.name} ({bytesToSize(blob.contentLength)}) + + +
+ ))}{" "} ); }; diff --git a/components/myportal/uploadFile.js b/components/myportal/uploadFile.js index e3c560a1..45a82ccf 100644 --- a/components/myportal/uploadFile.js +++ b/components/myportal/uploadFile.js @@ -8,6 +8,7 @@ import { Field, reduxForm } from "redux-form"; import { connect } from "react-redux"; import Dropzone, { useDropzone } from "react-dropzone"; import { uploadFiles } from "../../actions"; +import { getContainers } from "../../actions/azurestorage"; const required = (errorMsg) => (value) => value || typeof value === "number" ? undefined : errorMsg; @@ -158,6 +159,60 @@ const RenderFileUpload = (field) => { break; } }; + + const getDocumentType = (docCode) => { + switch (docCode) { + case "000001": + return "000001"; + break; + case "000002": + return "000002"; + break; + case "000003": + return "000003"; + break; + case "000004": + return "000004"; + break; + case "000005": + return "000005"; + break; + case "000006": + return "000006"; + break; + case "000007": + return "000007"; + break; + case "000008": + return "000008"; + break; + case "000009": + return "000009"; + break; + case "000010": + return "000010"; + break; + case "000011": + return "000011"; + break; + case "000012": + return "000012"; + break; + case "000013": + return "000013"; + break; + case "000014": + return "000014"; + break; + case "000015": + return "000015"; + break; + + default: + return "000000"; + } + }; + return (
{ // } onDrop={(filesToUpload, e) => { + console.log(field.ticketnumber, field.documentTypeCode); const renamedAcceptedFiles = filesToUpload.map( (file) => - new File([file], `${file.name}_${+new Date()}`, { - type: file.type, - }) + new File( + [file], + `${field.ticketnumber}_${getDocumentType( + field.documentTypeCode + )}_${file.name}`, + { + type: file.type, + } + ) ); field.input.onChange(renamedAcceptedFiles); - console.log(renamedAcceptedFiles); }} > {({ getRootProps, getInputProps }) => ( @@ -221,7 +282,7 @@ const RenderFileUpload = (field) => { let UploadFile = (props) => { let { t } = useTranslation(); //const [showSpinnerState, setShowSpinnerState] = useState(false); - const { handleSubmit, pristine, reset, submitting } = props; + const { handleSubmit, pristine, reset, submitting, formContent } = props; const router = useRouter(); const { locale } = router; @@ -229,8 +290,19 @@ let UploadFile = (props) => { const onHandleSubmit = async (values) => { //spinnerState(); console.log(values); + console.log(formContent); + // getContainers(); - const uploadAction = await uploadFiles(values); + var dataObj = Object.assign(values, formContent); + var fileObj = dataObj.fileList; + + delete dataObj.fileList; + + const uploadAction = await uploadFiles(dataObj, fileObj).then( + (data) => { + console.log("hello", data); + } + ); // router.push({ // pathname: // router.locale == "cy" @@ -270,6 +342,7 @@ let UploadFile = (props) => { type="text" aria-describedby="caseReference-hint" hint1="Case Reference" + value="CAS-00764-L0Q9W2" />
@@ -281,6 +354,7 @@ let UploadFile = (props) => { //validate={[required]} label={props.label} errorMsg="Is required" + ticketnumber="CAS-00764-L0Q9W2" />
@@ -306,6 +380,69 @@ const mapStateToProps = (state) => { watchedCases: state.watchedCases, myRepresentations: state.myRepresentations, awaitingSubmission: state.awaitingSubmission, + formContent: { + "pinswg_caserecovered": "No", + "pinswg_siteaddresspostcode": "Site Address Postcode", + "pinswg_inspectorneededonsite": "Yes", + "pinswg_sitewithingreenbelt": "No", + "pinswg_siteaddressline2": "Site Address Line 2", + "pinswg_siteaddressline1": "Site Address Line 1", + "pinswg_s106unilateralsubmitted": "No", + "modifiedon": "2022-06-28T13:11:34Z", + "pinswg_developmentaffectsettingofalisted": "No", + "pinswg_planningappeals78id": + "57b533aa-43f1-ec11-aade-00224800be9c", + "pinswg_name": "CAS-00764-L0Q9W2", + "pinswg_whyisinspectorneededonsite": "to be nosey", + "pinswg_sitewithinsssi": "No", + "pinswg_healthandsafetyissuesonsite": "No", + "pinswg_developmentdescriptionchanged": "Yes", + "_stageid_value": "126e9c46-d51c-4623-aabf-aa30b34bfa54", + "traversedpath": "126e9c46-d51c-4623-aabf-aa30b34bfa54", + "createdon": "2022-06-21T09:22:29Z", + "timezoneruleversionnumber": 0, + "processid": "e4f065a6-157e-42f9-963a-9292d5041509", + "pinswg_priornotificationpriorapproval": "No", + "pinswg_predeterminedindicator": "No", + "pinswg_protectedspecies": "No", + "versionnumber": 912609, + "pinswg_siteaddresscounty": "Site Address County", + "pinswg_procedure": 846040000, + "pinswg_dateoflpadecision": "2022-06-22T23:00:00Z", + "pinswg_eiarequired": "No", + "pinswg_costappliedforindicator": "No", + "pinswg_areaofsiteinhectaresdec": 22, + "pinswg_detailedeiascreeningrequired": "No", + "pinswg_casepublishflag": "No", + "pinswg_siteaddresstown": "Site Address Town", + "utcconversiontimezonecode": 85, + "pinswg_caseworkreason": 846040005, + "pinswg_ownershipcertificate": 846040002, + "pinswg_lpadecisiononapplicationmade": "Yes", + "pinswg_floorspaceinsquaremeters": 111, + "_modifiedby_value": "149b3e11-5206-ec11-aac8-00224800be9c", + "pinswg_dateofapplication": "2022-06-20T23:00:00Z", + "pinswg_incarelatestoca": "No", + "_createdby_value": "149b3e11-5206-ec11-aac8-00224800be9c", + "pinswg_additionalappealsmade": "No", + "statuscode": 1, + "pinswg_siteviewablefromroad": "Yes", + "_pinswg_localplanningauthority_value": + "744c69f4-48da-eb11-aac5-00224800be9c", + "pinswg_lpaapplicationreference": "11111weweqweqwe", + "pinswg_recovered": "No", + "statecode": 0, + "pinswg_isfloodingandissue": "No", + "pinswg_isthesitewithinanaonb": "No", + "_organizationid_value": "01191a4d-105f-eb11-aaba-00224800be9c", + "pinswg_developmentdescription": "something to go here", + "pinswg_agriculturalholding": 846040000, + "_pinswg_planningappeals78ids_value": + "50b533aa-43f1-ec11-aade-00224800be9c", + }, + initialValues: { + caseReference: "CAS-00764-L0Q9W2", + }, }; }; @@ -320,5 +457,6 @@ export default connect( reduxForm({ form: "uploadFileForm", destroyOnUnmount: false, + enableReinitialize: true, // this is needed!! })(UploadFile) ); diff --git a/components/newappeal/buildCheckfield.js b/components/newappeal/buildCheckfield.js index 6e34ec66..1fdef5df 100644 --- a/components/newappeal/buildCheckfield.js +++ b/components/newappeal/buildCheckfield.js @@ -39,7 +39,7 @@ export default function BuildField(props) { case "{07FAC785-CD58-4f9f-ABB3-4B7DDC6ED5ED}": return <>{fieldValue}; break; - case "{ 4273EDBD-AC1D-40d3-9FB2-095C621B552D}": + case "{4273EDBD-AC1D-40d3-9FB2-095C621B552D}": return <>{fieldValue}; break; case "{5B773807-9FB2-42db-97C3-7A91EFF8ADFF}": @@ -58,8 +58,12 @@ export default function BuildField(props) { case "{C3EFE0C3-0EC6-42be-8349-CBD9079DFD8E}": return <>{fieldValue}; break; + case "{16D63FD6-119B-4353-BDCA-18358721C3FE}": + return <> docs {fieldValue}; + break; + default: - return <>{fieldValue}; + return <>ss{fieldValue}; } }; diff --git a/components/newappeal/buildchecksection.js b/components/newappeal/buildchecksection.js index 68137f51..1f373ef9 100644 --- a/components/newappeal/buildchecksection.js +++ b/components/newappeal/buildchecksection.js @@ -55,6 +55,14 @@ let BuildCheckSection = (props) => { // /searchresults }; + const onHandleSubmit = (values) => { + //alert(1); + //updateCaseProgress(values); + // setCurrentSection(9999); + + alert(1); + }; + return (
@@ -83,12 +91,21 @@ let BuildCheckSection = (props) => {
+ setCurrentSection(9999)} > - {t("newappeal:submit-appeal-button")} + sss{t("newappeal:submit-appeal-button")}
diff --git a/components/newappeal/buildfield.js b/components/newappeal/buildfield.js index 508807bb..33dab549 100644 --- a/components/newappeal/buildfield.js +++ b/components/newappeal/buildfield.js @@ -183,6 +183,12 @@ export default function BuildField(props) { } documentTypeCode={props.documentTypeCode} hint={props.hint} + fileList={ + props.props.props.props.appealType.fileList + } + setFilesForAppeal={ + props.props.props.setFilesForAppeal + } />
); diff --git a/components/newappeal/buildrow.js b/components/newappeal/buildrow.js index c4414367..946aeadf 100644 --- a/components/newappeal/buildrow.js +++ b/components/newappeal/buildrow.js @@ -157,6 +157,9 @@ export default function BuildRow(props) { props.props.appealType.documentList } documentTypeCode={documentTypeCode} + setFilesForAppeal={ + props.props.setFilesForAppeal + } /> ); // } else { diff --git a/components/newappeal/buildsection.js b/components/newappeal/buildsection.js index da9fb730..e18850d1 100644 --- a/components/newappeal/buildsection.js +++ b/components/newappeal/buildsection.js @@ -11,9 +11,10 @@ import { setCaseReference, setCurrentSection, setDocumentsList, + setFilesForAppeal, } from "../../store/appealType/action"; import { getFormCollectionByID } from "../utils"; -import { updateCase, patchCase } from "../../actions"; +import { updateCase, patchCase, uploadFiles } from "../../actions"; import { FieldsTranslations } from "../elements"; import jsonpath from "jsonpath"; @@ -69,10 +70,33 @@ let BuildSection = (props) => { const handleParam = (setValue) => (e) => setValue(e.target.value); + const uploadAppealFiles = (values) => { + // get filelists from values object + let fileListObj = _.filter(values, function (v, key) { + return _.includes(key, "pinswg_fileUpload"); + }); + + console.log(fileListObj); + + var dataObj = values; + + for (let key in dataObj) { + _.includes(key, "pinswg_fileUpload") == true && delete dataObj[key]; + } + + uploadFiles(dataObj, fileListObj).then((data) => { + console.log("hello", data); + + return data; + }); + }; + const updateCaseProgress = (values) => { let incidentId = props.appealType.caseReference.incidentid; let updateBody = values || {}; + uploadAppealFiles(values); + let appTypeCollection = getFormCollectionByID( props.appealType.appealTypeID ); @@ -402,6 +426,9 @@ const mapDispatchToProps = (dispatch) => { setDocumentsList: (documentList) => { dispatch(setDocumentsList(documentList)); }, + setFilesForAppeal: (blobList) => { + dispatch(setFilesForAppeal(blobList)); + }, }; }; diff --git a/components/newappeal/enforcementnotice.js b/components/newappeal/enforcementnotice.js deleted file mode 100644 index ca00f199..00000000 --- a/components/newappeal/enforcementnotice.js +++ /dev/null @@ -1,24 +0,0 @@ -import Link from "next/link"; -import { useRouter } from "next/router"; -import useTranslation from "next-translate/useTranslation"; - -export default function MakeEnforcementNotice() { - let { t } = useTranslation(); - - const router = useRouter(); - const { locale } = router; - return ( -
-
-

- Including an enforcement listed building notice. -

- -
-
- ); -} diff --git a/package.json b/package.json index be26b246..24b2ab45 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "start": "node_modules/next/dist/bin/next start" }, "dependencies": { + "@azure/identity": "^2.0.5", + "@azure/storage-blob": "^12.10.0", "@googlemaps/react-wrapper": "^1.1.24", "@magic-sdk/admin": "^1.4.0", "@next-auth/prisma-adapter": "^1.0.3", @@ -24,6 +26,7 @@ "dom": "^0.0.3", "dynamics-web-api": "^1.7.4", "express": "^4.17.2", + "formidable": "^2.0.1", "fs": "^0.0.1-security", "google-map-react": "^2.1.10", "govuk-frontend": "3.13.0", @@ -37,6 +40,7 @@ "magic-sdk": "^8.1.0", "moment": "^2.29.1", "multer": "^1.4.4", + "multiparty": "^4.2.3", "mysql": "^2.18.1", "nanoid": "^3.2.0", "next": "^12.0.8", @@ -69,6 +73,7 @@ "sass-loader": "^12.4.0", "swr": "^1.1.2", "typeorm": "^0.3.5", + "uuid": "^8.3.2", "webpack": "^5.66.0", "xml2js": "^0.4.23", "xmldom": "^0.6.0", @@ -81,4 +86,4 @@ "prettier": "2.5.1", "prisma": "^3.12.0" } -} \ No newline at end of file +} diff --git a/pages/api/endpoint/getawaitingsubmission_api.js b/pages/api/endpoint/getawaitingsubmission_api.js index 5b90d275..105c2909 100644 --- a/pages/api/endpoint/getawaitingsubmission_api.js +++ b/pages/api/endpoint/getawaitingsubmission_api.js @@ -26,7 +26,7 @@ export default async function ApiProxy(req, res) { loggedInUserId + " and servicestage eq 1 and pinswg_appealcasetype ne null&$orderby=createdon desc&$count=true"; - console.log(WEBAPI_URL + queryUrl + hashAPIPath(queryUrl)); + //console.log(WEBAPI_URL + queryUrl + hashAPIPath(queryUrl)); return axios .get( WEBAPI_URL + queryUrl + hashAPIPath(queryUrl), diff --git a/pages/api/endpoint/getbasicsearch_api.js b/pages/api/endpoint/getbasicsearch_api.js index 90ccf283..396fa471 100644 --- a/pages/api/endpoint/getbasicsearch_api.js +++ b/pages/api/endpoint/getbasicsearch_api.js @@ -32,7 +32,7 @@ export default async function ApiProxy(req, res) { searchString + "')) and pinswg_appealcasetype ne null and pinswg_publishtoweb eq true&$orderby=createdon desc&$count=true"; - console.log("basic search ", queryUrl); + //console.log("basic search ", queryUrl); var apiResponse = _.isEmpty(req.query) ? res.status(400).json() diff --git a/pages/api/endpoint/getlpa_api.js b/pages/api/endpoint/getlpa_api.js index a04bd51a..525f4831 100644 --- a/pages/api/endpoint/getlpa_api.js +++ b/pages/api/endpoint/getlpa_api.js @@ -26,6 +26,7 @@ export default async function ApiProxy(req, res) { var queryUrl = "accounts?$count=true&$filter=pinswg_isalocalplanningauthorityaccount eq 846040000&$select=name&$orderby=name asc"; + console.log(WEBAPI_URL + queryUrl + hashAPIPath(queryUrl)); return axios .get( WEBAPI_URL + queryUrl + hashAPIPath(queryUrl), @@ -35,6 +36,7 @@ export default async function ApiProxy(req, res) { res.status(200).json(data); }) .catch(({ err }) => { + console.log(err); res.status(400).json(err); }); } diff --git a/pages/api/file/deleteblob.js b/pages/api/file/deleteblob.js new file mode 100644 index 00000000..4ee2a864 --- /dev/null +++ b/pages/api/file/deleteblob.js @@ -0,0 +1,32 @@ +import { + createContainer, + getContainers, + getBlobs, + createBlob, + uploadFile, + deleteBlob, +} from "../../../actions/azurestorage"; + +import middleware from "../middleware/middleware"; +import nextConnect from "next-connect"; + +const ApiProxy = nextConnect(); +ApiProxy.use(middleware); + +ApiProxy.get(async (req, res) => { + var containerName = req.query.container; + var blobName = req.query.blobname; + console.log(containerName, blobName); + + await deleteBlob(containerName, "files/" + blobName).then((data) => { + return res.status(200).json({ data: data }); + }); +}); + +export const config = { + api: { + bodyParser: false, + }, +}; + +export default ApiProxy; diff --git a/pages/api/file/downloadblob.js b/pages/api/file/downloadblob.js new file mode 100644 index 00000000..0735d293 --- /dev/null +++ b/pages/api/file/downloadblob.js @@ -0,0 +1,84 @@ +import { + createContainer, + getContainers, + getBlobs, + createBlob, + uploadFile, + deleteBlob, + downloadFile, +} from "../../../actions/azurestorage"; +import { BlobServiceClient, ContainerClient } from "@azure/storage-blob"; +import { + DefaultAzureCredential, + InteractiveBrowserCredential, + EnvironmentCredential, + ClientSecretCredential, +} from "@azure/identity"; + +import middleware from "../middleware/middleware"; +import nextConnect from "next-connect"; +import CryptoJS from "crypto-js"; +import { hashAPIPath } from "../../../actions"; + +const WORDKEY = process.env.HASHKEY; + +const ApiProxy = nextConnect(); + +ApiProxy.use(middleware); + +ApiProxy.get(async (req, res) => { + var containerName = req.query.container; + var blobName = req.query.blobname; + var checkHash = req.query.hash; + + var checkquerypath = + "/api/file/downloadblob?container=" + + containerName + + "&blobname=" + + blobName; + + console.log( + "containerName " + + containerName + + `\n` + + blobName + + `\n` + + checkHash + + `\n` + + checkquerypath + ); + console.log(hashAPIPath(checkquerypath), checkHash); + console.log(hashAPIPath(checkquerypath) == "&hash=" + checkHash); + if (hashAPIPath(checkquerypath) == "&hash=" + checkHash) { + const downloaded = await downloadFile(containerName, blobName); + + res.setHeader( + "content-disposition", + "attachment; filename=" + blobName + ); + return res.status(200).send(downloaded); + } else { + return res.status(400).json(); + } +}); + +async function streamToBuffer(readableStream) { + return new Promise((resolve, reject) => { + const chunks = []; + readableStream.on("data", (data) => { + chunks.push(data instanceof Buffer ? data : Buffer.from(data)); + }); + readableStream.on("end", () => { + resolve(Buffer.concat(chunks)); + }); + readableStream.on("error", reject); + }); +} + +export const config = { + api: { + bodyParser: false, + }, +}; + +export default ApiProxy; diff --git a/pages/api/file/getbloblist.js b/pages/api/file/getbloblist.js new file mode 100644 index 00000000..89b05cb0 --- /dev/null +++ b/pages/api/file/getbloblist.js @@ -0,0 +1,29 @@ +import { + createContainer, + getContainers, + getBlobs, + createBlob, + uploadFile, +} from "../../../actions/azurestorage"; + +import middleware from "../middleware/middleware"; +import nextConnect from "next-connect"; + +const ApiProxy = nextConnect(); +ApiProxy.use(middleware); + +ApiProxy.get(async (req, res) => { + var containerName = req.query.container; + console.log(containerName); + await getBlobs(containerName).then((data) => { + return res.status(200).json({ files: data }); + }); +}); + +export const config = { + api: { + bodyParser: false, + }, +}; + +export default ApiProxy; diff --git a/pages/api/file/upload.js b/pages/api/file/upload.js new file mode 100644 index 00000000..17850a00 --- /dev/null +++ b/pages/api/file/upload.js @@ -0,0 +1,36 @@ +import { + createContainer, + getContainers, + getBlobs, + createBlob, + uploadFile, +} from "../../../actions/azurestorage"; + +import middleware from "../middleware/middleware"; +import nextConnect from "next-connect"; + +const ApiProxy = nextConnect(); +ApiProxy.use(middleware); + +ApiProxy.post(async (req, res) => { + console.log(JSON.parse(req.body.appealData)); + console.log(req.files); + + const appealData = JSON.parse(req.body.appealData); + + createContainer(appealData.pinswg_name.trim()).then((containerName) => { + createBlob(appealData, containerName).then((data) => { + uploadFile(req.files, containerName, data); + }); + }); + + return res.status(200).json({ data: "success" }); +}); + +export const config = { + api: { + bodyParser: false, + }, +}; + +export default ApiProxy; diff --git a/pages/api/middleware/middleware.js b/pages/api/middleware/middleware.js new file mode 100644 index 00000000..90daa602 --- /dev/null +++ b/pages/api/middleware/middleware.js @@ -0,0 +1,16 @@ +import nextConnect from "next-connect"; +import multiparty from "multiparty"; + +const middleware = nextConnect(); + +middleware.use(async (req, res, next) => { + const form = new multiparty.Form(); + + await form.parse(req, function (err, fields, files) { + req.body = fields; + req.files = files; + next(); + }); +}); + +export default middleware; diff --git a/pages/myportal/[appealtypes].js b/pages/myportal/[appealtypes].js index e8803812..2aec26bf 100644 --- a/pages/myportal/[appealtypes].js +++ b/pages/myportal/[appealtypes].js @@ -11,6 +11,7 @@ import { getMandatoryFields, getPickLists, getPartSavedAppeal, + getFilesFromBlob, } from "../../actions"; import { getProviderConfig } from "../../actions/govgateway"; import Breadcrumbs from "../../components/breadcrumbs"; @@ -29,6 +30,7 @@ import { setAppealTypeID, setAppealTypeTitle, setCaseReference, + setFilesForAppeal, } from "../../store/appealType/action"; import { setForm } from "../../store/formData/action"; import { getGovGatewayConfig } from "../../store/govgateway/action"; @@ -213,11 +215,12 @@ export const getServerSideProps = wrapper.getServerSideProps( console.log("the query", query.appealtypes); let loggedInUser = cookies.pinsUser; - const [appealTypeData, mandatoryFieldsData, pickListData] = + const [appealTypeData, mandatoryFieldsData, pickListData, blobList] = await Promise.all([ await getAppealsTypes(), await getMandatoryFields(query.appealtypes), await getPickLists(query.appealtypes), + await getFilesFromBlob(query.casereference), ]); let searchResultsObj = await getPartSavedAppeal(query.casereference); @@ -232,7 +235,7 @@ export const getServerSideProps = wrapper.getServerSideProps( } }); - console.log("appeal:", searchDetailsObj); + //console.log("appeal:", searchDetailsObj); searchDetailsObj = JSON.stringify(searchDetailsObj); searchDetailsObj = searchDetailsObj.replace(/:true/gm, `:"Yes"`); @@ -265,6 +268,7 @@ export const getServerSideProps = wrapper.getServerSideProps( store.dispatch(setCaseReference(caseReference)); store.dispatch(setForm(xmlStr, mandatoryFieldsData, pickListData)); store.dispatch(setAppealType(appealTypeData)); + store.dispatch(setFilesForAppeal(blobList)); } ); @@ -290,6 +294,9 @@ const mapDispatchToProps = (dispatch) => { setCaseReference: (refno) => { dispatch(setCaseReference(refno)); }, + setFilesForAppeal: (blobList) => { + dispatch(setFilesForAppeal(blobList)); + }, }; }; diff --git a/pages/myportal/index.js b/pages/myportal/index.js index 6c513cbd..de9e6f98 100644 --- a/pages/myportal/index.js +++ b/pages/myportal/index.js @@ -229,7 +229,7 @@ export const getServerSideProps = wrapper.getServerSideProps( ); const getDetails = (resultsObj, detailsType) => { - console.log(detailsType); + //console.log(detailsType); let detailsArr = []; resultsObj = resultsObj.value; const detailsObj = resultsObj.map((searchDetail, index) => { diff --git a/pages/newappeal/[appealtypes].js b/pages/newappeal/[appealtypes].js index 36f807b2..fc5829ba 100644 --- a/pages/newappeal/[appealtypes].js +++ b/pages/newappeal/[appealtypes].js @@ -12,6 +12,7 @@ import { getAppealsTypes, getMandatoryFields, getPickLists, + getFilesFromBlobs, } from "../../actions"; import { getProviderConfig } from "../../actions/govgateway"; import Breadcrumbs from "../../components/breadcrumbs"; @@ -30,6 +31,7 @@ import { setAppealTypeID, setAppealTypeTitle, setCaseReference, + setFilesForAppeal, } from "../../store/appealType/action"; import { setForm } from "../../store/formData/action"; import { getGovGatewayConfig } from "../../store/govgateway/action"; @@ -220,11 +222,13 @@ export const getServerSideProps = wrapper.getServerSideProps( appealTypeData, mandatoryFieldsData, pickListData, + blobList, ] = await Promise.all([ await getCase(query.id), await getAppealsTypes(), await getMandatoryFields(query.appealtypes), await getPickLists(query.appealtypes), + await getFilesFromBlob(query.casereference), ]); console.log("anything here", loggedInUser, query.id, caseReferenceData); @@ -251,6 +255,7 @@ export const getServerSideProps = wrapper.getServerSideProps( store.dispatch(setCaseReference(caseReference)); store.dispatch(setForm(xmlStr, mandatoryFieldsData, pickListData)); store.dispatch(setAppealType(appealTypeData)); + store.dispatch(setFilesForAppeal(blobList)); } ); diff --git a/pages/storage.js b/pages/storage.js new file mode 100644 index 00000000..b2e3c96b --- /dev/null +++ b/pages/storage.js @@ -0,0 +1,104 @@ +import { v4 as uuidv4 } from "uuid"; +import { BlobServiceClient, ContainerClient } from "@azure/storage-blob"; +import { + DefaultAzureCredential, + InteractiveBrowserCredential, + EnvironmentCredential, + ClientSecretCredential, +} from "@azure/identity"; +import { wrapper } from "../store/store"; + +import Head from "next/head"; + +const ShowStorage = (props) => { + const { container } = props; + // console.log("Containers:"); + // // for await (const container of blobServiceClient.listContainers()) { + // // console.log(`- ${container.name}`); + // // } + + console.log(container); + + return
hello
; +}; + +export const getServerSideProps = wrapper.getServerSideProps( + (store) => async (ctx) => { + const { query, req, res } = ctx; + + console.log( + "have therese?:", + process.env.AZURE_TENANT_ID, + process.env.AZURE_CLIENT_ID, + process.env.AZURE_CLIENT_SECRET + ); + + const creds = new DefaultAzureCredential(); + + const blobServiceClient = new BlobServiceClient( + `https://pedwdev.blob.core.windows.net`, + creds + ); + + const containerName = `CAS-999999-22222`; + + const containerClient = new ContainerClient( + `https://pedwdev.blob.core.windows.net/${containerName}`, + creds + ); + + // const createContainerResponse = await containerClient.create(); + // console.log( + // `Created container ${containerName} successfully`, + // createContainerResponse.requestId + // ); + + console.log("Containers:"); + for await (const container of blobServiceClient.listContainers()) { + console.log(`- ${container.name}`); + } + + const appealID = uuidv4(); + const uploadOptions = { + metadata: { reviewer: "john", reviewDate: "2022-04-01" }, + tags: { owner: appealID }, + }; + // for (let index = 0; index < 7; index++) { + // // Create a blob + // const content = "hello"; + // const blobName = appealID + "/BOBnewblob" + new Date().getTime(); + // const blockBlobClient = + // containerClient.getBlockBlobClient(blobName); + + // // const uploadBlobResponse = await blockBlobClient.upload( + // // content, + // // Buffer.byteLength(content), + // // uploadOptions + // // ); + + // const uploadBlobResponse = await blockBlobClient.uploadFile( + // "public/assets/images/HM-Govt-Supplier-to-Govt.jpg" + // // uploadOptions + // ); + + // const tags = { + // owner: appealID, + // }; + + // //console.log(tags); + // //await blockBlobClient.setTags(tags); + + // // console.log( + // // `Uploaded block blob ${blobName} successfully`, + // // uploadBlobResponse.requestId + // // ); + // } + + console.log("Blobs:"); + for await (const blob of containerClient.listBlobsFlat()) { + console.log(`- ${blob.name}`); + } + } +); + +export default ShowStorage; diff --git a/public/assets/images/documenttypes/jpg.png b/public/assets/images/documenttypes/jpg.png new file mode 100644 index 0000000000000000000000000000000000000000..0b5bdc622abc00e9c95d8f34262c61fb3730948e GIT binary patch literal 964 zcmV;#13UbQP)MV6byJh=Kz?Qv(L0{cFH{6gMLW5L`8s!GbP; z&11a$0*n~eqiclmiB?Fo?};`ML!n%0Cd^qVG=@SEUsn_bCdW5}`9!-BCBRXP0&?(0 z0J>UaK1SpqOTyULA`B*nOoJj36EB2CDp+M!_?5wh!MG%$7cbkXa)hAxO9XYN-&~80Z&5ScL1p$ zxV#UtofI%|u@5Vy>_6RPFuS;iL32(I+~J0MS236z+r+>k$PX7I$)*2({$e<#@51o^ zKcpQco1emP-Z_lnjFle)yNncxhC&TtWMX3AGdEZ#&TZEd4##8x;kiwV)tRvBCwNqH2mYXBId z4yAUW51A09NkK~O&;*$`WI}Y*p`=DXc(evsbKnA03sF!b3)awrS!VnCGK>lC7J^1% z#6o=FV(c=K45nwcGjJ-)GyMJfjp0AADpIT~WKe1;gR6lts8Tb*3`3_GSy&iUx@&>W zX$A&vbrl8{0X~K|n|HyLDYsQJ+;8Y(_i m$4{7XSZS(dCak7YQ4j!AiQ^h#o+5w%0000%bF zNpzAlqtI$26opX>Gig;6QLT~+X;DTCvxO}xqO2A{MM$WLT9mc$e@siKjfLS~{tY-f zndj=g%;+26({U)v2k)Nq&i(Fp&OPVOeRrM;(^p+x?F83Jx>!Gig@k}^x>OaKU>hU{ zx^X4?`8`-nCb($SXxf)!e}syUuy!ttLjymBA4#VoM7^OB!MUdyn}) zMx*V_tvjW3Nq15}vWb8>fHFmww)htGpQ@RzZ>eI^CT8mdv0kf2G;@KrBXt|DPAD+A(fqeO0NM44dnlYrxM;Ni~k=fl($}o z#Kv+YHdj!p;FA=O6&nMY9&qdslk2iG5mWL2A$jN7Gc~e>`VhT~fi;0^nG7$ug2s$2 zrc+~T>p;WaBhYM^az-}UoKmzzIQp%N@QiTF+hXGzVuNsIYNC<{GkNKh#<$*w3RENV5hN^tYVn9VToF^dwY zR;z1K`oiIk<>d6KOu%GUzCiwAWjWBrF2It>s8cr5#m*-2Et!li2fC2*yRG(_JSa-P zEFH=y9a;F-6$;GV&llUi*Fo*?$Ks>|S8`7?VoM*1xasd9KYWqVH*Gs&UlqgE(*xuE z1MHt39msssQp6Zi9DXx-MuZ|PKbJZ1PKZU%i&B_>^av+?rB(QH<`TBHzhj3t(J`$= z+|I|xn4PeUnBW;14&U@t^q0Sx?H`4v&%00000NkvXXu0mjf1?$Tk literal 0 HcmV?d00001 diff --git a/store/appealType/action.js b/store/appealType/action.js index eeb937e3..863c6e51 100644 --- a/store/appealType/action.js +++ b/store/appealType/action.js @@ -9,6 +9,7 @@ export const appealTypeDataActionTypes = { SETCASEREFERENCE: "SETCASEREFERENCE", SETFORMCOMPLETE: "SETFORMCOMPLETE", SETDOCUMENTLIST: "SETDOCUMENTLIST", + SETFILELIST: "SETFILELIST", }; export const getAppealTypeObj = () => (dispatch) => { @@ -70,3 +71,11 @@ export const setDocumentsList = (documentList) => (dispatch) => { documentList: documentList, }); }; + +export const setFilesForAppeal = (fileList) => (dispatch) => { + console.log("have got here"); + return dispatch({ + type: appealTypeDataActionTypes.SETFILELIST, + fileList: fileList, + }); +}; diff --git a/store/appealType/reducer.js b/store/appealType/reducer.js index 9b473d52..75333e6d 100644 --- a/store/appealType/reducer.js +++ b/store/appealType/reducer.js @@ -9,6 +9,7 @@ const appealTypeDataInitialState = { caseReference: {}, formComplete: "false", documentList: {}, + fileList: {}, }; export default function reducer(state = appealTypeDataInitialState, action) { @@ -53,6 +54,11 @@ export default function reducer(state = appealTypeDataInitialState, action) { ...state, documentList: action.documentList, }; + case appealTypeDataActionTypes.SETFILELIST: + return { + ...state, + fileList: action.fileList, + }; default: return state; } From be9f86b6b29bf6bfe39022b1de4e9f4eaac04c54 Mon Sep 17 00:00:00 2001 From: rdbsolutions Date: Tue, 12 Jul 2022 12:49:26 +0100 Subject: [PATCH 2/6] added hashing on api calls --- actions/azurestorage.js | 9 ++ actions/index.js | 32 ++++++- components/elements/index.js | 25 +++-- components/newappeal/buildcheckrow.js | 25 +++++ components/newappeal/buildchecksection.js | 109 +++++++++++++++++++--- components/newappeal/buildsection.js | 96 ++----------------- pages/api/file/deleteblob.js | 22 ++++- pages/api/file/downloadblob.js | 12 --- pages/api/file/getbloblist.js | 18 +++- pages/api/file/upload.js | 10 ++ pages/index.js | 26 ++---- pages/myportal/index.js | 7 -- store/appealType/action.js | 1 - 13 files changed, 234 insertions(+), 158 deletions(-) diff --git a/actions/azurestorage.js b/actions/azurestorage.js index a6efb105..bdcd8d78 100644 --- a/actions/azurestorage.js +++ b/actions/azurestorage.js @@ -85,6 +85,15 @@ export const getBlobs = async (containerName) => { "&blobname=" + blob.name.split("/")[1] ), + "hasheddeletepath": hashAPIPath( + "/api/file/deleteblob?container=" + + containerName.toLowerCase() + + "&blobname=" + + blob.name.split("/")[1] + ), + "hashgetblobs": hashAPIPath( + "/api/file/getbloblist?container=" + containerName.toLowerCase() + ), }); } diff --git a/actions/index.js b/actions/index.js index 4f40fb86..fccd1ea1 100644 --- a/actions/index.js +++ b/actions/index.js @@ -1024,7 +1024,7 @@ export const createAccount = (formValues) => { }); }; -export const uploadFiles = async (formValues, filesObj) => { +export const uploadFiles = async (formValues, filesObj, uploadhash) => { let files = filesObj; console.log(formValues); @@ -1065,10 +1065,14 @@ export const getFilesFromBlob = (containerName) => { .get( BASE_URL + "/api/file/getbloblist?container=" + - containerName.toLowerCase() + containerName.toLowerCase() + + hashAPIPath( + "/api/file/getbloblist?container=" + + containerName.toLowerCase() + ) ) .then((res) => { - console.log("//////////----", res.data); + //console.log("//////////----", res.data); return res.data; }) .catch((error) => { @@ -1076,14 +1080,32 @@ export const getFilesFromBlob = (containerName) => { }); }; -export const deleteBlob = async (containerName, blobName) => { +export const getFilesFromBlobHashed = (containerName, getblobshash) => { + return axios + .get( + BASE_URL + + "/api/file/getbloblist?container=" + + containerName.toLowerCase() + + getblobshash + ) + .then((res) => { + //console.log("//////////----", res.data); + return res.data; + }) + .catch((error) => { + console.log("this serror", error); + }); +}; + +export const deleteBlob = async (containerName, blobName, deleteblobhash) => { try { const res = await axios.get( BASE_URL + "/api/file/deleteblob?container=" + containerName.toLowerCase() + "&blobname=" + - blobName + blobName + + deleteblobhash ); return res.data; } catch (error) { diff --git a/components/elements/index.js b/components/elements/index.js index 238c2e20..78f62222 100644 --- a/components/elements/index.js +++ b/components/elements/index.js @@ -15,10 +15,9 @@ import Dropzone, { useDropzone } from "react-dropzone"; import _ from "lodash"; import { connect, useDispatch } from "react-redux"; import { - getFilesFromBlob, + getFilesFromBlobHashed, deleteBlob, downloadBlob, - hashAPIPath, } from "../../actions"; import { setFilesForAppeal } from "../../store/appealType/action"; import Link from "next/link"; @@ -1456,13 +1455,18 @@ const RenderFileUpload = (field) => { "$..[?(@.documentType=='" + field.documentTypeCode + "')]" ); - const deleteThisBlob = async (containerName, blobName) => { - console.log(containerName, blobName); - deleteBlob(containerName, blobName) + const deleteThisBlob = async ( + containerName, + blobName, + deleteblobhash, + getblobshash + ) => { + //console.log(containerName, blobName, deleteblobhash); + deleteBlob(containerName, blobName, deleteblobhash) .then((data) => data) .then(() => { - getFilesFromBlob(containerName).then((newfilelist) => - field.setFilesForAppeal(newfilelist) + getFilesFromBlobHashed(containerName, getblobshash).then( + (newfilelist) => field.setFilesForAppeal(newfilelist) ); }); }; @@ -1538,7 +1542,12 @@ const RenderFileUpload = (field) => { { - deleteThisBlob(field.ticketnumber, blob.name); + deleteThisBlob( + field.ticketnumber, + blob.name, + blob.hasheddeletepath, + blob.hashgetblobs + ); }} title="Remove this file" > diff --git a/components/newappeal/buildcheckrow.js b/components/newappeal/buildcheckrow.js index 09d0a6b5..41723f6d 100644 --- a/components/newappeal/buildcheckrow.js +++ b/components/newappeal/buildcheckrow.js @@ -95,6 +95,28 @@ let BuildCheckRow = (props) => { "')]" ); + // if (datafieldname[0].value.indexOf("fileUpload") > 0) { + // isRequiredField.push({ + // "LogicalName": datafieldname[0].value, + // "RequiredLevel": { + // "Value": "Recommended", + // "CanBeChanged": true, + // "ManagedPropertyLogicalName": + // "canmodifyrequirementlevelsettings", + // }, + // "MetadataId": + // "32a7e6fc-4283-eb11-aac0-00224800be9c", + // }); + // } + + // console.log( + // datafieldname[0].value.indexOf("fileUpload") > 0, + // isRequiredField, + // props.props.form["appealForm"].values[ + // datafieldname[0].value + // ] + // ); + if ( typeof props.props.form["appealForm"].values[ datafieldname[0].value @@ -147,6 +169,9 @@ let BuildCheckRow = (props) => { datafieldname[0].value ] ) + : fieldtype[0].value == + "{16D63FD6-119B-4353-BDCA-18358721C3FE}" + ? "ssss" : props.props.form["appealForm"] .values[ datafieldname[0].value diff --git a/components/newappeal/buildchecksection.js b/components/newappeal/buildchecksection.js index 1f373ef9..2d873e26 100644 --- a/components/newappeal/buildchecksection.js +++ b/components/newappeal/buildchecksection.js @@ -28,6 +28,7 @@ let BuildCheckSection = (props) => { handleSubmit, formTitle, setCurrentSection, + updateCurrentSection, mandatoryFieldsData, } = props; @@ -63,6 +64,54 @@ let BuildCheckSection = (props) => { alert(1); }; + function bytesToSize(bytes) { + var sizes = ["Bytes", "KB", "MB", "GB", "TB"]; + if (bytes == 0) return "0 Byte"; + var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); + return Math.round(bytes / Math.pow(1024, i), 2) + " " + sizes[i]; + } + + const getThumbnailIconByExtension = (blobName) => { + let fileType = blobName.slice(blobName.lastIndexOf(".") + 1); + + switch (fileType) { + case "html": + return "/assets/images/documenttypes/html.png"; + break; + case "txt": + return "/assets/images/documenttypes/txt.png"; + break; + case "doc": + return "/assets/images/documenttypes/doc.png"; + break; + case "pdf": + return "/assets/images/documenttypes/pdf.png"; + break; + case "docx": + return "/assets/images/documenttypes/docx.png"; + break; + case "csv": + return "/assets/images/documenttypes/csv.png"; + break; + case "xlsx": + return "/assets/images/documenttypes/xlsx.png"; + break; + + case "zip": + return "/assets/images/documenttypes/zip.png"; + break; + case "jpeg": + case "jpg": + return "/assets/images/documenttypes/jpg.png"; + case "png": + return "/assets/images/documenttypes/png.png"; + break; + default: + return "/assets/images/documenttypes/txt.png"; + break; + } + }; + return (
@@ -88,24 +137,58 @@ let BuildCheckSection = (props) => {
))} -
+
+
+
+ {props.props.appealType.fileList.files.length > + 0 + ? props.props.appealType.fileList.files.map( + (blob, i) => ( +
+ {blob.name} + + + {blob.name} ( + {bytesToSize( + blob.contentLength + )} + ) + +
+ ) + ) + : ""} +
+
+ { + updateCurrentSection(sectionCount); + }} + > + {t("newappeal:change-link-label")} + +
+
+
- setCurrentSection(9999)} > - sss{t("newappeal:submit-appeal-button")} + {t("newappeal:submit-appeal-button")}
@@ -128,6 +211,10 @@ const mapDispatchToProps = (dispatch) => { setCurrentSection: (currentSection) => { dispatch(setCurrentSection(currentSection)); }, + updateCurrentSection: (whichSection) => { + dispatch(setCurrentSection(whichSection)); + dispatch(setFormComplete("true")); + }, }; }; diff --git a/components/newappeal/buildsection.js b/components/newappeal/buildsection.js index e18850d1..42aa3b36 100644 --- a/components/newappeal/buildsection.js +++ b/components/newappeal/buildsection.js @@ -142,8 +142,14 @@ let BuildSection = (props) => { }; const onHandleSubmit = (values) => { - //alert(1); - //updateCaseProgress(values); + if (currentSection == sectionCount) { + let valuesObj = values || {}; + + delete valuesObj["_pinswg_appellant_value"]; + + updateCaseProgress(valuesObj); + } + setCurrentSection(currentSection + 1); }; @@ -289,93 +295,7 @@ let BuildSection = (props) => { currentSection={currentSection} initialValues={props.initialValues} /> - {/* - - {Object.keys(documentListObj).length > 0 && ( -
-

- {t("newappeal:new-appeal-documentlist-nav")}: -

-
-
    -
      -
    • -
        - {Object.entries( - documentListObj - ).map(([key, value]) => { - return ( -
      1. {value}
      2. - ); - })} -
      -
    • -
    -
-
- )} */}
@@ -286,7 +336,11 @@ let BuildSection = (props) => {
+ {/* + Last section is {lastIndex} - {currentSection} + */} { mandatoryFieldsData={mandatoryFieldsData} currentSection={currentSection} initialValues={props.initialValues} + setCurrentSection={setCurrentSection} />
@@ -349,6 +404,9 @@ const mapDispatchToProps = (dispatch) => { setFilesForAppeal: (blobList) => { dispatch(setFilesForAppeal(blobList)); }, + setNewAppealProgress: (progressObj) => { + dispatch(setNewAppealProgress(progressObj)); + }, }; }; diff --git a/components/utils/index.js b/components/utils/index.js index 32ac3c10..bb2004cb 100644 --- a/components/utils/index.js +++ b/components/utils/index.js @@ -6,6 +6,8 @@ import { getBasicPartSavedDetails, } from "../../actions"; +import xpath from "xpath"; + /* * Get the lookup collection by the appeal type entity * @param String formtype - string from appeal entity name @@ -164,3 +166,178 @@ export const getSearchDetailsPaged = (searchResultsObj) => { // origin: protocol + "//" + host, // }; // }; + +export const getProgressObj = ( + formObjXML, + titleList, + mandatoryFieldsData, + props +) => { + const parser = new DOMParser(); + const BuildFormObj = (formObjXML) => { + var doc = parser.parseFromString(formObjXML, "text/xml"); + + var tabs = xpath.select("//form/tabs/tab[*]", doc); + + const formObj = Object.keys(tabs).map((key, index) => { + var sectionXML = xpath.select( + "//tabs/tab[" + + (index + 1) + + "]//rows/row/*/control[not(@parentField)]/../..", + doc + ); + + return BuildRowObj(sectionXML, titleList[key].value); + }); + + const progressObj = Object.keys(formObj).map((key, index) => { + let fieldObj = formObj[key].fieldsrowObj; + + let rowCount = 0; + + !props.dirty && + typeof props.initialValues != "undefined" && + Object.keys(fieldObj).map((key, index) => { + fieldObj[key].datafieldname in props.initialValues && + props.initialValues[fieldObj[key].datafieldname] != + null && + rowCount++; + }); + props.dirty && + Object.keys(fieldObj).map((key, index) => { + fieldObj[key].datafieldname in + props.props.form["appealForm"].values && + props.props.form["appealForm"].values[ + fieldObj[key].datafieldname + ] != null && + rowCount++; + }); + + return { + "title": titleList[key].value, + rowCount, + "totalFields": formObj[key].fieldsTotal, + "allFieldsComplete": rowCount == formObj[key].fieldsTotal, + }; + }); + + return progressObj; + }; + + const BuildRowObj = (rowXML, titleList) => { + const rowObj = Object.keys(rowXML).map((key, index) => { + var doc = parser.parseFromString(rowXML[key].outerHTML, "text/xml"); + if (_.isEmpty(rowXML[key].innerHTML)) { + (""); + } else { + var rows = xpath.select("//label/@description", doc); + var fieldtype = xpath.select("//@classid", doc); + var validation = xpath.select("//@validation", doc); + var datafieldname = xpath.select("//@datafieldname", doc); + var datafieldcontent = xpath.select("//@datafieldcontent", doc); + var parentField = xpath.select("//@parentField", doc); + + var parentFieldShowOnValue = xpath.select( + "//@parentFieldShowOnValue", + doc + ); + + var requiredDocumentValue = xpath.select( + "//@requiredDocumentValue", + doc + ); + + var requiredDocumentLabel = xpath.select( + "//@requiredDocumentLabel", + doc + ); + + var hint = xpath.select("//label/@hint", doc); + var dateStart = xpath.select("//@dateStart", doc); + var dateEnd = xpath.select("//@dateEnd", doc); + + hint = !_.isEmpty(hint) && hint[0].value; + + validation = !_.isEmpty(validation) ? validation[0].value : []; + + parentField = !_.isEmpty(parentField) && parentField[0].value; + + parentFieldShowOnValue = + !_.isEmpty(parentFieldShowOnValue) && + parentFieldShowOnValue[0].value; + + datafieldcontent = + !_.isEmpty(datafieldcontent) && datafieldcontent[0].value; + + requiredDocumentValue = + !_.isEmpty(requiredDocumentValue) && + requiredDocumentValue[0].value; + + requiredDocumentLabel = + !_.isEmpty(requiredDocumentLabel) && + requiredDocumentLabel[0].value; + + dateStart = !_.isEmpty(dateStart) && dateStart[0].value; + + dateEnd = !_.isEmpty(dateEnd) && dateEnd[0].value; + + const isRequiredField = jsonpath.query( + mandatoryFieldsData, + "$..value[?(@.LogicalName=='" + + datafieldname[0].value + + "')]" + ); + + if (datafieldname[0].value == "pinswg_fileUpload") { + return { + "fieldType": fieldtype[0].value, + "label": rows[0].value, + "datafieldname": datafieldname[0].value, + "key": key, + "picklistData": props.props.formData.pickListData, + "mandatoryFieldsData": mandatoryFieldsData, + }; + } else { + if (typeof isRequiredField[0] != "undefined") { + if (isRequiredField[0].RequiredLevel.Value != "None") { + //console.log(datafieldname[0].value, validation); + return { + "fieldType": fieldtype[0].value, + "label": rows[0].value, + "datafieldname": datafieldname[0].value, + "datafieldcontent": datafieldcontent, + "validation": validation, + }; + } else { + return null; + } + } else { + return null; + } + } + } + }); + + rowObj = rowObj.filter(function (el) { + return el != null; + }); + + const filterUnwanted = (rowObj) => { + const required = rowObj.filter((el) => { + return !_.isEmpty(el.validation); + }); + return required; + }; + + return { + "title": titleList, + "validationFieldsTotal": filterUnwanted(rowObj).length, + "fieldsTotal": rowObj.length, + "fieldsrowObj": rowObj, + }; + }; + + const progObj = BuildFormObj(formObjXML); + //console.log(progObj); + return progObj; +}; diff --git a/store/appealType/action.js b/store/appealType/action.js index b93b21e5..1f9a6037 100644 --- a/store/appealType/action.js +++ b/store/appealType/action.js @@ -10,6 +10,7 @@ export const appealTypeDataActionTypes = { SETFORMCOMPLETE: "SETFORMCOMPLETE", SETDOCUMENTLIST: "SETDOCUMENTLIST", SETFILELIST: "SETFILELIST", + SETNEWAPPEALPROGRESS: "SETNEWAPPEALPROGRESS", }; export const getAppealTypeObj = () => (dispatch) => { @@ -78,3 +79,10 @@ export const setFilesForAppeal = (fileList) => (dispatch) => { fileList: fileList, }); }; + +export const setNewAppealProgress = (progress) => (dispatch) => { + return dispatch({ + type: appealTypeDataActionTypes.SETNEWAPPEALPROGRESS, + progress: progress, + }); +}; diff --git a/store/appealType/reducer.js b/store/appealType/reducer.js index 75333e6d..f6eca85b 100644 --- a/store/appealType/reducer.js +++ b/store/appealType/reducer.js @@ -10,6 +10,7 @@ const appealTypeDataInitialState = { formComplete: "false", documentList: {}, fileList: {}, + progress: [], }; export default function reducer(state = appealTypeDataInitialState, action) { @@ -59,6 +60,11 @@ export default function reducer(state = appealTypeDataInitialState, action) { ...state, fileList: action.fileList, }; + case appealTypeDataActionTypes.SETNEWAPPEALPROGRESS: + return { + ...state, + progress: action.progress, + }; default: return state; } diff --git a/styles/sass/welshgov/_application.scss b/styles/sass/welshgov/_application.scss index 902d81d4..bea42056 100644 --- a/styles/sass/welshgov/_application.scss +++ b/styles/sass/welshgov/_application.scss @@ -40,6 +40,7 @@ h2, h3, h4, h5, +.app-step-nav__circle-background, a { font-family: Arial, Helvetica, "Nimbus Sans L", sans-serif; } @@ -770,6 +771,82 @@ textarea, margin-bottom: 0px; } + +.at-nav { + .sectionCompleteTick { + display:inline-block; + width: 16px; + height:16px; + background: $darkgrey; + border-radius:50%; + -ms-transform: rotate(45deg); /* IE 9 */ + -webkit-transform: rotate(45deg); /* Chrome, Safari, Opera */ + transform: rotate(45deg); + margin-left: -20px; + margin-right: 5px; + } + + .sectionCompleteTick:before{ + content: ""; + position: absolute; + width: 3px; + height: 9px; + background-color: #fff; + left: 8px; + top: 3px; + } + + .sectionCompleteTick:after{ + content: ""; + position: absolute; + width: 3px; + height: 3px; + background-color: #fff; + left: 5px; + top: 9px; + } + + + +.app-step-nav__step { + position: relative; + padding-left: 45px; + list-style: none; +} +*, .govuk-body, .govuk-body-xs, .govuk-body-s, .govuk-body-m, .govuk-footer, .govuk-heading-xl, .govuk-heading-l, .govuk-heading-m, .govuk-heading-s, .govuk-header-l, .govuk-header-m, .govuk-header__link, .govuk-footer__link, .govuk-link, .govuk-button, .govuk-label, .govuk-select, .govuk-radios__item, .govuk-breadcrumbs__link, .govuk-fieldset__legend--s, .govuk-input, .govuk-textarea, .govuk-panel, .govuk-panel__title, .govuk-warning-text__text, .govuk-hint, .app-cookie-banner, .govuk-error-message.govuk-form-group--error, h1, h2, h3, h4, h5, a { + font-family: Arial, Helvetica, "Nimbus Sans L", sans-serif; +} +user agent stylesheet +li { + display: list-item; + text-align: -webkit-match-parent; +} +user agent stylesheet +ol { + list-style-type: decimal; +} +.app-step-nav__step--active:last-child:before, .app-step-nav__step--active .app-step-nav__circle--number, .app-step-nav__step--active:after, .app-step-nav__step--active .app-step-nav__help:after +{ + border-color: $lightgrey !important; +} + .app-step-nav__circle--number{ + border-color: $lightgrey !important; + } + .app-step-nav__circle-background{ + color:$lightgrey; + } + .sectionComplete{ + .app-step-nav__circle { + background-color: $lightgrey; + } + + .app-step-nav__circle-background{ + color:$white; + text-shadow: none; + } + } +} + .form-inline-label { .inlineFieldset { display: flex; @@ -864,6 +941,7 @@ textarea, } } + .progress-save{ padding: 10px 15px !important; font-size: 16px;