From 4601cf30111c6b18ce9cbb7bc524a40af9f0e19f Mon Sep 17 00:00:00 2001 From: Stephen Stefanatos Date: Mon, 11 Mar 2024 03:21:22 -0400 Subject: [PATCH 1/7] Editing Challenge files --- src/components/StandardNav.jsx | 1 + src/components/dashboard/QuickSetttings.jsx | 4 +- .../groups/assignments/create-challenge.jsx | 3 + .../assignments/updateChallengeInfo.jsx | 149 +++++++++++++--- src/components/studio/forking/editor.jsx | 159 +++++++++++++++--- src/pages/challenges/[id].jsx | 26 +-- src/utils/file-api.js | 90 +++++++++- 7 files changed, 363 insertions(+), 69 deletions(-) diff --git a/src/components/StandardNav.jsx b/src/components/StandardNav.jsx index 4ee520a0..42c9a9b3 100644 --- a/src/components/StandardNav.jsx +++ b/src/components/StandardNav.jsx @@ -112,6 +112,7 @@ export function StandardNav() { const result = await request(endPoint, 'GET', null); if (!result || !result.length) return; + console.log("Here is the result"); console.log(result); setNotificationData( diff --git a/src/components/dashboard/QuickSetttings.jsx b/src/components/dashboard/QuickSetttings.jsx index 869aec8b..aef752f3 100644 --- a/src/components/dashboard/QuickSetttings.jsx +++ b/src/components/dashboard/QuickSetttings.jsx @@ -15,7 +15,9 @@ export function QuickSettings() { try { request(`${process.env.NEXT_PUBLIC_API_URL}/account`, "GET", null) .then((data) => { - document.getElementById('bio').value = data.bio; + if(document.getElementById('bio')) { + document.getElementById('bio').value = data.bio; + } }) .catch((err) => { console.log(err); diff --git a/src/components/groups/assignments/create-challenge.jsx b/src/components/groups/assignments/create-challenge.jsx index e7851986..5879ad6b 100644 --- a/src/components/groups/assignments/create-challenge.jsx +++ b/src/components/groups/assignments/create-challenge.jsx @@ -7,6 +7,7 @@ import { MarkdownViewer } from '@/components/MarkdownViewer'; import { ToastContainer, toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; import fileApi from '@/utils/file-api'; +import { getAuth } from 'firebase/auth'; import request from '@/utils/request'; const styles = { @@ -18,6 +19,8 @@ const styles = { h6: { fontSize: '1.2rem' }, }; +const auth = getAuth(); + export default function Createchall(props) { const pages = [ { diff --git a/src/components/groups/assignments/updateChallengeInfo.jsx b/src/components/groups/assignments/updateChallengeInfo.jsx index 1fc4c908..34d308bd 100644 --- a/src/components/groups/assignments/updateChallengeInfo.jsx +++ b/src/components/groups/assignments/updateChallengeInfo.jsx @@ -9,8 +9,8 @@ import request from '@/utils/request'; import { ToastContainer, toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; +import fileApi, { deleteFiles, getFileName } from '@/utils/file-api'; import { getAuth } from 'firebase/auth'; -import fileApi from '@/utils/file-api'; const auth = getAuth(); @@ -30,6 +30,8 @@ const Editor = (props) => { const [penaltyErr, setPenaltyErr] = useState(''); const [username, setUsername] = useState('anonymous'); + const [existingFiles, setExistingFiles] = useState([]); + const [existingConfig, setExistingConfig] = useState(''); const [newConfig, setNewConfig] = useState(''); const [selectedFile, setSelectedFile] = useState(null); @@ -50,26 +52,41 @@ const Editor = (props) => { }; const sendToFileApi = async () => { - await uploadChallenge([]); - return; - const isValid = await validateNewChallege(); - if (isValid) { - setIsCreating(true); - if (!selectedFile) { - await uploadChallenge(''); + if(!validateNewChallege()) { + return; + } + + const token = await auth.currentUser.accessToken; + setIsCreating(true); + let fileIds = []; + let idsToDelete = []; + + for(let i = 0; i < existingFiles.length; i++) { + if(existingFiles[i].using) { + fileIds.push(existingFiles[i].fileId); + } else { + idsToDelete.push(existingFiles[i].fileId); + } + } + + if(idsToDelete.length > 0) { + const res = await deleteFiles(idsToDelete, token); + if(!res) { + toast.error('Something went wrong with the file upload'); return; + } + } + + if (selectedFile) { + const fileId = await fileApi(token, selectedFile); + if (fileId !== null) { + fileIds.push(fileId); } else { - const token = await auth.currentUser.accessToken; - const fileId = await fileApi(token, selectedFile); - if(fileId !== null) { - await uploadChallenge(fileId); - } else { - toast.error('Something went wrong with the file upload'); - } + toast.error('Something went wrong with the file upload'); + return; } - } else { - console.warn('Either the file, toke, or challenge is invalid'); } + await uploadChallenge(fileIds); }; const uploadChallenge = async (fileIds) => { @@ -97,7 +114,6 @@ const Editor = (props) => { const url = `${process.env.NEXT_PUBLIC_API_URL}/challenges/update-assignment-challenge`; const data = await request(url, 'POST', challengeInfo); - //console.log(data); if (data && data.success) { window.location.href = ``; @@ -108,13 +124,12 @@ const Editor = (props) => { async function loadSolution() { const url = `${process.env.NEXT_PUBLIC_API_URL}/challenges/solution/${props.challenge.id}/${props.classCode}`; const data = await request(url, 'GET', null); - console.log(data); if (data && data.success) { setSolution(data.body.keyword); } } - function loadProps() { + async function loadProps() { setClassCode(props.classCode); setHints(props.challenge.hints); setContentPreview(props.challenge.content); @@ -133,13 +148,20 @@ const Editor = (props) => { hintMessage.push(h[i].message); hintPoints.push(h[i].penalty); } + + let tmp = []; + for (let i = 0; i < props.challenge.fileIds.length; i++) { + const name = await getFileName(props.challenge.fileIds[i]); + tmp.push({ name: name || "File " + i, + using: true, fileId: props.challenge.fileIds[i] }); + } + setExistingFiles(tmp); + setPenalty(hintPoints); setHints(hintMessage); loadSolution(); } - console.log(hints); - useEffect(() => { setUsername(localStorage.getItem('username')); loadProps(); @@ -416,10 +438,66 @@ const Editor = (props) => {

-
-

Import files from your computer

-
-

Import files from your computer

-
+ { + loading && ( +
+ +
+ ) + } +
diff --git a/src/pages/users/[user].jsx b/src/pages/users/[user].jsx index 485ef7bb..fda18c7e 100644 --- a/src/pages/users/[user].jsx +++ b/src/pages/users/[user].jsx @@ -66,6 +66,8 @@ export default function Users() { const [activity, setActivity] = useState([]); + const [isAdmin, setIsAdmin] = useState(false); + const [friendedUser, setFriendedUser] = useState(null); const [pendingRequest, setPendingRequest] = useState(null); const [friendList, setFriendList] = useState(null); @@ -392,6 +394,11 @@ export default function Users() { setRank(result.leaderboardNum); setEmail(result.email); setUserData(result); + + if (result.role === "ADMIN") { + setIsAdmin(true); + } + console.log("USERDATA: " + result) result.username == localStorage.getItem('username') ? setOwnUser(true) : setOwnUser(false); result.location === '????' ? setLocation(null) : setLocation(result.location); @@ -936,9 +943,8 @@ export default function Users() {
-

- {/* {' '} - ADMIN */} +

+ {isAdmin && CTFGuide Employee }

From f8305f15ac3d0500e661b167888ea4edac11910b Mon Sep 17 00:00:00 2001 From: Stephen Stefanatos Date: Mon, 11 Mar 2024 22:46:26 -0400 Subject: [PATCH 3/7] Done file downloads and edits --- .../assignments/updateChallengeInfo.jsx | 63 +++++++++++------- src/components/studio/forking/editor.jsx | 64 +++++++++++-------- src/utils/file-api.js | 38 ++++++++--- 3 files changed, 108 insertions(+), 57 deletions(-) diff --git a/src/components/groups/assignments/updateChallengeInfo.jsx b/src/components/groups/assignments/updateChallengeInfo.jsx index 34d308bd..5b4e96d5 100644 --- a/src/components/groups/assignments/updateChallengeInfo.jsx +++ b/src/components/groups/assignments/updateChallengeInfo.jsx @@ -9,7 +9,7 @@ import request from '@/utils/request'; import { ToastContainer, toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; -import fileApi, { deleteFiles, getFileName } from '@/utils/file-api'; +import fileApi, { deleteFiles, getFileName, getFile } from '@/utils/file-api'; import { getAuth } from 'firebase/auth'; const auth = getAuth(); @@ -89,6 +89,10 @@ const Editor = (props) => { await uploadChallenge(fileIds); }; + const downloadFile = async (fileId, filename) => { + await getFile(fileId, filename); + } + const uploadChallenge = async (fileIds) => { const exConfig = existingConfig.replace('\n', ' && '); const nConfig = newConfig.replace('\n', ' && '); @@ -462,37 +466,48 @@ const Editor = (props) => {

No files attached

)} {existingFiles && existingFiles.map((file, idx) => { - if(file.name.length > 28) { - file.name = file.name.substring(0, 28) + "..."; + if(file.name.length > 24) { + file.name = file.name.substring(0, 24) + "..."; } return(
{ - let tmp = [...existingFiles]; - tmp[idx].using = !tmp[idx].using; - setExistingFiles(tmp); - }} - className="cursor-pointer rounded-lg bg-neutral-700 px-4 py-1 mb-2 text-white hover:bg-neutral-500/40"> + style={{maxHeight: "35px", fontSize: "13px"}} + className="rounded-lg bg-neutral-700 px-4 py-2 mb-2 text-white hover:bg-neutral-500/40">

- {file.name}{' '} - {file.using ? ( -
- -
- ) : ( -
+ { + let tmp = [...existingFiles]; + tmp[idx].using = !tmp[idx].using; + setExistingFiles(tmp); + }} + >{file.name}{' '} +
+ { + file.using ? ( + ) : ( + -
- )} -

+ ) + } +
+
+ downloadFile(file.fileId, file.name)} + className="cursor-pointer fas fa-download text-green-500 px-3"> + +
+
)})} diff --git a/src/components/studio/forking/editor.jsx b/src/components/studio/forking/editor.jsx index 17350c89..144196e9 100644 --- a/src/components/studio/forking/editor.jsx +++ b/src/components/studio/forking/editor.jsx @@ -8,7 +8,7 @@ import 'react-toastify/dist/ReactToastify.css'; import { useRouter } from 'next/router'; import { getAuth } from 'firebase/auth'; -import fileApi, { getNewFileIds, getFileName } from '@/utils/file-api'; +import fileApi, {getNewFileIds, getFileName, getFile } from '@/utils/file-api'; const auth = getAuth(); @@ -117,6 +117,10 @@ const Editor = (props) => { } }; + const downloadFile = async (fileId, filename) => { + await getFile(fileId, filename); + } + const getChallenge = async (isDefault, idName) => { try { const url = `${process.env.NEXT_PUBLIC_API_URL}/challenges/basicInfo/${idName}`; @@ -136,7 +140,6 @@ const Editor = (props) => { let tmp = []; for (let i = 0; i < data.body.fileIds.length; i++) { const name = await getFileName(data.body.fileIds[i]); - console.log(name); tmp.push({ name: name || "File " + i, using: true, fileId: data.body.fileIds[i] }); } @@ -429,37 +432,48 @@ const Editor = (props) => {

No files attached

)} {existingFiles && existingFiles.map((file, idx) => { - if(file.name.length > 28) { - file.name = file.name.substring(0, 28) + "..."; + if(file.name.length > 24) { + file.name = file.name.substring(0, 24) + "..."; } return(
{ - let tmp = [...existingFiles]; - tmp[idx].using = !tmp[idx].using; - setExistingFiles(tmp); - }} - className="cursor-pointer rounded-lg bg-neutral-700 px-4 py-1 mb-2 text-white hover:bg-neutral-500/40"> + style={{maxHeight: "35px", fontSize: "13px"}} + className="rounded-lg bg-neutral-700 px-4 py-2 mb-2 text-white hover:bg-neutral-500/40">

- {file.name}{' '} - {file.using ? ( -
- -
- ) : ( -
+ { + let tmp = [...existingFiles]; + tmp[idx].using = !tmp[idx].using; + setExistingFiles(tmp); + }} + >{file.name}{' '} +
+ { + file.using ? ( + ) : ( + -
- )} -

+ ) + } +
+
+ downloadFile(file.fileId, file.name)} + className="cursor-pointer fas fa-download text-green-500 px-3"> + +
+ )})} diff --git a/src/utils/file-api.js b/src/utils/file-api.js index b922094c..eb4b7f12 100644 --- a/src/utils/file-api.js +++ b/src/utils/file-api.js @@ -1,18 +1,37 @@ -export const getFile = async (fileId) => { - const url = `${process.env.NEXT_PUBLIC_TERM_URL}/upload/getFile?fileID=${fileId}`; +export const getFile = async (fileId, filename) => { + const url = `${process.env.NEXT_PUBLIC_TERM_URL}files/get?fileID=${fileId}`; const requestOptions = { method: 'GET', + headers: {"Content-Type": "application/json"}, }; const response = await fetch(url, requestOptions); - console.log('response', response); - //return response; + + if(!response.ok) { + console.error('Error during file download:', response); + return; + } + + const blob = await response.blob(); + + const tempLink = document.createElement('a'); + tempLink.href = URL.createObjectURL(blob); + tempLink.setAttribute('download', filename); + tempLink.style.display = 'none'; + document.body.appendChild(tempLink); + tempLink.click(); + document.body.removeChild(tempLink); + + setTimeout(() => { + URL.revokeObjectURL(tempLink.href); + }, 100); } export const getFileName = async (fileId) => { const url = `${process.env.NEXT_PUBLIC_TERM_URL}/upload/getFileName?fileID=${fileId}`; const requestOptions = { method: 'GET', + headers: {"Content-Type": "application/json"}, }; const response = await fetch(url, requestOptions); @@ -43,11 +62,11 @@ export const getNewFileIds = async (oldFileIds, token) => { const requestOptions = { method: 'POST', + headers: {"Content-Type": "application/json"}, body: JSON.stringify(body), }; const response = await fetch(url, requestOptions); - console.log('response', response); if(!response.ok) { console.error('Error during file duplication:', response); @@ -66,8 +85,10 @@ export const getNewFileIds = async (oldFileIds, token) => { data.push(fileId); result = await reader.read(); } - console.log('data', data); - return data; + + const fileIds = JSON.parse(data); + const backToStringIds = fileIds.map((id) => id.toString()); + return backToStringIds; } export const deleteFiles = async (fileIds, token) => { @@ -76,11 +97,12 @@ export const deleteFiles = async (fileIds, token) => { const requestOptions = { method: 'POST', + headers: {"Content-Type": "application/json"}, body: JSON.stringify({ fileIDs: parsedIds, jwtToken: token}), }; const response = await fetch(url, requestOptions); - console.log('response', response); + //console.log('response', response); return response.ok; }; From 980de343df57b12580e669d215ac505370d831c7 Mon Sep 17 00:00:00 2001 From: AbhiByreddy <54539671+ThunderBird260@users.noreply.github.com> Date: Mon, 11 Mar 2024 23:49:22 -0400 Subject: [PATCH 4/7] removed dead return home button in submission view --- src/components/dashboard/DashboardHeader.jsx | 2 +- src/pages/assignments/[code]/submissions/[id].jsx | 7 +------ src/pages/users/[user].jsx | 3 +++ 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/dashboard/DashboardHeader.jsx b/src/components/dashboard/DashboardHeader.jsx index f820ea90..2fbe597a 100644 --- a/src/components/dashboard/DashboardHeader.jsx +++ b/src/components/dashboard/DashboardHeader.jsx @@ -84,7 +84,7 @@ export function DashboardHeader() {
- + {(pfp && ( - - Return Home - +

diff --git a/src/pages/users/[user].jsx b/src/pages/users/[user].jsx index fda18c7e..67d57bcd 100644 --- a/src/pages/users/[user].jsx +++ b/src/pages/users/[user].jsx @@ -48,6 +48,9 @@ const shades = [ export default function Users() { const router = useRouter(); const { user } = router.query; +if (!user) { + return window.location.href = '/404'; +} let invalidUser = null; From e2848e73271d35baaa17a9c00aa3d66bdcd2ea68 Mon Sep 17 00:00:00 2001 From: Stephen Stefanatos Date: Wed, 13 Mar 2024 11:46:22 -0400 Subject: [PATCH 5/7] Teacher viewing student view bug fixed --- src/pages/assignments/student/[code].jsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/pages/assignments/student/[code].jsx b/src/pages/assignments/student/[code].jsx index 84b2b82d..f0fb8e77 100644 --- a/src/pages/assignments/student/[code].jsx +++ b/src/pages/assignments/student/[code].jsx @@ -51,6 +51,7 @@ export default function Slug() { const [open, setOpen] = useState(true); const [terminalPopup, setTerminalPopup] = useState(false); const [loadingMessage, setLoadingMessage] = useState(''); + const [isStudent, setIsStudent] = useState(true); const [submissionId, setSubmissionId] = useState(null); @@ -132,7 +133,9 @@ export default function Slug() { const authenticate = async (assignment) => { const url = `${baseUrl}/classroom/inClass/${assignment.classroom.id}`; const data = await request(url, "GET", null); - return data && data.body.isStudent; + if(!data) return false; + setIsStudent(data.body.isStudent); + return (data.body.isStudent || data.body.isTeacher); }; const createTerminal = async (skipToCheckStatus) => { @@ -261,6 +264,11 @@ export default function Slug() { return; } + if(!isStudent) { + toast.error('Teachers cannot submit assignments.'); + return; + } + handleDataAsk(); setLoading(true); @@ -393,7 +401,7 @@ export default function Slug() { Return Home { - assignment && assignment.isOpen && ( + (assignment && assignment.isOpen) && (