Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,11 @@ export async function post(req: Request, res: Response, next: NextFunction) {
if (!req.files || !('graderFile' in req.files)) {
return res.status(400).json(new GenericResponse('Container Auto Grader requires file upload for grader'));
}
const graderFile = req.files['graderFile'][0]?.buffer
const makefile = req.files['makefileFile'] ? req.files['makefileFile'][0]?.buffer : null
const graderFile = req.files['graderFile'][0]
const makefile = req.files?.['makefileFile'][0] ?? null
const requestBody = req.body

const containerAutoGrader = await ContainerAutoGraderService.create(req.body, graderFile, makefile)
const containerAutoGrader = await ContainerAutoGraderService.create(requestBody, graderFile, makefile)
const response = serialize(containerAutoGrader)

res.status(201).json(response)
Expand All @@ -54,20 +55,15 @@ export async function post(req: Request, res: Response, next: NextFunction) {
}


/*
* for the put method, I am not sure if the graderFilename is necessary, since there are two
* files that can be uploaded, the grader and the makefile. I am currently assuming that the
* graderFile is required(following the same logic as the post method), but it can be changed
* to make it optional if needed.
*/

export async function put(req: Request, res: Response, next: NextFunction) {
try {
if (!req.files || !('graderFile' in req.files)) {
return res.status(400).json(new GenericResponse('Container Auto Grader requires file upload for grader'));
if (req.files && (!('graderFile' in req.files) && !('makefileFile' in req.files))) {
return res.status(400).json(new GenericResponse('Uploaded files must be grader or makefile'));
}

const graderFile = req.files['graderFile'][0]?.buffer
const makefile = req.files['makefileFile'] ? req.files['makefileFile'][0]?.buffer : null
const graderFile = req.files?.['graderFile'][0]?? null
const makefile = req.files?.['makefileFile'][0] ?? null

req.body.id = parseInt(req.params.id)
const results = await ContainerAutoGraderService.update(req.body, graderFile, makefile)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,53 +1,68 @@
import { getRepository, IsNull } from 'typeorm'

import { ContainerAutoGrader } from 'devu-shared-modules'
import { ContainerAutoGrader, FileUpload } from 'devu-shared-modules'

import ContainerAutoGraderModel from './containerAutoGrader.model'
import { BucketNames, minioClient } from '../../fileStorage'
import FileModel from "../../fileUpload/fileUpload.model";

import { uploadFile } from '../../fileStorage'
import {generateFilename} from "../../utils/fileUpload.utils";

const connect = () => getRepository(ContainerAutoGraderModel)
const fileConn = () => getRepository(FileModel)

async function filesUpload(bucket: string, file: Express.Multer.File, containerAutoGrader: ContainerAutoGrader,filename: string) {
const Etag: string = await uploadFile(bucket, file)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

variable name that starts with a capital letter

const assignmentId = containerAutoGrader.assignmentId

const fileModel: FileUpload = {
etags: Etag,
fieldName: bucket,
originalName: file.originalname,
filename: filename,
assignmentId: assignmentId,
}
//TODO: This is a temporary fix to get the function to pass. CourseId and UserId should be modified in the future
fileModel.courseId = 1
fileModel.userId = 1

await fileConn().save(fileModel)

function assignmentGraderFileRecordName(containerAutoGrader: ContainerAutoGrader) {
if (!containerAutoGrader.id) throw new Error('Missing Id')
return containerAutoGrader.id.toString()
return Etag
}

export async function create(containerAutoGrader: ContainerAutoGrader, graderInputFile: Buffer, makefileInputFile: Buffer | null = null) {
containerAutoGrader.graderFile = "Temp value. You'll see this if the file upload to MinIO fails"
const newContainerAutoGrader = await connect().save(containerAutoGrader)
const FileRecordName: string = assignmentGraderFileRecordName(newContainerAutoGrader)

/*
* For the minioClient.putObject method, I am not sure how the objectName is, but it seems like
* the objectName is the id of the containerAutoGrader.(inherits from the codeAssignment.service.ts file),
* but I am not sure if the makefile should also use the same objectName as the grader. Marking this
* for review. Same with the update method.
*/
await minioClient.putObject(BucketNames.GRADERS, FileRecordName, graderInputFile)
newContainerAutoGrader.graderFile = FileRecordName

export async function create(containerAutoGrader: ContainerAutoGrader, graderInputFile: Express.Multer.File, makefileInputFile: Express.Multer.File | null = null) {
const bucket: string = 'graders'
const filename: string = generateFilename(graderInputFile.originalname)
await filesUpload(bucket, graderInputFile, containerAutoGrader, filename)
containerAutoGrader.graderFile = filename

if (makefileInputFile) {
await minioClient.putObject(BucketNames.MAKEFILES, FileRecordName, makefileInputFile)
newContainerAutoGrader.makefileFile = FileRecordName
const makefileFilename: string = generateFilename(makefileInputFile.originalname)
await filesUpload(bucket, makefileInputFile, containerAutoGrader, makefileFilename)
containerAutoGrader.makefileFile = makefileFilename
}

const { id, assignmentId, graderFile, makefileFile, autogradingImage, timeout } = newContainerAutoGrader

await connect().update(id, { assignmentId, graderFile, makefileFile, autogradingImage, timeout })

return newContainerAutoGrader
const { id, assignmentId, graderFile, makefileFile, autogradingImage, timeout } = containerAutoGrader
return await connect().save({ id, assignmentId, graderFile, makefileFile, autogradingImage, timeout })
}

export async function update(containerAutoGrader: ContainerAutoGrader, graderInputFile: Buffer, makefileInputFile: Buffer | null = null) {
export async function update(containerAutoGrader: ContainerAutoGrader, graderInputFile: Express.Multer.File | null = null, makefileInputFile: Express.Multer.File | null = null) {
if (!containerAutoGrader.id) throw new Error('Missing Id')
const FileRecordName: string = assignmentGraderFileRecordName(containerAutoGrader)

await minioClient.putObject(BucketNames.GRADERS, FileRecordName, graderInputFile)
containerAutoGrader.graderFile = FileRecordName
if (graderInputFile) {
const bucket: string = 'graders'
const filename: string = generateFilename(graderInputFile.originalname)
await filesUpload(bucket, graderInputFile, containerAutoGrader, filename)
containerAutoGrader.graderFile = filename
}

if (makefileInputFile) {
await minioClient.putObject(BucketNames.MAKEFILES, FileRecordName, makefileInputFile)
containerAutoGrader.makefileFile = FileRecordName
const bucket: string = 'makefiles'
const makefileFilename: string = generateFilename(makefileInputFile.originalname)
await filesUpload(bucket, makefileInputFile, containerAutoGrader, makefileFilename)
containerAutoGrader.makefileFile = makefileFilename
}

const { id, assignmentId, graderFile, makefileFile, autogradingImage, timeout } = containerAutoGrader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,23 @@ import validate from '../../middleware/validator/generic.validator'

const assignmentId = check('assignmentId').isNumeric()

const graderFile = check('graderFile').isString()

const makefileFile = check('makefileFile').isString().optional({ nullable: true })
const graderFile = check('graderFile').optional({ nullable: true }).custom(({ req }) => {
const file = req.files['grader']
if (file !== null) {
if (file.size <= 0) {
throw new Error('File is empty')
}
}
})

const makefileFile = check('makefileFile').optional({ nullable: true }).custom(({ req }) => {
const file = req.files['makefile']
if (file !== null) {
if (file.size <= 0) {
throw new Error('File is empty')
}
}
})


const autogradingImage = check('autogradingImage').isString()
Expand Down
8 changes: 7 additions & 1 deletion devU-api/src/entities/course/course.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@ import { getRepository, IsNull } from 'typeorm'
import CourseModel from './course.model'

import { Course } from 'devu-shared-modules'
import { initializeMinio } from '../../fileStorage'



const connect = () => getRepository(CourseModel)

export async function create(course: Course) {
return await connect().save(course)
const output = await connect().save(course)
const bucketName = (course.name).toLowerCase()+course.number+course.semester+course.id
await initializeMinio(bucketName)
return output
}

export async function update(course: Course) {
Expand Down
8 changes: 4 additions & 4 deletions devU-api/src/entities/submission/submission.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import SubmissionService from '../submission/submission.service'

import { GenericResponse, NotFound } from '../../utils/apiResponse.utils'

import { serialize } from '../submission/submission.serializer'
import { serialize } from './submission.serializer'

export async function get(req: Request, res: Response, next: NextFunction) {
try {
Expand Down Expand Up @@ -43,12 +43,12 @@ export async function post(req: Request, res: Response, next: NextFunction) {
requestBody.submitterIp = req.header('x-forwarded-for') || req.socket.remoteAddress
requestBody.submittedBy = req.currentUser?.userId

const submission = await SubmissionService.create(requestBody)
const submission = await SubmissionService.create(requestBody, req.file)
const response = serialize(submission)

res.status(201).json(response)
} catch (err) {
res.status(400).json(new GenericResponse(err.message))
} catch (err:any) {
next(err)
}
}

Expand Down
4 changes: 0 additions & 4 deletions devU-api/src/entities/submission/submission.model.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import {
JoinColumn,
ManyToOne,
Expand Down Expand Up @@ -35,9 +34,6 @@ export default class SubmissionModel {
* type: integer
* content:
* type: string
* type:
* type: string
* description: Must be either "filepath", "json", or "text"
* submitterIp:
* type: string
* submittedBy:
Expand Down
4 changes: 3 additions & 1 deletion devU-api/src/entities/submission/submission.router.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Libraries
import express from 'express'
import Multer from 'multer'

// Middleware
import validator from '../submission/submission.validator'
Expand All @@ -9,6 +10,7 @@ import { asInt } from '../../middleware/validator/generic.validator'
import SubmissionController from '../submission/submission.controller'

const Router = express.Router()
const upload = Multer()

/**
* @swagger
Expand Down Expand Up @@ -58,7 +60,7 @@ Router.get('/:id', asInt(), SubmissionController.detail)
* schema:
* $ref: '#/components/schemas/Submission'
*/
Router.post('/', validator, SubmissionController.post)
Router.post('/',upload.single("files"), validator, SubmissionController.post)

/**
* @swagger
Expand Down
45 changes: 35 additions & 10 deletions devU-api/src/entities/submission/submission.service.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,50 @@
import { getRepository, IsNull } from 'typeorm'

import SubmissionModel from '../submission/submission.model'

import { Submission } from 'devu-shared-modules'

const connect = () => getRepository(SubmissionModel)

export async function create(submission: Submission) {
return await connect().save(submission)
import FileModel from '../../fileUpload/fileUpload.model'
import CourseModel from '../course/course.model'

import { Submission, FileUpload } from 'devu-shared-modules'
import { uploadFile } from '../../fileStorage'

const submissionConn = () => getRepository(SubmissionModel)
const fileConn = () => getRepository(FileModel)

export async function create(submission: Submission, file?: Express.Multer.File|undefined) {
if (file) {
const bucket : string = await getRepository(CourseModel).findOne({id: submission.courseId}).then((course) => {
if (course) {
return (course.name).toLowerCase() + course.number + course.semester + course.id
}
return 'submission'
})

const Etag : string = await uploadFile(bucket, file)
const fileModel : FileUpload = {
userId : submission.userId,
assignmentId : submission.assignmentId,
courseId : submission.courseId,
etags : Etag,
fieldName : file.fieldname,
originalName : file.originalname,
filename : bucket,

}
await fileConn().save(fileModel)
}
return await submissionConn().save(submission)
}

export async function _delete(id: number) {
return await connect().softDelete({ id, deletedAt: IsNull() })
return await submissionConn().softDelete({ id, deletedAt: IsNull() })
}

export async function retrieve(id: number) {
return await connect().findOne({ id, deletedAt: IsNull() })
return await submissionConn().findOne({ id, deletedAt: IsNull() })
}

export async function list() {
return await connect().find({ deletedAt: IsNull() })
return await submissionConn().find({ deletedAt: IsNull() })
}

export default {
Expand Down
11 changes: 10 additions & 1 deletion devU-api/src/entities/submission/submission.validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ const assignmentId = check('assignmentId').isNumeric()
const courseId = check('courseId').isNumeric()
const content = check('content').isString()

const validator = [courseId, assignmentId, userId, content, validate]
const file = check('file').optional({ nullable: true }).custom ((value, { req }) => {
if(req.file !== null){
if (req.file.size == 0){
throw new Error('File is empty')
}
}

})

const validator = [courseId, assignmentId, userId, content, file, validate]

export default validator
38 changes: 32 additions & 6 deletions devU-api/src/fileStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,42 @@ const minioConfiguration: Minio.ClientOptions = {

export const minioClient = new Minio.Client(minioConfiguration)

export async function initializeMinio() {
for (const bucketName of Object.values(BucketNames)) {
const bucketExists = await minioClient.bucketExists(bucketName)
export async function initializeMinio(inputBucketName?: string) {
if (inputBucketName) {
const bucketExists = await minioClient.bucketExists(inputBucketName)

if (bucketExists) continue
if (bucketExists) return

minioClient.makeBucket(bucketName, 'us-east-1', function (err) {
minioClient.makeBucket(inputBucketName, 'us-east-1', function (err) {
if (err) {
throw new Error(`Error creating MinIO bucket '${bucketName}'`)
throw new Error(`Error creating MinIO bucket '${inputBucketName}'`)
}
})
return
}else{
for (const bucketName of Object.values(BucketNames)) {
const bucketExists = await minioClient.bucketExists(bucketName)

if (bucketExists) continue

minioClient.makeBucket(bucketName, 'us-east-1', function (err) {
if (err) {
throw new Error(`Error creating MinIO bucket '${bucketName}'`)
}
})
}
}
}


export async function uploadFile(bucketName: string, file: Express.Multer.File): Promise<string> {
return new Promise((resolve, reject) => {
minioClient.putObject(bucketName, file.originalname, file.buffer, (err, etag) => {
if (err) {
reject(new Error('File failed to upload'))
} else {
resolve(etag.etag)
}
})
})
}
Loading