Skip to content

Commit

Permalink
better upload & download
Browse files Browse the repository at this point in the history
  • Loading branch information
AWerbrouck committed May 9, 2024
1 parent ef0dabf commit 089a961
Show file tree
Hide file tree
Showing 8 changed files with 359 additions and 131 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("*")
.allowedOrigins("*")
.exposedHeaders("Content-Disposition")
.allowedHeaders("*");

}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

Expand Down Expand Up @@ -159,6 +160,7 @@ public ResponseEntity<?> getSubmissions(@PathVariable("projectid") long projecti
*/
@PostMapping(ApiRoutes.PROJECT_BASE_PATH + "/{projectid}/submit")
//Route to submit a file, it accepts a multiform with the file and submissionTime
@Transactional
@Roles({UserRole.teacher, UserRole.student})
public ResponseEntity<?> submitFile(@RequestParam("file") MultipartFile file, @PathVariable("projectid") long projectid, Auth auth) {
long userId = auth.getUserEntity().getId();
Expand Down
1 change: 1 addition & 0 deletions frontend/src/i18n/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"groupEmpty": "No members in this group",
"testFailed": "Tests failed",
"structureFailed": "Structure failed",
"uploadDirectory": "Upload Directory",
"submission": "Submission",
"passed": "Passed",
"notSubmitted": "Not submitted",
Expand Down
1 change: 1 addition & 0 deletions frontend/src/i18n/nl/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
"uploadAreaTitle": "Bestanden slepen of klikken om bestanden toe te voegen",
"uploadAreaSubtitle": "Maximum bestandsgrootte is 100MB",
"deadlinePassed": "Deadline is verstreken",
"uploadDirectory": "folder uploaden",
"downloadSubmissions": "Download alle indieningen",
"group": "Groep",
"status": "Status",
Expand Down
245 changes: 135 additions & 110 deletions frontend/src/pages/submission/components/SubmissionCard.tsx
Original file line number Diff line number Diff line change
@@ -1,130 +1,155 @@
import { Card, Spin, theme, Input, Button, Typography } from "antd"
import { useTranslation } from "react-i18next"
import { GET_Responses } from "../../../@types/requests"
import { ApiRoutes } from "../../../@types/requests"
import { ArrowLeftOutlined } from "@ant-design/icons"
import { useNavigate } from "react-router-dom"
import {Card, Spin, theme, Input, Button, Typography} from "antd"
import {useTranslation} from "react-i18next"
import {GET_Responses} from "../../../@types/requests"
import {ApiRoutes} from "../../../@types/requests"
import {ArrowLeftOutlined} from "@ant-design/icons"
import {useNavigate} from "react-router-dom"
import "@fontsource/jetbrains-mono"
import { useEffect, useState } from "react"
import {useEffect, useState} from "react"
import apiCall from "../../../util/apiFetch"

export type SubmissionType = GET_Responses[ApiRoutes.SUBMISSION]

const SubmissionCard: React.FC<{ submission: SubmissionType }> = ({ submission }) => {
const { token } = theme.useToken()
const { t } = useTranslation()
const [structureFeedback, setStructureFeedback] = useState<string | null>(null)
const [dockerFeedback, setDockerFeedback] = useState<string | null>(null)
const navigate = useNavigate()
useEffect(() => {
if (!submission.dockerAccepted) apiCall.get(submission.dockerFeedbackUrl).then((res) => setDockerFeedback(res.data ? res.data : ""))
if (!submission.structureAccepted) apiCall.get(submission.structureFeedbackUrl).then((res) => setStructureFeedback(res.data ? res.data : ""))
}, [submission.dockerFeedbackUrl, submission.structureFeedbackUrl])
const SubmissionCard: React.FC<{ submission: SubmissionType }> = ({submission}) => {
const {token} = theme.useToken()
const {t} = useTranslation()
const [structureFeedback, setStructureFeedback] = useState<string | null>(null)
const [dockerFeedback, setDockerFeedback] = useState<string | null>(null)
const navigate = useNavigate()
useEffect(() => {
if (!submission.dockerAccepted) apiCall.get(submission.dockerFeedbackUrl).then((res) => setDockerFeedback(res.data ? res.data : ""))
if (!submission.structureAccepted) apiCall.get(submission.structureFeedbackUrl).then((res) => setStructureFeedback(res.data ? res.data : ""))
}, [submission.dockerFeedbackUrl, submission.structureFeedbackUrl])

const downloadSubmission = async () => {
//TODO: testen of dit wel echt werkt
try {
const fileContent = await apiCall.get(submission.fileUrl)
console.log(fileContent)
const blob = new Blob([fileContent.data], { type: "text/plain" })
const url = URL.createObjectURL(blob)
const link = document.createElement("a")
link.href = url
link.download = "indiening.zip"
document.body.appendChild(link)
link.click()
URL.revokeObjectURL(url)
document.body.removeChild(link)
} catch (err) {
// TODO: handle error
const downloadSubmission = async () => {
try {
const response = await apiCall.get(submission.fileUrl, undefined, undefined, {
responseType: 'blob',
transformResponse: [(data) => data],
});
console.log(response);
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
const contentDisposition = response.headers['content-disposition'];
console.log(contentDisposition);
let fileName = 'file.zip'; // default filename
if (contentDisposition) {
const fileNameMatch = contentDisposition.match(/filename=([^;]+)/);
console.log(fileNameMatch);
if (fileNameMatch && fileNameMatch[1]) {
fileName = fileNameMatch[1]; // use the filename from the headers
}
}
link.setAttribute('download', fileName);
document.body.appendChild(link);
link.click();
} catch (err) {
console.error(err);
}
}
}

return (
<Card
styles={{
header: {
background: token.colorPrimaryBg,
},
title: {
fontSize: "1.1em",
},
}}
type="inner"
title={
<span>
return (
<Card
styles={{
header: {
background: token.colorPrimaryBg,
},
title: {
fontSize: "1.1em",
},
}}
type="inner"
title={
<span>
<Button
onClick={() => navigate(-1)}
type="text"
style={{ marginRight: 16 }}
onClick={() => navigate(-1)}
type="text"
style={{marginRight: 16}}
>
<ArrowLeftOutlined />
<ArrowLeftOutlined/>
</Button>
{t("submission.submission")}
{t("submission.submission")}
</span>
}
>
{t("submission.submittedFiles")}
}
>
{t("submission.submittedFiles")}

<ul style={{ listStyleType: "none" }}>
<li>
<Button
type="link"
style={{ padding: 0 }}
onClick={downloadSubmission}
>
<u>indiening.zip</u>
</Button>
</li>
</ul>
<ul style={{listStyleType: "none"}}>
<li>
<Button
type="link"
style={{padding: 0}}
onClick={downloadSubmission}
>
<u>indiening.zip</u>
</Button>
</li>
</ul>

{t("submission.structuretest")}
{t("submission.structuretest")}

<ul style={{ listStyleType: "none" }}>
<li>
<Typography.Text type={submission.structureAccepted ? "success" : "danger"}>{submission.structureAccepted ? t("submission.status.accepted") : t("submission.status.failed")}</Typography.Text>
{submission.structureAccepted ? null : (
<div>
{structureFeedback === null ? (
<Spin />
) : (
<Input.TextArea
readOnly
value={structureFeedback}
style={{ width: "100%", overflowX: "auto", overflowY: "auto", resize: "none", fontFamily: "Jetbrains Mono", marginTop: 8 }}
rows={4}
autoSize={{ minRows: 4, maxRows: 8 }}
/>
)}
</div>
)}
</li>
</ul>
<ul style={{listStyleType: "none"}}>
<li>
<Typography.Text
type={submission.structureAccepted ? "success" : "danger"}>{submission.structureAccepted ? t("submission.status.accepted") : t("submission.status.failed")}</Typography.Text>
{submission.structureAccepted ? null : (
<div>
{structureFeedback === null ? (
<Spin/>
) : (
<Input.TextArea
readOnly
value={structureFeedback}
style={{
width: "100%",
overflowX: "auto",
overflowY: "auto",
resize: "none",
fontFamily: "Jetbrains Mono",
marginTop: 8
}}
rows={4}
autoSize={{minRows: 4, maxRows: 8}}
/>
)}
</div>
)}
</li>
</ul>

{t("submission.dockertest")}
{t("submission.dockertest")}

<ul style={{ listStyleType: "none" }}>
<li>
<Typography.Text type={submission.dockerAccepted ? "success" : "danger"}>{submission.dockerAccepted ? t("submission.status.accepted") : t("submission.status.failed")}</Typography.Text>
{submission.dockerAccepted ? null : (
<div>
{dockerFeedback === null ? (
<Spin />
) : (
<Input.TextArea
readOnly
value={dockerFeedback}
style={{ width: "100%", overflowX: "auto", overflowY: "auto", resize: "none", fontFamily: "Jetbrains Mono", marginTop: 8 }}
rows={4}
autoSize={{ minRows: 4, maxRows: 16 }}
/>
)}
</div>
)}
</li>
</ul>
</Card>
)
<ul style={{listStyleType: "none"}}>
<li>
<Typography.Text
type={submission.dockerAccepted ? "success" : "danger"}>{submission.dockerAccepted ? t("submission.status.accepted") : t("submission.status.failed")}</Typography.Text>
{submission.dockerAccepted ? null : (
<div>
{dockerFeedback === null ? (
<Spin/>
) : (
<Input.TextArea
readOnly
value={dockerFeedback}
style={{
width: "100%",
overflowX: "auto",
overflowY: "auto",
resize: "none",
fontFamily: "Jetbrains Mono",
marginTop: 8
}}
rows={4}
autoSize={{minRows: 4, maxRows: 16}}
/>
)}
</div>
)}
</li>
</ul>
</Card>
)
}

export default SubmissionCard
13 changes: 11 additions & 2 deletions frontend/src/pages/submit/Submit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,24 @@ const Submit = () => {

const zip = new JSZip();
files.forEach((file: any) => {
zip.file(file.name, file);
zip.file(file.webkitRelativePath || file.name, file);
});
const content = await zip.generateAsync({type: "blob"});
formData.append("file", content, "files.zip");

if (!projectId) return;
const response = await apiCall.post(ApiRoutes.PROJECT_SUBMIT, formData, {id: projectId})
console.log(response)

const projectUrl = new URL(response.data.projectUrl, 'http://localhost:3001');

const courseUrl = new URL(projectUrl.origin + projectUrl.pathname.split('/').slice(0, 3).join('/'), 'http://localhost:3001');
const courseId = courseUrl.pathname.split('/')[2];

const submissionId = response.data.submissionId;
navigate(`/courses/${courseId}/projects/${projectId}/submissions/${submissionId}`);
}

return (
<>
<div>
Expand Down Expand Up @@ -95,4 +104,4 @@ const Submit = () => {
)
}

export default Submit
export default Submit
Loading

0 comments on commit 089a961

Please sign in to comment.