Skip to content

Commit 15b43c3

Browse files
committed
Merge branch 'feat/file-upload' of https://github.com/boostcampwm-2024/web05-Denamu into feat/file-upload
2 parents ebb4b04 + 55424e4 commit 15b43c3

File tree

54 files changed

+1389
-102
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1389
-102
lines changed

docker-compose/docker-compose.prod.infra.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,21 @@ services:
3535
timeout: 5s
3636
retries: 3
3737

38+
# MySQL Metrics
39+
mysql_metrics:
40+
image: prom/mysqld-exporter
41+
ports:
42+
- "9104:9104"
43+
networks:
44+
- Denamu
45+
command:
46+
- "--mysqld.username=$MYSQL_USER:$MYSQL_PASSWORD"
47+
- "--mysqld.address=mysql-db:3306"
48+
environment:
49+
TZ: "Asia/Seoul"
50+
depends_on:
51+
- mysql-db
52+
3853
# Redis 서비스
3954
redis:
4055
image: "redis:6.0.16-alpine"
@@ -58,6 +73,21 @@ services:
5873
environment:
5974
TZ: "Asia/Seoul"
6075

76+
# Redis Metrics
77+
redis_metrics:
78+
image: oliver006/redis_exporter
79+
ports:
80+
- "9121:9121"
81+
networks:
82+
- Denamu
83+
environment:
84+
REDIS_ADDR: "redis://redis:6379"
85+
REDIS_USER: "${REDIS_USER}"
86+
REDIS_PASSWORD: "${REDIS_PASSWORD}"
87+
TZ: "Asia/Seoul"
88+
depends_on:
89+
- redis
90+
6191
# Prometheus
6292
prometheus:
6393
image: "prom/prometheus"

docker-compose/docker-compose.prod.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ services:
2020
condition: service_healthy
2121
volumes:
2222
- ../server/logs:/var/web05-Denamu/server/logs
23+
- /var/web05-Denamu/objects:/var/web05-Denamu/objects
2324
environment:
2425
NODE_ENV: "PROD"
2526
TZ: "Asia/Seoul"

