활동게시글은 동아리 활동에 대한 글입니다.
매 활동마다 활동시 찍은 사진 몇장과 그에 대한 간략한 내용을 업로드 할 예정인데요,
어떤 경우엔 활동게시글을 수정해야할 수 있습니다.
사진을 잘못 올렸다던가... 내용에 오타가 있다던가...
이미 업로드한 사진은 그대로 두고, 다른 내용을 수정하려할 때 에러가 발생했습니다.
문제상황
위와 같이 content에 값만 수정을 하려할 때, 에러가 발생했습니다.
게시글을 수정하는 로직은 아래와 같습니다.
async updateActivity(
userId: number,
activityId: number,
dto: EditActivityDto,
images?: string[],
) {
const { content, date } = dto;
if (!content && !date && !images) {
throw new BadRequestException('수정할 내용이 없습니다.');
}
const user = await this.userService.findUserById(userId);
if (!user) {
throw new BadRequestException('존재하지 않는 사용자입니다.');
}
if (user.isStaff === false) {
throw new BadRequestException('임원진만 글을 수정할 수 있습니다.');
}
const activity = await this.activityRepository.findOne({
where: {
id: activityId,
},
});
if (!activity) {
throw new BadRequestException('존재하지 않는 글입니다.');
}
try {
activity.content = content ? content : activity.content;
if (date) {
activity.date = dto.date;
activity.semester =
dto.date.getMonth() >= 3 && dto.date.getMonth() <= 8 ? 1 : 2;
activity.year = dto.date.getFullYear();
}
if (images.length !== 0) {
activity.images = JSON.stringify(images);
}
await this.activityRepository.save({
...activity,
});
return activity;
} catch (error) {
throw new BadRequestException(error.message);
}
}
문제를 확인하기 위해 , database에 보내는 쿼리를 보았습니다.
위 빨간 박스를 보면 이미지 주소에 '/public/activity/'가 붙어서 들어가는 것을 볼 수 있습니다.
Post 요청을 보내는 경우, 이미지의 prefix에 아무것도 없는 것을 볼 수 있습니다.
프론트엔드에서 게시글을 보고 싶은 경우, 백엔드에서 사진 주소를 보내줍니다.
이때 프론트엔드에서 백엔드의 스태틱 폴더에 접근할 때 하나하나 주소를 붙여줄 필요가 없게 하려고, 백엔드에서 미리 스태틱 폴더 주소를 붙여서 프론트엔드에 보내주도록 설계를 했기 때문입니다.
// src/activity/entities/activity.entity.ts
@Entity({
name: 'activities',
})
export class ActivityModel extends BaseModel {
...
@AfterLoad()
setImagePath() {
if (this.images) {
const imagesPath = JSON.parse(this.images);
this.images = imagesPath.map(
(image) => `/${join(ACTIVITY_PUBLIC_IMAGE_PATH, image)}`,
);
}
}
}
@AfterLoad() 는 엔티티 리스너로 엔티티에 적용할 수 있는 메서드입니다.
@AfterLoad()는 TypeORM으로 엔티티를 부른 뒤(로딩한 뒤) 실행되는 메서드입니다.
위 setImagePath의 경우, 각 image의 앞에 스태틱 폴더 주소를 붙여주고 있습니다.
async updateActivity(
userId: number,
activityId: number,
dto: EditActivityDto,
images?: string[],
) {
const { content, date } = dto;
if (!content && !date && !images) {
throw new BadRequestException('수정할 내용이 없습니다.');
}
const user = await this.userService.findUserById(userId);
if (!user) {
throw new BadRequestException('존재하지 않는 사용자입니다.');
}
if (user.isStaff === false) {
throw new BadRequestException('임원진만 글을 수정할 수 있습니다.');
}
const activity = await this.activityRepository.findOne({
where: {
id: activityId,
},
});
if (!activity) {
throw new BadRequestException('존재하지 않는 글입니다.');
}
try {
activity.content = content ? content : activity.content;
if (date) {
activity.date = dto.date;
activity.semester =
dto.date.getMonth() >= 3 && dto.date.getMonth() <= 8 ? 1 : 2;
activity.year = dto.date.getFullYear();
}
if (images.length !== 0) {
activity.images = JSON.stringify(images);
}
await this.activityRepository.save({
...activity,
});
return activity;
} catch (error) {
throw new BadRequestException(error.message);
}
}
위 코드의 이미지 저장 부분을 보면 사용자가 images를 입력했다면 images를 입력 값으로 저장하고 Images를 입력하지 않았다면 이미 존재하는 activity의 images를 저장합니다.
문제는 이때 activity를 findOne() 메서드로 찾습니다.
const activity = await this.activityRepository.findOne({
where: {
id: activityId,
},
});
이렇게 TypeORM을 통해 엔티티를 찾아왔기에 setImagePath가 실행이 되어, 각 image에 prefix가 붙게 되는 것이었습니다.
실제 db엔 prefix가 없는 상태로 저장이 됩니다.
그러나 activity를 불러온 뒤의 images엔 prefix가 붙게 됩니다.(setImagePath)
그러므로 prefix를 제거해 저장해야 합니다.(유저가 image를 업로드 하지 않은 경우)
문제해결
// src/activity/activity.service.ts
async updateActivity(
userId: number,
activityId: number,
dto: EditActivityDto,
images?: string[],
) {
const { content, date } = dto;
if (!content && !date && !images) {
throw new BadRequestException('수정할 내용이 없습니다.');
}
const user = await this.userService.findUserById(userId);
if (!user) {
throw new BadRequestException('존재하지 않는 사용자입니다.');
}
if (user.isStaff === false) {
throw new BadRequestException('임원진만 글을 수정할 수 있습니다.');
}
const activity = await this.activityRepository.findOne({
where: {
id: activityId,
},
});
if (!activity) {
throw new BadRequestException('존재하지 않는 글입니다.');
}
try {
activity.content = content ? content : activity.content;
if (date) {
activity.date = dto.date;
activity.semester =
dto.date.getMonth() >= 3 && dto.date.getMonth() <= 8 ? 1 : 2;
activity.year = dto.date.getFullYear();
}
if (images.length !== 0) {
activity.images = JSON.stringify(images);
} else {
activity.images = JSON.stringify(activity.images).replaceAll(
`${ACTIVITY_PUBLIC_IMAGE_PATH}/`,
'',
);
activity.images = activity.images;
}
await this.activityRepository.save({
...activity,
});
return activity;
} catch (error) {
throw new BadRequestException(error.message);
}
}
위처럼 ACTIVITY_PUBLIC_IMAGE_PATH를 제거해준 뒤 저장을 하면 됩니다.
위와 같이 사용자가 content만 업데이트를 했을 때 원래 사진 그대로 저장이 되는 것을 볼 수 있습니다.