Skip to content

Commit c4a86af

Browse files
authored
Merge pull request #9 from oodd-team/OD-83
게시글 삭제 기능
2 parents dce7076 + 2f0c32d commit c4a86af

11 files changed

+215
-5
lines changed

src/post-clothing/post-clothing.service.ts

+21
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,25 @@ export class PostClothingService {
121121
}),
122122
);
123123
}
124+
125+
// Post에 연결된 PostClothing 및 Clothing 삭제 처리
126+
async deletePostClothingByPostId(
127+
postId: number,
128+
queryRunner: QueryRunner,
129+
): Promise<void> {
130+
const clothingToRemove = await queryRunner.manager.find(PostClothing, {
131+
where: { post: { id: postId } },
132+
relations: ['clothing'],
133+
});
134+
135+
await Promise.all(
136+
clothingToRemove.map(async (Postclothing) => {
137+
Postclothing.status = 'deactivated';
138+
Postclothing.softDelete();
139+
await this.clothingService.deleteClothing(Postclothing.clothing, queryRunner);
140+
141+
return queryRunner.manager.save(Postclothing);
142+
}),
143+
);
144+
}
124145
}
+5-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import { Module } from '@nestjs/common';
22
import { PostCommentController } from './post-comment.controller';
33
import { PostCommentService } from './post-comment.service';
4+
import { TypeOrmModule } from '@nestjs/typeorm';
5+
import { PostComment } from 'src/common/entities/post-comment.entity';
46

57
@Module({
8+
imports: [TypeOrmModule.forFeature([PostComment])],
69
controllers: [PostCommentController],
7-
providers: [PostCommentService]
10+
providers: [PostCommentService],
11+
exports: [PostCommentService],
812
})
913
export class PostCommentModule {}
+21-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,24 @@
11
import { Injectable } from '@nestjs/common';
2+
import { PostComment } from 'src/common/entities/post-comment.entity';
3+
import { QueryRunner } from 'typeorm';
24

35
@Injectable()
4-
export class PostCommentService {}
6+
export class PostCommentService {
7+
// post와 연결된 댓글 삭제
8+
async deleteCommentsByPostId(
9+
postId: number,
10+
queryRunner: QueryRunner,
11+
): Promise<void> {
12+
const commentsToRemove = await queryRunner.manager.find(PostComment, {
13+
where: { post: { id: postId } },
14+
});
15+
16+
await Promise.all(
17+
commentsToRemove.map(async (comment) => {
18+
comment.status = 'deactivated';
19+
comment.softDelete();
20+
return queryRunner.manager.save(comment);
21+
}),
22+
);
23+
}
24+
}

src/post-image/post-image.service.ts

+19
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,23 @@ export class PostImageService {
101101
}),
102102
);
103103
}
104+
105+
// Post와 연결된 이미지 삭제 처리
106+
async deleteImagesByPostId(
107+
postId: number,
108+
queryRunner: QueryRunner,
109+
): Promise<void> {
110+
const imagesToRemove = await queryRunner.manager.find(PostImage, {
111+
where: { post: { id: postId } },
112+
});
113+
114+
await Promise.all(
115+
imagesToRemove.map(async (image) => {
116+
image.status = 'deactivated';
117+
image.softDelete();
118+
image.orderNum = 0;
119+
return queryRunner.manager.save(image);
120+
}),
121+
);
122+
}
104123
}

src/post-like/post-like.module.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import { Module } from '@nestjs/common';
22
import { PostLikeController } from './post-like.controller';
33
import { PostLikeService } from './post-like.service';
4+
import { TypeOrmModule } from '@nestjs/typeorm';
5+
import { PostLike } from 'src/common/entities/post-like.entity';
46

57
@Module({
8+
imports: [TypeOrmModule.forFeature([PostLike])],
69
controllers: [PostLikeController],
7-
providers: [PostLikeService]
10+
providers: [PostLikeService],
11+
exports: [PostLikeService],
812
})
913
export class PostLikeModule {}

