From 2e64f12941fdea09d18a17d848715a8f9c5fbc13 Mon Sep 17 00:00:00 2001 From: BryannYeap Date: Thu, 12 Jan 2023 17:00:59 +0800 Subject: [PATCH 1/7] feat: Update schema to support sorting game --- prisma/schema.prisma | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e4c077c6..7c991943 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -190,6 +190,7 @@ model Image { spotTheDifferenceGameLeft SpotTheDifferenceGame? @relation("LeftImage") spotTheDifferenceGameRight SpotTheDifferenceGame? @relation("RightImage") Course Course[] + SortingGameObject SortingGameObject[] @@map("images") } @@ -242,6 +243,36 @@ model MatchingGame { model SortingGame { gameId String @id game Game @relation(fields: [gameId], references: [assetId], onDelete: Cascade) + + correctPairs SortingGameObjectBucketPair[] +} + +model SortingGameObjectBucketPair { + id String @id + + object SortingGameObject[] + + bucket SortingGameBucket? + + SortingGame SortingGame? @relation(fields: [sortingGameGameId], references: [gameId]) + sortingGameGameId String? +} + +model SortingGameObject { + id String @id + text String? + image Image? @relation(fields: [imageId], references: [assetId], onDelete: Cascade) + imageId String? + + bucketPair SortingGameObjectBucketPair @relation(fields: [bucketPairId], references: [id]) + bucketPairId String +} + +model SortingGameBucket { + id String @id + + bucketPair SortingGameObjectBucketPair @relation(fields: [bucketPairId], references: [id]) + bucketPairId String @unique } enum UserType { From 8f290b98b215b74ded77dd83faa08951b8122ea4 Mon Sep 17 00:00:00 2001 From: BryannYeap Date: Fri, 13 Jan 2023 16:40:06 +0800 Subject: [PATCH 2/7] feat: Add skeleton for sorting game API --- lib/server/sorting.ts | 86 +++++++++++++++++++++++++++++++++ pages/api/sorting-game/[id].ts | 67 +++++++++++++++++++++++++ pages/api/sorting-game/index.ts | 35 ++++++++++++++ prisma/schema.prisma | 28 ++++++++--- utils/sorting-game-validator.ts | 58 ++++++++++++++++++++++ 5 files changed, 266 insertions(+), 8 deletions(-) create mode 100644 lib/server/sorting.ts create mode 100644 pages/api/sorting-game/[id].ts create mode 100644 pages/api/sorting-game/index.ts create mode 100644 utils/sorting-game-validator.ts diff --git a/lib/server/sorting.ts b/lib/server/sorting.ts new file mode 100644 index 00000000..2359b668 --- /dev/null +++ b/lib/server/sorting.ts @@ -0,0 +1,86 @@ +import { Prisma, GameType, AssetType, SortingGame, SortingGameObjectType } from '@prisma/client'; +import prisma from '../prisma'; + +export type SerializedObjectBucketPair = { + objects: { + objectType: SortingGameObjectType; + text: string | null; + imageId: string | null; + }[]; + bucket: { + description: string; + }; +}; + +export const createSorting = async (description: string, correctPairs: SerializedObjectBucketPair[]) => { + return (await prisma.sortingGame.create({ + data: { + game: { + create: { + type: GameType.sortingGame, + asset: { + create: { + assetType: AssetType.game, + }, + }, + }, + }, + description, + correctPairs: { + create: correctPairs.map(pair => ({ + objects: { + create: pair.objects.map(object => ({ + objectType: object.objectType, + text: object.text, + image: object.imageId && { + connect: { + assetId: object.imageId, + }, + }, + })), + }, + bucket: pair.bucket + ? { + create: { + description: pair.bucket.description, + }, + } + : null, + })), + }, + }, + })) as SortingGame; +}; + +export const findSorting = async (where?: Partial, select?: Prisma.SortingGameSelect) => { + return (await prisma.sortingGame.findMany({ + where, + select, + })) as SortingGame[]; +}; + +export const findUniqueSorting = async (where: Prisma.SortingGameWhereUniqueInput, select?: Prisma.SortingGameSelect) => { + return (await prisma.sortingGame.findUnique({ + where, + select, + })) as SortingGame; +}; + +export const updateSorting = async ( + where: Partial, + data: Prisma.SortingGameUpdateInput, + select?: Prisma.SortingGameSelect, +) => { + return (await prisma.sortingGame.update({ + where, + data, + select, + })) as SortingGame; +}; + +export const deleteSorting = async (where: Partial, select?: Prisma.SortingGameSelect) => { + return (await prisma.sortingGame.delete({ + where, + select, + })) as SortingGame; +}; diff --git a/pages/api/sorting-game/[id].ts b/pages/api/sorting-game/[id].ts new file mode 100644 index 00000000..e963fbae --- /dev/null +++ b/pages/api/sorting-game/[id].ts @@ -0,0 +1,67 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { deleteSorting, findUniqueSorting, updateSorting, SerializedObjectBucketPair } from '../../../lib/server/sorting'; +import { entityMessageCreator } from '../../../utils/api-messages'; +import { errorMessageHandler } from '../../../utils/error-message-handler'; +import validatePairs from '../../../utils/sorting-game-validator'; + +const entityMessageObj = entityMessageCreator('sortingGame'); + +export const handler = async (req: NextApiRequest, res: NextApiResponse) => { + try { + const httpMethod = req.method; + const gameId = req.query.id as string; + + if (httpMethod == 'GET') { + const game = await findUniqueSorting({ gameId }); + res.status(200).json({ message: entityMessageObj.getOneSuccess, data: game }); + } else if (httpMethod == 'DELETE') { + const deletedGame = await deleteSorting({ gameId }); + res.status(200).json({ message: entityMessageObj.deleteSuccess, data: deletedGame }); + } else if (httpMethod == 'PUT') { + const { description, correctPairs }: { description: string; correctPairs: SerializedObjectBucketPair[] } = req.body; + + // const result = validatePairs(correctPairs); + // if (!result.valid) { + // return res.status(400).end(`The input is not valid. ${result.message}`); + // } + + const updatedGame = await updateSorting( + { gameId }, + { + description, + correctPairs: { + create: correctPairs.map(pair => ({ + objects: { + create: pair.objects.map(object => ({ + objectType: object.objectType, + text: object.text, + image: object.imageId && { + connect: { + assetId: object.imageId, + }, + }, + })), + }, + bucket: pair.bucket + ? { + create: { + description: pair.bucket.description, + }, + } + : null, + })), + }, + }, + ); + res.status(200).json({ message: entityMessageObj.updateSuccess, data: updatedGame }); + } else { + res.setHeader('Allow', ['GET', 'DELETE', 'PUT']); + res.status(405).end(`Method ${httpMethod} not allowed`); + } + } catch (error) { + console.log(error); + res.status(500).json({ message: errorMessageHandler({ httpMethod: req.method, isSingleEntity: true }, entityMessageObj) }); + } +}; + +export default handler; diff --git a/pages/api/sorting-game/index.ts b/pages/api/sorting-game/index.ts new file mode 100644 index 00000000..9f22fa2f --- /dev/null +++ b/pages/api/sorting-game/index.ts @@ -0,0 +1,35 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { createSorting, findSorting, SerializedObjectBucketPair } from '../../../lib/server/sorting'; +import { entityMessageCreator } from '../../../utils/api-messages'; +import { errorMessageHandler } from '../../../utils/error-message-handler'; +import validatePairs from '../../../utils/sorting-game-validator'; + +const entityMessageObj = entityMessageCreator('sortingGame'); + +export const handler = async (req: NextApiRequest, res: NextApiResponse) => { + try { + const httpMethod = req.method; + if (httpMethod == 'GET') { + const games = await findSorting(); + res.status(200).json({ message: entityMessageObj.getAllSuccess, data: games }); + } else if (httpMethod == 'POST') { + const { description, correctPairs }: { description: string; correctPairs: SerializedObjectBucketPair[] } = req.body; + + // const result = validatePairs(correctPairs); + // if (!result.valid) { + // return res.status(400).end(`The input is not valid. ${result.message}`); + // } + + const created = await createSorting(description, correctPairs); + res.status(200).json({ message: entityMessageObj.createSuccess, data: created }); + } else { + res.setHeader('Allow', ['GET', 'POST']); + res.status(405).end(`Method ${httpMethod} not allowed`); + } + } catch (error) { + console.log(error); + res.status(500).json({ message: errorMessageHandler({ httpMethod: req.method }, entityMessageObj) }); + } +}; + +export default handler; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 7c991943..5f363fdd 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -244,13 +244,15 @@ model SortingGame { gameId String @id game Game @relation(fields: [gameId], references: [assetId], onDelete: Cascade) + description String + correctPairs SortingGameObjectBucketPair[] } model SortingGameObjectBucketPair { - id String @id + id String @id @default(cuid()) - object SortingGameObject[] + objects SortingGameObject[] bucket SortingGameBucket? @@ -258,8 +260,20 @@ model SortingGameObjectBucketPair { sortingGameGameId String? } +model SortingGameBucket { + id String @id @default(cuid()) + + description String + + bucketPair SortingGameObjectBucketPair @relation(fields: [bucketPairId], references: [id]) + bucketPairId String @unique +} + model SortingGameObject { - id String @id + id String @id @default(cuid()) + + objectType SortingGameObjectType + text String? image Image? @relation(fields: [imageId], references: [assetId], onDelete: Cascade) imageId String? @@ -268,11 +282,9 @@ model SortingGameObject { bucketPairId String } -model SortingGameBucket { - id String @id - - bucketPair SortingGameObjectBucketPair @relation(fields: [bucketPairId], references: [id]) - bucketPairId String @unique +enum SortingGameObjectType { + text + image } enum UserType { diff --git a/utils/sorting-game-validator.ts b/utils/sorting-game-validator.ts new file mode 100644 index 00000000..2699e3fa --- /dev/null +++ b/utils/sorting-game-validator.ts @@ -0,0 +1,58 @@ +import { SortingGameObjectType } from '@prisma/client'; +import { SerializedObjectBucketPair } from '../lib/server/sorting'; + +export const validatePairs = (correctPairs: SerializedObjectBucketPair[]) => { + // if (questions.length <= 0) { + // return { valid: false, message: 'There should be at least 1 question for each quiz.' }; + // } + + // const validatedQuestions = questions.map(question => validateQuestion(question)); + // const invalidResults = validatedQuestions.filter(result => !result.valid); + // return invalidResults.length != 0 ? invalidResults.at(0) : { valid: true }; + return { valid: true }; +}; + +// const validateQuestion = (question: SerializedQuizQuestion) => { +// if (question.quizGameOptions.length <= 0) { +// return { valid: false, message: 'There should be at least 1 option for each question.' }; +// } + +// const { isMultipleResponse, questionTitle, quizGameOptions } = question; +// const correctOptions = quizGameOptions.filter(option => option.isCorrectOption); + +// if (questionTitle.length == 0) { +// return { valid: false, message: 'Question should not be empty.' }; +// } + +// if (correctOptions.length <= 0) { +// return { valid: false, message: 'There should be at least 1 correct option for each question.' }; +// } + +// if (!isMultipleResponse && correctOptions.length > 1) { +// return { valid: false, message: 'Only multiple response questions should have more than 1 correct option.' }; +// } + +// const validatedOptions = quizGameOptions.map(option => validateOption(option)); +// const invalidResults = validatedOptions.filter(result => !result.valid); +// return invalidResults.length != 0 ? invalidResults.at(0) : { valid: true }; +// }; + +// const validateOption = (option: SerializedQuizOption) => { +// const { quizGameOptionType, quizGameOptionImage, quizGameOptionText } = option; + +// if (quizGameOptionType == QuizGameOptionType.textAndImage && (quizGameOptionImage == null || quizGameOptionText == null)) { +// return { valid: false, message: 'Quiz option type of text and image should have non-null text and image.' }; +// } + +// if (quizGameOptionType == QuizGameOptionType.image && quizGameOptionImage == null) { +// return { valid: false, message: 'Quiz option type of image should have non-null image.' }; +// } + +// if (quizGameOptionType == QuizGameOptionType.text && quizGameOptionText == null) { +// return { valid: false, message: 'Quiz option type of text should have non-null text.' }; +// } + +// return { valid: true }; +// }; + +export default validatePairs; From 544e56685ee7bc17086801e9f80295c1a1088c35 Mon Sep 17 00:00:00 2001 From: BryannYeap Date: Fri, 13 Jan 2023 17:17:24 +0800 Subject: [PATCH 3/7] feat: Add validation and finish sorting game backend API --- lib/server/sorting.ts | 12 +++--- pages/api/sorting-game/[id].ts | 8 ++-- pages/api/sorting-game/index.ts | 8 ++-- prisma/schema.prisma | 3 +- utils/sorting-game-validator.ts | 70 ++++++++++++--------------------- 5 files changed, 43 insertions(+), 58 deletions(-) diff --git a/lib/server/sorting.ts b/lib/server/sorting.ts index 2359b668..7337e1b7 100644 --- a/lib/server/sorting.ts +++ b/lib/server/sorting.ts @@ -1,12 +1,14 @@ import { Prisma, GameType, AssetType, SortingGame, SortingGameObjectType } from '@prisma/client'; import prisma from '../prisma'; +export type SerializedSortingGameObject = { + objectType: SortingGameObjectType; + text: string | null; + imageId: string | null; +}; + export type SerializedObjectBucketPair = { - objects: { - objectType: SortingGameObjectType; - text: string | null; - imageId: string | null; - }[]; + objects: SerializedSortingGameObject[]; bucket: { description: string; }; diff --git a/pages/api/sorting-game/[id].ts b/pages/api/sorting-game/[id].ts index e963fbae..e0c80c01 100644 --- a/pages/api/sorting-game/[id].ts +++ b/pages/api/sorting-game/[id].ts @@ -20,10 +20,10 @@ export const handler = async (req: NextApiRequest, res: NextApiResponse) => { } else if (httpMethod == 'PUT') { const { description, correctPairs }: { description: string; correctPairs: SerializedObjectBucketPair[] } = req.body; - // const result = validatePairs(correctPairs); - // if (!result.valid) { - // return res.status(400).end(`The input is not valid. ${result.message}`); - // } + const result = validatePairs(correctPairs); + if (!result.valid) { + return res.status(400).end(`The input is not valid. ${result.message}`); + } const updatedGame = await updateSorting( { gameId }, diff --git a/pages/api/sorting-game/index.ts b/pages/api/sorting-game/index.ts index 9f22fa2f..1ad9e088 100644 --- a/pages/api/sorting-game/index.ts +++ b/pages/api/sorting-game/index.ts @@ -15,10 +15,10 @@ export const handler = async (req: NextApiRequest, res: NextApiResponse) => { } else if (httpMethod == 'POST') { const { description, correctPairs }: { description: string; correctPairs: SerializedObjectBucketPair[] } = req.body; - // const result = validatePairs(correctPairs); - // if (!result.valid) { - // return res.status(400).end(`The input is not valid. ${result.message}`); - // } + const result = validatePairs(correctPairs); + if (!result.valid) { + return res.status(400).end(`The input is not valid. ${result.message}`); + } const created = await createSorting(description, correctPairs); res.status(200).json({ message: entityMessageObj.createSuccess, data: created }); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5f363fdd..fc1c53d3 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -274,7 +274,8 @@ model SortingGameObject { objectType SortingGameObjectType - text String? + text String? + image Image? @relation(fields: [imageId], references: [assetId], onDelete: Cascade) imageId String? diff --git a/utils/sorting-game-validator.ts b/utils/sorting-game-validator.ts index 2699e3fa..1e416eef 100644 --- a/utils/sorting-game-validator.ts +++ b/utils/sorting-game-validator.ts @@ -1,58 +1,40 @@ import { SortingGameObjectType } from '@prisma/client'; -import { SerializedObjectBucketPair } from '../lib/server/sorting'; +import { SerializedSortingGameObject, SerializedObjectBucketPair } from '../lib/server/sorting'; export const validatePairs = (correctPairs: SerializedObjectBucketPair[]) => { - // if (questions.length <= 0) { - // return { valid: false, message: 'There should be at least 1 question for each quiz.' }; - // } + if (correctPairs.length <= 0) { + return { valid: false, message: 'There should be at least 1 bucket-object pair for each game.' }; + } - // const validatedQuestions = questions.map(question => validateQuestion(question)); - // const invalidResults = validatedQuestions.filter(result => !result.valid); - // return invalidResults.length != 0 ? invalidResults.at(0) : { valid: true }; - return { valid: true }; + const validatedPairs = correctPairs.map(pair => validatePair(pair)); + const invalidResults = validatedPairs.filter(result => !result.valid); + return invalidResults.length != 0 ? invalidResults.at(0) : { valid: true }; }; -// const validateQuestion = (question: SerializedQuizQuestion) => { -// if (question.quizGameOptions.length <= 0) { -// return { valid: false, message: 'There should be at least 1 option for each question.' }; -// } - -// const { isMultipleResponse, questionTitle, quizGameOptions } = question; -// const correctOptions = quizGameOptions.filter(option => option.isCorrectOption); - -// if (questionTitle.length == 0) { -// return { valid: false, message: 'Question should not be empty.' }; -// } - -// if (correctOptions.length <= 0) { -// return { valid: false, message: 'There should be at least 1 correct option for each question.' }; -// } +const validatePair = (pair: SerializedObjectBucketPair) => { + const { objects, bucket } = pair; -// if (!isMultipleResponse && correctOptions.length > 1) { -// return { valid: false, message: 'Only multiple response questions should have more than 1 correct option.' }; -// } + if (bucket == null) { + return { valid: false, message: 'There should be at least 1 option for each question.' }; + } -// const validatedOptions = quizGameOptions.map(option => validateOption(option)); -// const invalidResults = validatedOptions.filter(result => !result.valid); -// return invalidResults.length != 0 ? invalidResults.at(0) : { valid: true }; -// }; - -// const validateOption = (option: SerializedQuizOption) => { -// const { quizGameOptionType, quizGameOptionImage, quizGameOptionText } = option; + const validatedObjects = objects.map(object => validateObject(object)); + const invalidResults = validatedObjects.filter(result => !result.valid); + return invalidResults.length != 0 ? invalidResults.at(0) : { valid: true }; +}; -// if (quizGameOptionType == QuizGameOptionType.textAndImage && (quizGameOptionImage == null || quizGameOptionText == null)) { -// return { valid: false, message: 'Quiz option type of text and image should have non-null text and image.' }; -// } +const validateObject = (object: SerializedSortingGameObject) => { + const { objectType, text, imageId } = object; -// if (quizGameOptionType == QuizGameOptionType.image && quizGameOptionImage == null) { -// return { valid: false, message: 'Quiz option type of image should have non-null image.' }; -// } + if (objectType == SortingGameObjectType.text && (text == null || text.length == 0 || imageId != null)) { + return { valid: false, message: 'There should be text, and no image, for a sorting game text object.' }; + } -// if (quizGameOptionType == QuizGameOptionType.text && quizGameOptionText == null) { -// return { valid: false, message: 'Quiz option type of text should have non-null text.' }; -// } + if (objectType == SortingGameObjectType.image && (imageId == null || imageId.length == 0 || text != null)) { + return { valid: false, message: 'There should be an image, and no text, for a sorting game text object.' }; + } -// return { valid: true }; -// }; + return { valid: true }; +}; export default validatePairs; From f4f54b695cc0337ffa80f4aa481d80fbbc34239d Mon Sep 17 00:00:00 2001 From: BryannYeap Date: Fri, 13 Jan 2023 18:22:42 +0800 Subject: [PATCH 4/7] fix: Prevent a bucket from having 0 objects --- utils/sorting-game-validator.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/utils/sorting-game-validator.ts b/utils/sorting-game-validator.ts index 1e416eef..d23977f5 100644 --- a/utils/sorting-game-validator.ts +++ b/utils/sorting-game-validator.ts @@ -15,7 +15,11 @@ const validatePair = (pair: SerializedObjectBucketPair) => { const { objects, bucket } = pair; if (bucket == null) { - return { valid: false, message: 'There should be at least 1 option for each question.' }; + return { valid: false, message: 'There should be at least 1 bucket for each bucket-object pair.' }; + } + + if (objects.length <= 0) { + return { valid: false, message: 'There should be at least 1 object in each bucket.' }; } const validatedObjects = objects.map(object => validateObject(object)); From cc4299659052bbf6edb876f921e49082b542e472 Mon Sep 17 00:00:00 2001 From: BryannYeap Date: Tue, 31 Jan 2023 22:23:37 +0800 Subject: [PATCH 5/7] chore: Change structure of sorting game schema --- lib/server/sorting.ts | 34 +++++++++++++-------------------- pages/api/sorting-game/[id].ts | 28 +++++++++++---------------- pages/api/sorting-game/index.ts | 10 +++++----- prisma/schema.prisma | 23 +++++++--------------- utils/sorting-game-validator.ts | 34 ++++++++++++++------------------- 5 files changed, 50 insertions(+), 79 deletions(-) diff --git a/lib/server/sorting.ts b/lib/server/sorting.ts index 7337e1b7..c12c0f9e 100644 --- a/lib/server/sorting.ts +++ b/lib/server/sorting.ts @@ -1,20 +1,18 @@ -import { Prisma, GameType, AssetType, SortingGame, SortingGameObjectType } from '@prisma/client'; +import { Prisma, GameType, AssetType, SortingGame, SortingGameObjectType, Image } from '@prisma/client'; import prisma from '../prisma'; export type SerializedSortingGameObject = { objectType: SortingGameObjectType; text: string | null; - imageId: string | null; + image: Image | null; }; -export type SerializedObjectBucketPair = { - objects: SerializedSortingGameObject[]; - bucket: { - description: string; - }; +export type SerializedBucket = { + description: string; + sortingGameObjects: SerializedSortingGameObject[]; }; -export const createSorting = async (description: string, correctPairs: SerializedObjectBucketPair[]) => { +export const createSorting = async (description: string, sortingGameBuckets: SerializedBucket[]) => { return (await prisma.sortingGame.create({ data: { game: { @@ -28,26 +26,20 @@ export const createSorting = async (description: string, correctPairs: Serialize }, }, description, - correctPairs: { - create: correctPairs.map(pair => ({ - objects: { - create: pair.objects.map(object => ({ + sortingGameBuckets: { + create: sortingGameBuckets.map(bucket => ({ + description: bucket.description, + sortingGameObjects: { + create: bucket.sortingGameObjects.map(object => ({ objectType: object.objectType, text: object.text, - image: object.imageId && { + image: object.image && { connect: { - assetId: object.imageId, + assetId: object.image.assetId, }, }, })), }, - bucket: pair.bucket - ? { - create: { - description: pair.bucket.description, - }, - } - : null, })), }, }, diff --git a/pages/api/sorting-game/[id].ts b/pages/api/sorting-game/[id].ts index e0c80c01..422794f3 100644 --- a/pages/api/sorting-game/[id].ts +++ b/pages/api/sorting-game/[id].ts @@ -1,8 +1,8 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { deleteSorting, findUniqueSorting, updateSorting, SerializedObjectBucketPair } from '../../../lib/server/sorting'; +import { deleteSorting, findUniqueSorting, updateSorting, SerializedBucket } from '../../../lib/server/sorting'; import { entityMessageCreator } from '../../../utils/api-messages'; import { errorMessageHandler } from '../../../utils/error-message-handler'; -import validatePairs from '../../../utils/sorting-game-validator'; +import validateBuckets from '../../../utils/sorting-game-validator'; const entityMessageObj = entityMessageCreator('sortingGame'); @@ -18,9 +18,9 @@ export const handler = async (req: NextApiRequest, res: NextApiResponse) => { const deletedGame = await deleteSorting({ gameId }); res.status(200).json({ message: entityMessageObj.deleteSuccess, data: deletedGame }); } else if (httpMethod == 'PUT') { - const { description, correctPairs }: { description: string; correctPairs: SerializedObjectBucketPair[] } = req.body; + const { description, sortingGameBuckets }: { description: string; sortingGameBuckets: SerializedBucket[] } = req.body; - const result = validatePairs(correctPairs); + const result = validateBuckets(sortingGameBuckets); if (!result.valid) { return res.status(400).end(`The input is not valid. ${result.message}`); } @@ -29,26 +29,20 @@ export const handler = async (req: NextApiRequest, res: NextApiResponse) => { { gameId }, { description, - correctPairs: { - create: correctPairs.map(pair => ({ - objects: { - create: pair.objects.map(object => ({ + sortingGameBuckets: { + create: sortingGameBuckets.map(bucket => ({ + description: bucket.description, + sortingGameObjects: { + create: bucket.sortingGameObjects.map(object => ({ objectType: object.objectType, text: object.text, - image: object.imageId && { + image: object.image && { connect: { - assetId: object.imageId, + assetId: object.image.assetId, }, }, })), }, - bucket: pair.bucket - ? { - create: { - description: pair.bucket.description, - }, - } - : null, })), }, }, diff --git a/pages/api/sorting-game/index.ts b/pages/api/sorting-game/index.ts index 1ad9e088..6605c0dc 100644 --- a/pages/api/sorting-game/index.ts +++ b/pages/api/sorting-game/index.ts @@ -1,8 +1,8 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { createSorting, findSorting, SerializedObjectBucketPair } from '../../../lib/server/sorting'; +import { createSorting, findSorting, SerializedBucket } from '../../../lib/server/sorting'; import { entityMessageCreator } from '../../../utils/api-messages'; import { errorMessageHandler } from '../../../utils/error-message-handler'; -import validatePairs from '../../../utils/sorting-game-validator'; +import validateBuckets from '../../../utils/sorting-game-validator'; const entityMessageObj = entityMessageCreator('sortingGame'); @@ -13,14 +13,14 @@ export const handler = async (req: NextApiRequest, res: NextApiResponse) => { const games = await findSorting(); res.status(200).json({ message: entityMessageObj.getAllSuccess, data: games }); } else if (httpMethod == 'POST') { - const { description, correctPairs }: { description: string; correctPairs: SerializedObjectBucketPair[] } = req.body; + const { description, sortingGameBuckets }: { description: string; sortingGameBuckets: SerializedBucket[] } = req.body; - const result = validatePairs(correctPairs); + const result = validateBuckets(sortingGameBuckets); if (!result.valid) { return res.status(400).end(`The input is not valid. ${result.message}`); } - const created = await createSorting(description, correctPairs); + const created = await createSorting(description, sortingGameBuckets); res.status(200).json({ message: entityMessageObj.createSuccess, data: created }); } else { res.setHeader('Allow', ['GET', 'POST']); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index fc1c53d3..62e11e18 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -246,18 +246,7 @@ model SortingGame { description String - correctPairs SortingGameObjectBucketPair[] -} - -model SortingGameObjectBucketPair { - id String @id @default(cuid()) - - objects SortingGameObject[] - - bucket SortingGameBucket? - - SortingGame SortingGame? @relation(fields: [sortingGameGameId], references: [gameId]) - sortingGameGameId String? + sortingGameBuckets SortingGameBucket[] } model SortingGameBucket { @@ -265,8 +254,10 @@ model SortingGameBucket { description String - bucketPair SortingGameObjectBucketPair @relation(fields: [bucketPairId], references: [id]) - bucketPairId String @unique + sortingGameObjects SortingGameObject[] + + sortingGame SortingGame? @relation(fields: [sortingGameId], references: [gameId]) + sortingGameId String? } model SortingGameObject { @@ -279,8 +270,8 @@ model SortingGameObject { image Image? @relation(fields: [imageId], references: [assetId], onDelete: Cascade) imageId String? - bucketPair SortingGameObjectBucketPair @relation(fields: [bucketPairId], references: [id]) - bucketPairId String + correctBucket SortingGameBucket @relation(fields: [correctBucketId], references: [id]) + correctBucketId String } enum SortingGameObjectType { diff --git a/utils/sorting-game-validator.ts b/utils/sorting-game-validator.ts index d23977f5..bbe5a064 100644 --- a/utils/sorting-game-validator.ts +++ b/utils/sorting-game-validator.ts @@ -1,44 +1,38 @@ import { SortingGameObjectType } from '@prisma/client'; -import { SerializedSortingGameObject, SerializedObjectBucketPair } from '../lib/server/sorting'; +import { SerializedSortingGameObject, SerializedBucket } from '../lib/server/sorting'; -export const validatePairs = (correctPairs: SerializedObjectBucketPair[]) => { - if (correctPairs.length <= 0) { - return { valid: false, message: 'There should be at least 1 bucket-object pair for each game.' }; +export const validateBuckets = (sortingGameBuckets: SerializedBucket[]) => { + if (sortingGameBuckets.length <= 0) { + return { valid: false, message: 'There should be at least 1 bucket for each game.' }; } - const validatedPairs = correctPairs.map(pair => validatePair(pair)); - const invalidResults = validatedPairs.filter(result => !result.valid); + const validatedObjects = sortingGameBuckets.map(bucket => validateObjects(bucket.sortingGameObjects)); + const invalidResults = validatedObjects.filter(result => !result.valid); return invalidResults.length != 0 ? invalidResults.at(0) : { valid: true }; }; -const validatePair = (pair: SerializedObjectBucketPair) => { - const { objects, bucket } = pair; - - if (bucket == null) { - return { valid: false, message: 'There should be at least 1 bucket for each bucket-object pair.' }; - } - - if (objects.length <= 0) { +const validateObjects = (sortingGameObjects: SerializedSortingGameObject[]) => { + if (sortingGameObjects.length <= 0) { return { valid: false, message: 'There should be at least 1 object in each bucket.' }; } - const validatedObjects = objects.map(object => validateObject(object)); + const validatedObjects = sortingGameObjects.map(object => validateObject(object)); const invalidResults = validatedObjects.filter(result => !result.valid); return invalidResults.length != 0 ? invalidResults.at(0) : { valid: true }; }; const validateObject = (object: SerializedSortingGameObject) => { - const { objectType, text, imageId } = object; + const { objectType, text, image } = object; - if (objectType == SortingGameObjectType.text && (text == null || text.length == 0 || imageId != null)) { - return { valid: false, message: 'There should be text, and no image, for a sorting game text object.' }; + if (objectType == SortingGameObjectType.text && (text == null || text.length == 0 || image != null)) { + return { valid: false, message: 'There should be non-empty text, and no image, for a sorting game text object.' }; } - if (objectType == SortingGameObjectType.image && (imageId == null || imageId.length == 0 || text != null)) { + if (objectType == SortingGameObjectType.image && (image == null || text != null)) { return { valid: false, message: 'There should be an image, and no text, for a sorting game text object.' }; } return { valid: true }; }; -export default validatePairs; +export default validateBuckets; From e8405f14cccb853646e3a2cfd421526462634b1e Mon Sep 17 00:00:00 2001 From: BryannYeap Date: Thu, 2 Feb 2023 00:07:32 +0800 Subject: [PATCH 6/7] fix: add required line for prisma relation --- prisma/schema.prisma | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0375f220..3379a973 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -218,6 +218,7 @@ model Game { matchingGame MatchingGame? spotTheDifferenceGame SpotTheDifferenceGame? QuizGame QuizGame? + SortingGame SortingGame? @@map("games") } @@ -241,7 +242,7 @@ model MatchingGame { model QuizGame { gameId String @id game Game @relation(fields: [gameId], references: [assetId], onDelete: Cascade) - + quizGameQuestions QuizGameQuestion[] } @@ -268,8 +269,8 @@ model QuizGameOption { optionText String? - optionImage Image? @relation(fields: [optionImageId], references: [assetId]) - optionImageId String? + optionImage Image? @relation(fields: [optionImageId], references: [assetId]) + optionImageId String? quizGameQuestion QuizGameQuestion @relation(fields: [quizGameQuestionId], references: [id], onDelete: Cascade) quizGameQuestionId String From 4a6931ac2e7d3030206c5ed2ce7a7b042418e057 Mon Sep 17 00:00:00 2001 From: sltsheryl Date: Fri, 8 Dec 2023 00:15:50 +0800 Subject: [PATCH 7/7] fix: update merge --- components/sorting-game-editor/Creator.tsx | 1 - lib/server/page.ts | 36 ++-- lib/server/sorting.ts | 24 ++- lib/server/sortingGame.ts | 185 ------------------ pages/api/sorting-game/[id].ts | 2 +- .../20231018150103_update/migration.sql | 159 --------------- .../20231207154129_reset_db/migration.sql | 87 ++++++++ prisma/schema.prisma | 32 --- 8 files changed, 124 insertions(+), 402 deletions(-) delete mode 100644 lib/server/sortingGame.ts delete mode 100644 prisma/migrations/20231018150103_update/migration.sql create mode 100644 prisma/migrations/20231207154129_reset_db/migration.sql diff --git a/components/sorting-game-editor/Creator.tsx b/components/sorting-game-editor/Creator.tsx index 50af6dce..3e1e18a9 100644 --- a/components/sorting-game-editor/Creator.tsx +++ b/components/sorting-game-editor/Creator.tsx @@ -24,7 +24,6 @@ type EditorSortingGameBucketItem = { export type EditorSerializedSortingGame = Omit & { text: string; buckets: EditorSortingGameBucket[]; - duration: number; }; type SortingGameCreatorProp = { diff --git a/lib/server/page.ts b/lib/server/page.ts index 7c3a0732..5c133e27 100644 --- a/lib/server/page.ts +++ b/lib/server/page.ts @@ -5,8 +5,8 @@ import { EditorPageFormValues } from '../../pages/courses/staff/editor/content/p import prisma from '../prisma'; import { findUniqueMatchingGame } from './matchingGame'; import { findUniqueQuiz } from './quiz'; +import { findUniqueSorting } from './sorting'; import { findQuiz } from './quiz'; -import { findUniqueSortingGame } from './sortingGame'; import { EditorSerializedSortingGame } from '../../components/sorting-game-editor/Creator'; const getEditorQuizGame = async (gameId: string): Promise<{ questions: EditorSerializedQuizQuestion[] }> => { @@ -50,26 +50,18 @@ const getEditorQuizGame = async (gameId: string): Promise<{ questions: EditorSer }; }; -const getEditorMatchingGame = async (gameId: string): Promise => { - const matchingGame = await findUniqueMatchingGame(gameId); - return { - duration: matchingGame.duration, - images: matchingGame.images.map(image => image.image), - }; -}; - const getEditorSortingGame = async (gameId: string): Promise => { - const sortingGame = await findUniqueSortingGame(gameId); + const sortingGame = await findUniqueSorting(gameId); return { - text: '', - duration: sortingGame.duration, - buckets: sortingGame.buckets.map(bucket => { + text: sortingGame.description, + buckets: sortingGame.sortingGameBuckets.map(bucket => { return { - name: bucket.name, - bucketItems: bucket.bucketItems.map(bucketItem => { + name: '', + bucketItems: bucket.sortingGameObjects.map(object => { return { - text: bucketItem.text, - image: bucketItem.image ? { assetId: bucketItem.image.imageId } : undefined, + objectType: object.objectType, + text: object.text, + image: object.image, }; }), }; @@ -77,6 +69,15 @@ const getEditorSortingGame = async (gameId: string): Promise => { + const matchingGame = await findUniqueMatchingGame(gameId); + return { + duration: matchingGame.duration, + images: matchingGame.images.map(image => image.image), + }; +}; + + // fill in here with whatever value is needed for the SSR form data const getPageEditorFormValue = async (id: string): Promise => { const page = await prisma.page.findUnique({ @@ -145,7 +146,6 @@ const getPageEditorFormValue = async (id: string): Promise ? await getEditorSortingGame(page.asset.game.assetId) : { text: '', - duration: 0, buckets: [], }, diff --git a/lib/server/sorting.ts b/lib/server/sorting.ts index c12c0f9e..448f774a 100644 --- a/lib/server/sorting.ts +++ b/lib/server/sorting.ts @@ -53,12 +53,24 @@ export const findSorting = async (where?: Partial, })) as SortingGame[]; }; -export const findUniqueSorting = async (where: Prisma.SortingGameWhereUniqueInput, select?: Prisma.SortingGameSelect) => { - return (await prisma.sortingGame.findUnique({ - where, - select, - })) as SortingGame; -}; +export const findUniqueSorting = async (gameId: string) => { + return await prisma.sortingGame.findUnique({ + where: { + gameId: gameId, + }, + include: { + sortingGameBuckets: { + include: { + sortingGameObjects: { + include: { + image: true, + }, + }, + }, + }, + }, + }); +} export const updateSorting = async ( where: Partial, diff --git a/lib/server/sortingGame.ts b/lib/server/sortingGame.ts deleted file mode 100644 index 2cff87b7..00000000 --- a/lib/server/sortingGame.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { Prisma, GameType, AssetType, SortingGame, Bucket, Image } from '@prisma/client'; -import prisma from '../prisma'; -import { deleteOldAsset } from './asset'; - -export type SerializedSortingGame = { - duration: number; - buckets: { - name: string; - bucketItems: { - text: string; - image?: { - assetId: string; - }; - }[]; - }[]; -}; - -export const createSortingGame = async ({ duration, buckets }: SerializedSortingGame) => { - return await prisma.sortingGame.create({ - data: { - game: { - create: { - type: GameType.sortingGame, - asset: { - create: { - assetType: AssetType.game, - }, - }, - }, - }, - duration: duration, - buckets: { - create: buckets.map((bucket, index) => ({ - name: bucket.name, - bucketItems: { - create: bucket.bucketItems.map((bucketItem, itemIndex) => ({ - text: bucketItem.text, - image: bucketItem.image - ? { - create: { - image: { - connect: { - assetId: bucketItem.image.assetId, - }, - }, - }, - } - : undefined, - })), - }, - })), - }, - }, - include: { - buckets: { - include: { - bucketItems: { - include: { - image: true, - }, - }, - }, - }, - }, - }); -}; - -export const findUniqueSortingGame = async (gameId: string) => { - return await prisma.sortingGame.findUnique({ - where: { - gameId: gameId, - }, - include: { - buckets: { - include: { - bucketItems: { - include: { - image: true, - }, - }, - }, - }, - }, - }); -}; - -export const deleteSortingGame = async (gameId: string) => { - return await prisma.sortingGame.delete({ - where: { - gameId: gameId, - }, - }); -}; - -export const updateSortingGame = async (gameId: string, { duration, buckets }: SerializedSortingGame) => { - // First get all the old images, delete them, then update the sorting game items - return await prisma.$transaction(async tx => { - const oldSortingGameItems = await tx.bucketItem.findMany({ - where: { - bucket: { - sortingGameId: gameId, - }, - }, - include: { - image: true, - }, - orderBy: { - bucketId: 'asc', - id: 'asc', - }, - }); - - // Delete old assets if necessary - for (let i = 0; i < oldSortingGameItems.length; i++) { - const oldBucketItem = oldSortingGameItems[i]; - const newBucket = buckets[i]; - - // Delete old image if assetId has changed - if (oldBucketItem.image && newBucket.bucketItems[i].image?.assetId !== oldBucketItem.image.imageId) { - deleteOldAsset(oldBucketItem.image.imageId, AssetType.image, tx); - } - } - - // Update sorting game duration - await prisma.sortingGame.update({ - where: { - gameId: gameId, - }, - data: { - duration: duration, - }, - }); - - // Update sorting game items - await Promise.all( - buckets.map(async (bucket, index) => { - const existingBucketItem = oldSortingGameItems[index]; - - // Update existing bucket item if it exists - if (existingBucketItem) { - await prisma.bucketItem.update({ - where: { - id: existingBucketItem.id, - }, - data: { - text: bucket.bucketItems[index].text, - image: bucket.bucketItems[index].image - ? { - connect: { - imageId: bucket.bucketItems[index].image.assetId, - }, - } - : undefined, - }, - }); - } - // Create new bucket item if it doesn't exist - else { - await prisma.bucket.create({ - data: { - name: bucket.name, - sortingGame: { - connect: { - gameId: gameId, - }, - }, - bucketItems: { - create: bucket.bucketItems.map(bucketItem => ({ - text: bucketItem.text, - image: bucketItem.image - ? { - create: { - imageId: bucketItem.image.assetId, - }, - } - : undefined, - })), - }, - }, - }); - } - }), - ); - }); -}; diff --git a/pages/api/sorting-game/[id].ts b/pages/api/sorting-game/[id].ts index 422794f3..29290a23 100644 --- a/pages/api/sorting-game/[id].ts +++ b/pages/api/sorting-game/[id].ts @@ -12,7 +12,7 @@ export const handler = async (req: NextApiRequest, res: NextApiResponse) => { const gameId = req.query.id as string; if (httpMethod == 'GET') { - const game = await findUniqueSorting({ gameId }); + const game = await findUniqueSorting(gameId); res.status(200).json({ message: entityMessageObj.getOneSuccess, data: game }); } else if (httpMethod == 'DELETE') { const deletedGame = await deleteSorting({ gameId }); diff --git a/prisma/migrations/20231018150103_update/migration.sql b/prisma/migrations/20231018150103_update/migration.sql deleted file mode 100644 index fad72e24..00000000 --- a/prisma/migrations/20231018150103_update/migration.sql +++ /dev/null @@ -1,159 +0,0 @@ -/* - Warnings: - - - The values [superAdmin] on the enum `UserType` will be removed. If these variants are still used in the database, this will fail. - - You are about to drop the column `assetType` on the `pages` table. All the data in the column will be lost. - - You are about to drop the `PageItem` table. If the table is not empty, all the data it contains will be lost. - - You are about to drop the `SortingGame` table. If the table is not empty, all the data it contains will be lost. - - You are about to drop the `superAdmins` table. If the table is not empty, all the data it contains will be lost. - - A unique constraint covering the columns `[assetId]` on the table `pages` will be added. If there are existing duplicate values, this will fail. - - Added the required column `assetId` to the `pages` table without a default value. This is not possible if the table is not empty. - - Added the required column `lastSeenBy` to the `userCourses` table without a default value. This is not possible if the table is not empty. - -*/ --- CreateEnum -CREATE TYPE "QuizGameOptionType" AS ENUM ('text', 'image', 'textAndImage'); - --- AlterEnum -ALTER TYPE "GameType" ADD VALUE 'quizGame'; - --- AlterEnum -BEGIN; -CREATE TYPE "UserType_new" AS ENUM ('normalUser', 'admin', 'courseEditor', 'staff'); -ALTER TABLE "users" ALTER COLUMN "type" DROP DEFAULT; -ALTER TABLE "users" ALTER COLUMN "type" TYPE "UserType_new" USING ("type"::text::"UserType_new"); -ALTER TYPE "UserType" RENAME TO "UserType_old"; -ALTER TYPE "UserType_new" RENAME TO "UserType"; -DROP TYPE "UserType_old"; -ALTER TABLE "users" ALTER COLUMN "type" SET DEFAULT 'normalUser'; -COMMIT; - --- DropForeignKey -ALTER TABLE "PageItem" DROP CONSTRAINT "PageItem_assetId_fkey"; - --- DropForeignKey -ALTER TABLE "PageItem" DROP CONSTRAINT "PageItem_pageId_fkey"; - --- DropForeignKey -ALTER TABLE "SortingGame" DROP CONSTRAINT "SortingGame_gameId_fkey"; - --- DropForeignKey -ALTER TABLE "admins" DROP CONSTRAINT "admins_userId_fkey"; - --- DropForeignKey -ALTER TABLE "superAdmins" DROP CONSTRAINT "superAdmins_userId_fkey"; - --- AlterTable -ALTER TABLE "admins" ADD COLUMN "disabled" BOOLEAN NOT NULL DEFAULT true, -ADD COLUMN "role" TEXT NOT NULL DEFAULT 'Staff'; - --- AlterTable -ALTER TABLE "pages" DROP COLUMN "assetType", -ADD COLUMN "assetId" TEXT NOT NULL, -ALTER COLUMN "description" DROP NOT NULL; - --- AlterTable -ALTER TABLE "userCourses" ADD COLUMN "lastSeenBy" TIMESTAMP(3) NOT NULL; - --- AlterTable -ALTER TABLE "users" ADD COLUMN "password" TEXT; - --- DropTable -DROP TABLE "PageItem"; - --- DropTable -DROP TABLE "SortingGame"; - --- DropTable -DROP TABLE "superAdmins"; - --- CreateTable -CREATE TABLE "courseEditors" ( - "id" TEXT NOT NULL, - "courseId" TEXT NOT NULL, - "adminId" TEXT NOT NULL, - - CONSTRAINT "courseEditors_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "QuizGame" ( - "gameId" TEXT NOT NULL, - - CONSTRAINT "QuizGame_pkey" PRIMARY KEY ("gameId") -); - --- CreateTable -CREATE TABLE "QuizGameQuestion" ( - "id" TEXT NOT NULL, - "questionNumber" INTEGER NOT NULL, - "isMultipleResponse" BOOLEAN NOT NULL, - "questionTitle" TEXT NOT NULL, - "imageId" TEXT, - "quizGameId" TEXT NOT NULL, - - CONSTRAINT "QuizGameQuestion_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "QuizGameOption" ( - "id" TEXT NOT NULL, - "isCorrectOption" BOOLEAN NOT NULL, - "quizGameOptionType" "QuizGameOptionType" NOT NULL, - "optionText" TEXT, - "optionImageId" TEXT, - "quizGameQuestionId" TEXT NOT NULL, - - CONSTRAINT "QuizGameOption_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "_CoursesInCart" ( - "A" TEXT NOT NULL, - "B" TEXT NOT NULL -); - --- CreateIndex -CREATE UNIQUE INDEX "courseEditors_adminId_courseId_key" ON "courseEditors"("adminId", "courseId"); - --- CreateIndex -CREATE UNIQUE INDEX "_CoursesInCart_AB_unique" ON "_CoursesInCart"("A", "B"); - --- CreateIndex -CREATE INDEX "_CoursesInCart_B_index" ON "_CoursesInCart"("B"); - --- CreateIndex -CREATE UNIQUE INDEX "pages_assetId_key" ON "pages"("assetId"); - --- AddForeignKey -ALTER TABLE "admins" ADD CONSTRAINT "admins_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "courseEditors" ADD CONSTRAINT "courseEditors_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "courses"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "courseEditors" ADD CONSTRAINT "courseEditors_adminId_fkey" FOREIGN KEY ("adminId") REFERENCES "admins"("userId") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "pages" ADD CONSTRAINT "pages_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "assets"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "QuizGame" ADD CONSTRAINT "QuizGame_gameId_fkey" FOREIGN KEY ("gameId") REFERENCES "games"("assetId") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "QuizGameQuestion" ADD CONSTRAINT "QuizGameQuestion_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "images"("assetId") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "QuizGameQuestion" ADD CONSTRAINT "QuizGameQuestion_quizGameId_fkey" FOREIGN KEY ("quizGameId") REFERENCES "QuizGame"("gameId") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "QuizGameOption" ADD CONSTRAINT "QuizGameOption_optionImageId_fkey" FOREIGN KEY ("optionImageId") REFERENCES "images"("assetId") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "QuizGameOption" ADD CONSTRAINT "QuizGameOption_quizGameQuestionId_fkey" FOREIGN KEY ("quizGameQuestionId") REFERENCES "QuizGameQuestion"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_CoursesInCart" ADD CONSTRAINT "_CoursesInCart_A_fkey" FOREIGN KEY ("A") REFERENCES "courses"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_CoursesInCart" ADD CONSTRAINT "_CoursesInCart_B_fkey" FOREIGN KEY ("B") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/20231207154129_reset_db/migration.sql b/prisma/migrations/20231207154129_reset_db/migration.sql new file mode 100644 index 00000000..4f4e9106 --- /dev/null +++ b/prisma/migrations/20231207154129_reset_db/migration.sql @@ -0,0 +1,87 @@ +/* + Warnings: + + - You are about to drop the column `duration` on the `SortingGame` table. All the data in the column will be lost. + - You are about to drop the `Bucket` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `BucketItem` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `SortingGameImage` table. If the table is not empty, all the data it contains will be lost. + - Added the required column `description` to the `SortingGame` table without a default value. This is not possible if the table is not empty. + +*/ +-- CreateEnum +CREATE TYPE "SortingGameObjectType" AS ENUM ('text', 'image'); + +-- DropForeignKey +ALTER TABLE "Bucket" DROP CONSTRAINT "Bucket_sortingGameId_fkey"; + +-- DropForeignKey +ALTER TABLE "BucketItem" DROP CONSTRAINT "BucketItem_bucketId_fkey"; + +-- DropForeignKey +ALTER TABLE "BucketItem" DROP CONSTRAINT "BucketItem_imageId_fkey"; + +-- DropForeignKey +ALTER TABLE "SortingGameImage" DROP CONSTRAINT "SortingGameImage_imageId_fkey"; + +-- AlterTable +ALTER TABLE "SortingGame" DROP COLUMN "duration", +ADD COLUMN "description" TEXT NOT NULL; + +-- AlterTable +ALTER TABLE "users" ADD COLUMN "password" TEXT; + +-- DropTable +DROP TABLE "Bucket"; + +-- DropTable +DROP TABLE "BucketItem"; + +-- DropTable +DROP TABLE "SortingGameImage"; + +-- CreateTable +CREATE TABLE "SortingGameBucket" ( + "id" TEXT NOT NULL, + "description" TEXT NOT NULL, + "sortingGameId" TEXT, + + CONSTRAINT "SortingGameBucket_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "SortingGameObject" ( + "id" TEXT NOT NULL, + "objectType" "SortingGameObjectType" NOT NULL, + "text" TEXT, + "imageId" TEXT, + "correctBucketId" TEXT NOT NULL, + + CONSTRAINT "SortingGameObject_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "_CoursesInCart" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "_CoursesInCart_AB_unique" ON "_CoursesInCart"("A", "B"); + +-- CreateIndex +CREATE INDEX "_CoursesInCart_B_index" ON "_CoursesInCart"("B"); + +-- AddForeignKey +ALTER TABLE "SortingGameBucket" ADD CONSTRAINT "SortingGameBucket_sortingGameId_fkey" FOREIGN KEY ("sortingGameId") REFERENCES "SortingGame"("gameId") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "SortingGameObject" ADD CONSTRAINT "SortingGameObject_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "images"("assetId") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "SortingGameObject" ADD CONSTRAINT "SortingGameObject_correctBucketId_fkey" FOREIGN KEY ("correctBucketId") REFERENCES "SortingGameBucket"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_CoursesInCart" ADD CONSTRAINT "_CoursesInCart_A_fkey" FOREIGN KEY ("A") REFERENCES "courses"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_CoursesInCart" ADD CONSTRAINT "_CoursesInCart_B_fkey" FOREIGN KEY ("B") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e0cce771..c9791a4a 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -196,7 +196,6 @@ model Image { QuizGameQuestion QuizGameQuestion[] QuizGameOption QuizGameOption[] MatchingGameImage MatchingGameImage? - SortingGameImage SortingGameImage? @@map("images") } @@ -242,37 +241,6 @@ model SpotTheDifferenceGame { differences Float[] } -model SortingGame { - gameId String @id - game Game @relation(fields: [gameId], references: [assetId], onDelete: Cascade) - duration Float - buckets Bucket[] -} - -model Bucket { - id String @id @default(cuid()) - name String - sortingGame SortingGame @relation(fields: [sortingGameId], references: [gameId]) - sortingGameId String - bucketItems BucketItem[] -} - -model BucketItem { - id String @id @default(cuid()) - text String? - image SortingGameImage? @relation(fields: [imageId], references: [id]) - imageId String? - bucket Bucket @relation(fields: [bucketId], references: [id]) - bucketId String -} - -model SortingGameImage { - id String @id @default(cuid()) - image Image @relation(fields: [imageId], references: [assetId]) - imageId String @unique - bucketItems BucketItem[] -} - model MatchingGame { gameId String @id game Game @relation(fields: [gameId], references: [assetId], onDelete: Cascade)