diff --git a/prisma/migrations/20240731134520_add_student_exercise_relation/migration.sql b/prisma/migrations/20240731134520_add_student_exercise_relation/migration.sql new file mode 100644 index 00000000..1b39fc70 --- /dev/null +++ b/prisma/migrations/20240731134520_add_student_exercise_relation/migration.sql @@ -0,0 +1,135 @@ +-- CreateTable +CREATE TABLE "Course" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3), + + CONSTRAINT "Course_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Module" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3), + + CONSTRAINT "Module_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Unit" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "moduleId" INTEGER NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3), + + CONSTRAINT "Unit_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Exercise" ( + "id" SERIAL NOT NULL, + "title" TEXT NOT NULL, + "description" TEXT NOT NULL, + "githubRepo" TEXT NOT NULL, + "unitId" INTEGER NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3), + + CONSTRAINT "Exercise_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "StudentExercise" ( + "id" SERIAL NOT NULL, + "exerciseId" INTEGER NOT NULL, + "studentId" INTEGER NOT NULL, + "isComplete" BOOLEAN NOT NULL DEFAULT false, + + CONSTRAINT "StudentExercise_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Tag" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3), + + CONSTRAINT "Tag_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "_CohortToCourse" ( + "A" INTEGER NOT NULL, + "B" INTEGER NOT NULL +); + +-- CreateTable +CREATE TABLE "_CourseToModule" ( + "A" INTEGER NOT NULL, + "B" INTEGER NOT NULL +); + +-- CreateTable +CREATE TABLE "_ExerciseToTag" ( + "A" INTEGER NOT NULL, + "B" INTEGER NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "Unit_moduleId_key" ON "Unit"("moduleId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Exercise_unitId_key" ON "Exercise"("unitId"); + +-- CreateIndex +CREATE UNIQUE INDEX "_CohortToCourse_AB_unique" ON "_CohortToCourse"("A", "B"); + +-- CreateIndex +CREATE INDEX "_CohortToCourse_B_index" ON "_CohortToCourse"("B"); + +-- CreateIndex +CREATE UNIQUE INDEX "_CourseToModule_AB_unique" ON "_CourseToModule"("A", "B"); + +-- CreateIndex +CREATE INDEX "_CourseToModule_B_index" ON "_CourseToModule"("B"); + +-- CreateIndex +CREATE UNIQUE INDEX "_ExerciseToTag_AB_unique" ON "_ExerciseToTag"("A", "B"); + +-- CreateIndex +CREATE INDEX "_ExerciseToTag_B_index" ON "_ExerciseToTag"("B"); + +-- AddForeignKey +ALTER TABLE "Unit" ADD CONSTRAINT "Unit_moduleId_fkey" FOREIGN KEY ("moduleId") REFERENCES "Module"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Exercise" ADD CONSTRAINT "Exercise_unitId_fkey" FOREIGN KEY ("unitId") REFERENCES "Unit"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "StudentExercise" ADD CONSTRAINT "StudentExercise_exerciseId_fkey" FOREIGN KEY ("exerciseId") REFERENCES "Exercise"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "StudentExercise" ADD CONSTRAINT "StudentExercise_studentId_fkey" FOREIGN KEY ("studentId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_CohortToCourse" ADD CONSTRAINT "_CohortToCourse_A_fkey" FOREIGN KEY ("A") REFERENCES "Cohort"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_CohortToCourse" ADD CONSTRAINT "_CohortToCourse_B_fkey" FOREIGN KEY ("B") REFERENCES "Course"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_CourseToModule" ADD CONSTRAINT "_CourseToModule_A_fkey" FOREIGN KEY ("A") REFERENCES "Course"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_CourseToModule" ADD CONSTRAINT "_CourseToModule_B_fkey" FOREIGN KEY ("B") REFERENCES "Module"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_ExerciseToTag" ADD CONSTRAINT "_ExerciseToTag_A_fkey" FOREIGN KEY ("A") REFERENCES "Exercise"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_ExerciseToTag" ADD CONSTRAINT "_ExerciseToTag_B_fkey" FOREIGN KEY ("B") REFERENCES "Tag"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 7f322c7e..9e0520db 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -16,19 +16,20 @@ enum UserRole { } model User { - id Int @id @default(autoincrement()) - email String @unique - password String - role UserRole @default(STUDENT) - profile Profile? - comments Comment[] + id Int @id @default(autoincrement()) + email String @unique + password String + role UserRole @default(STUDENT) + profile Profile? + comments Comment[] postLikes PostReaction[] - cohortId Int? - cohort Cohort? @relation(fields: [cohortId], references: [id]) - posts Post[] - deliveryLogs DeliveryLog[] - notesCreated Note[] @relation("TeacherNotes") - notesReceived Note[] @relation("StudentNotes") + cohortId Int? + cohort Cohort? @relation(fields: [cohortId], references: [id]) + posts Post[] + deliveryLogs DeliveryLog[] + notesCreated Note[] @relation("TeacherNotes") + notesReceived Note[] @relation("StudentNotes") + studentExercise StudentExercise[] } model Profile { @@ -46,6 +47,7 @@ model Profile { model Cohort { id Int @id @default(autoincrement()) name String? + courses Course[] users User[] deliveryLogs DeliveryLog[] startDate DateTime? @db.Date @@ -54,6 +56,64 @@ model Cohort { updatedAt DateTime? @updatedAt } +model Course { + id Int @id @default(autoincrement()) + name String + modules Module[] + cohorts Cohort[] + createdAt DateTime @default(now()) + updatedAt DateTime? @updatedAt +} + +model Module { + id Int @id @default(autoincrement()) + name String + units Unit[] + courses Course[] + createdAt DateTime @default(now()) + updatedAt DateTime? @updatedAt +} + +model Unit { + id Int @id @default(autoincrement()) + name String + module Module @relation(fields: [moduleId], references: [id]) + moduleId Int @unique + exercises Exercise[] + createdAt DateTime @default(now()) + updatedAt DateTime? @updatedAt +} + +model Exercise { + id Int @id @default(autoincrement()) + title String + description String + githubRepo String + tags Tag[] + unit Unit @relation(fields: [unitId], references: [id]) + unitId Int @unique + createdAt DateTime @default(now()) + updatedAt DateTime? @updatedAt + studentExercise StudentExercise[] +} + +model StudentExercise { + id Int @id @default(autoincrement()) + exercise Exercise @relation(fields: [exerciseId], references: [id]) + exerciseId Int + student User @relation(fields: [studentId], references: [id]) + studentId Int + isComplete Boolean @default(false) +} + +model Tag { + id Int @id @default(autoincrement()) + name String + exercises Exercise[] + createdAt DateTime @default(now()) + updatedAt DateTime? @updatedAt +} + model Post { id Int @id @default(autoincrement()) content String diff --git a/prisma/seed.js b/prisma/seed.js index 04f2c829..dfd251cd 100644 --- a/prisma/seed.js +++ b/prisma/seed.js @@ -51,7 +51,9 @@ async function createPost(userId, content) { async function createCohort() { const cohort = await prisma.cohort.create({ - data: {} + data: { + name: 'Cohort 1' + } }) console.info('Cohort created', cohort) diff --git a/src/controllers/cohort.js b/src/controllers/cohort.js index 91e8fd5a..5a5fa8ae 100644 --- a/src/controllers/cohort.js +++ b/src/controllers/cohort.js @@ -5,11 +5,12 @@ import ERR from '../utils/errors.js' import { dateRegex } from '../utils/regexMatchers.js' export const create = async (req, res) => { - const { startDate, endDate } = req.body + const { name, startDate, endDate } = req.body - if (!startDate || !endDate) { + if (!name || !startDate || !endDate) { return sendMessageResponse(res, 400, { error: ERR.DATE_REQUIRED }) } + if (!dateRegex.test(startDate) || !dateRegex.test(endDate)) { return sendMessageResponse(res, 400, { error: ERR.DATE_FORMATTING }) } @@ -19,7 +20,7 @@ export const create = async (req, res) => { startDate, endDate ) - const createdCohort = await createCohort(parsedStartDate, parsedEndDate) + const createdCohort = createCohort(name, parsedStartDate, parsedEndDate) return sendDataResponse(res, 201, createdCohort) } catch (e) { diff --git a/src/controllers/course.js b/src/controllers/course.js new file mode 100644 index 00000000..78e92b05 --- /dev/null +++ b/src/controllers/course.js @@ -0,0 +1,27 @@ +import dbClient from '../utils/dbClient.js' +import { sendDataResponse, sendMessageResponse } from '../utils/responses.js' +import ERR from '../utils/errors.js' + +const getAllCourses = async (req, res) => { + const courses = await dbClient.course.findMany() + + return sendDataResponse(res, 200, courses) +} + +const createCourse = async (req, res) => { + const { name } = req.body + + if (!name) { + return sendMessageResponse(res, 400, { error: ERR.INCOMPLETE_REQUEST }) + } + + const course = await dbClient.course.create({ + data: { + name: name + } + }) + + return sendDataResponse(res, 201, course) +} + +export { getAllCourses, createCourse } diff --git a/src/controllers/user.js b/src/controllers/user.js index 65016051..30dff909 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -18,10 +18,10 @@ export const create = async (req, res) => { const existingUser = await User.findByEmail(userToCreate.email) if (existingUser) { - return sendDataResponse(res, 400, { error: ERR.EMAIL_IN_USE }) + return sendMessageResponse(res, 400, { error: ERR.EMAIL_IN_USE }) } - const createdUser = await userToCreate.save() + const createdUser = userToCreate.save() return sendDataResponse(res, 201, createdUser) } catch (error) { diff --git a/src/domain/cohort.js b/src/domain/cohort.js index b4fdf1d2..55536180 100644 --- a/src/domain/cohort.js +++ b/src/domain/cohort.js @@ -4,9 +4,10 @@ import dbClient from '../utils/dbClient.js' * Create a new Cohort in the database * @returns {Cohort} */ -export async function createCohort(startDate, endDate) { +export async function createCohort(name, startDate, endDate) { const createdCohort = await dbClient.cohort.create({ data: { + name, startDate, endDate }, @@ -28,6 +29,7 @@ export class Cohort { return { cohort: { id: this.id, + name: this.name, startDate: this.startDate, endDate: this.endDate } diff --git a/src/routes/course.js b/src/routes/course.js new file mode 100644 index 00000000..c8f63d8b --- /dev/null +++ b/src/routes/course.js @@ -0,0 +1,10 @@ +import { Router } from 'express' +import { validateAuthentication } from '../middleware/auth.js' +import { createCourse, getAllCourses } from '../controllers/course.js' + +const route = Router() + +route.get('/', validateAuthentication, getAllCourses) +route.post('/', validateAuthentication, createCourse) + +export default route diff --git a/src/server.js b/src/server.js index 8db5ed08..38241482 100644 --- a/src/server.js +++ b/src/server.js @@ -11,6 +11,7 @@ import cohortRouter from './routes/cohort.js' import deliveryLogRouter from './routes/deliveryLog.js' import noteRouter from './routes/note.js' import commentRouter from './routes/comment.js' +import courseRouter from './routes/course.js' import reactionsRouter from './routes/postReactions.js' const app = express() @@ -29,6 +30,7 @@ app.use('/posts', postRouter) app.use('/comments', commentRouter) app.use('/react', reactionsRouter) app.use('/cohorts', cohortRouter) +app.use('/courses', courseRouter) app.use('/logs', deliveryLogRouter) app.use('/notes', noteRouter) app.use('/', authRouter)