Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
"dotenv": "^16.4.5",
"ioredis": "^5.4.1",
"mysql2": "^3.11.4",
"nestjs-paginate": "^10.0.0",
"nestjs-redis-om": "^0.1.2",
"passport": "^0.7.0",
"passport-custom": "^1.1.1",
"passport-jwt": "^4.0.1",
Expand Down
3 changes: 0 additions & 3 deletions backend/src/question-list/dto/my-question-list.dto.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { Question } from "../question.entity";

export interface MyQuestionListDto {
id: number;
title: string;
contents: Question[];
categoryNames: string[];
isPublic: boolean;
usage: number;
Expand Down
58 changes: 44 additions & 14 deletions backend/src/question-list/question-list.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
import { Body, Controller, Delete, Get, Param, Post, Req, Res, UseGuards } from "@nestjs/common";
import {
Body,
Controller,
Delete,
Get,
Param,
Post,
Query,
Req,
Res,
UseGuards,
} from "@nestjs/common";
import { QuestionListService } from "./question-list.service";
import { CreateQuestionListDto } from "./dto/create-question-list.dto";
import { GetAllQuestionListDto } from "./dto/get-all-question-list.dto";
Expand All @@ -7,21 +18,23 @@ import { AuthGuard } from "@nestjs/passport";
import { JwtPayload } from "@/auth/jwt/jwt.decorator";
import { IJwtPayload } from "@/auth/jwt/jwt.model";
import { MyQuestionListDto } from "./dto/my-question-list.dto";
import { PaginateQuery } from "nestjs-paginate";

@Controller("question-list")
export class QuestionListController {
constructor(private readonly questionListService: QuestionListService) {}

@Get()
async getAllQuestionLists(@Res() res) {
async getAllQuestionLists(@Query() query: PaginateQuery, @Res() res) {
try {
const allQuestionLists: GetAllQuestionListDto[] =
await this.questionListService.getAllQuestionLists();
const { allQuestionLists, meta } =
await this.questionListService.getAllQuestionLists(query);
return res.send({
success: true,
message: "All question lists received successfully.",
data: {
allQuestionLists,
meta,
},
});
} catch (error) {
Expand Down Expand Up @@ -82,6 +95,7 @@ export class QuestionListController {

@Post("category")
async getAllQuestionListsByCategoryName(
@Query() query: PaginateQuery,
@Res() res,
@Body()
body: {
Expand All @@ -90,13 +104,17 @@ export class QuestionListController {
) {
try {
const { categoryName } = body;
const allQuestionLists: GetAllQuestionListDto[] =
await this.questionListService.getAllQuestionListsByCategoryName(categoryName);
const { allQuestionLists, meta } =
await this.questionListService.getAllQuestionListsByCategoryName(
categoryName,
query
);
return res.send({
success: true,
message: "All question lists received successfully.",
data: {
allQuestionLists,
meta,
},
});
} catch (error) {
Expand Down Expand Up @@ -138,16 +156,23 @@ export class QuestionListController {

@Get("my")
@UseGuards(AuthGuard("jwt"))
async getMyQuestionLists(@Res() res, @JwtPayload() token: IJwtPayload) {
async getMyQuestionLists(
@Query() query: PaginateQuery,
@Res() res,
@JwtPayload() token: IJwtPayload
) {
try {
const userId = token.userId;
const myQuestionLists: MyQuestionListDto[] =
await this.questionListService.getMyQuestionLists(userId);
const { myQuestionLists, meta } = await this.questionListService.getMyQuestionLists(
userId,
query
);
return res.send({
success: true,
message: "My question lists received successfully.",
data: {
myQuestionLists,
meta,
},
});
} catch (error) {
Expand All @@ -161,16 +186,21 @@ export class QuestionListController {

@Get("scrap")
@UseGuards(AuthGuard("jwt"))
async getScrappedQuestionLists(@Res() res, @JwtPayload() token: IJwtPayload) {
async getScrappedQuestionLists(
@Query() query: PaginateQuery,
@Res() res,
@JwtPayload() token: IJwtPayload
) {
try {
const userId = token.userId;
const scrappedQuestionLists =
await this.questionListService.getScrappedQuestionLists(userId);
const { scrappedQuestionLists, meta } =
await this.questionListService.getScrappedQuestionLists(userId, query);
return res.send({
success: true,
message: "Scrapped question lists received successfully.",
data: {
scrappedQuestionLists,
questionList: scrappedQuestionLists,
meta,
},
});
} catch (error) {
Expand Down Expand Up @@ -201,7 +231,7 @@ export class QuestionListController {
success: true,
message: "Question list is scrapped successfully.",
data: {
scrappedQuestionList,
questionList: scrappedQuestionList,
},
});
} catch (error) {
Expand Down
44 changes: 25 additions & 19 deletions backend/src/question-list/question-list.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { QuestionList } from "./question-list.entity";
import { Question } from "./question.entity";
import { Category } from "./category.entity";
import { User } from "@/user/user.entity";
import { PaginateQuery } from "nestjs-paginate";

@Injectable()
export class QuestionListRepository {
Expand All @@ -18,9 +19,10 @@ export class QuestionListRepository {
}

findPublicQuestionLists() {
return this.dataSource.getRepository(QuestionList).find({
where: { isPublic: true },
});
return this.dataSource
.getRepository(QuestionList)
.createQueryBuilder("question_list")
.where("question_list.is_public = :isPublic", { isPublic: true });
Comment on lines +22 to +25
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 코드 보면서 느끼는건데, ORM 쓰길 엄청 잘했다고 생각듭니다 ㅋㅋ 코드가 엄청 깔끔하네요

}

async getCategoryIdByName(categoryName: string) {
Expand All @@ -33,13 +35,12 @@ export class QuestionListRepository {
}

findPublicQuestionListsByCategoryId(categoryId: number) {
return this.dataSource.getRepository(QuestionList).find({
where: {
isPublic: true,
categories: { id: categoryId },
},
relations: ["categories"],
});
return this.dataSource
.getRepository(QuestionList)
.createQueryBuilder("question_list")
.innerJoin("question_list.categories", "category")
.where("question_list.is_public = :isPublic", { isPublic: true })
.andWhere("category.id = :categoryId", { categoryId });
Comment on lines -36 to +43
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

깔끔하게 리팩토링 된 것 같아서 좋습니다! 쿼리빌더 이제 잘 활용하시는 것 같아서� 같은 백엔드 개발자로써 뿌듯하네용 ㅎㅎㅎ

}

async findCategoryNamesByQuestionListId(questionListId: number) {
Expand All @@ -66,9 +67,11 @@ export class QuestionListRepository {
}

getContentsByQuestionListId(questionListId: number) {
return this.dataSource.getRepository(Question).find({
where: { questionListId },
});
return this.dataSource
.getRepository(Question)
.createQueryBuilder("question")
.where("question.question_list_id = :questionListId", { questionListId })
.getMany();
}

async getUsernameById(userId: number) {
Expand All @@ -80,9 +83,10 @@ export class QuestionListRepository {
}

getQuestionListsByUserId(userId: number) {
return this.dataSource.getRepository(QuestionList).find({
where: { userId },
});
return this.dataSource
.getRepository(QuestionList)
.createQueryBuilder("question_list")
.where("question_list.userId = :userId", { userId });
}

getQuestionCountByQuestionListId(questionListId: number) {
Expand All @@ -109,9 +113,11 @@ export class QuestionListRepository {
}

getScrappedQuestionListsByUser(user: User) {
return this.dataSource.getRepository(QuestionList).find({
where: { scrappedByUsers: user },
});
return this.dataSource
.getRepository(QuestionList)
.createQueryBuilder("question_list")
.innerJoin("question_list.scrappedByUsers", "user")
.where("user.id = :userId", { userId: user.id });
}

unscrapQuestionList(questionListId: number, userId: number) {
Expand Down
61 changes: 41 additions & 20 deletions backend/src/question-list/question-list.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { MyQuestionListDto } from "./dto/my-question-list.dto";
import { Question } from "./question.entity";
import { Transactional } from "typeorm-transactional";
import { QuestionList } from "@/question-list/question-list.entity";
import { paginate, PaginateQuery } from "nestjs-paginate";

@Injectable()
export class QuestionListService {
Expand All @@ -16,12 +17,16 @@ export class QuestionListService {
private readonly userRepository: UserRepository
) {}

async getAllQuestionLists() {
async getAllQuestionLists(query: PaginateQuery) {
const allQuestionLists: GetAllQuestionListDto[] = [];

const publicQuestionLists = await this.questionListRepository.findPublicQuestionLists();
const result = await paginate(query, publicQuestionLists, {
sortableColumns: ["usage"],
defaultSortBy: [["usage", "DESC"]],
});
Comment on lines +24 to +27
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sort 기준과 기본 sort 기준까지.. 괜찮네요! 이부분 나중에 enum으로 리팩토링 해보시겠다고 하셨는데 이쪽에서도 활용해보시면 괜찮을 것 같네요!


for (const publicQuestionList of publicQuestionLists) {
for (const publicQuestionList of result.data) {
const { id, title, usage } = publicQuestionList;
const categoryNames: string[] =
await this.questionListRepository.findCategoryNamesByQuestionListId(id);
Expand All @@ -38,22 +43,26 @@ export class QuestionListService {
};
allQuestionLists.push(questionList);
}
return allQuestionLists;
return { allQuestionLists, meta: result.meta };
}

async getAllQuestionListsByCategoryName(categoryName: string) {
async getAllQuestionListsByCategoryName(categoryName: string, query: PaginateQuery) {
const allQuestionLists: GetAllQuestionListDto[] = [];

const categoryId = await this.questionListRepository.getCategoryIdByName(categoryName);

if (!categoryId) {
return [];
return {};
}

const publicQuestionLists =
await this.questionListRepository.findPublicQuestionListsByCategoryId(categoryId);
const result = await paginate(query, publicQuestionLists, {
sortableColumns: ["usage"],
defaultSortBy: [["usage", "DESC"]],
});

for (const publicQuestionList of publicQuestionLists) {
for (const publicQuestionList of result.data) {
const { id, title, usage } = publicQuestionList;
const categoryNames: string[] =
await this.questionListRepository.findCategoryNamesByQuestionListId(id);
Expand All @@ -70,7 +79,7 @@ export class QuestionListService {
};
allQuestionLists.push(questionList);
}
return allQuestionLists;
return { allQuestionLists, meta: result.meta };
}

// 질문 생성 메서드
Expand Down Expand Up @@ -107,7 +116,10 @@ export class QuestionListService {

async getQuestionListContents(questionListId: number) {
const questionList = await this.questionListRepository.getQuestionListById(questionListId);
const { id, title, usage, userId } = questionList;
const { id, title, usage, isPublic, userId } = questionList;
if (!isPublic) {
throw new Error("This is private question list.");
}

const contents =
await this.questionListRepository.getContentsByQuestionListId(questionListId);
Expand All @@ -116,7 +128,6 @@ export class QuestionListService {
await this.questionListRepository.findCategoryNamesByQuestionListId(questionListId);

const username = await this.questionListRepository.getUsernameById(userId);

const questionListContents: QuestionListContentsDto = {
id,
title,
Expand All @@ -129,28 +140,29 @@ export class QuestionListService {
return questionListContents;
}

async getMyQuestionLists(userId: number) {
async getMyQuestionLists(userId: number, query: PaginateQuery) {
const questionLists = await this.questionListRepository.getQuestionListsByUserId(userId);
const result = await paginate(query, questionLists, {
sortableColumns: ["usage"],
defaultSortBy: [["usage", "DESC"]],
});

const myQuestionLists: MyQuestionListDto[] = [];
for (const myQuestionList of questionLists) {
for (const myQuestionList of result.data) {
const { id, title, isPublic, usage } = myQuestionList;
const categoryNames: string[] =
await this.questionListRepository.findCategoryNamesByQuestionListId(id);

const contents = await this.questionListRepository.getContentsByQuestionListId(id);

const questionList: MyQuestionListDto = {
id,
title,
contents,
categoryNames,
isPublic,
usage,
};
myQuestionLists.push(questionList);
}
return myQuestionLists;
return { myQuestionLists, meta: result.meta };
}

async findCategoriesByNames(categoryNames: string[]) {
Expand All @@ -163,9 +175,15 @@ export class QuestionListService {
return categories;
}

async getScrappedQuestionLists(userId: number) {
async getScrappedQuestionLists(userId: number, query: PaginateQuery) {
const user = await this.userRepository.getUserByUserId(userId);
return await this.questionListRepository.getScrappedQuestionListsByUser(user);
const scrappedQuestionLists =
await this.questionListRepository.getScrappedQuestionListsByUser(user);
const result = await paginate(query, scrappedQuestionLists, {
sortableColumns: ["usage"],
defaultSortBy: [["usage", "DESC"]],
});
return { scrappedQuestionLists: result.data, meta: result.meta };
}

async scrapQuestionList(questionListId: number, userId: number) {
Expand All @@ -176,13 +194,16 @@ export class QuestionListService {
if (!questionList) throw new Error("Question list not found.");

// 스크랩하려는 질문지가 내가 만든 질문지인지 확인
const myQuestionLists = await this.questionListRepository.getQuestionListsByUserId(userId);
const myQuestionLists = await this.questionListRepository
.getQuestionListsByUserId(userId)
.getMany();
const isMyQuestionList = myQuestionLists.some((list) => list.id === questionListId);
if (isMyQuestionList) throw new Error("Can't scrap my question list.");

// 스크랩하려는 질문지가 이미 스크랩한 질문지인지 확인
const alreadyScrappedQuestionLists =
await this.questionListRepository.getScrappedQuestionListsByUser(user);
const alreadyScrappedQuestionLists = await this.questionListRepository
.getScrappedQuestionListsByUser(user)
.getMany();
const isAlreadyScrapped = alreadyScrappedQuestionLists.some(
(list) => list.id === questionListId
);
Expand Down