src/post-like/post-like.service.ts

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,23 @@
11
import { Injectable } from '@nestjs/common';
2+
import { PostLike } from 'src/common/entities/post-like.entity';
3+
import { QueryRunner } from 'typeorm';
24

35
@Injectable()
4-
export class PostLikeService {}
6+
export class PostLikeService {
7+
async deletePostLikeByPostId(
8+
postId: number,
9+
queryRunner: QueryRunner,
10+
): Promise<void> {
11+
const likesToRemove = await queryRunner.manager.find(PostLike, {
12+
where: { post: { id: postId } },
13+
});
14+
15+
await Promise.all(
16+
likesToRemove.map(async (like) => {
17+
like.status = 'deactivated';
18+
like.softDelete();
19+
return queryRunner.manager.save(like);
20+
}),
21+
);
22+
}
23+
}

src/post-styletag/post-styletag.service.ts

+18
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,22 @@ export class PostStyletagService {
144144
}
145145
await queryRunner.manager.save(tagsToDeleye);
146146
}
147+
148+
// Post에 연결된 PostStyletag 삭제 처리
149+
async deletePostStyletagsByPostId(
150+
postId: number,
151+
queryRunner: QueryRunner,
152+
): Promise<void> {
153+
const tagsToRemove = await queryRunner.manager.find(PostStyletag, {
154+
where: { post: { id: postId } },
155+
});
156+
157+
await Promise.all(
158+
tagsToRemove.map(async (tag) => {
159+
tag.status = 'deactivated';
160+
tag.softDelete();
161+
return queryRunner.manager.save(tag);
162+
}),
163+
);
164+
}
147165
}

src/post/post.controller.ts

+16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
Body,
33
Controller,
4+
Delete,
45
Get,
56
Param,
67
Patch,
@@ -133,6 +134,21 @@ export class PostController {
133134
return new BaseResponse(true, '게시글 수정 성공', updatedPost);
134135
}
135136

