From 684750832807630c1557834e1f106d1c5885b0ea Mon Sep 17 00:00:00 2001 From: RA341 Date: Sun, 6 Apr 2025 13:58:36 -0400 Subject: [PATCH 1/7] metdata migration --- .../src/migration/1743395416522-updateCag.ts | 6 ++--- ...1277791-moveMetadataToAssignmentProblem.ts | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 devU-api/src/migration/1743961277791-moveMetadataToAssignmentProblem.ts diff --git a/devU-api/src/migration/1743395416522-updateCag.ts b/devU-api/src/migration/1743395416522-updateCag.ts index 44df3fca..a3b21490 100644 --- a/devU-api/src/migration/1743395416522-updateCag.ts +++ b/devU-api/src/migration/1743395416522-updateCag.ts @@ -8,9 +8,9 @@ export class UpdateCag1743395416522 implements MigrationInterface { await queryRunner.query(`ALTER TABLE "container_auto_grader" DROP COLUMN "makefile_filename"`); await queryRunner.query(`ALTER TABLE "container_auto_grader" DROP COLUMN "autograding_image"`); await queryRunner.query(`ALTER TABLE "container_auto_grader" DROP COLUMN "grader_filename"`); - await queryRunner.query(`ALTER TABLE "FilesAuth" ADD "etag" character varying NOT NULL`); - await queryRunner.query(`ALTER TABLE "FilesAuth" ADD "name" character varying NOT NULL`); - await queryRunner.query(`ALTER TABLE "FilesAuth" ADD "type" character varying NOT NULL`); + await queryRunner.query(`ALTER TABLE "FilesAuth" ADD "etag" character varying NOT NULL DEFAULT ''`); + await queryRunner.query(`ALTER TABLE "FilesAuth" ADD "name" character varying NOT NULL DEFAULT ''`); + await queryRunner.query(`ALTER TABLE "FilesAuth" ADD "type" character varying NOT NULL DEFAULT ''`); await queryRunner.query(`ALTER TABLE "container_auto_grader" ADD "dockerfile_id" character varying(512) NOT NULL`); await queryRunner.query(`ALTER TABLE "container_auto_grader" ADD "job_file_ids" jsonb NOT NULL DEFAULT '[]'`); await queryRunner.query(`ALTER TABLE "container_auto_grader" ADD "timeout_in_seconds" integer NOT NULL`); diff --git a/devU-api/src/migration/1743961277791-moveMetadataToAssignmentProblem.ts b/devU-api/src/migration/1743961277791-moveMetadataToAssignmentProblem.ts new file mode 100644 index 00000000..b2312979 --- /dev/null +++ b/devU-api/src/migration/1743961277791-moveMetadataToAssignmentProblem.ts @@ -0,0 +1,26 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class MoveMetadataToAssignmentProblem1743961277791 implements MigrationInterface { + name = 'MoveMetadataToAssignmentProblem1743961277791' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "nonContainerAutoGrader" DROP COLUMN "metadata"`); + await queryRunner.query(`ALTER TABLE "assignment_problems" ADD "metadata" jsonb DEFAULT '{}'`); + await queryRunner.query(`ALTER TABLE "FilesAuth" ALTER COLUMN "etag" SET DEFAULT ''`); + await queryRunner.query(`ALTER TABLE "FilesAuth" ALTER COLUMN "name" SET DEFAULT ''`); + await queryRunner.query(`ALTER TABLE "FilesAuth" ALTER COLUMN "type" SET DEFAULT ''`); + await queryRunner.query(`ALTER TABLE "FilesAuth" ALTER COLUMN "filename" SET DEFAULT ''`); + await queryRunner.query(`ALTER TABLE "FilesAuth" ALTER COLUMN "bucket" SET DEFAULT ''`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "FilesAuth" ALTER COLUMN "bucket" DROP DEFAULT`); + await queryRunner.query(`ALTER TABLE "FilesAuth" ALTER COLUMN "filename" DROP DEFAULT`); + await queryRunner.query(`ALTER TABLE "FilesAuth" ALTER COLUMN "type" DROP DEFAULT`); + await queryRunner.query(`ALTER TABLE "FilesAuth" ALTER COLUMN "name" DROP DEFAULT`); + await queryRunner.query(`ALTER TABLE "FilesAuth" ALTER COLUMN "etag" DROP DEFAULT`); + await queryRunner.query(`ALTER TABLE "assignment_problems" DROP COLUMN "metadata"`); + await queryRunner.query(`ALTER TABLE "nonContainerAutoGrader" ADD "metadata" jsonb DEFAULT '{}'`); + } + +} From b682cabb2fc410337a8033649b0d46c6083fc3e1 Mon Sep 17 00:00:00 2001 From: RA341 Date: Sun, 6 Apr 2025 13:58:50 -0400 Subject: [PATCH 2/7] updated types --- devU-shared/src/types/assignmentProblem.types.ts | 1 + devU-shared/src/types/nonContainerAutoGrader.types.ts | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/devU-shared/src/types/assignmentProblem.types.ts b/devU-shared/src/types/assignmentProblem.types.ts index 52796ef4..e174db25 100644 --- a/devU-shared/src/types/assignmentProblem.types.ts +++ b/devU-shared/src/types/assignmentProblem.types.ts @@ -2,6 +2,7 @@ export type AssignmentProblem = { id?: number assignmentId: number problemName: string + metadata: string, maxScore: number createdAt?: string updatedAt?: string diff --git a/devU-shared/src/types/nonContainerAutoGrader.types.ts b/devU-shared/src/types/nonContainerAutoGrader.types.ts index f42d0568..30412d5a 100644 --- a/devU-shared/src/types/nonContainerAutoGrader.types.ts +++ b/devU-shared/src/types/nonContainerAutoGrader.types.ts @@ -4,7 +4,6 @@ export type NonContainerAutoGrader = { updatedAt?: string assignmentId: number question: string - metadata: string, score:number correctString: string isRegex: boolean From 2553184f0e391b9dc638fcb0aecb4f536f7c0064 Mon Sep 17 00:00:00 2001 From: RA341 Date: Sun, 6 Apr 2025 13:59:02 -0400 Subject: [PATCH 3/7] added placeholders for the frontend team --- .../listItems/assignmentProblemListItem.tsx | 17 +++++++---------- .../forms/assignments/assignmentUpdatePage.tsx | 1 + 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/devU-client/src/components/listItems/assignmentProblemListItem.tsx b/devU-client/src/components/listItems/assignmentProblemListItem.tsx index 29fe50fe..e7760476 100644 --- a/devU-client/src/components/listItems/assignmentProblemListItem.tsx +++ b/devU-client/src/components/listItems/assignmentProblemListItem.tsx @@ -1,7 +1,7 @@ -import React, { useState, useEffect} from 'react' +import React, { useEffect, useState } from 'react' import { useParams } from 'react-router-dom' import RequestService from 'services/request.service' -import {AssignmentProblem, NonContainerAutoGrader} from 'devu-shared-modules' +import { AssignmentProblem, NonContainerAutoGrader } from 'devu-shared-modules' import styles from './assignmentProblemListItem.scss' @@ -23,11 +23,8 @@ const AssignmentProblemListItem = ({problem, handleChange, disabled}: Props) => const getMeta = () => { if (ncags && ncags.length > 0){ - const ncag = ncags.find(ncag => ncag.question == problem.problemName) - if (!ncag || !ncag.metadata) { - return undefined - } - return JSON.parse(ncag.metadata) + ncags.find(ncag => ncag.question == problem.problemName) + return undefined // todo metadata moved to assignment problem } } @@ -37,14 +34,14 @@ const AssignmentProblemListItem = ({problem, handleChange, disabled}: Props) => }, []) const meta = getMeta() - if (!meta || !meta.type){ + if (!meta){ return (
Metadata missing, error!
) } - const type = meta.type + const type = null // todo metadata moved if (type == "Text") { return (
@@ -60,7 +57,7 @@ const AssignmentProblemListItem = ({problem, handleChange, disabled}: Props) =>
)} else if(type == "MCQ") { - const options = meta.options + const options = null // todo metadata moved if (!options){ return
} diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx index 2b912079..2878c3ac 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx @@ -78,6 +78,7 @@ const AssignmentUpdatePage = () => { assignmentId: currentAssignmentId, problemName: '', maxScore: -1, + metadata: "", // todo new metadata field }) From 11d01477d8d5f8f405f02e4da87bd2703d4b772b Mon Sep 17 00:00:00 2001 From: RA341 Date: Sun, 6 Apr 2025 13:59:17 -0400 Subject: [PATCH 4/7] added default values for columns --- devU-api/src/fileUpload/fileUpload.model.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/devU-api/src/fileUpload/fileUpload.model.ts b/devU-api/src/fileUpload/fileUpload.model.ts index 2958d727..a3e3970c 100644 --- a/devU-api/src/fileUpload/fileUpload.model.ts +++ b/devU-api/src/fileUpload/fileUpload.model.ts @@ -27,19 +27,19 @@ export default class FileModel { @DeleteDateColumn({ name: 'deleted_at' }) deletedAt?: Date - @Column({ name: 'etag' }) + @Column({ name: 'etag', default: "" }) etag: string - @Column({ name: 'name' }) + @Column({ name: 'name' , default: ""}) name: string - @Column({ name: 'type' }) + @Column({ name: 'type' , default: ""}) type: string - @Column({ name: 'filename', length: 128 }) + @Column({ name: 'filename', length: 128 , default: ""}) filename: string - @Column({ name: 'bucket', length: 64 }) + @Column({ name: 'bucket', length: 64 , default: ""}) fieldName: string @Column({ name: 'course_id' }) From eb846453b293928c760e27618ab6daf7607014a7 Mon Sep 17 00:00:00 2001 From: RA341 Date: Sun, 6 Apr 2025 13:59:34 -0400 Subject: [PATCH 5/7] updated ncag to remove metdata field --- .../nonContainerAutoGrader.controller.ts | 9 ++++++++- .../nonContainerAutoGrader.model.ts | 6 ------ .../nonContainerAutoGrader.serializer.ts | 3 +-- .../nonContainerAutoGrader.service.ts | 18 ++++++++++++++++-- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.controller.ts b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.controller.ts index a0cea1dc..b89aed60 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.controller.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.controller.ts @@ -42,7 +42,14 @@ export async function detail(req: Request, res: Response, next: NextFunction) { export async function post(req: Request, res: Response, next: NextFunction) { try { - const nonContainer = await NonContainerAutoGraderService.create(req.body) + req.body.assignmentId = parseInt(req.params.assignmentId) + const nonContainer = await NonContainerAutoGraderService.create( + req.body.assignmentId, + req.body.question, + req.body.score, + req.body.isRegex, + req.body.correctString + ) const response = serialize(nonContainer) res.status(201).json(response) diff --git a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.model.ts b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.model.ts index 8f79626a..0287e51e 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.model.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.model.ts @@ -27,9 +27,6 @@ export default class NonContainerAutoGraderModel { * type: integer * question: * type: string - * metadata: - * type: any - * description: this contains a valid json string tha contains info about any arbitrary question type (MCQ, Fill in the blanks etc.) * score: * type: number * correctString: @@ -56,9 +53,6 @@ export default class NonContainerAutoGraderModel { @Column({ name: 'question', length: 128 }) question: string - @Column({ name: 'metadata', type: 'jsonb', nullable: true, default: {} }) - metadata: any // use any since this can be any arbitrary structure - @Column({ name: 'score' }) score: number diff --git a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.serializer.ts b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.serializer.ts index 4db25712..93785ef1 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.serializer.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.serializer.ts @@ -8,9 +8,8 @@ export function serialize(nonContainerAutoGrader: NonContainerAutoGraderModel): assignmentId: nonContainerAutoGrader.assignmentId, question: nonContainerAutoGrader.question, score: nonContainerAutoGrader.score, - metadata: JSON.stringify(nonContainerAutoGrader.metadata ?? ''), - correctString: nonContainerAutoGrader.correctString, isRegex: nonContainerAutoGrader.isRegex, + correctString: nonContainerAutoGrader.correctString, createdAt: nonContainerAutoGrader.createdAt.toISOString(), updatedAt: nonContainerAutoGrader.updatedAt.toISOString(), } diff --git a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.service.ts b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.service.ts index ca786586..7dde4522 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.service.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.service.ts @@ -5,8 +5,22 @@ import { NonContainerAutoGrader } from 'devu-shared-modules' const connect = () => dataSource.getRepository(NonContainerAutoGraderModel) -export async function create(nonContainerQuestion: NonContainerAutoGrader) { - return await connect().save(nonContainerQuestion) +export async function create( + assignmentId: number, + question: string, + score: number, + isRegex: boolean, + correctString: string, +) { + const nonContainerAutoGrader = { + assignmentId: assignmentId, + question: question, + score: score, + isRegex: isRegex, + correctString: correctString, + } + + return await connect().save(nonContainerAutoGrader) } export async function update(nonContainerQuestion: NonContainerAutoGrader) { From a7279a75dc73db6956c5e46e9c037cb30f06a75b Mon Sep 17 00:00:00 2001 From: RA341 Date: Sun, 6 Apr 2025 13:59:58 -0400 Subject: [PATCH 6/7] removed log --- devU-api/src/entities/assignment/assignment.service.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/devU-api/src/entities/assignment/assignment.service.ts b/devU-api/src/entities/assignment/assignment.service.ts index b8e89cd1..b8af698b 100644 --- a/devU-api/src/entities/assignment/assignment.service.ts +++ b/devU-api/src/entities/assignment/assignment.service.ts @@ -112,8 +112,6 @@ async function processFiles(req: Request) { } else { console.warn(`Files where not in array format ${req.files}`) } - } else { - console.warn(`No files where processed`) } return { fileHashes, fileNames } From 606eb40008f68aabce700fa0e5b272c368361c5c Mon Sep 17 00:00:00 2001 From: RA341 Date: Sun, 6 Apr 2025 14:00:14 -0400 Subject: [PATCH 7/7] updated assignment problem to add metadata --- .../assignmentProblem.controller.ts | 14 ++- .../assignmentProblem.model.ts | 6 ++ .../assignmentProblem.router.ts | 88 ++++++++++++++++++- .../assignmentProblem.serializer.ts | 1 + .../assignmentProblem.service.ts | 22 +++-- .../assignmentProblem.validator.ts | 6 +- 6 files changed, 124 insertions(+), 13 deletions(-) diff --git a/devU-api/src/entities/assignmentProblem/assignmentProblem.controller.ts b/devU-api/src/entities/assignmentProblem/assignmentProblem.controller.ts index ef6553ac..44e8ae5b 100644 --- a/devU-api/src/entities/assignmentProblem/assignmentProblem.controller.ts +++ b/devU-api/src/entities/assignmentProblem/assignmentProblem.controller.ts @@ -1,4 +1,4 @@ -import { Request, Response, NextFunction } from 'express' +import { NextFunction, Request, Response } from 'express' import AssignmentProblemService from './assignmentProblem.service' @@ -33,15 +33,21 @@ export async function detail(req: Request, res: Response, next: NextFunction) { } } -export async function post(req: Request, res: Response, next: NextFunction) { +export async function post(req: Request, res: Response, _: NextFunction) { try { - const assignmentProblem = await AssignmentProblemService.create(req.body) + req.body.assignmentId = parseInt(req.params.assignmentId) + const assignmentProblem = await AssignmentProblemService.create( + req.body.assignmentId, + req.body.problemName, + req.body.maxScore, + req.body.metadata, + ) const response = serialize(assignmentProblem) res.status(201).json(response) } catch (err) { if (err instanceof Error) { - res.status(400).json(new GenericResponse(err.message)) + res.status(400).json(new GenericResponse(err.message)) } } } diff --git a/devU-api/src/entities/assignmentProblem/assignmentProblem.model.ts b/devU-api/src/entities/assignmentProblem/assignmentProblem.model.ts index adda17a4..73e8da28 100644 --- a/devU-api/src/entities/assignmentProblem/assignmentProblem.model.ts +++ b/devU-api/src/entities/assignmentProblem/assignmentProblem.model.ts @@ -28,6 +28,9 @@ export default class AssignmentProblemModel { * type: integer * problemName: * type: string + * metadata: + * type: string + * description: A json string containing additional problem metadata * maxScore: * type: integer */ @@ -43,6 +46,9 @@ export default class AssignmentProblemModel { @Column({ name: 'problem_name', length: 128 }) problemName: string + @Column({ name: 'metadata', type: 'jsonb', nullable: true, default: {} }) + metadata: any // use any since this can be any arbitrary structure + @Column({ name: 'max_score' }) maxScore: number diff --git a/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts b/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts index 13b9bca4..00f7c36b 100644 --- a/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts +++ b/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts @@ -21,6 +21,24 @@ const Router = express.Router({ mergeParams: true }) * responses: * '200': * description: OK + * content: + * application/json: + * schema: + * type: array + * items: + * type: object + * properties: + * id: + * type: integer + * assignmentId: + * type: integer + * problemName: + * type: string + * metadata: + * type: string + * description: A json string containing additional problem metadata + * maxScore: + * type: integer * parameters: * - name: assignmentId * description: Enter Assignment Id @@ -43,6 +61,22 @@ Router.get('/', isAuthorized('enrolled'), AssignmentProblemController.get) * responses: * '200': * description: OK + * content: + * application/json: + * schema: + * type: object + * properties: + * id: + * type: integer + * assignmentId: + * type: integer + * problemName: + * type: string + * metadata: + * type: string + * description: A json string containing additional problem metadata + * maxScore: + * type: integer * parameters: * - name: id * description: Enter AssignmentProblem Id @@ -63,8 +97,33 @@ Router.get('/:id', isAuthorized('assignmentEditAll'), asInt(), AssignmentProblem * tags: * - AssignmentProblems * responses: - * '200': - * description: OK + * '201': + * description: Created + * content: + * application/json: + * schema: + * type: object + * properties: + * id: + * type: integer + * assignmentId: + * type: integer + * problemName: + * type: string + * metadata: + * type: string + * description: A json string containing additional problem metadata + * maxScore: + * type: integer + * '400': + * description: Bad Request + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string * requestBody: * content: * application/x-www-form-urlencoded: @@ -75,7 +134,7 @@ Router.post('/', isAuthorized('assignmentEditAll'), validator, AssignmentProblem /** * @swagger - * /course/:courseId/assignment/:assignmentId/assignment-problems: + * /course/:courseId/assignment/:assignmentId/assignment-problems/{id}: * put: * summary: Update an assignment problem * tags: @@ -83,6 +142,29 @@ Router.post('/', isAuthorized('assignmentEditAll'), validator, AssignmentProblem * responses: * '200': * description: OK + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * '404': + * description: Not Found + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * parameters: + * - name: id + * description: Enter AssignmentProblem Id + * in: path + * required: true + * schema: + * type: integer * requestBody: * content: * application/x-www-form-urlencoded: diff --git a/devU-api/src/entities/assignmentProblem/assignmentProblem.serializer.ts b/devU-api/src/entities/assignmentProblem/assignmentProblem.serializer.ts index 6f188c7f..be79bbdd 100644 --- a/devU-api/src/entities/assignmentProblem/assignmentProblem.serializer.ts +++ b/devU-api/src/entities/assignmentProblem/assignmentProblem.serializer.ts @@ -8,6 +8,7 @@ export function serialize(assignmentProblem: AssignmentProblemModel): Assignment assignmentId: assignmentProblem.assignmentId, problemName: assignmentProblem.problemName, maxScore: assignmentProblem.maxScore, + metadata: assignmentProblem.metadata, createdAt: assignmentProblem.createdAt.toISOString(), updatedAt: assignmentProblem.updatedAt.toISOString(), } diff --git a/devU-api/src/entities/assignmentProblem/assignmentProblem.service.ts b/devU-api/src/entities/assignmentProblem/assignmentProblem.service.ts index 7eff05e6..da656bdd 100644 --- a/devU-api/src/entities/assignmentProblem/assignmentProblem.service.ts +++ b/devU-api/src/entities/assignmentProblem/assignmentProblem.service.ts @@ -7,10 +7,6 @@ import { AssignmentProblem } from 'devu-shared-modules' const connect = () => dataSource.getRepository(AssignmentProblemModel) -export async function create(assignmentProblem: AssignmentProblem) { - return await connect().save(assignmentProblem) -} - export async function update(assignmentProblem: AssignmentProblem) { const { id, assignmentId, problemName, maxScore } = assignmentProblem if (!id) throw new Error('Missing Id') @@ -30,10 +26,26 @@ export async function list(assignmentId: number) { return await connect().findBy({ assignmentId: assignmentId, deletedAt: IsNull() }) } +export async function create( + assignmentId: number, + problemName: string, + maxScore: number, + metadata?: any, +) { + const assignmentProblem = { + assignmentId: assignmentId, + problemName: problemName, + maxScore: maxScore, + metadata: metadata, + } + + return await connect().save(assignmentProblem) +} + export default { - create, retrieve, update, _delete, list, + create, } diff --git a/devU-api/src/entities/assignmentProblem/assignmentProblem.validator.ts b/devU-api/src/entities/assignmentProblem/assignmentProblem.validator.ts index 1e162f09..70d8a16a 100644 --- a/devU-api/src/entities/assignmentProblem/assignmentProblem.validator.ts +++ b/devU-api/src/entities/assignmentProblem/assignmentProblem.validator.ts @@ -5,7 +5,11 @@ import validate from '../../middleware/validator/generic.validator' const assignmentId = check('assignmentId').isNumeric() const problemName = check('problemName').isString().trim().isLength({ max: 128 }) const maxScore = check('maxScore').isNumeric() +const metadata = check('metadata') + .optional({ nullable: true }) + .isObject() + .default({}) -const validator = [assignmentId, problemName, maxScore, validate] +const validator = [assignmentId, problemName, maxScore, metadata, validate] export default validator