diff --git a/@types/user.d.ts b/@types/user.d.ts index 6762f5e..e09fc68 100644 --- a/@types/user.d.ts +++ b/@types/user.d.ts @@ -56,4 +56,5 @@ export type FirebaseContext = { setIPData : Dispatch>; resetStatisticsData: Dispatch>; resetData: ()=>void; + resetAllData: ()=>void; } \ No newline at end of file diff --git a/app/(landingpages)/about/page.tsx b/app/(landingpages)/about/page.tsx index 98a9e74..1d641c2 100644 --- a/app/(landingpages)/about/page.tsx +++ b/app/(landingpages)/about/page.tsx @@ -1,60 +1,82 @@ import logo from "@/public/Logo.svg" -import clientSubmission from "@/public/clientSubmission.png" +import clientSubmission from "@/public/mediumClientSide2.png"//"@/public/clientSubmission.png" +import serverSubmission from "@/public/mediumServerSide.png"//from "@/public/serverSubmission.png" +import medium from "@/public/medium.png" +import words from "@/public/words.png" +import portrait from "@/public/portrait2.jpg" export default function Page() { return ( <> -
-
-
+
+ +
-
-
+
+

Developer

Adam Gerhant


-
Hi, I'm Adam. I had the idea for Print Submit after starting a 3D printing service at my local library, +
Hi, I'm Adam. I had the idea for Print Submit after starting a 3D printing service at my local library, and realizing there were no good tools for creating an online submission form specifically for 3D print requests. This app helps simplify this process and includes many security, customizeablilty, and quality of life features.
- -
-

How a server side submission process enables maximum security

-

THe simplest way to upload files to a server is by allowing the database to be publicly writeable, + + +

+

Server side submission process for maximum security

+ +
+ +

The simplest way to upload files to a server is by allowing the database to be publicly writeable, and have the client send files as needed. However, this is not a safe method, since there is no way to enfore security. Any data can be uploaded at any time.

- -

With client side submissions, if a user can upload a file, then a malicious user can as well. This is why Print Submit + +

With a client side submission process, if a user can upload a file, then a malicious user can as well. This is why Print Submit uses a server side submission process. - The basic process beind this is to first verify the request, send a temporary upload link to the user, and then verify if the correct files were submitted.

+ +

The basic process beind this is to first verify the request, then send a temporary storage key to the user which can be used to upload files. + This allows for the database and storage and be locked, which provides security against attacks and malicious users. +

+
+

Read the full article for a more detailed explanation

