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

-
- - A enforcement 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 00000000..0b5bdc62 Binary files /dev/null and b/public/assets/images/documenttypes/jpg.png differ diff --git a/public/assets/images/documenttypes/png.png b/public/assets/images/documenttypes/png.png new file mode 100644 index 00000000..18527c55 Binary files /dev/null and b/public/assets/images/documenttypes/png.png differ 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; }