feat: add medical test entities
This commit is contained in:
@@ -16,6 +16,10 @@ import redisConfig from './config/redis.config';
|
||||
import { APP_FILTER } from '@nestjs/core';
|
||||
import { GlobalExceptionFilter } from './common/filters/global-exception.filter';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { TestsModule } from './modules/tests/tests.module';
|
||||
import { QuestionsModule } from './modules/questions/questions.module';
|
||||
import { SubmissionsModule } from './modules/submissions/submissions.module';
|
||||
import { ScoringModule } from './modules/scoring/scoring.module';
|
||||
|
||||
const ENV = process.env.NODE_ENV;
|
||||
|
||||
@@ -71,6 +75,10 @@ const ENV = process.env.NODE_ENV;
|
||||
OtpModule,
|
||||
HashingModule,
|
||||
RedisModule,
|
||||
TestsModule,
|
||||
QuestionsModule,
|
||||
SubmissionsModule,
|
||||
ScoringModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { User } from '@/modules/users/user.entity';
|
||||
import { User } from '@/modules/users/entities/user.entity';
|
||||
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
||||
import type { Request } from 'express';
|
||||
import { REQUEST_USER_KEY } from '../constants/request-headers.constants';
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export enum Role {
|
||||
SUPERUSER = 'superuser',
|
||||
ADMIN = 'admin',
|
||||
USER = 'user',
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { REDIS } from '../constants/redis.constants';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import jwtConfig from '@/config/jwt.config';
|
||||
import { User } from '@/modules/users/user.entity';
|
||||
import { User } from '@/modules/users/entities/user.entity';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { ConfigType } from '@nestjs/config';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { ConfigType } from '@nestjs/config';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
import { AccessTokenPayload } from '../interfaces/jwt.interface';
|
||||
import { User } from '@/modules/users/user.entity';
|
||||
import { User } from '@/modules/users/entities/user.entity';
|
||||
import { UsersService } from '@/modules/users/providers/users.service';
|
||||
|
||||
@Injectable()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { OtpService } from './providers/otp.service';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { OTP } from './otp.entity';
|
||||
import { OTP } from './entities/otp.entity';
|
||||
import { HashingModule } from '@/common/modules/hashing/hashing.module';
|
||||
|
||||
@Module({
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { LessThan, MoreThanOrEqual, Repository } from 'typeorm';
|
||||
import { OTP } from '../otp.entity';
|
||||
import { OTP } from '../entities/otp.entity';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Cron } from '@nestjs/schedule';
|
||||
import { HashingProvider } from '@/common/modules/hashing/providers/hashing.provider';
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Question } from './question.entity';
|
||||
|
||||
@Entity()
|
||||
export class Choice {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@ManyToOne(() => Question, (question) => question.choices, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
question: Question;
|
||||
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
length: 255,
|
||||
nullable: false,
|
||||
})
|
||||
text: string;
|
||||
|
||||
@Column({ type: 'int', nullable: false })
|
||||
value: number;
|
||||
|
||||
@Column({ type: 'int', nullable: false })
|
||||
order: number;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Test } from '@/modules/tests/entities/test.entity';
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
} from 'typeorm';
|
||||
import { QuestionType } from '../enums/question-type.enum';
|
||||
import type { QuestionMetadata } from '../types/question-metadata.type';
|
||||
import { Choice } from './choice.entity';
|
||||
|
||||
@Entity()
|
||||
export class Question {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@ManyToOne(() => Test, (test) => test.questions, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
test: Test;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: QuestionType,
|
||||
default: QuestionType.MULTI_CHOICE,
|
||||
})
|
||||
type: QuestionType;
|
||||
|
||||
@Column({ type: 'text' })
|
||||
content: string;
|
||||
|
||||
@Column({ type: 'int', nullable: false })
|
||||
order: number;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
required: boolean;
|
||||
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
metadata: QuestionMetadata;
|
||||
|
||||
@OneToMany(() => Choice, (choice) => choice.question)
|
||||
choices?: Array<Choice>;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export enum QuestionType {
|
||||
MULTI_CHOICE = 'multi-choice',
|
||||
NUMERIC = 'numeric',
|
||||
TEXT = 'text',
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface MultiChoiceMetadata {
|
||||
allowOther?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface NumericMetadata {
|
||||
min: number;
|
||||
max: number;
|
||||
step?: number;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Question } from './entities/question.entity';
|
||||
import { Choice } from './entities/choice.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Question, Choice])],
|
||||
})
|
||||
export class QuestionsModule {}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { MultiChoiceMetadata } from '../interfaces/multi-choice-metadata.interface';
|
||||
import { NumericMetadata } from '../interfaces/numeric-metadata.interface';
|
||||
|
||||
export type QuestionMetadata = NumericMetadata | MultiChoiceMetadata;
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Test } from '@/modules/tests/entities/test.entity';
|
||||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
export class ResultRange {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@ManyToOne(() => Test, (test) => test.resultRanges, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
test: Test;
|
||||
|
||||
@Column({
|
||||
type: 'int',
|
||||
nullable: false,
|
||||
})
|
||||
minScore: number;
|
||||
|
||||
@Column({
|
||||
type: 'int',
|
||||
nullable: false,
|
||||
})
|
||||
maxScore: number;
|
||||
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
length: 255,
|
||||
nullable: false,
|
||||
})
|
||||
title: string;
|
||||
|
||||
@Column({
|
||||
type: 'text',
|
||||
nullable: false,
|
||||
})
|
||||
description: string;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Test } from '@/modules/tests/entities/test.entity';
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
OneToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
export class RuleSet {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@OneToOne(() => Test, (test) => test.ruleSet)
|
||||
@JoinColumn()
|
||||
test: Test;
|
||||
|
||||
@Column({ type: 'jsonb', nullable: false })
|
||||
config: any;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ResultRange } from './entities/result-range.entity';
|
||||
import { RuleSet } from './entities/rule-set.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([ResultRange, RuleSet])],
|
||||
})
|
||||
export class ScoringModule {}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Submission } from './submission.entity';
|
||||
import { Question } from '@/modules/questions/entities/question.entity';
|
||||
import { Choice } from '@/modules/questions/entities/choice.entity';
|
||||
|
||||
@Entity()
|
||||
export class SubmissionAnswer {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@ManyToOne(() => Submission, (submission) => submission.answers, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
submission: Submission;
|
||||
|
||||
@ManyToOne(() => Question, { onDelete: 'CASCADE' })
|
||||
question: Question;
|
||||
|
||||
@ManyToOne(() => Choice, { nullable: true, onDelete: 'CASCADE' })
|
||||
choice?: Choice;
|
||||
|
||||
@Column({ type: 'int', nullable: true })
|
||||
numericValue?: number;
|
||||
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
length: 255,
|
||||
nullable: true,
|
||||
})
|
||||
textValue?: string;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import {
|
||||
Check,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
} from 'typeorm';
|
||||
import { Submission } from './submission.entity';
|
||||
import { ResultRange } from '@/modules/scoring/entities/result-range.entity';
|
||||
|
||||
@Entity()
|
||||
@Check(`"probability" >= 0 AND "probability" <= 100`)
|
||||
export class SubmissionResult {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@ManyToOne(() => Submission, { onDelete: 'CASCADE' })
|
||||
submission: Submission;
|
||||
|
||||
@Column({ type: 'int', nullable: false })
|
||||
score: number;
|
||||
|
||||
@Column({ type: 'int', nullable: false })
|
||||
probability: number;
|
||||
|
||||
@ManyToOne(() => ResultRange, { onDelete: 'CASCADE' })
|
||||
resultRange: ResultRange;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Test } from '@/modules/tests/entities/test.entity';
|
||||
import { User } from '@/modules/users/entities/user.entity';
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
} from 'typeorm';
|
||||
import { SubmissionStatus } from '../enums/submission-status.enum';
|
||||
import { SubmissionAnswer } from './submission-answer.entity';
|
||||
|
||||
@Entity()
|
||||
export class Submission {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@ManyToOne(() => User, { nullable: true, onDelete: 'SET NULL' })
|
||||
user?: User;
|
||||
|
||||
@ManyToOne(() => Test, { onDelete: 'CASCADE' })
|
||||
test: Test;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: SubmissionStatus,
|
||||
default: SubmissionStatus.IN_PROGRESS,
|
||||
})
|
||||
status: SubmissionStatus;
|
||||
|
||||
@OneToMany(() => SubmissionAnswer, (answer) => answer.submission)
|
||||
answers: Array<SubmissionAnswer>;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum SubmissionStatus {
|
||||
IN_PROGRESS = 'in-progress',
|
||||
COMPLETED = 'completed',
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Submission } from './entities/submission.entity';
|
||||
import { SubmissionAnswer } from './entities/submission-answer.entity';
|
||||
import { SubmissionResult } from './entities/submission-result.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Submission, SubmissionAnswer, SubmissionResult]),
|
||||
],
|
||||
})
|
||||
export class SubmissionsModule {}
|
||||
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
OneToMany,
|
||||
OneToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
} from 'typeorm';
|
||||
import { AccessType } from '../enums/access-types.enum';
|
||||
import { Question } from '@/modules/questions/entities/question.entity';
|
||||
import { ResultRange } from '@/modules/scoring/entities/result-range.entity';
|
||||
import { RuleSet } from '@/modules/scoring/entities/rule-set.entity';
|
||||
|
||||
@Entity()
|
||||
export class Test {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
length: 255,
|
||||
nullable: false,
|
||||
})
|
||||
title: string;
|
||||
|
||||
@Column({
|
||||
type: 'text',
|
||||
nullable: false,
|
||||
})
|
||||
description: string;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: AccessType,
|
||||
default: AccessType.LOGIN,
|
||||
})
|
||||
accessType: AccessType;
|
||||
|
||||
@Column({
|
||||
type: 'bigint',
|
||||
nullable: true,
|
||||
})
|
||||
price: string;
|
||||
|
||||
@Column({
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
})
|
||||
isActive: boolean;
|
||||
|
||||
@OneToMany(() => Question, (question) => question.test)
|
||||
questions: Array<Question>;
|
||||
|
||||
@OneToOne(() => RuleSet, (ruleSet) => ruleSet.test)
|
||||
ruleSet: RuleSet;
|
||||
|
||||
@OneToMany(() => ResultRange, (range) => range.test)
|
||||
resultRanges: Array<ResultRange>;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export enum AccessType {
|
||||
FREE = 'free',
|
||||
LOGIN = 'login',
|
||||
PAID = 'paid',
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Test } from './entities/test.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Test])],
|
||||
})
|
||||
export class TestsModule {}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Role } from '@/common/enums/roles.enum';
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
@@ -42,6 +43,13 @@ export class User {
|
||||
})
|
||||
password?: string;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: Role,
|
||||
default: Role.USER,
|
||||
})
|
||||
role: Role;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { HashingProvider } from '@/common/modules/hashing/providers/hashing.provider';
|
||||
import { Injectable, RequestTimeoutException } from '@nestjs/common';
|
||||
import { I18nService } from 'nestjs-i18n';
|
||||
import { User } from '../user.entity';
|
||||
import { User } from '../entities/user.entity';
|
||||
import { UpdateProfileDTO } from '../dto/update-profile.dto';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
RequestTimeoutException,
|
||||
} from '@nestjs/common';
|
||||
import { Repository } from 'typeorm';
|
||||
import { User } from '../user.entity';
|
||||
import { User } from '../entities/user.entity';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { UpdateProfileDTO } from '../dto/update-profile.dto';
|
||||
import { UserProfileProvider } from './user-profile.provider';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Body, Controller, Get, Patch } from '@nestjs/common';
|
||||
import { UsersService } from './providers/users.service';
|
||||
import { ActiveUser } from '@/common/decorators/active-user.decorator';
|
||||
import { User } from './user.entity';
|
||||
import { User } from './entities/user.entity';
|
||||
import { UpdateProfileDTO } from './dto/update-profile.dto';
|
||||
|
||||
@Controller('users')
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Module } from '@nestjs/common';
|
||||
import { UsersController } from './users.controller';
|
||||
import { UsersService } from './providers/users.service';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { User } from './user.entity';
|
||||
import { User } from './entities/user.entity';
|
||||
import { HashingModule } from '@/common/modules/hashing/hashing.module';
|
||||
import { UserProfileProvider } from './providers/user-profile.provider';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user