Skip to content

Commit fe613bf

Browse files
authored
Merge pull request #7 from oodd-team/OD-40
게시글 수정 기능
2 parents 1d1fd84 + 97d9d44 commit fe613bf

10 files changed

+483
-10
lines changed

oodd.erd.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
"settings": {
55
"width": 5000,
66
"height": 5000,
7-
"scrollTop": -141.7535,
8-
"scrollLeft": -27.0249,
9-
"zoomLevel": 0.94,
7+
"scrollTop": -911.9711,
8+
"scrollLeft": -1208.5347,
9+
"zoomLevel": 1,
1010
"show": 511,
1111
"database": 4,
1212
"databaseName": "oodd_dev",

src/clothing/clothing.service.ts

+41
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { InjectRepository } from '@nestjs/typeorm';
33
import { QueryRunner, Repository } from 'typeorm';
44
import { Clothing } from 'src/common/entities/clothing.entity';
55
import { UploadClothingDto } from 'src/post/dtos/create-post.dto';
6+
import { DataNotFoundException } from 'src/common/exception/service.exception';
7+
import { PatchClothingDto } from 'src/post/dtos/patch-Post.dto';
68

79
@Injectable()
810
export class ClothingService {
@@ -29,4 +31,43 @@ export class ClothingService {
2931

3032
return await queryRunner.manager.save(clothingEntities);
3133
}
34+
35+
// Clothing 수정
36+
async updateClothing(
37+
uploadClothingDto: PatchClothingDto,
38+
queryRunner: QueryRunner,
39+
): Promise<Clothing> {
40+
const existingClothing = await this.clothingRepository.findOne({
41+
where: { id: uploadClothingDto.id, status: 'activated' },
42+
});
43+
44+
if (!existingClothing) {
45+
throw DataNotFoundException(
46+
`Clothing ID ${uploadClothingDto.id}를 찾을 수 없습니다.`,
47+
);
48+
}
49+
50+
// 필드 업데이트
51+
if (uploadClothingDto.imageUrl)
52+
existingClothing.imageUrl = uploadClothingDto.imageUrl;
53+
if (uploadClothingDto.brandName)
54+
existingClothing.brandName = uploadClothingDto.brandName;
55+
if (uploadClothingDto.modelName)
56+
existingClothing.modelName = uploadClothingDto.modelName;
57+
if (uploadClothingDto.modelNumber)
58+
existingClothing.modelNumber = uploadClothingDto.modelNumber;
59+
if (uploadClothingDto.url) existingClothing.url = uploadClothingDto.url;
60+
61+
return await queryRunner.manager.save(existingClothing);
62+
}
63+
64+
// Clothing 삭제 처리
65+
async deleteClothing(
66+
clothing: Clothing,
67+
queryRunner: QueryRunner,
68+
): Promise<Clothing> {
69+
clothing.status = 'deactivated';
70+
clothing.softDelete();
71+
return await queryRunner.manager.save(clothing);
72+
}
3273
}

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

+87
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Clothing } from 'src/common/entities/clothing.entity';
55
import { PostClothing } from 'src/common/entities/post-clothing.entity';
66
import { Post } from 'src/common/entities/post.entity';
77
import { UploadClothingDto } from 'src/post/dtos/create-post.dto';
8+
import { PatchClothingDto } from 'src/post/dtos/patch-Post.dto';
89
import { QueryRunner, Repository } from 'typeorm';
910

1011
@Injectable()
@@ -15,6 +16,7 @@ export class PostClothingService {
1516
private readonly clothingService: ClothingService,
1617
) {}
1718