docker-compose/init.sql

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -130,14 +130,14 @@ CREATE TABLE `comment` (
130130

131131
CREATE TABLE `likes` (
132132
`id` int NOT NULL AUTO_INCREMENT,
133+
`like_date` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
133134
`feed_id` int NOT NULL,
134135
`user_id` int NOT NULL,
135-
`like_date` datetime NOT NULL,
136136
PRIMARY KEY (`id`),
137-
UNIQUE KEY `UQ_likes_user_feed` (`user_id`,`feed_id`),
138-
KEY `FK_like_feed` (`feed_id`),
139-
CONSTRAINT `FK_like_feed` FOREIGN KEY (`feed_id`) REFERENCES `feed` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
140-
CONSTRAINT `FK_like_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
137+
UNIQUE KEY `IDX_0be1d6ca115f56ed76c65e6bda` (`user_id`,`feed_id`),
138+
KEY `FK_85b0dbd1e7836d0f8cdc38fe830` (`feed_id`),
139+
CONSTRAINT `FK_3f519ed95f775c781a254089171` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
140+
CONSTRAINT `FK_85b0dbd1e7836d0f8cdc38fe830` FOREIGN KEY (`feed_id`) REFERENCES `feed` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
141141
);
142142

143143
-- denamu.provider definition
@@ -155,6 +155,18 @@ CREATE TABLE `provider` (
155155
CONSTRAINT `FK_d3d18186b602240b93c9f1621ea` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
156156
);
157157

158+
-- denamu.rss_remove definition
159+
160+
CREATE TABLE `rss_remove` (
161+
`id` int NOT NULL AUTO_INCREMENT,
162+
`request_date` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
163+
`reason` text COLLATE utf8mb4_unicode_ci NOT NULL,
164+
`blog_id` int NOT NULL,
165+
PRIMARY KEY (`id`),
166+
UNIQUE KEY `REL_69e45fd3ff04dac43a89e1951e` (`blog_id`),
167+
CONSTRAINT `FK_69e45fd3ff04dac43a89e1951e4` FOREIGN KEY (`blog_id`) REFERENCES `rss_accept` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
168+
);
169+
158170
-- denamu.admin insert data
159171

160172
INSERT INTO admin (login_id, password) VALUES
@@ -371,11 +383,16 @@ INSERT INTO comment(comment, date, feed_id, user_id) VALUES
371383

372384
-- denamu.activity insert data
373385

374-
-- INSERT INTO activity (activity_date, view_count, user_id) VALUES
375-
-- ();
386+
INSERT INTO activity (activity_date, view_count, user_id) VALUES
387+
('2025-07-01 11:48:00', 1, 1);
376388

377389
-- denamu.like insert data
378390

379391
INSERT INTO likes(feed_id, user_id, like_date) VALUES
380392
(94,1,'2025-06-13 17:47:05'),
381-
(95,1,'2025-06-13 17:47:07');
393+
(95,1,'2025-06-13 17:47:07');
394+
395+
-- denamu.rss_remove insert data
396+
397+
INSERT INTO rss_remove(request_date, reason, blog_id) VALUES
398+
('2025-07-01 11:48:00', 'example reason', 1);

docker-compose/prometheus.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,11 @@ scrape_configs:
1111
metrics_path: "/api/metrics"
1212
static_configs:
1313
- targets: ["app:8080"]
14+
15+
- job_name: "mysql_exporter"
16+
static_configs:
17+
- targets: ["mysql_metrics:9104"]
18+
19+
- job_name: "redis_exporter"
20+
static_configs:
21+
- targets: ["redis_metrics:9121"]

nginx/nginx.conf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ http {
8787
try_files $uri $uri/ =404;
8888
}
8989

90+
# 업로드된 파일 서빙
91+
location /objects {
92+
alias /var/web05-Denamu/objects/;
93+
try_files $uri $uri/ =404;
94+
}
95+
9096
# API 요청을 NestJS로 프록시
9197
location /api {
9298
proxy_pass http://app:8080;

server/src/admin/entity/admin.entity.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
22

3-
abstract class UserInformation extends BaseEntity {
3+
@Entity({
4+
name: 'admin',
5+
})
6+
export class Admin extends BaseEntity {
47
@PrimaryGeneratedColumn()
58
id: number;
69

@@ -17,8 +20,3 @@ abstract class UserInformation extends BaseEntity {
1720
})
1821
password: string;
1922
}
20-
21-
@Entity({
22-
name: 'admin',
23-
})
24-
export class Admin extends UserInformation {}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { applyDecorators } from '@nestjs/common';
2+
import {
3+
ApiBadRequestResponse,
4+
ApiNotFoundResponse,
5+
ApiOkResponse,
6+
ApiOperation,
7+
} from '@nestjs/swagger';
8+
9+
export function ApiGetComment() {
10+
return applyDecorators(
11+
ApiOperation({
12+
summary: '댓글 조회 API',
13+
}),
14+
ApiOkResponse({
15+
description: 'Ok',
16+
schema: {
17+
properties: {
18+
message: {
19+
type: 'string',
20+
},
21+
data: {
22+
type: 'array',
23+
items: {
24+
type: 'object',
25+
properties: {
26+
id: { type: 'number' },
27+
comment: { type: 'string' },
28+
date: { type: 'string', format: 'date-time' },
29+
user: {
30+
type: 'object',
31+
properties: {
32+
id: { type: 'number' },
33+
userName: { type: 'string' },
34+
profileImage: { type: 'string' },
35+
},
36+
},
37+
},
38+
},
39+
},
40+
},
41+
},
42+
example: {
43+
message: '댓글 조회를 성공했습니다.',
44+
data: [
45+
{
46+
id: 1,
47+
comment: 'example',
48+
date: '2025-01-01T00:00:00.000Z',
49+
user: {
50+
id: 1,
51+
userName: 'example',
52+
profileImage: 'https://example.com',
53+
},
54+
},
55+
],
56+
},
57+
}),
58+
ApiBadRequestResponse({
59+
description: 'Bad Request',
60+
example: {
61+
message: '오류 메세지',
62+
},
63+
}),
64+
ApiNotFoundResponse({
65+
description: 'Not Found',
66+
example: {
67+
message: '게시글을 찾을 수 없습니다.',
68+
},
69+
}),
70+
);
71+
}

server/src/comment/controller/comment.controller.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import {
22
Body,
33
Controller,
44
Delete,
5+
Get,
56
HttpCode,
67
HttpStatus,
8+
Param,
79
Patch,
810
Post,
911
Req,
@@ -19,15 +21,27 @@ import { ApiResponse } from '../../common/response/common.response';
1921
import { CreateCommentRequestDto } from '../dto/request/create-comment.dto';
2022
import { DeleteCommentRequestDto } from '../dto/request/delete-comment.dto';
2123
import { UpdateCommentRequestDto } from '../dto/request/update-comment.dto';
24+
import { GetCommentRequestDto } from '../dto/request/get-comment.dto';
25+
import { ApiGetComment } from '../api-docs/getComment.api-docs';
2226

2327
@ApiTags('Comment')
2428
@Controller('comment')
25-
@UseGuards(JwtGuard)
2629
export class CommentController {
2730
constructor(private readonly commentService: CommentService) {}
2831

32+
@ApiGetComment()
33+
@Get('/:feedId')
34+
@HttpCode(HttpStatus.OK)
35+
async getComment(@Param() getCommentRequestDto: GetCommentRequestDto) {
36+
return ApiResponse.responseWithData(
37+
'댓글 조회를 성공했습니다.',
38+
await this.commentService.get(getCommentRequestDto),
39+
);
40+
}
41+
2942
@ApiCreateComment()
3043
@Post()
44+
@UseGuards(JwtGuard)
3145
@HttpCode(HttpStatus.CREATED)
3246
async createComment(@Req() req, @Body() commentDto: CreateCommentRequestDto) {
3347
await this.commentService.create(req.user, commentDto);
@@ -36,6 +50,7 @@ export class CommentController {
3650

3751
@ApiDeleteComment()
3852
@Delete()
53+
@UseGuards(JwtGuard)
3954
@HttpCode(HttpStatus.OK)
4055
async deleteComment(@Req() req, @Body() commentDto: DeleteCommentRequestDto) {
4156
await this.commentService.delete(req.user, commentDto);
@@ -44,6 +59,7 @@ export class CommentController {
4459

4560
@ApiUpdateComment()
4661
@Patch()
62+
@UseGuards(JwtGuard)
4763
@HttpCode(HttpStatus.OK)
4864
async updateComment(@Req() req, @Body() commentDto: UpdateCommentRequestDto) {
4965
await this.commentService.update(req.user, commentDto);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
import { Type } from 'class-transformer';
3+
import { IsInt } from 'class-validator';
4+
5+
export class GetCommentRequestDto {
6+
@ApiProperty({
7+
example: '게시글 ID',
8+
description: '게시글 ID를 입력해주세요',
9+
})
10+
@IsInt({
11+
message: '숫자로 입력해주세요.',
12+
})
13+
@Type(() => Number)
14+
feedId: number;
15+
16+
constructor(partial: Partial<GetCommentRequestDto>) {
17+
Object.assign(this, partial);
18+
}
19+
}

server/src/comment/repository/comment.repository.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,11 @@ export class CommentRepository extends Repository<Comment> {
99
}
1010

1111
async getCommentInformation(feedId: number) {
12-
const results = await this.createQueryBuilder('comment')
13-
.innerJoin('comment.user', 'user') // ✅ 관계명은 'user'로
12+
return await this.createQueryBuilder('comment')
13+
.innerJoin('comment.user', 'user')
1414
.where('comment.feed_id = :feedId', { feedId })
1515
.select(['comment', 'comment.date', 'user'])
1616
.orderBy('comment.date', 'ASC')
1717
.getMany();
18-
return results.map((row) => ({
19-
id: row.id,
20-
comment: row.comment,
21-
date: row.date,
22-
user: {
23-
id: row.user.id,
24-
userName: row.user.userName,
25-
profileImage: row.user.profileImage,
26-
},
27-
}));
2818
}
2919
}

0 commit comments

Comments
 (0)