diff --git a/schema.gql b/schema.gql new file mode 100644 index 0000000000000000000000000000000000000000..6780fcc50c98d4013aba16b49f112a60418f401e --- /dev/null +++ b/schema.gql @@ -0,0 +1,53 @@ +# ------------------------------------------------------ +# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) +# ------------------------------------------------------ + +"""answer """ +type Answer { + id: ID! + text: String! + imgURL: String + isCorrect: Boolean! + question: Question! +} + +"""question """ +type Question { + id: ID! + content: String! + imgUrl: String + quiz: Quiz! + answers: [Answer!]! +} + +"""quiz """ +type Quiz { + id: ID! + name: String! + questions: [Question!]! +} + +type Query { + quizzes: [Quiz!]! +} + +type Mutation { + addQuiz(newQuizData: NewQuizInput!): Quiz! +} + +input NewQuizInput { + name: String! + questions: [NewQuestionInput!]! +} + +input NewQuestionInput { + content: String! + imgURL: String = null + answers: [NewAnswerInput!]! +} + +input NewAnswerInput { + text: String! + imgURL: String + isCorrect: Boolean = false +} diff --git a/src/answers/answers.module.ts b/src/answers/answers.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..da34a5a73b3c2a1a99ca8efae441ae8b407ad4b9 --- /dev/null +++ b/src/answers/answers.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { AnswersService } from './answers.service'; +import { AnswersResolver } from './answers.resolver'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Answer } from './models/answer.model'; + +@Module({ + providers: [AnswersService, AnswersResolver], + imports: [TypeOrmModule.forFeature([Answer])], + exports: [AnswersService], +}) +export class AnswersModule {} diff --git a/src/answers/answers.resolver.ts b/src/answers/answers.resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..f0dba46cb78cdb0250616136f4c2219d43ff6c18 --- /dev/null +++ b/src/answers/answers.resolver.ts @@ -0,0 +1,7 @@ +import { Resolver } from '@nestjs/graphql'; +import { AnswersService } from './answers.service'; + +@Resolver() +export class AnswersResolver { + constructor(private readonly answersService: AnswersService) {} +} diff --git a/src/answers/answers.service.ts b/src/answers/answers.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..f5d3ab252840618a403050e738b712a166825edf --- /dev/null +++ b/src/answers/answers.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Answer } from './models/answer.model'; +import { NewAnswerInput } from './dto/new-answer.input'; + +@Injectable() +export class AnswersService { + constructor( + @InjectRepository(Answer) + private readonly answersRepository: Repository, + ) {} + + async createAnswer(data: NewAnswerInput[]) { + return await this.answersRepository.save(data); + } +} diff --git a/src/answers/dto/new-answer.input.ts b/src/answers/dto/new-answer.input.ts new file mode 100644 index 0000000000000000000000000000000000000000..290878559a98cd2a4cebdea2a61aa143e913682c --- /dev/null +++ b/src/answers/dto/new-answer.input.ts @@ -0,0 +1,16 @@ +import { Field, InputType } from '@nestjs/graphql'; +import { IsOptional, Length } from 'class-validator'; + +@InputType() +export class NewAnswerInput { + @Field({ nullable: false }) + @Length(1, 30) + text: string; + + @Field({ nullable: true }) + @IsOptional() + imgURL?: string; + + @Field({ nullable: false, defaultValue: false }) + isCorrect: boolean; +} diff --git a/src/answers/models/answer.model.ts b/src/answers/models/answer.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..340fadf0b0cf363d27f4d918229322763b29dd9c --- /dev/null +++ b/src/answers/models/answer.model.ts @@ -0,0 +1,36 @@ +import { Field, ID, ObjectType } from '@nestjs/graphql'; +import { + Column, + Entity, + Generated, + ManyToOne, + PrimaryGeneratedColumn, +} from 'typeorm'; +import { Question } from '../../questions/models/question.model'; +import { IsOptional } from 'class-validator'; + +@ObjectType({ description: 'answer ' }) +@Entity() +export class Answer { + @Field((type) => ID) + @PrimaryGeneratedColumn('uuid') + @Generated('uuid') + id: string; + + @Field() + @Column() + text: string; + + @Field({ nullable: true, defaultValue: null }) + @IsOptional() + @Column({ nullable: true }) + imgURL?: string | null; + + @Field({ nullable: false }) + @Column({ nullable: false }) + isCorrect: boolean; + + @Field((type) => Question, { nullable: false }) + @ManyToOne(() => Question, (question) => question.answers) + question: Question; +} diff --git a/src/app.module.ts b/src/app.module.ts index 7e879d51d32f45104237f9881a1e39abdc4208eb..35f80d94ade351f7a3a75ad3335daaf88747c2d5 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,9 +1,19 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ConfigModule } from '@nestjs/config'; +import { GraphQLModule } from '@nestjs/graphql'; +import { QuizzesModule } from './quizzes/quizzes.module'; +import { Quiz } from './quizzes/models/quiz.model'; +import { QuestionsModule } from './questions/questions.module'; +import { AnswersModule } from './answers/answers.module'; +import { Question } from './questions/models/question.model'; +import { Answer } from './answers/models/answer.model'; @Module({ imports: [ + GraphQLModule.forRoot({ + autoSchemaFile: 'schema.gql', + }), ConfigModule.forRoot({ envFilePath: '.env', }), @@ -14,9 +24,12 @@ import { ConfigModule } from '@nestjs/config'; username: process.env.POSTGRES_USER || 'postgres', password: process.env.POSTGRES_PASSWORD || 'root', database: process.env.POSTGRES_DB || 'kahoot', - entities: [], + entities: [Quiz, Question, Answer], synchronize: true, }), + QuizzesModule, + QuestionsModule, + AnswersModule, ], }) export class AppModule {} diff --git a/src/questions/dto/new-question.input.ts b/src/questions/dto/new-question.input.ts new file mode 100644 index 0000000000000000000000000000000000000000..505817eb0cbe8b80716c647966348012f0ff51c5 --- /dev/null +++ b/src/questions/dto/new-question.input.ts @@ -0,0 +1,28 @@ +import { Field, InputType } from '@nestjs/graphql'; +import { + ArrayMaxSize, + ArrayMinSize, + IsArray, + IsOptional, + Length, + MinLength, +} from 'class-validator'; +import { NewAnswerInput } from '../../answers/dto/new-answer.input'; + +@InputType() +export class NewQuestionInput { + @Field({ nullable: false }) + @Length(2, 30) + content: string; + + @Field({ nullable: true, defaultValue: null }) + @IsOptional() + @MinLength(5) + imgURL?: string; + + @Field((type) => [NewAnswerInput], { nullable: false }) + @IsArray() + @ArrayMinSize(2) + @ArrayMaxSize(8) + answers: NewAnswerInput[]; +} diff --git a/src/questions/models/question.model.ts b/src/questions/models/question.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..f1a840ac7cb1a1656aef57448723145345e5ace1 --- /dev/null +++ b/src/questions/models/question.model.ts @@ -0,0 +1,36 @@ +import { Field, ID, ObjectType } from '@nestjs/graphql'; +import { + Column, + Entity, + Generated, + ManyToOne, + OneToMany, + PrimaryGeneratedColumn, +} from 'typeorm'; +import { Quiz } from '../../quizzes/models/quiz.model'; +import { Answer } from '../../answers/models/answer.model'; + +@ObjectType({ description: 'question ' }) +@Entity() +export class Question { + @Field((type) => ID) + @PrimaryGeneratedColumn('uuid') + @Generated('uuid') + id: string; + + @Field({ nullable: false }) + @Column() + content: string; + + @Field({ nullable: true }) + @Column({ nullable: true }) + imgUrl?: string | null; + + @Field((type) => Quiz) + @ManyToOne(() => Quiz, (quiz) => quiz.questions) + quiz: Quiz; + + @Field((type) => [Answer]) + @OneToMany(() => Answer, (answer) => answer.question) + answers: Answer[]; +} diff --git a/src/questions/questions.module.ts b/src/questions/questions.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..4b6296b8b67e85bcfa215dbcc379e164b43b9a44 --- /dev/null +++ b/src/questions/questions.module.ts @@ -0,0 +1,16 @@ +import { forwardRef, Module } from '@nestjs/common'; +import { QuestionsService } from './questions.service'; +import { QuestionsResolver } from './questions.resolver'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Question } from './models/question.model'; +import { AnswersModule } from '../answers/answers.module'; + +@Module({ + providers: [QuestionsService, QuestionsResolver], + imports: [ + TypeOrmModule.forFeature([Question]), + forwardRef(() => AnswersModule), + ], + exports: [QuestionsService], +}) +export class QuestionsModule {} diff --git a/src/questions/questions.resolver.ts b/src/questions/questions.resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..fbc65ca0e47f0f6f903e78f8b8de0e480f28a7a1 --- /dev/null +++ b/src/questions/questions.resolver.ts @@ -0,0 +1,22 @@ +import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; +import { Question } from './models/question.model'; +import { QuestionsService } from './questions.service'; +import { NewQuestionInput } from './dto/new-question.input'; + +@Resolver((of: Question) => Question) +export class QuestionsResolver { + constructor(private readonly questionsService: QuestionsService) {} + + // @Query((returns) => Question) + // async getQuestionById(@Args('id') id: string): Promise { + // const res: Question = await this.questionsService.getById(id); + // return res; + // } + + // @Mutation((returns) => Question) + // async addQuestion( + // @Args('newQuestionData') newQuestionData: NewQuestionInput, + // ): Promise { + // return await this.questionsService.createNewQuestion(newQuestionData); + // } +} diff --git a/src/questions/questions.service.ts b/src/questions/questions.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..fbd7eab1fceda1993056e9e8faf08ef78277ee17 --- /dev/null +++ b/src/questions/questions.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Question } from './models/question.model'; +import { Repository } from 'typeorm'; +import { NewQuestionInput } from './dto/new-question.input'; +import { AnswersService } from '../answers/answers.service'; + +@Injectable() +export class QuestionsService { + constructor( + @InjectRepository(Question) + private readonly questionRepository: Repository, + private readonly answerService: AnswersService, + ) {} + + // async getById(id: string): Promise { + // return await this.questionRepository.findOne({ + // where: id, + // }); + // } + + async createNewQuestion(data: NewQuestionInput[]) { + for (const elem of data) { + await this.answerService.createAnswer(elem.answers); + } + return await this.questionRepository.save(data); + } +} diff --git a/src/quizzes/dto/new-quiz.input.ts b/src/quizzes/dto/new-quiz.input.ts new file mode 100644 index 0000000000000000000000000000000000000000..6e5bac3ae8dae2d117c8304a08c313fec13bd79b --- /dev/null +++ b/src/quizzes/dto/new-quiz.input.ts @@ -0,0 +1,15 @@ +import { Field, InputType } from '@nestjs/graphql'; +import { ArrayMinSize, IsArray, Length } from 'class-validator'; +import { NewQuestionInput } from '../../questions/dto/new-question.input'; + +@InputType() +export class NewQuizInput { + @Field({ nullable: false }) + @Length(2, 30) + name: string; + + @Field((type) => [NewQuestionInput], { nullable: false }) + @IsArray() + @ArrayMinSize(1) + questions: NewQuestionInput[]; +} diff --git a/src/quizzes/models/quiz.model.ts b/src/quizzes/models/quiz.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..5d0645ff29f02cc753e8a781ebfa283da67505c4 --- /dev/null +++ b/src/quizzes/models/quiz.model.ts @@ -0,0 +1,26 @@ +import { Field, ID, ObjectType } from '@nestjs/graphql'; +import { + Column, + Entity, + Generated, + OneToMany, + PrimaryGeneratedColumn, +} from 'typeorm'; +import { Question } from '../../questions/models/question.model'; + +@ObjectType({ description: 'quiz ' }) +@Entity() +export class Quiz { + @Field((type) => ID) + @PrimaryGeneratedColumn('uuid') + @Generated('uuid') + id: string; + + @Field((type) => String) + @Column() + name: string; + + @Field((type) => [Question]) + @OneToMany(() => Question, (question) => question.quiz) + questions: Question[]; +} diff --git a/src/quizzes/quizzes.module.ts b/src/quizzes/quizzes.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..eb71a4edd0d473592c4c00a03bc9354306bcd4d6 --- /dev/null +++ b/src/quizzes/quizzes.module.ts @@ -0,0 +1,15 @@ +import { forwardRef, Module } from '@nestjs/common'; +import { QuizzesService } from './quizzes.service'; +import { QuizzesResolver } from './quizzes.resolver'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Quiz } from './models/quiz.model'; +import { QuestionsModule } from '../questions/questions.module'; + +@Module({ + providers: [QuizzesService, QuizzesResolver], + imports: [ + TypeOrmModule.forFeature([Quiz]), + forwardRef(() => QuestionsModule), + ], +}) +export class QuizzesModule {} diff --git a/src/quizzes/quizzes.resolver.ts b/src/quizzes/quizzes.resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..b68229c8d2d5c81631d1f5d3825ad1a924461b35 --- /dev/null +++ b/src/quizzes/quizzes.resolver.ts @@ -0,0 +1,19 @@ +import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; +import { Quiz } from './models/quiz.model'; +import { QuizzesService } from './quizzes.service'; +import { NewQuizInput } from './dto/new-quiz.input'; + +@Resolver((of: Quiz) => Quiz) +export class QuizzesResolver { + constructor(private readonly quizzesService: QuizzesService) {} + + @Query((returns) => [Quiz]) + async quizzes(): Promise { + return await this.quizzesService.findAll(); + } + + @Mutation((returns) => Quiz) + async addQuiz(@Args('newQuizData') newQuizData: NewQuizInput) { + return await this.quizzesService.addQuiz(newQuizData); + } +} diff --git a/src/quizzes/quizzes.service.ts b/src/quizzes/quizzes.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..27a239c2b7822bf3556851e4df080357f3428233 --- /dev/null +++ b/src/quizzes/quizzes.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common'; +import { Repository } from 'typeorm'; +import { Quiz } from './models/quiz.model'; +import { NewQuizInput } from './dto/new-quiz.input'; +import { InjectRepository } from '@nestjs/typeorm'; +import { QuestionsService } from '../questions/questions.service'; + +@Injectable() +export class QuizzesService { + constructor( + @InjectRepository(Quiz) + private readonly quizRepository: Repository, + private readonly questionsService: QuestionsService, + ) {} + + async findAll(): Promise { + return await this.quizRepository.find({ + relations: ['questions', 'questions.answers'], + }); + } + + async addQuiz(data: NewQuizInput) { + await this.questionsService.createNewQuestion(data.questions); + return await this.quizRepository.save(data); + } +}