19+
// 옷 정보 저장
1820
async savePostClothings(
1921
post: Post,
2022
uploadClothingDtos: UploadClothingDto[],
@@ -34,4 +36,89 @@ export class PostClothingService {
3436

3537
await queryRunner.manager.save(postClothingEntities);
3638
}
39+
40+
// 옷 정보 수정
41+
async updatePostClothings(
42+
post: Post,
43+
uploadClothingDtos: PatchClothingDto[],
44+
queryRunner?: QueryRunner,
45+
): Promise<void> {
46+
const existingPostClothings = await this.postClothingRepository.find({
47+
where: { post: post, status: 'activated' },
48+
relations: ['clothing'],
49+
});
50+
51+
// 빈 배열이 들어온 경우
52+
if (uploadClothingDtos.length === 0) {
53+
const clothingsToDeactivate = existingPostClothings.filter(
54+
(existingClothing) => existingClothing.status === 'activated',
55+
);
56+
57+
await this.deletePostClothing(clothingsToDeactivate, queryRunner);
58+
return; // 함수 종료
59+
}
60+
61+
// 삭제할 PostClothing
62+
const postClothingsToRemove = existingPostClothings.filter(
63+
(existingPostClothing) =>
64+
existingPostClothing.status === 'activated' &&
65+
!uploadClothingDtos.some(
66+
(newClothing) => newClothing.id === existingPostClothing.clothing.id,
67+
),
68+
);
69+
70+
if (postClothingsToRemove.length > 0) {
71+
await this.deletePostClothing(postClothingsToRemove, queryRunner);
72+
}
73+
74+
// PostClothing 추가 및 수정
75+
const newPostClothings: PostClothing[] = [];
76+
77+
for (const newClothing of uploadClothingDtos) {
78+
const existingPostClothing = existingPostClothings.find(
79+
(postClothing) => postClothing.clothing.id === newClothing.id,
80+
);
81+
82+
if (existingPostClothing) {
83+
// Clothing 정보 수정
84+
await this.clothingService.updateClothing(newClothing, queryRunner);
85+
} else {
86+
// 새로운 Clothing 추가
87+
const newClothingEntity = await this.clothingService.saveClothings(
88+
[newClothing],
89+
queryRunner,
90+
);
91+
for (const clothing of newClothingEntity) {
92+
const newPostClothingEntity = this.postClothingRepository.create({
93+
post,
94+
clothing,
95+
});
96+
newPostClothings.push(newPostClothingEntity);
97+
}
98+
}
99+
}
100+
if (newPostClothings.length > 0) {
101+
await queryRunner.manager.save(newPostClothings);
102+
}
103+
}
104+
105+
// PostClothing 및 Clothing 삭제 처리
106+
async deletePostClothing(
107+
postClothing: PostClothing[],
108+
queryRunner: QueryRunner,
109+
): Promise<void> {
110+
await Promise.all(
111+
postClothing.map(async (postClothingItem) => {
112+
postClothingItem.status = 'deactivated';
113+
postClothingItem.softDelete();
114+
115+
await this.clothingService.deleteClothing(
116+
postClothingItem.clothing,
117+
queryRunner,
118+
);
119+
120+
await queryRunner.manager.save(postClothingItem);
121+
}),
122+
);
123+
}
37124
}

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

+69
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export class PostImageService {
1313
private readonly postImageRepository: Repository<PostImage>,
1414
) {}
1515

16+
// 이미지 저장
1617
async savePostImages(
1718
postImages: UploadImageDto[],
1819
post: Post,
@@ -32,4 +33,72 @@ export class PostImageService {
3233
});
3334
await queryRunner.manager.save(postImageEntities);
3435
}
36+
37+
// 이미지 수정
38+
async updatePostImages(
39+
postImages: UploadImageDto[],
40+
post: Post,
41+
queryRunner?: QueryRunner,
42+
) {
43+
// 빈 배열이 들어온 경우
44+
if (postImages.length === 0) {
45+
throw InvalidInputValueException('하나 이상의 이미지를 업로드하세요.');
46+
}
47+
48+
const existingImages = await queryRunner.manager.find(PostImage, {
49+
where: { post: post },
50+
order: { orderNum: 'ASC' },
51+
});
52+
53+
const existingImageUrls = new Set(existingImages.map((img) => img.url));
54+
55+
// 삭제할 이미지 목록
56+
const imagesToRemove = existingImages.filter(
57+
(existingImage) =>
58+
existingImage.status === 'activated' &&
59+
!postImages.some((newImage) => newImage.imageurl === existingImage.url),
60+
);
61+
62+
// 이미지 삭제
63+
await this.deleteImages(imagesToRemove, queryRunner);
64+
65+
// 새 이미지 추가
66+
await Promise.all(
67+
postImages.map(async (newImage) => {
68+
if (existingImageUrls.has(newImage.imageurl)) {
69+
const existingImage = existingImages.find(
70+
(image) => image.url === newImage.imageurl,
71+
);
72+
73+
// 기존 이미지의 orderNum이 수정된 경우
74+
if (existingImage && existingImage.orderNum !== newImage.orderNum) {
75+
existingImage.orderNum = newImage.orderNum;
76+
return queryRunner.manager.save(existingImage);
77+
}
78+
} else {
79+
// 새로운 이미지 저장
80+
const newPostImage = this.postImageRepository.create({
81+
url: newImage.imageurl,
82+
orderNum: newImage.orderNum,
83+
post,
84+
});
85+
return queryRunner.manager.save(newPostImage);
86+
}
87+
}),
88+
);
89+
}
90+
// 이미지 삭제 처리
91+
async deleteImages(
92+
imagesToRemove: PostImage[],
93+
queryRunner?: QueryRunner,
94+
): Promise<void> {
95+
await Promise.all(
96+
imagesToRemove.map(async (image) => {
97+
image.status = 'deactivated';
98+
image.softDelete();
99+
image.orderNum = 0;
100+
return queryRunner.manager.save(image);
101+
}),
102+
);
103+
}
35104
}

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

