diff --git a/devU-api/src/entities/role/role.defaults.ts b/devU-api/src/entities/role/role.defaults.ts index dbf90ea..ffe5f7f 100644 --- a/devU-api/src/entities/role/role.defaults.ts +++ b/devU-api/src/entities/role/role.defaults.ts @@ -18,6 +18,8 @@ const student: Role = { submissionCreateAll: false, submissionCreateSelf: true, submissionViewAll: false, + stickyNoteViewAll: false, + stickyNoteEditAll: false, userCourseEditAll: false, } @@ -39,6 +41,8 @@ const instructor: Role = { submissionCreateAll: true, submissionCreateSelf: true, submissionViewAll: true, + stickyNoteViewAll: true, + stickyNoteEditAll: true, userCourseEditAll: true, } diff --git a/devU-api/src/entities/role/role.model.ts b/devU-api/src/entities/role/role.model.ts index 05a1341..76920b4 100644 --- a/devU-api/src/entities/role/role.model.ts +++ b/devU-api/src/entities/role/role.model.ts @@ -107,4 +107,10 @@ export default class RoleModel { @Column({ name: 'user_course_edit_all' }) // TODO: Don't let the last instructor change their role userCourseEditAll: boolean + + @Column({ name: 'sticky_note_view_all' }) + stickyNoteViewAll: boolean + + @Column({ name: 'sticky_note_edit_all' }) + stickyNoteEditAll: boolean } diff --git a/devU-api/src/entities/role/role.serializer.ts b/devU-api/src/entities/role/role.serializer.ts index ab6ead3..6b999ef 100644 --- a/devU-api/src/entities/role/role.serializer.ts +++ b/devU-api/src/entities/role/role.serializer.ts @@ -26,5 +26,7 @@ export function serialize(role: RoleModel): Role { submissionCreateSelf: role.submissionCreateSelf, submissionViewAll: role.submissionViewAll, userCourseEditAll: role.userCourseEditAll, + stickyNoteViewAll: role.stickyNoteViewAll, + stickyNoteEditAll: role.stickyNoteEditAll, } } diff --git a/devU-api/src/entities/stickyNote/stickyNote.controller.ts b/devU-api/src/entities/stickyNote/stickyNote.controller.ts new file mode 100644 index 0000000..29aab12 --- /dev/null +++ b/devU-api/src/entities/stickyNote/stickyNote.controller.ts @@ -0,0 +1,79 @@ +import {NextFunction, Request, Response} from 'express' + +import StickyNoteService from './stickyNote.service' + +import {NotFound, Updated} from '../../utils/apiResponse.utils' + +import {serialize} from './stickyNote.serializer' + +export async function retrieve(req: Request, res: Response, next: NextFunction) { + try { + const id = parseInt(req.params.id) + const stickyNote = await StickyNoteService.retrieve(id) + + if (!stickyNote) return res.status(404).json(NotFound) + + const response = serialize(stickyNote) + + res.status(200).json(response) + } catch (err) { + next(err) + } +} + +export async function post(req: Request, res: Response, next: NextFunction) { + try { + const reqStickyNote = req.body + const stickyNote = await StickyNoteService.create(reqStickyNote) + const response = serialize(stickyNote) + + res.status(201).json(response) + } catch (err) { + next(err) + } +} + +export async function put(req: Request, res: Response, next: NextFunction) { + try { + const id = parseInt(req.params.id) + const reqStickyNote = req.body + const stickyNote = await StickyNoteService.update(id, reqStickyNote) + + if (!stickyNote.affected) return res.status(404).json(NotFound) + + res.status(200).json(Updated) + } catch (err) { + next(err) + } +} + +export async function remove(req: Request, res: Response, next: NextFunction) { + try { + const id = parseInt(req.params.id) + await StickyNoteService._delete(id) + + res.status(204).send() + } catch (err) { + next(err) + } +} + +export async function listBySubmission(req: Request, res: Response, next: NextFunction) { + try { + const submissionId = parseInt(req.params.submissionId) + const stickyNotes = await StickyNoteService.listBySubmission(submissionId) + const response = stickyNotes.map(serialize) + + res.status(200).json(response) + } catch (err) { + next(err) + } +} + +export default { + retrieve, + post, + put, + remove, + listBySubmission, +} \ No newline at end of file diff --git a/devU-api/src/entities/stickyNote/stickyNote.model.ts b/devU-api/src/entities/stickyNote/stickyNote.model.ts new file mode 100644 index 0000000..f6ce4c9 --- /dev/null +++ b/devU-api/src/entities/stickyNote/stickyNote.model.ts @@ -0,0 +1,28 @@ +import { + JoinColumn, + ManyToOne, + Entity, + Column, + DeleteDateColumn, + PrimaryGeneratedColumn, +} from 'typeorm' + +import SubmissionModel from '../submission/submission.model' + +@Entity('sticky_notes') +export default class StickyNotesModel { + + @PrimaryGeneratedColumn() + id: number + + @Column({ name: 'submissionId' }) + @JoinColumn({ name: 'submissionId' }) + @ManyToOne(() => SubmissionModel) + submissionId: number + + @Column({ name: 'content' }) + content: string + + @DeleteDateColumn({ name: 'deleted_at' }) + deletedAt?: Date +} \ No newline at end of file diff --git a/devU-api/src/entities/stickyNote/stickyNote.router.ts b/devU-api/src/entities/stickyNote/stickyNote.router.ts new file mode 100644 index 0000000..34dbf49 --- /dev/null +++ b/devU-api/src/entities/stickyNote/stickyNote.router.ts @@ -0,0 +1,25 @@ +import express from 'express' + +// Middleware +import validator from './stickyNote.validator' +import { isAuthorized } from '../../authorization/authorization.middleware' +import { asInt } from '../../middleware/validator/generic.validator' + +// Controller +import StickyNoteController from './stickyNote.controller' + +const Router = express.Router({ mergeParams: true }) + +Router.get('/all', isAuthorized("stickyNoteViewAll"), validator , StickyNoteController.listBySubmission) + +Router.get('/:id' ,isAuthorized("stickyNoteViewAll") , asInt("id"), validator , StickyNoteController.retrieve) + +Router.post('/',isAuthorized("stickyNoteEditAll") ,validator, StickyNoteController.post) + +Router.put('/:id',isAuthorized("stickyNoteEditAll") , validator, StickyNoteController.put) + +Router.delete('/:id',isAuthorized("stickyNoteEditAll") , asInt("id"), validator, StickyNoteController.remove) + +export default Router + + diff --git a/devU-api/src/entities/stickyNote/stickyNote.serializer.ts b/devU-api/src/entities/stickyNote/stickyNote.serializer.ts new file mode 100644 index 0000000..72b0cd7 --- /dev/null +++ b/devU-api/src/entities/stickyNote/stickyNote.serializer.ts @@ -0,0 +1,11 @@ +import {StickyNote} from 'devu-shared-modules' + +import StickyNoteModel from './stickyNote.model' + +export function serialize(stickyNote: StickyNoteModel): StickyNote { + return { + id: stickyNote.id, + submissionId: stickyNote.submissionId, + content: stickyNote.content, + } +} \ No newline at end of file diff --git a/devU-api/src/entities/stickyNote/stickyNote.service.ts b/devU-api/src/entities/stickyNote/stickyNote.service.ts new file mode 100644 index 0000000..eca8b17 --- /dev/null +++ b/devU-api/src/entities/stickyNote/stickyNote.service.ts @@ -0,0 +1,37 @@ +import {IsNull} from 'typeorm' +import {dataSource} from '../../database' + +import StickyNotesModel from './stickyNote.model' +import {StickyNote} from 'devu-shared-modules' + +const StickyNoteConn = () => dataSource.getRepository(StickyNotesModel) + +export async function create(stickyNote: StickyNote) { + return await StickyNoteConn().save(stickyNote) +} + +export async function update(id : number,stickyNote: StickyNote) { + const {submissionId, content} = stickyNote + if (!id) throw new Error('Missing Id') + return await StickyNoteConn().update(id, {submissionId, content}) +} + +export async function _delete(id: number) { + return await StickyNoteConn().softDelete({id, deletedAt: IsNull()}) +} + +export async function retrieve(id: number) { + return await StickyNoteConn().findOneBy({id, deletedAt: IsNull()}) +} + +export async function listBySubmission(submissionId: number) { + return await StickyNoteConn().findBy({submissionId: submissionId , deletedAt: IsNull()}) +} + +export default { + create, + update, + _delete, + retrieve, + listBySubmission, +} \ No newline at end of file diff --git a/devU-api/src/entities/stickyNote/stickyNote.validator.ts b/devU-api/src/entities/stickyNote/stickyNote.validator.ts new file mode 100644 index 0000000..74902a4 --- /dev/null +++ b/devU-api/src/entities/stickyNote/stickyNote.validator.ts @@ -0,0 +1,10 @@ +import {check} from 'express-validator' + +import validate from '../../middleware/validator/generic.validator' + +const submissionId = check('submissionId').isNumeric() +const content = check('content').isString() + +const validator = [submissionId, content, validate] + +export default validator \ No newline at end of file diff --git a/devU-api/src/migration/1731427638811-add-sticky-note-endpoints.ts b/devU-api/src/migration/1731427638811-add-sticky-note-endpoints.ts new file mode 100644 index 0000000..0b0100f --- /dev/null +++ b/devU-api/src/migration/1731427638811-add-sticky-note-endpoints.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddStickyNoteEndpoints1731427638811 implements MigrationInterface { + name = 'AddStickyNoteEndpoints1731427638811' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "sticky_notes" ADD "deleted_at" TIMESTAMP`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "sticky_notes" DROP COLUMN "deleted_at"`); + } + +} diff --git a/devU-api/src/migration/1731433278166-updatedRolesWithStickyNotes.ts b/devU-api/src/migration/1731433278166-updatedRolesWithStickyNotes.ts new file mode 100644 index 0000000..c8e6636 --- /dev/null +++ b/devU-api/src/migration/1731433278166-updatedRolesWithStickyNotes.ts @@ -0,0 +1,16 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class UpdatedRolesWithStickyNotes1731433278166 implements MigrationInterface { + name = 'UpdatedRolesWithStickyNotes1731433278166' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "role" ADD "sticky_note_view_all" boolean NOT NULL`); + await queryRunner.query(`ALTER TABLE "role" ADD "sticky_note_edit_all" boolean NOT NULL`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "role" DROP COLUMN "sticky_note_edit_all"`); + await queryRunner.query(`ALTER TABLE "role" DROP COLUMN "sticky_note_view_all"`); + } + +} diff --git a/devU-api/src/router/courseData.router.ts b/devU-api/src/router/courseData.router.ts index 174d0d7..c388ee8 100644 --- a/devU-api/src/router/courseData.router.ts +++ b/devU-api/src/router/courseData.router.ts @@ -14,13 +14,19 @@ import categoryScores from '../entities/categoryScore/categoryScore.router' import courseScores from '../entities/courseScore/courseScore.router' import assignmentScore from '../entities/assignmentScore/assignmentScore.router' import role from '../entities/role/role.router' +import stickyNotes from '../entities/stickyNote/stickyNote.router' import nonContainerAutoGraderRouter from '../entities/nonContainerAutoGrader/nonContainerAutoGrader.router' import { asInt } from '../middleware/validator/generic.validator' import webhooksRouter from '../entities/webhooks/webhooks.router' +const submissionRouter = express.Router({ mergeParams: true }) +submissionRouter.use('/sticky-notes', stickyNotes) + const assignmentRouter = express.Router({ mergeParams: true }) +assignmentRouter.use('/submission/:submissionId', asInt('submissionId'), submissionRouter) + assignmentRouter.use('/assignment-problems', assignmentProblem) assignmentRouter.use('/container-auto-graders', containerAutoGrader) assignmentRouter.use('/deadline-extensions', deadlineExtensions) diff --git a/devU-shared/src/index.ts b/devU-shared/src/index.ts index 464b8ba..20cc1a8 100644 --- a/devU-shared/src/index.ts +++ b/devU-shared/src/index.ts @@ -22,6 +22,7 @@ export * from './types/deadlineExtensions.types' export * from './types/grader.types' export * from './types/role.types' export * from './types/webhooks.types' +export * from './types/stickyNote.types' export * from './utils/object.utils' export * from './utils/string.utils' diff --git a/devU-shared/src/types/role.types.ts b/devU-shared/src/types/role.types.ts index 38c3e53..4877598 100644 --- a/devU-shared/src/types/role.types.ts +++ b/devU-shared/src/types/role.types.ts @@ -20,4 +20,6 @@ export type Role = { submissionCreateSelf: boolean submissionViewAll: boolean userCourseEditAll: boolean + stickyNoteViewAll: boolean + stickyNoteEditAll: boolean } diff --git a/devU-shared/src/types/stickyNote.types.ts b/devU-shared/src/types/stickyNote.types.ts new file mode 100644 index 0000000..bf7cc76 --- /dev/null +++ b/devU-shared/src/types/stickyNote.types.ts @@ -0,0 +1,5 @@ +export type StickyNote = { + id?: number + submissionId: number + content: string +} \ No newline at end of file