Skip to content

Commit

Permalink
Merge pull request #45 from modern-agile-team/feat/#44/part-progress-api
Browse files Browse the repository at this point in the history
part progress API 작성
  • Loading branch information
dg1418 authored Dec 19, 2024
2 parents a0306e5 + 2b0ff5d commit 8b658ca
Show file tree
Hide file tree
Showing 17 changed files with 350 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- CreateEnum
CREATE TYPE "PartStatus" AS ENUM ('LOCKED', 'STARTED', 'IN_PROGRESS', 'COMPLETED');

-- CreateTable
CREATE TABLE "part_progress" (
"user_id" INTEGER NOT NULL,
"part_id" INTEGER NOT NULL,
"status" "PartStatus" NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,

CONSTRAINT "part_progress_pkey" PRIMARY KEY ("user_id","part_id")
);

-- AddForeignKey
ALTER TABLE "part_progress" ADD CONSTRAINT "part_progress_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "part_progress" ADD CONSTRAINT "part_progress_part_id_fkey" FOREIGN KEY ("part_id") REFERENCES "parts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
62 changes: 42 additions & 20 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ enum Category {
SHORT_ANSWER
}

enum PartStatus {
LOCKED
STARTED
IN_PROGRESS
COMPLETED
}

model Quiz {
id Int @id @default(autoincrement())
partId Int @map("part_id")
Expand Down Expand Up @@ -48,22 +55,23 @@ model Section {
}

model User {
id Int @id @default(autoincrement())
provider String @db.VarChar(20)
providerId String @unique @map("provider_id")
name String @db.VarChar(30)
profileImage String? @map("profile_image")
maxHealthPoint Int @default(5) @map("max_health_point")
lastLogin DateTime @default(now()) @map("last_login")
level Int @default(1)
experience Int @default(0)
experienceForNextLevel Int @default(50) @map("experience_for_next_level")
point Int @default(0)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
id Int @id @default(autoincrement())
provider String @db.VarChar(20)
providerId String @unique @map("provider_id")
name String @db.VarChar(30)
profileImage String? @map("profile_image")
maxHealthPoint Int @default(5) @map("max_health_point")
lastLogin DateTime @default(now()) @map("last_login")
level Int @default(1)
experience Int @default(0)
experienceForNextLevel Int @default(50) @map("experience_for_next_level")
point Int @default(0)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
Progress Progress[]
userItems UserItem[]
token Token[]
partProgress PartProgress[]
@@map("users")
}
Expand All @@ -82,13 +90,14 @@ model Progress {
}

model Part {
id Int @id @default(autoincrement())
sectionId Int @map("section_id")
name String @unique @db.VarChar(255)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
section Section @relation(fields: [sectionId], references: [id], onDelete: Restrict)
quiz Quiz[]
id Int @id @default(autoincrement())
sectionId Int @map("section_id")
name String @unique @db.VarChar(255)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
section Section @relation(fields: [sectionId], references: [id], onDelete: Restrict)
quiz Quiz[]
PartProgress PartProgress[]
@@map("parts")
}
Expand Down Expand Up @@ -131,3 +140,16 @@ model Token {
@@map("token")
}

model PartProgress {
userId Int @map("user_id")
partId Int @map("part_id")
status PartStatus
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
part Part @relation(fields: [partId], references: [id], onDelete: Cascade)
@@id([userId, partId])
@@map("part_progress")
}
2 changes: 2 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { SectionsModule } from './sections/sections.module';
import { ConfigModule } from '@nestjs/config';
import { AuthModule } from './auth/auth.module';
import { AppController } from './app.controller';
import { PartProgressModule } from './part-progress/part-progress.module';

@Module({
imports: [
Expand All @@ -22,6 +23,7 @@ import { AppController } from './app.controller';
isGlobal: true,
}),
AuthModule,
PartProgressModule,
],
controllers: [AppController],
providers: [AppService],
Expand Down
1 change: 1 addition & 0 deletions src/common/util/type-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type ValueOf<T extends Record<any, any>> = T[keyof T];
20 changes: 20 additions & 0 deletions src/part-progress/dto/create-part-progress.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ApiProperty } from '@nestjs/swagger';
import { PartStatus } from '../entities/part-progress.entity';
import { IsEnum } from 'class-validator';
import { CategoryValues } from 'src/quizzes/entities/quizzes.entity';

export class CreatePartProgressDto {
@ApiProperty({
description: `
part의 status 값 넣기
1. LOCKED
2. STARTED
3. IN_PROGRESS
4. COMPLETED
`,
example: 'LOCKED',
enum: CategoryValues,
})
@IsEnum(CategoryValues, { message: 'bad status value' })
readonly status: PartStatus;
}
25 changes: 25 additions & 0 deletions src/part-progress/dto/res-part-progress.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ApiProperty } from '@nestjs/swagger';
import { PartProgress, PartStatus } from '../entities/part-progress.entity';

export class ResPartProgressDto {
@ApiProperty({ example: 1, description: '유저 아이디' })
readonly userId: number;

@ApiProperty({ example: 3, description: '파트 아이디' })
readonly partId: number;

@ApiProperty({ example: 'LOCKED', description: '파트 상태' })
readonly status: PartStatus;

constructor({ userId, partId, status }: PartProgress) {
this.userId = userId;
this.partId = partId;
this.status = status;
}

static fromArray(partProgress: PartProgress[]): ResPartProgressDto[] {
return partProgress.map(
(partProgress) => new ResPartProgressDto(partProgress),
);
}
}
25 changes: 25 additions & 0 deletions src/part-progress/entities/part-progress.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ValueOf } from 'src/common/util/type-utils';