+
+ + + +
- +
diff --git a/app/(landingpages)/pricing/page.tsx b/app/(landingpages)/pricing/page.tsx index 01c5713..f8a2e30 100644 --- a/app/(landingpages)/pricing/page.tsx +++ b/app/(landingpages)/pricing/page.tsx @@ -121,7 +121,7 @@ export default function Page() {
-
+

Premium

$5 @@ -206,7 +206,7 @@ export default function Page() {
-

Buy now

+

Temporarily Unavailable

diff --git a/app/(landingpages)/privacy/page.tsx b/app/(landingpages)/privacy/page.tsx index 5a1f209..fcc68f7 100644 --- a/app/(landingpages)/privacy/page.tsx +++ b/app/(landingpages)/privacy/page.tsx @@ -40,7 +40,7 @@ export default function Page() { Registration information: We store information you provide us on this site when creating an account or filling out a form. This information commonly includes name and email address.
- Billing information: Our integrations with third party payment gateways are for processing only. We don't store or log any sensitive cardholder data provided by you or your form users. + Billing information: Our integrations with third party payment gateways are for processing only. We don't store or log any sensitive cardholder data provided by you or your form users.
Submission and Form data: When using the form editor, your changes are saved to a third party database provider. We currently use the Firebase Firestore database. Submissions are stored in a similar manner, but also include Google Cloud Storage for file storage. diff --git a/app/(main)/(dashboard)/Header.tsx b/app/(main)/(dashboard)/Header.tsx index 90ba4b3..5b1f2d2 100644 --- a/app/(main)/(dashboard)/Header.tsx +++ b/app/(main)/(dashboard)/Header.tsx @@ -11,7 +11,7 @@ import { useRouter } from 'next/navigation'; import { getAuth, deleteUser } from "firebase/auth"; const Header = () => { - const {resetData, accountInformation} = useFirebaseContext() as FirebaseContext; + const {resetAllData, accountInformation} = useFirebaseContext() as FirebaseContext; const {currentUser} = useAuthContext() as UserContext; const [confirmDeleteAccount, setConfirmDeleteAccount] = useState(false); @@ -21,9 +21,7 @@ const Header = () => { const handleClickOutside = (event:any) => { let clickedOutside = true - console.log("path: ") event.composedPath().map((element: { className: any; })=>{ - console.log(element.className); if(element.className=="profileWrapper"){ clickedOutside = false; } @@ -47,7 +45,7 @@ const Header = () => { const router = useRouter(); const signOutFunction = () =>{ signOut(auth).then(()=>{ - resetData(); + resetAllData(); router.push("/register?redirect=false") }) } @@ -81,7 +79,7 @@ const Header = () => { Profile
- {isOpen && } + {isOpen && }
{confirmDeleteAccount&& diff --git a/app/(main)/(dashboard)/Profile.tsx b/app/(main)/(dashboard)/Profile.tsx index b8a36ac..50f5446 100644 --- a/app/(main)/(dashboard)/Profile.tsx +++ b/app/(main)/(dashboard)/Profile.tsx @@ -34,7 +34,7 @@ const Profile = ({resetData, accountInformation, setConfirmDeleteAccount}:any, } {!accountInformation&& <> -

Getting account type:

+

Getting account type

} diff --git a/app/(main)/(dashboard)/emails/page.tsx b/app/(main)/(dashboard)/emails/page.tsx index 5fe23f0..0088eba 100644 --- a/app/(main)/(dashboard)/emails/page.tsx +++ b/app/(main)/(dashboard)/emails/page.tsx @@ -5,7 +5,7 @@ import { FirebaseContext } from "@/@types/user"; import Emails from "./Emails" import { getAuth } from "firebase/auth"; const EmailPage = () =>{ - const {emailData, setEmailData, submissionFormData} = useFirebaseContext() as FirebaseContext; + const {emailData, setEmailData, submissionFormData, accountInformation} = useFirebaseContext() as FirebaseContext; const [loading, setLoading] = useState(false); if(emailData&&submissionFormData){ const auth = getAuth(); @@ -88,6 +88,10 @@ const EmailPage = () =>{

This is the account emails will be sent from. This account can be any Google account

+ {accountInformation&&accountInformation.accountType=="Guest"&& +
+ Note: Emails cannot be sent from a guest account +
}
diff --git a/app/(main)/(dashboard)/layout.tsx b/app/(main)/(dashboard)/layout.tsx index 36003a5..bc450f1 100644 --- a/app/(main)/(dashboard)/layout.tsx +++ b/app/(main)/(dashboard)/layout.tsx @@ -41,7 +41,7 @@ const AppLayout = ({children}: {children: React.ReactNode})=>{ const emailCountRef = doc(db, "users", user.uid, "data", "emailCount") if(!submissionData){ - console.log("getting submission data") + //console.log("getting submission data") getDoc(submissionDataRef).then((docSnap)=>{ if (docSnap.exists()) { @@ -56,7 +56,7 @@ const AppLayout = ({children}: {children: React.ReactNode})=>{ }).catch((err)=>{console.log("error getting doc" + err)}) } if(!submissionFormData){ - console.log("getting submission form data") + //console.log("getting submission form data") getDoc(submissionFormRef).then((docSnap)=>{ if (docSnap.exists()) { setSubmissionFormData(docSnap.data()) @@ -85,7 +85,7 @@ const AppLayout = ({children}: {children: React.ReactNode})=>{ }) } if(!cancelRequests){ - console.log("getting cancel requests") + //console.log("getting cancel requests") getDocs(cancelRequestsRef).then((querySnapshot) => { let cancelRequestsObj : CancelRequests = {}; @@ -100,7 +100,7 @@ const AppLayout = ({children}: {children: React.ReactNode})=>{ }); } if(!settingsData){ - console.log("getting settings data") + //console.log("getting settings data") getDoc(settingsRef).then((docSnap)=>{ if (docSnap.exists()) { @@ -121,7 +121,7 @@ const AppLayout = ({children}: {children: React.ReactNode})=>{ }) } if(!emailData){ - console.log("getting email data") + //console.log("getting email data") getDoc(emailsRef).then((docSnap)=>{ if (docSnap.exists()) { setEmailData(docSnap.data()) @@ -147,7 +147,7 @@ const AppLayout = ({children}: {children: React.ReactNode})=>{ }) } if(!IPData){ - console.log("getting IP data") + //console.log("getting IP data") getDoc(IPRef).then(docSnap=>{ if (docSnap.exists()) { setIPData(docSnap.data()) @@ -159,12 +159,10 @@ const AppLayout = ({children}: {children: React.ReactNode})=>{ }) } if(!accountInformation){ - console.log("getting account information") + //console.log("getting account information") getDoc(accountInformationRef).then(docSnap=>{ if (docSnap.exists()) { setAccountInformation(docSnap.data()) - console.log("exists") - console.log(docSnap.data()) setAccountType(docSnap.data().accountType) } else{ @@ -181,16 +179,30 @@ const AppLayout = ({children}: {children: React.ReactNode})=>{ body: JSON.stringify({ "token": idToken, }) - }).then((response)=>{ - console.log("response status: ") - console.log(response.status) - setAccountInformation({ - storageUsed:0, - totalStorage:5, - dailyMax:20, - accountType:"Free" - }) - setAccountType("Free") + }).then(response=>response.json().then(data => ({ status: response.status, data: data }))) + .then(async (response)=>{ + //console.log("response status: ") + //console.log(response.status) + //console.log("response data: ") + //console.log(response.data) + if(response.data.accountType=="Free"){ + setAccountInformation({ + storageUsed:0, + totalStorage:5, + dailyMax:20, + accountType:"Free" + }) + setAccountType("Free") + } + else if(response.data.accountType=="Guest"){ + setAccountInformation({ + storageUsed:0, + totalStorage:5, + dailyMax:20, + accountType:"Guest" + }) + setAccountType("Guest") + } }).catch((error)=>{ console.log("error: ") console.log(error) @@ -201,7 +213,7 @@ const AppLayout = ({children}: {children: React.ReactNode})=>{ }) } if(!emailCount){ - console.log("getting email counts") + //console.log("getting email counts") getDoc(emailCountRef).then(docSnap=>{ if (docSnap.exists()) { setEmailCount(docSnap.data()) @@ -221,8 +233,8 @@ const AppLayout = ({children}: {children: React.ReactNode})=>{ "token": idToken, }) }).then((response)=>{ - console.log("response status: ") - console.log(response.status) + //console.log("response status: ") + //console.log(response.status) setEmailCount({ dailyTotal:0, total:0, @@ -258,11 +270,22 @@ const AppLayout = ({children}: {children: React.ReactNode})=>{ setEmailCount(null) setCancelRequests(null) } + const resetAllData = () =>{ + setSubmissionData + setSubmissionFormData + setCancelRequests + setSettingsData + setEmailData + setIPData + setAccountInformation + setAccountType + setEmailCount + } if(currentUser.email){ console.log("rendering dashboard") if(!submissionData||!submissionFormData||!settingsData||!emailData||!IPData||!accountInformation||!emailCount){ return( - +
@@ -276,7 +299,7 @@ const AppLayout = ({children}: {children: React.ReactNode})=>{ } else{ return( - +
diff --git a/app/(main)/(dashboard)/statistics/page.jsx b/app/(main)/(dashboard)/statistics/page.jsx index e0b912e..f248103 100644 --- a/app/(main)/(dashboard)/statistics/page.jsx +++ b/app/(main)/(dashboard)/statistics/page.jsx @@ -199,7 +199,11 @@ const Statistics = () =>{ const bytesStored = niceBytes(storageUsed) const totalStorageBytes = niceBytes(totalStorage) - const percentUsed = (storageUsed/(totalStorage) * 100).toFixed(3)+"%" + + let percentUsed = (storageUsed/(totalStorage) * 100).toFixed(3)+"%" + if(totalStorage==0){ + percentUsed = "100%" + } console.log(percentUsed) return(
diff --git a/app/(main)/(dashboard)/submissionForm/EditForm.jsx b/app/(main)/(dashboard)/submissionForm/EditForm.jsx index 2245939..587e722 100644 --- a/app/(main)/(dashboard)/submissionForm/EditForm.jsx +++ b/app/(main)/(dashboard)/submissionForm/EditForm.jsx @@ -61,7 +61,7 @@ const Questions = ({accountInformation, deleteQuestion, displayArray, onChange, const optionCards = questionObject.options.map((option, index)=>{ return( - + {option} deleteOption(questionObject.questionID, index)}/> diff --git a/app/(main)/(dashboard)/submissionForm/SubmissionForm.jsx b/app/(main)/(dashboard)/submissionForm/SubmissionForm.jsx index 995481a..4aaba5d 100644 --- a/app/(main)/(dashboard)/submissionForm/SubmissionForm.jsx +++ b/app/(main)/(dashboard)/submissionForm/SubmissionForm.jsx @@ -33,8 +33,8 @@ const Questions = ({displayArray, handleInputDataChange, highlightQuestions, acc if(questionObject.display){ - let allOptions = questionObject.options.map((option)=>{ - return() + let allOptions = questionObject.options.map((option, index)=>{ + return() }) return( @@ -61,6 +61,21 @@ const Questions = ({displayArray, handleInputDataChange, highlightQuestions, acc } }) } +async function uploadFile(url, data, contentType, contentLength) { + const response = await fetch(url, { + method: 'PUT', + headers: { + 'Content-type': contentType, + 'X-Upload-Content-Length': contentLength, + }, + body: data, + }); + + if (!response.ok) { + throw new Error(`Error uploading: ${response.statusText}`); + } + } + const SubmitButton = ({setUploadStatus, file, setFile, id, inputData, setSubmitted, setHighlightIDs, submissionFormData, setCancelID, imageURL, dimensions, setErrorUploading, recaptchaToken, accountInformation}) =>{ const [submitButton, setSubmitButton]= useState("submit") const [fileID, setFileID] = useState() @@ -81,10 +96,14 @@ const SubmitButton = ({setUploadStatus, file, setFile, id, inputData, setSubmitt if(fileData.transferred=="complete"){ setSubmitted(true) setCancelID(fileData.cancelID) + setUploadStatus(); + } else if(fileData.transferred=="error"){ setErrorUploading(fileData.errorMessage) setSubmitButton("submit") + setUploadStatus(); + } } }); @@ -192,7 +211,7 @@ const SubmitButton = ({setUploadStatus, file, setFile, id, inputData, setSubmitt "recaptchaToken" : recaptchaToken }) }).then(response=>response.json().then(data => ({ status: response.status, data: data }))) - .then((response)=>{ + .then(async (response)=>{ if(response.status==200){ setFileID(response.data.fileID) setUploadStatus("Uploading files") @@ -200,66 +219,40 @@ const SubmitButton = ({setUploadStatus, file, setFile, id, inputData, setSubmitt const uploadURL = response.data.uploadURL const thumbnailURL = response.data.thumbnailURL const inputDataURL = response.data.inputDataURL - fetch(uploadURL, { - method: 'PUT', - headers: { - 'Content-type': "application/octet-stream", - 'X-Upload-Content-Length': file.size - }, - body: file, - }).then(()=>{ - console.log("uploaded file") - fetch(thumbnailURL, { - method: 'PUT', - headers: { - 'Content-type': "image/png", - 'X-Upload-Content-Length': imageBuffer.length - }, - body: imageBuffer, - }).then(()=>{ - console.log("uploaded thumbnail") - fetch(inputDataURL, { - method: 'PUT', - headers: { - 'Content-type': "application/json", - 'X-Upload-Content-Length': inputDataFile.size - }, - body: inputDataFile, - }).then(()=>{ - console.log("uploaded inputData") - setUploadStatus("Processing submission") - - }).catch((error)=>{ - console.log("error uploading inputData") - setErrorUploading(true) - setSubmitButton("submit") - }); - }).catch(()=>{ - console.log("error uploading thumbnail") - setErrorUploading() - setSubmitButton("submit") - }); + try { + await uploadFile(uploadURL, file, 'application/octet-stream', file.size); + console.log("uploaded file"); + + await uploadFile(thumbnailURL, imageBuffer, 'image/png', imageBuffer.length); + console.log("uploaded thumbnail"); - }).catch(()=>{ - console.log("error uploading file") - setErrorUploading("Error uploading") - setSubmitButton("submit") - }); + await uploadFile(inputDataURL, inputDataFile, 'application/json', inputDataFile.size); + console.log("uploaded inputData"); + + setUploadStatus("Processing submission"); + } catch (error) { + console.log("error1: "+error); + setUploadStatus(); + setErrorUploading("Error uploading"); + } } else if(response.status==403){ console.log("setting file too large") setErrorUploading(response.data.message) setSubmitButton("submit") + setUploadStatus(); } else { setErrorUploading("Error uploading") setSubmitButton("submit") + setUploadStatus(); } }).catch((error)=>{ console.log("error: ") console.log(error) setErrorUploading("Error uploading") setSubmitButton("submit") + setUploadStatus(); }); @@ -283,6 +276,7 @@ const SubmitButton = ({setUploadStatus, file, setFile, id, inputData, setSubmitt } + const IncompleteForm = ({display})=>{ if(display){ return( @@ -342,6 +336,7 @@ const SubmissionForm = ({id, submissionFormData, accountInformation}) => { if(accountInformation.accountType!="Premium"){ submissionFormData.captchaEnabled=false; } + useEffect(()=>{ if(uploadStatus){ setTimeout(()=>{ @@ -354,7 +349,7 @@ const SubmissionForm = ({id, submissionFormData, accountInformation}) => { },500) } },[dots, uploadStatus]) - + const tempInputData = {} submissionFormData.questions.map(question=>{ if(question.type=="dropdown"&&question.display&&question.options.length>0){ diff --git a/app/styles.scss b/app/styles.scss index 82c2ff5..f7b5187 100644 --- a/app/styles.scss +++ b/app/styles.scss @@ -2152,7 +2152,12 @@ html, body, #root,.wrapper { } } } + .guestNotice{ + width: 100%; + text-align: center; + } } + } .loader { border: 2px solid #e7e7e7; diff --git a/functions/index.js b/functions/index.js index 9a88f82..4a10ee5 100644 --- a/functions/index.js +++ b/functions/index.js @@ -30,12 +30,12 @@ app.post('/initializeAccountFile', (request, response)=>{ .then(async (decodedToken) => { const uid = decodedToken.uid; const db = admin.firestore(); + console.log("provider: "+decodedToken.firebase.sign_in_provider) const accountInformationDocRef = db.doc("users/"+uid+"/data/accountInformation") try { await db.runTransaction(async (transaction) => { const accountInformationDocSnap = await transaction.get(accountInformationDocRef); - console.log("accountInformationDocSnap exists: "+accountInformationDocSnap.exists) if (!accountInformationDocSnap.exists) { console.log("setting data") @@ -57,9 +57,13 @@ app.post('/initializeAccountFile', (request, response)=>{ } transaction.set(db.doc("users/"+uid),{created:"true"}) - } - response.status(200).send() + if(decodedToken.firebase.sign_in_provider!="anonymous"){ + response.status(200).send({accountType:"Free"}) + } + else{ + response.status(200).send({accountType:"Guest"}) + } }) } catch (err) { console.log("first error") @@ -491,8 +495,8 @@ app.post('/uploadURL', async (request, response)=>{ if(request.body.password){ return response.status(200).send(); } - const db = admin.firestore() const clientIP = request.headers['x-forwarded-for'] || request.socket.remoteAddress || request.headers['fastly-client-ip'] || "none"; + const db = admin.firestore() const IPDocRef = db.doc("users/"+request.body.id+"/data/submissionData/ipData/ipData") const submissionFormDocRef = db.doc("users/"+request.body.id+"/data/submissionForm") const accountInformationDocRef = db.doc("users/"+request.body.id+"/data/accountInformation") @@ -507,6 +511,11 @@ app.post('/uploadURL', async (request, response)=>{ const recaptchaToken = request.body.recaptchaToken; console.log(recaptchaToken) + if(submissionFormData.captchaEnabled&&!recaptchaToken){ + return response.status(400).send({ + message: 'Error uploading\nno reCAPTCHA token.' + }); + } if(recaptchaToken&&accountInformation.accountType=="Premium"){ const secretKey = '6LfYj58nAAAAANOQsutWLUN7d_dXhUHgznMoLRJJ'; // Replace with your actual reCAPTCHA secret key const verificationUrl = `https://www.google.com/recaptcha/api/siteverify?secret=${secretKey}&response=${recaptchaToken}`; @@ -518,7 +527,8 @@ app.post('/uploadURL', async (request, response)=>{ if(!verificationData.success){ console.log("recaptcha verifation failed") return response.status(400).send({ - message: 'Error uploading\nreCAPTCHA verification failed.'}); + message: 'Error uploading\nreCAPTCHA verification failed.' + }); } } else if(recaptchaToken){ @@ -696,160 +706,159 @@ exports.formSubmit = onObjectFinalized({cpu: 2}, async (event) => { if (pathSegment.length >= 3) { const fileID = pathSegment[2]; const userID = pathSegment[1]; - db = admin.firestore() - const fileIDRef = db.doc("users/"+userID+"/data/submissionData/submittedFileIDs/"+fileID) - try { - await db.runTransaction(async (transaction) => { - const fileIDDocSnap = await transaction.get(fileIDRef) - if(fileIDDocSnap.exists){ - console.log("transaction field value: "+fileIDDocSnap.data().filesInFolder) - transaction.update(fileIDRef, { - filesInFolder: admin.firestore.FieldValue.increment(1) - }) - }else{ - console.log("setting files in folder") - transaction.set(fileIDRef, { - filesInFolder: 1 - }) - } - }) + db = admin.firestore() + const fileIDRef = db.doc("users/"+userID+"/data/submissionData/submittedFileIDs/"+fileID) + try { + const filesInFolder = await db.runTransaction(async (transaction) => { + const fileIDDocSnap = await transaction.get(fileIDRef) + if(fileIDDocSnap.exists){ + const currentFiles = fileIDDocSnap.data().filesInFolder + console.log("transaction value: "+currentFiles) + + transaction.update(fileIDRef, { + filesInFolder: currentFiles+1 + }) + return currentFiles+1; + }else{ + console.log("setting files in folder") + transaction.set(fileIDRef, { + filesInFolder: 1 + }) + return 1; + } + }) - fileIDRef.get().then(async fileIDDocSnap=>{ - const filesInFolder = fileIDDocSnap.data().filesInFolder - console.log("files in folder: "+filesInFolder); - if(filesInFolder==4){ - - const fileIDPath = pathSegment.slice(0, 3).join('/'); - const storage = new Storage({ - projectId: "print-submit", - keyFilename:"serviceAccountKey.json" - }); - const bucketName = 'print-submit.appspot.com'; - // List files in the specified folder - const [files] = await storage.bucket(bucketName).getFiles({ prefix: fileIDPath }); - const ipFileName = fileIDPath+"/ip.json" - const inputDataFileName = fileIDPath+"/inputData.json" - const bucket = storage.bucket(bucketName); - const ipFile = bucket.file(ipFileName); - const inputDataFile = bucket.file(inputDataFileName); - - const ipFileFileContent = await ipFile.download(); - const inputDataFileContent = await inputDataFile.download(); - console.log("file content:"+ipFileFileContent) - const ipData = JSON.parse(ipFileFileContent.toString()); - let inputData = JSON.parse(inputDataFileContent.toString()); - - const fileName = fileIDPath+"/"+inputData.fileName; - const thumbnailName = fileIDPath+"/thumbnail.png"; - - const [fileMetadata] = await bucket.file(fileName).getMetadata(); - inputData.modelSize = parseInt(fileMetadata.size, 10); - - const [thumbnailMetadata] = await bucket.file(thumbnailName).getMetadata(); - inputData.thumbnailSize = thumbnailMetadata.size - - inputData.fileDeleted = false; - - let totalSize = 0; - - for (const file of files) { - const [metadata] = await file.getMetadata(); - totalSize += parseInt(metadata.size, 10); + console.log("files in folder: "+filesInFolder); + if(filesInFolder==4){ + + const fileIDPath = pathSegment.slice(0, 3).join('/'); + const storage = new Storage({ + projectId: "print-submit", + keyFilename:"serviceAccountKey.json" + }); + const bucketName = 'print-submit.appspot.com'; + // List files in the specified folder + const [files] = await storage.bucket(bucketName).getFiles({ prefix: fileIDPath }); + const ipFileName = fileIDPath+"/ip.json" + const inputDataFileName = fileIDPath+"/inputData.json" + const bucket = storage.bucket(bucketName); + const ipFile = bucket.file(ipFileName); + const inputDataFile = bucket.file(inputDataFileName); + + const ipFileFileContent = await ipFile.download(); + const inputDataFileContent = await inputDataFile.download(); + console.log("file content:"+ipFileFileContent) + const ipData = JSON.parse(ipFileFileContent.toString()); + let inputData = JSON.parse(inputDataFileContent.toString()); + + const fileName = fileIDPath+"/"+inputData.fileName; + const thumbnailName = fileIDPath+"/thumbnail.png"; + + const [fileMetadata] = await bucket.file(fileName).getMetadata(); + inputData.modelSize = parseInt(fileMetadata.size, 10); + + const [thumbnailMetadata] = await bucket.file(thumbnailName).getMetadata(); + inputData.thumbnailSize = thumbnailMetadata.size + + inputData.fileDeleted = false; + + let totalSize = 0; + + for (const file of files) { + const [metadata] = await file.getMetadata(); + totalSize += parseInt(metadata.size, 10); + } + inputData.fileSize = totalSize; + + const newDate = new Date(inputData.date) + + inputData.fileID = fileID; + inputData.date = newDate.toUTCString() + inputData.ip = ipData.ip + + db = admin.firestore(); + + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let cancelID = ''; + for (let i = 0; i < 20; i++) { + cancelID += chars.charAt(Math.floor(Math.random() * chars.length)); + } + + const fileIDRef = db.doc("users/"+userID+"/data/submissionData/submittedFileIDs/"+fileID) + const cancelRequestsRef = db.doc("users/"+userID+"/data/submissionData/cancelRequests/"+cancelID) + const submissionDataRef = db.doc("users/"+userID+"/data/submissionData") + const ipRef = db.doc("users/"+userID+"/data/submissionData/ipData/ipData") + const accountInformationDocRef = db.doc("users/"+userID+"/data/accountInformation") + //const folderRef = db.doc(`users/${pathSegment[1]}/folderCounts/${pathSegment[2]}`); + + console.log("running transaction") + try { + await db.runTransaction(async (transaction) => { + // const fileIDSnap = await transaction.get(fileIDRef); + //if (!fileIDSnap.exists) { + //const ipSnap = await transaction.get(ipRef) + + transaction.set(cancelRequestsRef, { + cancelled: false, + reason: "", + date: newDate.getTime(), + fileID: fileID, + fileName: inputData.fileName, + }); + + inputData.cancelID = cancelID; + console.log("inputData sent: "); + console.log(inputData); + + transaction.update(submissionDataRef, { + data: admin.firestore.FieldValue.arrayUnion({ inputData }), + }); + console.log("setting file id ref to complete") + transaction.set(fileIDRef, { + transferred: "complete", + cancelID: cancelID + }) + transaction.update(accountInformationDocRef,{ + storageUsed: admin.firestore.FieldValue.increment(inputData.fileSize) + }) + //await transaction.set(folderRef,{fileCount:4}) + transaction.set(ipRef,{ + [ipData.ip.replace(/\./g, '%2E')]:{blocked:false} + }, { merge: true }) + //} + }); + let name="" + if(ipData.hasOwnProperty("nameID")){ + const nameID = ipData.nameID; + if(inputData.hasOwnProperty(nameID)){ + name=inputData[nameID] } - inputData.fileSize = totalSize; - - const newDate = new Date(inputData.date) - - inputData.fileID = fileID; - inputData.date = newDate.toUTCString() - inputData.ip = ipData.ip - - db = admin.firestore(); - - const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - let cancelID = ''; - for (let i = 0; i < 20; i++) { - cancelID += chars.charAt(Math.floor(Math.random() * chars.length)); + } + if(ipData.hasOwnProperty("emailID")){ + const emailID = ipData.emailID; + if(inputData.hasOwnProperty(emailID)){ + const emailInput = inputData[emailID] + console.log("emailInput: "+emailInput) + console.log("name: "+name) + + sendEmail(userID, "submitted", emailInput, name, "", cancelID); } - - const fileIDRef = db.doc("users/"+userID+"/data/submissionData/submittedFileIDs/"+fileID) - const cancelRequestsRef = db.doc("users/"+userID+"/data/submissionData/cancelRequests/"+cancelID) - const submissionDataRef = db.doc("users/"+userID+"/data/submissionData") - const ipRef = db.doc("users/"+userID+"/data/submissionData/ipData/ipData") - const accountInformationDocRef = db.doc("users/"+userID+"/data/accountInformation") - //const folderRef = db.doc(`users/${pathSegment[1]}/folderCounts/${pathSegment[2]}`); - - console.log("running transaction") - try { - await db.runTransaction(async (transaction) => { - // const fileIDSnap = await transaction.get(fileIDRef); - //if (!fileIDSnap.exists) { - //const ipSnap = await transaction.get(ipRef) - - transaction.set(cancelRequestsRef, { - cancelled: false, - reason: "", - date: newDate.getTime(), - fileID: fileID, - fileName: inputData.fileName, - }); - - inputData.cancelID = cancelID; - console.log("inputData sent: "); - console.log(inputData); - - transaction.update(submissionDataRef, { - data: admin.firestore.FieldValue.arrayUnion({ inputData }), - }); - console.log("setting file id ref to complete") - transaction.set(fileIDRef, { - transferred: "complete", - cancelID: cancelID - }) - transaction.update(accountInformationDocRef,{ - storageUsed: admin.firestore.FieldValue.increment(inputData.fileSize) - }) - //await transaction.set(folderRef,{fileCount:4}) - transaction.set(ipRef,{ - [ipData.ip.replace(/\./g, '%2E')]:{blocked:false} - }, { merge: true }) - //} - }); - let name="" - if(ipData.hasOwnProperty("nameID")){ - const nameID = ipData.nameID; - if(inputData.hasOwnProperty(nameID)){ - name=inputData[nameID] - } - } - if(ipData.hasOwnProperty("emailID")){ - const emailID = ipData.emailID; - if(inputData.hasOwnProperty(emailID)){ - const emailInput = inputData[emailID] - console.log("emailInput: "+emailInput) - console.log("name: "+name) - - sendEmail(userID, "submitted", emailInput, name, "", cancelID); - } - } - } catch (err) { - console.error("Transaction failed:", err); - try { - await fileIDRef.set({ - transferred: "error", - errorMessage: err.toString(), - }); - } catch (setErr) { - console.error("Failed to set fileIDRef:", setErr); - } - } } - }).catch(()=>{ - console.log("error getting files in folder") - }) - } catch(err) { - console.error("files in folder transaction error: ", err); + } catch (err) { + console.error("Transaction failed:", err); + try { + await fileIDRef.set({ + transferred: "error", + errorMessage: err.toString(), + }); + } catch (setErr) { + console.error("Failed to set fileIDRef:", setErr); + } + } } + } catch(err) { + console.error("files in folder transaction error: ", err); + } } }); /* diff --git a/public/functions.PNG b/public/functions.PNG new file mode 100644 index 0000000..f547f0b Binary files /dev/null and b/public/functions.PNG differ diff --git a/public/medium.PNG b/public/medium.PNG new file mode 100644 index 0000000..2ff5b06 Binary files /dev/null and b/public/medium.PNG differ diff --git a/public/mediumClientSide2.PNG b/public/mediumClientSide2.PNG new file mode 100644 index 0000000..09f399b Binary files /dev/null and b/public/mediumClientSide2.PNG differ diff --git a/public/mediumClientside.png b/public/mediumClientside.png new file mode 100644 index 0000000..a8b597e Binary files /dev/null and b/public/mediumClientside.png differ diff --git a/public/mediumServerside.png b/public/mediumServerside.png new file mode 100644 index 0000000..182e2ab Binary files /dev/null and b/public/mediumServerside.png differ diff --git a/public/portrait.JPG b/public/portrait.JPG new file mode 100644 index 0000000..39aedbf Binary files /dev/null and b/public/portrait.JPG differ diff --git a/public/portrait2.JPG b/public/portrait2.JPG new file mode 100644 index 0000000..49039a1 Binary files /dev/null and b/public/portrait2.JPG differ diff --git a/public/serverSubmission.PNG b/public/serverSubmission.PNG new file mode 100644 index 0000000..59a14b9 Binary files /dev/null and b/public/serverSubmission.PNG differ diff --git a/public/transaction.PNG b/public/transaction.PNG new file mode 100644 index 0000000..469eee2 Binary files /dev/null and b/public/transaction.PNG differ diff --git a/public/words.PNG b/public/words.PNG new file mode 100644 index 0000000..4ad751d Binary files /dev/null and b/public/words.PNG differ