+86
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,90 @@ export class PostStyletagService {
5858
}
5959
}
6060
}
61+
62+
// 스타일 태그 수정
63+
async updatePostStyletags(
64+
post: Post,
65+
newTags: string[],
66+
queryRunner?: QueryRunner,
67+
): Promise<void> {
68+
const existingPostStyletags = await this.postStyletagRepository.find({
69+
where: { post },
70+
relations: ['styletag'],
71+
});
72+
73+
// 빈 배열이 들어온 경우
74+
if (newTags.length === 0) {
75+
const tagsToDelete = existingPostStyletags.filter(
76+
(existingPostStyletag) => existingPostStyletag.status === 'activated',
77+
);
78+
79+
await this.deletePostStyletags(tagsToDelete, queryRunner);
80+
return; // 함수 종료
81+
}
82+
83+
// Styletag 조회
84+
const styleTags = await this.styletagService.findStyleTags(newTags);
85+
86+
if (styleTags.length === 0) {
87+
throw DataNotFoundException('일치하는 스타일 태그가 없습니다.');
88+
}
89+
90+
// 삭제할 PostStyletag
91+
const newTagIds = styleTags.map((tag) => tag.id);
92+
const tagsToRemove = existingPostStyletags.filter(
93+
(existingTag) =>
94+
existingTag.status === 'activated' &&
95+
!newTagIds.includes(existingTag.styletag.id),
96+
);
97+
98+
// PostStyletag 삭제
99+
if (tagsToRemove.length > 0) {
100+
await this.deletePostStyletags(tagsToRemove, queryRunner);
101+
}
102+
103+
// 새로운 PostStyletag 추가
104+
const newPostStyletags = [];
105+
for (const tag of styleTags) {
106+
// 태그 중복 검사
107+
const existingPostStyletag = existingPostStyletags.find(
108+
(existingTag) => existingTag.styletag.id === tag.id,
109+
);
110+
111+
if (existingPostStyletag) {
112+
// 기존 태그가 있을 경우
113+
if (existingPostStyletag.status === 'deactivated') {
114+
// 상태가 'deactivated'인 경우, 'activated'로 변경
115+
existingPostStyletag.status = 'activated';
116+
await queryRunner.manager.save(existingPostStyletag);
117+
}
118+
} else {
119+
// 새로운 태그 중복 검사
120+
if (!newPostStyletags.some((newTag) => newTag.tag === tag.tag)) {
121+
newPostStyletags.push(
122+
this.postStyletagRepository.create({
123+
post,
124+
styletag: tag,
125+
}),
126+
);
127+
} else {
128+
throw InvalidInputValueException(`중복된 스타일 태그: ${tag.tag}`);
129+
}
130+
}
131+
}
132+
if (newPostStyletags.length > 0) {
133+
await queryRunner.manager.save(newPostStyletags);
134+
}
135+
}
136+
// PostStyletag 삭제 처리
137+
async deletePostStyletags(
138+
tagsToDeleye: PostStyletag[],
139+
queryRunner?: QueryRunner,
140+
): Promise<void> {
141+
for (const tag of tagsToDeleye) {
142+
tag.status = 'deactivated';
143+
tag.softDelete();
144+
}
145+
await queryRunner.manager.save(tagsToDeleye);
146+
}
61147
}

0 commit comments

Comments
 (0)