137+
@Delete(':postId')
138+
@PatchPostSwagger('게시글 삭제 API')
139+
async deletePost(
140+
@Param('postId') postId: number,
141+
@Req() req: Request,
142+
): Promise<BaseResponse<any>> {
143+
const currentUserId = req.user.userId;
144+
145+
await this.postService.validatePost(postId, currentUserId);
146+
147+
await this.postService.deletePost(postId, currentUserId);
148+
149+
return new BaseResponse(true, '게시글이 삭제되었습니다.');
150+
}
151+
136152
@Patch()
137153
@PatchIsRepresentativeSwagger('대표 게시글 지정 API')
138154
patchIsRepresentative() {

src/post/post.module.ts

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { UserBlockModule } from 'src/user-block/user-block.module';
1010
import { PostClothingModule } from 'src/post-clothing/post-clothing.module';
1111
import { PostImageModule } from 'src/post-image/post-image.module';
1212
import { DayjsModule } from 'src/common/dayjs/dayjs.module';
13+
import { PostLikeModule } from 'src/post-like/post-like.module';
14+
import { PostCommentModule } from 'src/post-comment/post-comment.module';
1315

1416
@Module({
1517
imports: [
@@ -21,6 +23,8 @@ import { DayjsModule } from 'src/common/dayjs/dayjs.module';
2123
UserBlockModule,
2224
PostClothingModule,
2325
DayjsModule,
26+
PostLikeModule,
27+
PostCommentModule,
2428
],
2529
controllers: [PostController],
2630
providers: [PostService],

src/post/post.service.ts

+52
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import { PatchPostDto } from './dtos/patch-Post.dto';
2121
import { UserBlockService } from 'src/user-block/user-block.service';
2222
import { PostClothingService } from 'src/post-clothing/post-clothing.service';
2323
import dayjs from 'dayjs';
24+
import { PostLikeService } from 'src/post-like/post-like.service';
25+
import { PostCommentService } from 'src/post-comment/post-comment.service';
2426
@Injectable()
2527
export class PostService {
2628
constructor(
@@ -31,6 +33,8 @@ export class PostService {
3133
private readonly postImageService: PostImageService,
3234
private readonly postStyletagService: PostStyletagService,
3335
private readonly postClothingService: PostClothingService,
36+
private readonly postLikeService: PostLikeService,
37+
private readonly postCommentService: PostCommentService,
3438
private readonly dataSource: DataSource,
3539
) {}
3640

@@ -255,6 +259,54 @@ export class PostService {
255259
}
256260
}
257261

262+
// 게시글 삭제
263+
async deletePost(postId: number, userId: number): Promise<void> {
264+
const queryRunner = this.dataSource.createQueryRunner();
265+
266+
await queryRunner.startTransaction();
267+
268+
// 게시글 조회
269+
const post = await this.postRepository.findOne({
270+
where: { id: postId, user: { id: userId }, status: 'activated' },
271+
});
272+
273+
try {
274+
// 게시글 삭제
275+
post.status = 'deactivated';
276+
post.softDelete();
277+
278+
await queryRunner.manager.save(post);
279+
280+
// 연결된 PostImage 삭제
281+
await this.postImageService.deleteImagesByPostId(postId, queryRunner);
282+
283+
// 연결된 PostLike 삭제
284+
await this.postLikeService.deletePostLikeByPostId(postId, queryRunner);
285+
286+
// 연결된 PostComment 삭제
287+
await this.postCommentService.deleteCommentsByPostId(postId, queryRunner);
288+
289+
// 연결된 PostClothing 삭제
290+
await this.postClothingService.deletePostClothingByPostId(
291+
postId,
292+
queryRunner,
293+
);
294+
295+
// 연결된 PostStyleTag 삭제
296+
await this.postStyletagService.deletePostStyletagsByPostId(
297+
postId,
298+
queryRunner,
299+
);
300+
301+
await queryRunner.commitTransaction();
302+
} catch (error) {
303+
await queryRunner.rollbackTransaction();
304+
throw InternalServerException('게시글 삭제에 실패했습니다.');
305+
} finally {
306+
await queryRunner.release();
307+
}
308+
}
309+
258310
// 게시글 상세 조회
259311
async getPost(postId: number): Promise<Post> {
260312
const post = await this.postRepository.findOne({

src/post/post.swagger.ts

+34-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
ApiNotFoundResponse,
88
ApiOperation,
99
ApiUnauthorizedResponse,
10-
ApiUnprocessableEntityResponse,
1110
} from '@nestjs/swagger';
1211
import { BaseSwaggerDecorator } from 'nestjs-swagger-decorator';
1312
import { BaseResponse } from 'src/common/response/dto';
@@ -156,6 +155,40 @@ export function PatchPostSwagger(text: string) {
156155
);
157156
}
158157

158+
// 게시글 삭제 API Swagger
159+
export function DeletePostSwagger(text: string) {
160+
return BaseSwaggerDecorator(
161+
{ summary: text },
162+
[],
163+
[
164+
ApiAcceptedResponse({
165+
description: '게시글 삭제 성공',
166+
type: BaseResponse,
167+
}),
168+
ApiBadRequestResponse({
169+
description: '잘못된 요청입니다.',
170+
type: BaseResponse,
171+
}),
172+
ApiUnauthorizedResponse({
173+
description: '인증되지 않은 사용자입니다.',
174+
type: BaseResponse,
175+
}),
176+
ApiForbiddenResponse({
177+
description: '권한이 없습니다.',
178+
type: BaseResponse,
179+
}),
180+
ApiNotFoundResponse({
181+
description: '게시글을 찾을 수 없습니다.',
182+
type: BaseResponse,
183+
}),
184+
ApiInternalServerErrorResponse({
185+
description: '서버 오류입니다.',
186+
type: BaseResponse,
187+
}),
188+
],
189+
);
190+
}
191+
159192
// 대표 게시글 지정 API Swagger
160193
export function PatchIsRepresentativeSwagger(apiSummary: string) {
161194
return ApiOperation({ summary: apiSummary });

0 commit comments

Comments
 (0)