interface PartProgressModel {
userId: number;
partId: number;
status: PartStatus;
createdAt: Date;
updatedAt: Date;
}

export const PartStatusValues = {
LOCKED: 'LOCKED',
STARTED: 'STARTED',
IN_PROGRESS: 'IN_PROGRESS',
COMPLETED: 'COMPLETED',
} as const;
export type PartStatus = ValueOf<typeof PartStatusValues>;

export class PartProgress implements PartProgressModel {
userId: number;
partId: number;
status: PartStatus;
createdAt: Date;
updatedAt: Date;
}
20 changes: 20 additions & 0 deletions src/part-progress/part-progress.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { PartProgressController } from './part-progress.controller';
import { PartProgressService } from './part-progress.service';

describe('PartProgressController', () => {
let controller: PartProgressController;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [PartProgressController],
providers: [PartProgressService],
}).compile();

controller = module.get<PartProgressController>(PartProgressController);
});

it('should be defined', () => {
expect(controller).toBeDefined();
});
});
33 changes: 33 additions & 0 deletions src/part-progress/part-progress.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Controller, Get, Body, Param, Put, HttpCode } from '@nestjs/common';
import { PartProgressService } from './part-progress.service';
import { CreatePartProgressDto } from './dto/create-part-progress.dto';
import { PositiveIntPipe } from 'src/common/pipes/positive-int/positive-int.pipe';
import { ResPartProgressDto } from './dto/res-part-progress.dto';
import { ApiPartProgress } from './part-progress.swagger';
import { ApiTags } from '@nestjs/swagger';

@ApiTags('part-progress')
@Controller('users/:id/part-progress')
export class PartProgressController {
constructor(private readonly partProgressService: PartProgressService) {}

@ApiPartProgress.findAll()
@Get()
async findAll(
@Param('id', PositiveIntPipe) userId: number,
): Promise<ResPartProgressDto[]> {
const partProgress = await this.partProgressService.findAll(userId);
return ResPartProgressDto.fromArray(partProgress);
}

@ApiPartProgress.createOrUpdate()
@Put('parts/:partId')
@HttpCode(204)
async createOrUpdate(
@Param('id', PositiveIntPipe) userId: number,
@Param('partId', PositiveIntPipe) partId: number,
@Body() body: CreatePartProgressDto,
): Promise<void> {
await this.partProgressService.createOrUpdate(userId, partId, body);
}
}
18 changes: 18 additions & 0 deletions src/part-progress/part-progress.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { forwardRef, Module } from '@nestjs/common';
import { PartProgressService } from './part-progress.service';
import { PartProgressController } from './part-progress.controller';
import { PartProgressRepository } from './part-progress.repository';
import { SectionsModule } from 'src/sections/sections.module';
import { PartsModule } from 'src/parts/parts.module';
import { QuizzesModule } from 'src/quizzes/quizzes.module';

@Module({
imports: [
forwardRef(() => SectionsModule),
forwardRef(() => PartsModule),
forwardRef(() => QuizzesModule),
],
controllers: [PartProgressController],
providers: [PartProgressService, PartProgressRepository],
})
export class PartProgressModule {}
27 changes: 27 additions & 0 deletions src/part-progress/part-progress.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from 'src/prisma/prisma.service';
import { CreatePartProgressDto } from './dto/create-part-progress.dto';
import { PartProgress } from './entities/part-progress.entity';

@Injectable()
export class PartProgressRepository {
constructor(private readonly prisma: PrismaService) {}

async findAllByUserId(userId: number): Promise<PartProgress[]> {
return this.prisma.partProgress.findMany({
where: { userId },
});
}

async upsertPartProgress(
userId: number,
partId: number,
body: CreatePartProgressDto,
): Promise<PartProgress> {
return this.prisma.partProgress.upsert({
where: { userId_partId: { userId, partId } },
create: { userId, partId, ...body },
update: { userId, partId, ...body },
});
}
}
18 changes: 18 additions & 0 deletions src/part-progress/part-progress.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { PartProgressService } from './part-progress.service';

describe('PartProgressService', () => {
let service: PartProgressService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [PartProgressService],
}).compile();

service = module.get<PartProgressService>(PartProgressService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
48 changes: 48 additions & 0 deletions src/part-progress/part-progress.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { CreatePartProgressDto } from './dto/create-part-progress.dto';
import { PrismaService } from 'src/prisma/prisma.service';
import { PartProgressRepository } from './part-progress.repository';
import { PartsService } from 'src/parts/parts.service';
import { PartProgress } from './entities/part-progress.entity';

@Injectable()
export class PartProgressService {
constructor(
private readonly prisma: PrismaService,
private readonly partsService: PartsService,
private readonly partProgressRepository: PartProgressRepository,
) {}
// 추후 수정사항
private async findUserById(id: number) {
const user = await this.prisma.user.findUnique({
where: {
id,
},
});

if (!user) {
throw new NotFoundException();
}

return user;
}
//

async findAll(userId: number): Promise<PartProgress[]> {
await this.findUserById(userId);

return this.partProgressRepository.findAllByUserId(userId);
}

async createOrUpdate(
userId: number,
partId: number,
body: CreatePartProgressDto,
): Promise<PartProgress> {
await this.findUserById(userId);

await this.partsService.findOne(partId);

return this.partProgressRepository.upsertPartProgress(userId, partId, body);
}
}
Loading

0 comments on commit 8b658ca

Please sign in to comment.