Skip to content

Commit 5e6e4fb

Browse files
Merge pull request #336 from boostcampwm-2024/dev
[Deploy] 6주차 6차 배포
2 parents 6cb0e1b + 0634530 commit 5e6e4fb

File tree

21 files changed

+222
-100
lines changed

21 files changed

+222
-100
lines changed

backend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"@nestjs/typeorm": "^10.0.2",
3232
"@nestjs/websockets": "^10.4.6",
3333
"@socket.io/redis-adapter": "^8.3.0",
34+
"@types/k6": "^0.54.2",
3435
"@types/passport-jwt": "^4.0.1",
3536
"axios": "^1.7.7",
3637
"class-transformer": "^0.5.1",

backend/src/question-list/question-list.service.ts

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { DataSource, In, SelectQueryBuilder } from "typeorm";
1717
import { PaginateMetaDto } from "@/question-list/dto/paginate-meta.dto";
1818
import { PaginateDto } from "@/question-list/dto/paginate.dto";
1919
import { QuestionListDto } from "@/question-list/dto/question-list.dto";
20+
import { Transactional } from "typeorm-transactional";
2021

2122
@Injectable()
2223
export class QuestionListService {
@@ -71,6 +72,7 @@ export class QuestionListService {
7172
}
7273

7374
// 질문 생성 메서드
75+
@Transactional()
7476
async createQuestionList(createQuestionListDto: CreateQuestionListDto) {
7577
const { title, contents, categoryNames, isPublic, userId } = createQuestionListDto;
7678

@@ -81,37 +83,24 @@ export class QuestionListService {
8183
throw new Error("Some category names were not found.");
8284
}
8385

84-
const queryRunner = this.dataSource.createQueryRunner();
85-
await queryRunner.connect();
86-
await queryRunner.startTransaction();
87-
88-
try {
89-
const questionList = new QuestionList();
90-
questionList.title = title;
91-
questionList.categories = categories;
92-
questionList.isPublic = isPublic;
93-
questionList.userId = userId;
94-
95-
const createdQuestionList = await queryRunner.manager.save(questionList);
86+
const questionList = new QuestionList();
87+
questionList.title = title;
88+
questionList.categories = categories;
89+
questionList.isPublic = isPublic;
90+
questionList.userId = userId;
9691

97-
const questions = contents.map((content, index) => {
98-
const question = new Question();
99-
question.content = content;
100-
question.index = index;
101-
question.questionList = createdQuestionList;
92+
const createdQuestionList = await this.questionListRepository.save(questionList);
10293

103-
return question;
104-
});
105-
const createdQuestions = await queryRunner.manager.save(questions);
94+
const questions = contents.map((content, index) => {
95+
const question = new Question();
96+
question.content = content;
97+
question.index = index;
98+
question.questionList = createdQuestionList;
10699

107-
await queryRunner.commitTransaction();
108-
return { createdQuestionList, createdQuestions };
109-
} catch (error) {
110-
await queryRunner.rollbackTransaction();
111-
throw new Error(error.message);
112-
} finally {
113-
await queryRunner.release();
114-
}
100+
return question;
101+
});
102+
const createdQuestions = await this.questionRepository.save(questions);
103+
return { createdQuestionList, createdQuestions };
115104
}
116105

117106
async getQuestionListContents(questionListId: number, userId: number) {
@@ -272,7 +261,8 @@ export class QuestionListService {
272261
}
273262

274263
async deleteQuestion(deleteQuestionDto: DeleteQuestionDto) {
275-
const { id, questionListId, userId } = deleteQuestionDto;
264+
const { id, userId } = deleteQuestionDto;
265+
const questionListId = await this.questionRepository.getQuestionListIdByQuestionId(id);
276266

277267
const question = await this.questionRepository.findOne({
278268
where: { id },

backend/src/question-list/repository/question.respository.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,13 @@ export class QuestionRepository extends Repository<Question> {
2121
.orderBy("question.index", "ASC")
2222
.getMany();
2323
}
24+
25+
async getQuestionListIdByQuestionId(questionId: number) {
26+
const result = await this.createQueryBuilder("question")
27+
.select("question.questionListId")
28+
.where("question.id = :questionId", { questionId })
29+
.getOne();
30+
31+
return result ? result.questionListId : null;
32+
}
2433
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import http from "k6/http";
2+
import { check, sleep } from "k6";
3+
4+
export const options = {
5+
vus: 100, // 108명의 사용자
6+
duration: "5s", // 5초 동안 동시 요청을 실행
7+
};
8+
9+
const BASE_URL = "http://localhost:3000";
10+
const COOKIE =
11+
"accessToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsInVzZXJuYW1lIjoiY2FtcGVyXzEwMTYzODkyMyIsImxvZ2luVHlwZSI6ImxvY2FsIiwiaWF0IjoxNzMzMzI2NTIzfQ.OA9o0twIqKUNdTJhnHZkSKytoUvp052VsywKdYvSn30; refreshToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MzQxOTA1MjM5MDgsImlhdCI6MTczMzMyNjUyMywiYXVkIjoiMSJ9.ZXYCwUL8EOjc1xJ3BJ_bHCLQHa20_qxf1FYqolgxP4I";
12+
13+
const questionListData = {
14+
title: "Sample Question List for Test",
15+
contents: [
16+
"This is 1st question!",
17+
"This is 2nd question!",
18+
"This is 3rd question!",
19+
"This is 4th question!",
20+
"This is 5th question!",
21+
"This is 6th question!",
22+
"This is 7th question!",
23+
"This is 8th question!",
24+
"This is 9th question!",
25+
"This is 10th question!",
26+
],
27+
categoryNames: ["보안", "네트워크", "자료구조"],
28+
isPublic: true,
29+
};
30+
31+
export default function () {
32+
const url = `${BASE_URL}/question-list`;
33+
const params = {
34+
headers: {
35+
Cookie: `${COOKIE}`,
36+
"Content-Type": "application/json",
37+
},
38+
};
39+
40+
const response = http.post(url, JSON.stringify(questionListData), params);
41+
42+
check(response, {
43+
"is status 200": (r) => r.status === 200,
44+
});
45+
46+
sleep(1);
47+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import http from "k6/http";
2+
import { check, sleep } from "k6";
3+
4+
export const options = {
5+
vus: 100, // 100명의 사용자
6+
duration: "5s", // 5초 동안 동시 요청을 실행
7+
};
8+
9+
const BASE_URL = "http://localhost:3000";
10+
const COOKIE =
11+
"accessToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsInVzZXJuYW1lIjoiY2FtcGVyXzEwMTYzODkyMyIsImxvZ2luVHlwZSI6ImxvY2FsIiwiaWF0IjoxNzMzMzI2NTIzfQ.OA9o0twIqKUNdTJhnHZkSKytoUvp052VsywKdYvSn30; refreshToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MzQxOTA1MjM5MDgsImlhdCI6MTczMzMyNjUyMywiYXVkIjoiMSJ9.ZXYCwUL8EOjc1xJ3BJ_bHCLQHa20_qxf1FYqolgxP4I";
12+
13+
export default function () {
14+
const questionListId = 27;
15+
const questionId = 66176 + (__VU - 1) * 10 + __ITER;
16+
console.log(questionId);
17+
18+
const url = `${BASE_URL}/question-list/${questionListId}/question/${questionId}`;
19+
const params = {
20+
headers: {
21+
Cookie: `${COOKIE}`,
22+
},
23+
};
24+
25+
const response = http.del(url, null, params);
26+
27+
check(response, {
28+
"is status 200": (r) => r.status === 200,
29+
});
30+
31+
sleep(1);
32+
}

frontend/public/preview-logo2.png

539 KB
Loading

frontend/src/components/common/Sidebar/Sidebar.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const Sidebar = () => {
3636
return (
3737
<nav
3838
className={
39-
"min-w-17.5 w-17.5 h-screen flex flex-col border-r-custom-s gap-1.5 justify-between overflow-y-hidden bg-white transition-colors dark:bg-gray-black dark:border-r-gray-400"
39+
"min-w-sidebar w-sidebar h-screen flex flex-col border-r-custom-s gap-1.5 justify-between overflow-y-hidden bg-white transition-colors dark:bg-gray-black dark:border-r-gray-400"
4040
}
4141
>
4242
<div>
@@ -45,9 +45,9 @@ const Sidebar = () => {
4545
"text-green-400 text-5xl text-center py-7 font-bold hover:tracking-widest transition-all duration-700 font-raleway dark:text-green-100"
4646
}
4747
>
48-
Preview
48+
<img className="w-full px-9" src="/preview-logo2.png" alt="Preview 로고" />
4949
</header>
50-
<hr className={"mx-4 dark:border-gray-400"} />
50+
<hr className={"mx-3 dark:border-gray-400"} />
5151
<ul
5252
className={"flex flex-col gap-2 items-center mx-2 my-2 p-2"}
5353
aria-label={"사이드바 링크 리스트"}
@@ -63,9 +63,9 @@ const Sidebar = () => {
6363
onClick={
6464
route.path
6565
? () =>
66-
navigate(route.path!, {
67-
state: { from: route.path ?? "/" },
68-
})
66+
navigate(route.path!, {
67+
state: { from: route.path ?? "/" },
68+
})
6969
: route.onClick
7070
}
7171
/>

frontend/src/components/common/Sidebar/SidebarMenu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const SidebarMenu = ({
1616
onClick,
1717
}: SidebarMenuProps) => {
1818
const activeClass = isSelected
19-
? "bg-green-100 dark:text-gray-black text-white text-semibold-m"
19+
? "bg-[#53D187] dark:text-gray-black text-white text-semibold-m" // TODO: 나중에 색상 시스템 전체적으로 밝게 업데이트 예정
2020
: "bg-transparent dark:text-white text-gray-black text-medium-l transition-color duration-300 hover:bg-gray-200/30";
2121

2222
return (

frontend/src/components/questions/QuestionsPreviewCard.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,17 @@ const QuestionCard = ({
2121
return (
2222
<Link
2323
to={`/questions/${id}`}
24-
className="bg-white backdrop-blur-sm border border-gray-200 rounded-xl p-4 hover:bg-gray-200/70 transition-all cursor-pointer group hover:border-green-200 dark:bg-gray-900/80 dark:border-gray-700 dark:hover:bg-gray-600/70 hover:shadow-lg"
24+
className="bg-white backdrop-blur-sm border border-gray-200 rounded-xl p-4 hover:bg-gray-200/70 cursor-pointer group duration-200 ease-in-out hover:-translate-y-1.5 hover:border-green-200 dark:bg-gray-900/80 dark:border-gray-700 dark:hover:bg-gray-600/70 hover:shadow-16"
2525
>
2626
<div className="flex justify-between items-start mb-3 pt-1">
2727
<span className="px-3 py-0.5 bg-emerald-50 text-emerald-600 dark:bg-emerald-600/20 dark:text-emerald-400 text-sm rounded-full">
2828
{category}
2929
</span>
3030
<FaStar
31-
className={`w-5 h-5 ${
32-
isStarred
33-
? "text-yellow-400 fill-yellow-400"
34-
: "text-gray-500 group-hover:text-gray-400"
35-
}`}
31+
className={`w-5 h-5 ${isStarred
32+
? "text-yellow-400 fill-yellow-400"
33+
: "text-gray-500 group-hover:text-gray-400"
34+
}`}
3635
/>
3736
</div>
3837
<h3 className="text-lg font-semibold text-black dark:text-white pl-1 mb-4 line-clamp-2">

frontend/src/components/session/MediaPreviewModal.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,7 @@ const MediaPreviewModal = ({
3838
const getMediaPreview = useCallback(async () => {
3939
const mediaStream = await getMediaStream("video");
4040
if (!mediaStream) {
41-
toast.error(
42-
"비디오 장치를 찾을 수 없습니다. 비디오 장치 없이 세션에 참가합니다."
43-
);
41+
toast.error("비디오 장치를 찾을 수 없습니다.");
4442
}
4543
setPreview(mediaStream);
4644
}, []);
@@ -100,7 +98,7 @@ const MediaPreviewModal = ({
10098
className={"w-6 h-6"}
10199
type={"checkbox"}
102100
title={"dd"}
103-
onClick={() => setIsVideoOn(!isVideoOn)}
101+
onChange={() => setIsVideoOn(!isVideoOn)}
104102
/>
105103
<span>내 비디오 끄고 참가하기</span>
106104
</label>
@@ -134,6 +132,7 @@ const MediaPreviewModal = ({
134132
onConfirm();
135133
setReady(true);
136134
modal.closeModal();
135+
preview?.getTracks().forEach((track) => track.stop());
137136
}}
138137
className={
139138
"rounded-custom-m px-16 py-4 bg-green-500 text-white hover:bg-green-600"

0 commit comments

Comments
 (0)