diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.controller.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.controller.ts index c0e04f4d..98dcd8e6 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.controller.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.controller.ts @@ -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) @@ -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) diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts index fed092bd..d8fa768c 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts @@ -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) + 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 diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts index 19377807..638dcdde 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts @@ -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() diff --git a/devU-api/src/entities/course/course.service.ts b/devU-api/src/entities/course/course.service.ts index e069308c..e2565117 100644 --- a/devU-api/src/entities/course/course.service.ts +++ b/devU-api/src/entities/course/course.service.ts @@ -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) { diff --git a/devU-api/src/entities/submission/submission.controller.ts b/devU-api/src/entities/submission/submission.controller.ts index e9f46f08..8382bc7a 100644 --- a/devU-api/src/entities/submission/submission.controller.ts +++ b/devU-api/src/entities/submission/submission.controller.ts @@ -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 { @@ -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) } } diff --git a/devU-api/src/entities/submission/submission.model.ts b/devU-api/src/entities/submission/submission.model.ts index 16e4fc06..fdf2fda0 100644 --- a/devU-api/src/entities/submission/submission.model.ts +++ b/devU-api/src/entities/submission/submission.model.ts @@ -1,4 +1,3 @@ - import { JoinColumn, ManyToOne, @@ -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: diff --git a/devU-api/src/entities/submission/submission.router.ts b/devU-api/src/entities/submission/submission.router.ts index 276c5db5..20565bcf 100644 --- a/devU-api/src/entities/submission/submission.router.ts +++ b/devU-api/src/entities/submission/submission.router.ts @@ -1,5 +1,6 @@ // Libraries import express from 'express' +import Multer from 'multer' // Middleware import validator from '../submission/submission.validator' @@ -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 @@ -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 diff --git a/devU-api/src/entities/submission/submission.service.ts b/devU-api/src/entities/submission/submission.service.ts index abb55739..5c757092 100644 --- a/devU-api/src/entities/submission/submission.service.ts +++ b/devU-api/src/entities/submission/submission.service.ts @@ -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 { diff --git a/devU-api/src/entities/submission/submission.validator.ts b/devU-api/src/entities/submission/submission.validator.ts index 56e6c310..1d11e833 100644 --- a/devU-api/src/entities/submission/submission.validator.ts +++ b/devU-api/src/entities/submission/submission.validator.ts @@ -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 diff --git a/devU-api/src/fileStorage.ts b/devU-api/src/fileStorage.ts index 6868d3f6..4d70eb3c 100644 --- a/devU-api/src/fileStorage.ts +++ b/devU-api/src/fileStorage.ts @@ -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 { + 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) + } + }) + }) +} \ No newline at end of file diff --git a/devU-api/src/fileUpload/fileUpload.controller.ts b/devU-api/src/fileUpload/fileUpload.controller.ts index c156d4e7..abb9661c 100644 --- a/devU-api/src/fileUpload/fileUpload.controller.ts +++ b/devU-api/src/fileUpload/fileUpload.controller.ts @@ -2,10 +2,10 @@ import {NextFunction, Request, Response} from 'express' import FileUploadService from './fileUpload.service' import {serialize} from './fileUpload.serializer' -import {mappingFieldWithBucket} from '../utils/fileUpload.utils' import {fileUploadTypes} from '../../devu-shared-modules' -import {GenericResponse, NotFound, Updated} from '../utils/apiResponse.utils' +import {GenericResponse, NotFound} from '../utils/apiResponse.utils' +import {mappingFieldWithBucket} from '../utils/fileUpload.utils' export async function get(req: Request, res: Response, next: NextFunction) { try { @@ -49,7 +49,7 @@ export async function post(req: Request, res: Response, next: NextFunction) { if (!files) return res.status(400).json( new GenericResponse('Must upload files as an object wih the filetype as keys') ); - + // Input field name needs to update to adjust new pattern of bucket names const inputFileField = fileUploadTypes.find(type => files[type]) if (inputFileField === undefined) return res.status(403).json(new GenericResponse('File type not supported')) const bucketName = mappingFieldWithBucket(inputFileField) @@ -59,39 +59,15 @@ export async function post(req: Request, res: Response, next: NextFunction) { const response = serialize(fileUpload) res.status(201).json(response) - } catch (err: Error | any) { + } catch (err: any) { res.status(400).json(new GenericResponse(err.message)) } } -export async function put(req: Request, res: Response, next: NextFunction) { - try { - if (!req.files) return res.status(400).json(new GenericResponse('No file uploaded')) - - const files = req.files as { [fileType: string]: Express.Multer.File[] }; - if (!files) return res.status(400).json(new GenericResponse('Must upload files as an object wih the filetype as keys')); - - const inputFileField = fileUploadTypes.find(type => files[type]) - - if (inputFileField === undefined) return res.status(403).json(new GenericResponse('File type not supported')) - const bucketName = req.params.bucketName - const inputBucketName = mappingFieldWithBucket(inputFileField) - if (bucketName !== inputBucketName) return res.status(403).json(new GenericResponse('Wrong type of file uploaded')) - - const result = await FileUploadService.update(files[inputFileField], bucketName) - - if (!result) return res.status(404).json(NotFound) - - res.status(200).json(Updated) - } catch (err) { - next(err) - } -} export default { get, detail, post, - put, } \ No newline at end of file diff --git a/devU-api/src/fileUpload/fileUpload.model.ts b/devU-api/src/fileUpload/fileUpload.model.ts new file mode 100644 index 00000000..a4590843 --- /dev/null +++ b/devU-api/src/fileUpload/fileUpload.model.ts @@ -0,0 +1,55 @@ +import { + JoinColumn, + ManyToOne, + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + DeleteDateColumn, +} from 'typeorm' + +import AssignmentModel from '../entities/assignment/assignment.model' +import CourseModel from '../entities/course/course.model' +import UserModel from '../entities/user/user.model' + + +@Entity('FilesAuth') + +export default class FileModel { + @PrimaryGeneratedColumn() + id: number + + @CreateDateColumn({ name: 'created_at' }) + createdAt: Date + + @UpdateDateColumn({ name: 'updated_at' }) + updatedAt: Date + + @DeleteDateColumn({ name: 'deleted_at' }) + deletedAt?: Date + + @Column({ name: 'course_id' }) + @JoinColumn({ name: 'course_id' }) + @ManyToOne(() => CourseModel) + courseId: number + + @Column({ name: 'assignment_id' }) + @JoinColumn({ name: 'assignment_id' }) + @ManyToOne(() => AssignmentModel) + assignmentId: number + + @Column({ name: 'user_id' }) + @JoinColumn({ name: 'user_id' }) + @ManyToOne(() => UserModel) + userId: number + + @Column({ name: 'filename', length: 128 }) + filename: string + + @Column({ name: 'bucket', length: 64 }) + fieldName: string + + +} + diff --git a/devU-api/src/fileUpload/fileUpload.router.ts b/devU-api/src/fileUpload/fileUpload.router.ts index b9f2393e..e49e3ef0 100644 --- a/devU-api/src/fileUpload/fileUpload.router.ts +++ b/devU-api/src/fileUpload/fileUpload.router.ts @@ -27,8 +27,8 @@ const fields: Field[] = fileUploadTypes.map(name => ({name})) * get: * summary: Retrieve a list of all files in the bucket */ - Router.get('/:bucketName', FileUploadController.get); + /** * @swagger * /file-upload/{bucketName}/{fileName}: @@ -45,17 +45,5 @@ Router.get('/:bucketName/:fileName', FileUploadController.detail); */ Router.post('/', upload.fields(fields), validator, FileUploadController.post); -/** - * @swagger - * /file-upload/{bucketName}: - * put: - * summary: Update a file in the bucket - * - * - * does not have idea whether the path should have the bucketName or not - * leave for discussion - */ -Router.put('/:bucketName', upload.fields(fields), validator, FileUploadController.put); - export default Router; \ No newline at end of file diff --git a/devU-api/src/fileUpload/fileUpload.serializer.ts b/devU-api/src/fileUpload/fileUpload.serializer.ts index f54b6bea..0b66634e 100644 --- a/devU-api/src/fileUpload/fileUpload.serializer.ts +++ b/devU-api/src/fileUpload/fileUpload.serializer.ts @@ -5,7 +5,7 @@ export function serialize(file: FileUpload): FileUpload { return { fieldName: file.fieldName, originalName: file.originalName, - fileName: file.fileName, + filename: file.filename, etags: file.etags } } diff --git a/devU-api/src/fileUpload/fileUpload.service.ts b/devU-api/src/fileUpload/fileUpload.service.ts index 5375b01f..1677888f 100644 --- a/devU-api/src/fileUpload/fileUpload.service.ts +++ b/devU-api/src/fileUpload/fileUpload.service.ts @@ -1,108 +1,81 @@ -import {FileUpload} from '../../devu-shared-modules' -import {generateFilename} from '../utils/fileUpload.utils' - -import {minioClient, BucketNames} from '../fileStorage' - -export async function create(file: Express.Multer.File[], bucketName: BucketNames) { - let fileName: string[] = []; - let originalName: string[] = [] - let fieldName: string = file[0].fieldname; - let etags: string[] = [] - - for (let i = 0; i < file.length; i++) { - const filename = generateFilename(file[i].originalname) - fileName.push(filename); - originalName.push(file[i].originalname); - minioClient.putObject(bucketName, filename, file[i].buffer, (err, etag) => { - if (err) { - throw new Error("File failed to upload") - } - const info = etag.etag - etags.push(info) - }) +import { FileUpload } from '../../devu-shared-modules' +import { generateFilename } from '../utils/fileUpload.utils' + +import { minioClient, uploadFile } from '../fileStorage' + +export async function create(files: Express.Multer.File[], bucketName: string) { + try { + let fileName: string = "" + let originalName: string = "" + let fieldName: string = files[0].fieldname + let etags: string = "" + + await Promise.all(files.map(async (file) => { + const etag = await uploadFile(bucketName, file) + etags = etags ? etags + ', ' + etag : etag + const generatedFileName = generateFilename(file.originalname) + fileName = fileName ? fileName + ', ' + generatedFileName : generatedFileName + originalName = originalName ? originalName + ', ' + file.originalname : file.originalname + })) + + let fileUpload: FileUpload = { + fieldName: fieldName, + originalName: originalName, + filename: fileName, + etags: etags, } - - let fileUpload: FileUpload = {fieldName: fieldName, originalName: originalName, fileName: fileName, etags: etags} return fileUpload -} - -/* - * I am not sure if update is needed since it is the same as create. - * and since now the filename is random, I am not sure if we need to update the file. - * Or the update method should have the information of the file to be updated. So it can be updated. - * Marking for review - */ -export async function update(file: Express.Multer.File[], bucketName: BucketNames) { - let fileName: string[] = []; - let originalName: string[] = [] - let fieldName: string = file[0].fieldname; - let etags: string[] = [] - - for (let i = 0; i < file.length; i++) { - const filename = generateFilename(file[i].originalname) - fileName.push(filename); - - originalName.push(file[i].originalname); - - minioClient.putObject(bucketName, filename, file[i].buffer, (err, etag) => { - if (err) { - throw new Error("File failed to upload") - } - const info = etag.etag - etags.push(info) - }) + } catch (error: any) { + throw new Error('Error uploading files: ' + error.message) } - - let fileUpload: FileUpload = {fieldName: fieldName, originalName: originalName, fileName: fileName, etags: etags} - return fileUpload } + export async function retrieve(bucketName: string, fileName: string): Promise { return new Promise((resolve, reject) => { - minioClient.getObject(bucketName, fileName, function (err, dataStream) { + minioClient.getObject(bucketName, fileName, function(err, dataStream) { if (err) { - reject(err); + reject(err) } - - let file: Buffer[] = []; - dataStream.on('data', function (chunk: Buffer) { - file.push(chunk); - }); - - dataStream.on('end', function () { - resolve(Buffer.concat(file)); - }); - - dataStream.on('error', function (err: Error) { - reject(err); - }); - }); - }); + + let file: Buffer[] = [] + dataStream.on('data', function(chunk: Buffer) { + file.push(chunk) + }) + + dataStream.on('end', function() { + resolve(Buffer.concat(file)) + }) + + dataStream.on('error', function(err: Error) { + reject(err) + }) + }) + }) } export async function list(bucketName: string) { return new Promise((resolve, reject) => { - const stream = minioClient.listObjects(bucketName); - let files: object[] = []; - - stream.on('data', function (obj) { - files.push(obj); - }); - - stream.on('error', function (err) { - reject(err); - }); - - stream.on('end', function () { - resolve(files); - }); - }); + const stream = minioClient.listObjects(bucketName) + let files: object[] = [] + + stream.on('data', function(obj) { + files.push(obj) + }) + + stream.on('error', function(err) { + reject(err) + }) + + stream.on('end', function() { + resolve(files) + }) + }) } export default { create, retrieve, - update, list, } \ No newline at end of file diff --git a/devU-api/src/migration/1709541636414-fileUpload.ts b/devU-api/src/migration/1709541636414-fileUpload.ts new file mode 100644 index 00000000..cc4a1031 --- /dev/null +++ b/devU-api/src/migration/1709541636414-fileUpload.ts @@ -0,0 +1,20 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class fileUpload1709541636414 implements MigrationInterface { + name = 'fileUpload1709541636414' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "FilesAuth" ("id" SERIAL NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, "course_id" integer NOT NULL, "assignment_id" integer NOT NULL, "user_id" integer NOT NULL, "filename" character varying(128) NOT NULL, "bucket" character varying(64) NOT NULL, CONSTRAINT "PK_1f230e6f691b8fbdf0fb4b6cb79" PRIMARY KEY ("id"))`); + await queryRunner.query(`ALTER TABLE "FilesAuth" ADD CONSTRAINT "FK_8a72cf4a87234d581d04e34e5fe" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "FilesAuth" ADD CONSTRAINT "FK_a906cfad4f214af17efcdcc4a85" FOREIGN KEY ("assignment_id") REFERENCES "assignments"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "FilesAuth" ADD CONSTRAINT "FK_06a0cc4fd4dd414b312ca3de0ef" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "FilesAuth" DROP CONSTRAINT "FK_06a0cc4fd4dd414b312ca3de0ef"`); + await queryRunner.query(`ALTER TABLE "FilesAuth" DROP CONSTRAINT "FK_a906cfad4f214af17efcdcc4a85"`); + await queryRunner.query(`ALTER TABLE "FilesAuth" DROP CONSTRAINT "FK_8a72cf4a87234d581d04e34e5fe"`); + await queryRunner.query(`DROP TABLE "FilesAuth"`); + } + +} diff --git a/devU-api/src/model/index.ts b/devU-api/src/model/index.ts index 8b0a2358..91eefc92 100644 --- a/devU-api/src/model/index.ts +++ b/devU-api/src/model/index.ts @@ -12,6 +12,7 @@ import SubmissionProblemScoreModel from '../entities/submissionProblemScore/subm import SubmissionScoreModel from '../entities/submissionScore/submissionScore.model' import UserModel from '../entities/user/user.model' import UserCourseModel from '../entities/userCourse/userCourse.model' +import FileModel from '../fileUpload/fileUpload.model' import DeadlineExtensionsModel from "../entities/deadlineExtensions/deadlineExtensions.model"; type Models = @@ -29,6 +30,8 @@ type Models = | SubmissionScoreModel | UserModel | UserCourseModel + | CategoryModel + | FileModel | DeadlineExtensionsModel export default Models diff --git a/devU-api/src/router/index.ts b/devU-api/src/router/index.ts index 56906749..e709d771 100644 --- a/devU-api/src/router/index.ts +++ b/devU-api/src/router/index.ts @@ -17,6 +17,7 @@ import assignmentProblem from '../entities/assignmentProblem/assignmentProblem.r import submissionProblemScore from '../entities/submissionProblemScore/submissionProblemScore.router' import deadlineExtensions from "../entities/deadlineExtensions/deadlineExtensions.router"; import fileUpload from '../fileUpload/fileUpload.router' +import category from '../entities/category/category.router' import grader from '../entities/grader/grader.router' import { isAuthorized } from '../auth/auth.middleware' @@ -30,6 +31,7 @@ Router.use('/assignments', isAuthorized, assignments) Router.use('/assignment-problems', isAuthorized, assignmentProblem) Router.use('/users', isAuthorized, users) Router.use('/courses', isAuthorized, courses) +Router.use('/categories', isAuthorized, category) Router.use('/user-courses', isAuthorized, userCourse) Router.use('/submissions', isAuthorized, submissions) Router.use('/submission-scores', isAuthorized, submissionScore) diff --git a/devu-shared/src/types/fileUpload.types.ts b/devu-shared/src/types/fileUpload.types.ts index 292d882f..93979b9a 100644 --- a/devu-shared/src/types/fileUpload.types.ts +++ b/devu-shared/src/types/fileUpload.types.ts @@ -1,8 +1,15 @@ export type FileUpload = { + id?: number + userId?: number + assignmentId?: number + courseId?: number fieldName: string - originalName: string[] - fileName: string[] - etags: string[] + originalName: string + filename: string + etags: string + createdAt?: string + updatedAt?: string + deletedAt?: string } /* @@ -12,4 +19,4 @@ export type FileUpload = { Marked for discussion. */ -export const fileUploadTypes = ['studentSubmission', 'graderFile', 'makefileFile'] as const +export const fileUploadTypes = ['studentSubmission', 'graderFile', 'makefileFile','files'] as const