이제 마지막으로 Add 페이지에서 의류를 데이터에 저장할 때와, Details 페이지에서 데이터를 수정하여 데이터베이스에 저장하는 과정에 대해서 포스팅하고자 한다. 이번 포스팅을 마지막으로 Add, Details 페이지에 대한 구현 과정도 마무리 하려고 한다.
전체 테이블 구조도
데이터 저장은 테이블을 잘 확립하였다면 어렵지 않다. 시퀄라이즈의 테이블 관계가 잘 맺어져 있다면 알아서 잘 저장되기 때문이다. 기본적으로 현재 구성된 테이블관계는 복잡하지 않다. 의류의 전반적인 데이터를 나타내는 테이블이 있고, 각 의류마다 저장되는 여러장의 이미지를 저장하는 테이블, 의류의 카테고리마다 입력된 실측 데이터를 저장하는 테이블로 구성되어있다.
한개의 의류를 저장할 때 여러개의 이미지가 있을 수 있다. 따라서 의류와 이미지는 일대다 관계를 가진다.
// Model Cloth
static associate(db) {
db.Cloth.belongsTo(db.User);
db.Cloth.hasMany(db.Image, {
onDelete: "cascade",
hooks: true,
});
db.Cloth.hasOne(db.Outer, {
onDelete: "cascade",
hooks: true,
});
// 생략
}
참고로 onDelete 의 cascade 설정은 만일 의류를 삭제했을 시 이와 연관된 모든 데이터를 삭제하겠다는 설정이다.
여하튼 의류는 하나의 카테고리 테이블과 연관되기에 일대일 관계를 가지며, 유저는 여러벌의 의류를 가질 수 있기에 유저와 의류의 관계 역시 일대다를 형성한다. 그렇게 복잡하지 않다.
이러한 관계를 참고하여 associate 를 설정한 다음, 테이블을 create 해주면 기본적으로 mysql 의 테이블 셋팅이 완료되는데, 이번 포스팅에서는 테이블 셋팅에 관해서는 크게 다루지 않도록 하겠다. (어디까지나 데이터를 어떻게 저장하고, 어떻게 수정할지에 대한 API 에 대해 다루고자 하는 것이니깐). 그 대신 전체적인 ERM 다이그램으로 확인해보자
클라이언트에서 의류 데이터를 서버에 전송하면, 기본적으로 데이터는 Cloth 테이블에 해당 column 에 저장이 된다. 이름이라던지 가격, 색상 등의 정보는 Cloth 테이블에 저장이 되며, 특이점이라면 카테고리의 경우 어떤 카테고리인지는 Cloth 에 저장이 되지만, 실제 실측 데이터는 만들어둔 개별의 카테고리 테이블에 저장이 된다. 즉, 카테고리 테이블은 foreign key 로서 Cloth id 를 가지게 되는 것이다.
마찬가지로 이미지 역시 이미지 테이블에 저장이 되는데, 이미지 테이블도 foreign key 로서 Cloth id 를 가진다. 그림을 참고하도록 하자
의류 데이터 저장하기
잠깐 클라이언트로 돌아와서 제출 코드를 살펴보자
const onSubmit = (data: AddInitialValue) => {
data.image = imagePath;
const Type = Submit();
console.log(data);
dispatch({
type: Type,
data: { items: data, clothId: itemId },
});
};
지난시간 react-hook-form 에서 handleSubmit(onSubmit) 을 통해 data 에 접근할 수 있다고 했었다. 타입지정에서도 알 수 있듯이 data 는 기존 defaultValue 의 포맷을 그대로 따른다. 이제 여기에다가 기존 input 에서 입력값이 아닌 이미지 주소부분을 data.image 에 대입해주자. 이후 dispatch 를 통해서 data 를 서버에 전달하고자 saga 에 전송한다.
function uploadItemsAPI(data: Iterable<[PropertyKey, Object]>) {
return axios.post('/post/clothes', data);
}
function* uploadItems(action: AnyAction) {
try {
console.log('saga imageUpload');
const result: AxiosResponse<Success> = yield call(uploadItemsAPI, action.data);
yield put({
type: t.UPLOAD_ITEMS_SUCCESS,
data: result.data,
});
} catch (err: any) {
console.log(err);
yield put({
type: t.UPLOAD_ITEMS_FAILURE,
error: err.response.data,
});
}
}
saga 에서는 데이터를 받아 axios 를 통해 의류 데이터를 서버 API 를 통해 전송한다.
서버에서는 이 데이터를 받아 테이블에 저장할 준비를 한다.
router.post("/clothes", isLoggedIn, upload.none(), async (req, res, next) => {
// POST /post/clothes
// 미들웨어를 거친 뒤,
try {
// Cloth class 에서 설정해둔 custon method 를 활용해서 데이터를 저장한다.
const cloth = await Cloth.postClothbyReq(req);
// 이미지파일이 req 에 존재할 경우
if (req.body.items.image) {
// 여러장이라면 Promise.all 을 통해서 각각의 이미지들을 하나씩 Image 테이블에 저장시킨다.
if (Array.isArray(req.body.items.image)) {
const images = await Promise.all(req.body.items.image.map((image) => Image.create({ src: image.src, visionSearch: image.visionSearch })));
// cloth 테이블과 Image 테이블을 연동시킨다. 이로서 Image 테이블의 이미지마다 foreign key 로 cloth id 가 형성된다
await cloth.addImages(images);
} else {
// 한장이라도 위와 같은 과정을 거친다
const image = await Image.create({ src: req.body.items.image[0].filename });
await cloth.addImages(image);
}
}
// 이 부분이 핵심인데, req 에 담겨있는 카테고리를 파악해 그 카테고리 클래스의 custom method 를 실행시켜 데이터를 저장한다
getCatagori[req.body.items.categori]["post"](cloth, req);
const reverseId = await Cloth.findOne({
where: { id: cloth.id },
});
res.status(200).json(reverseId);
} catch (err) {
console.error(err);
next(err);
}
});
위 코드를 좀 더 이해하기 위해선, Sequelize 내에서 model 에 custom method 를 설정할 수 있다는 점을 우선적으로 확인해야 한다.
한 예시로서 Cloth 의 custom method 인 postClothbyReq 를 확인해보자
module.exports = class Cloth extends Model {
static init(sequelize) {
return super.init(
{
// 생략
},
{
// 유저 모델에 대한 셋팅
// 이 부분도 생략
}
);
}
// ES6 에 추가된 class 문법에 따라 내부 메서드를 추가한다
// db 저장은 모두 비동기 과정이니 async 처리를 해준다
// this 는 현 클래스인 Cloth 를 받는다.
static postClothbyReq = async (req) => {
const result = await this.create({
productName: req.body.items.productName,
description: req.body.items.description,
price: req.body.items.price,
color: req.body.items.color,
categori: req.body.items.categori,
purchaseDay: req.body.items.purchaseDay,
preference: req.body.items.preference,
UserId: req.user.id,
});
return result;
};
static associate(db) {
// 생략
}
};
사실 custom method 를 사용하지않고 그냥 API 내에서 위 postClothbyReq 내 body 부분을 처리해주어도 관계없다. 실제로 카테고리 부분을 적용하기 전까진 그냥 Cloth.create() 를 통해 받아온 데이터들을 테이블에 저장하였다.
custom method 를 사용함으로서 얻고싶었던 장점은 좀더 깔끔한 API 작성이었다. Cloth 부분은 위 crete 부분을 다 작성해도 그렇게까지 불편하지는 않았지만, 아래에 다룰 카테고리 부분은 조건문이자 모든 카테고리 테이블에 대해서 다루어야 했기에 코드량이 비정상적으로 늘어날것이 뻔했다.
router.post("/clothes", isLoggedIn, upload.none(), async (req, res, next) => {
// POST /post/clothes
try {
const cloth = await Cloth.postClothbyReq(req);
// 생략
// 모든 카테고리에 대해 조건을 걸어서 적합한 테이블에 저장을 해야한다
// 코드량이 너무 길어질뿐 아니라, 추가되는 테이블이 있다면 더 유지보수가 힘들것이다
if(req.body.items.categori === 'outer'){
// const categori = await Outer.create({ ... })
}else if(req.body.items.categori === 'Shirt'){
// const categori = await Shirt.create({ ... }){
}else if(....){
...
}
// 생략
} catch (err) {
console.error(err);
next(err);
}
});
위 코드처럼 카테고리가 무엇이냐에 따라 호출해야할 테이블이 달라지게 된다. if else 조건문이나 설사 switch case 문을 사용한다고 해도, 모든 경우를 다 작성해야하는 것은 달라지지 않는다. /clothes API 의 코드량이 너무 길어질 것이 뻔했고, 여기에 만약 카테고리가 2배로 늘어난다고 가정한다면, 그 만큼 코드량도 비례해서 증가할 것이다. 유지보수가 쉽지 않을 것이다.
테이블 Cloth 의 카테고리에 따라 자동으로 해당 테이블로 연결해주는 기능이 있다면 좋겠지만, 현재로서는 찾아볼 수가 없었다. 고민끝에 지금 가지고 있는 지식으로 가장 최선의 방법을 떠올려봤고, 단계별로 진행해보기로 했다.
- 우선, Cloth 테이블 처럼 각 카테고리 테이블에 custom method 를 생성하여 당장의 API 코드량을 감소시켜본다
- 조건문이 아닌, 객체의 key 값을 카테고리 명칭으로 지정하여, req.body.items.categori 가 바로 key 에 접근할 수 있도록 설정해보자. 이렇게 되면 카테고리가 추가된다하더라도 객체에 카테고리를 key 로서 추가만 해주면 된다.
위 방법이 현재로서는 최선이 아닐까 생각하였고, 우선 custom method 를 생성하여 적용시켜주기로 하였다.
// post.js
router.post("/clothes", isLoggedIn, upload.none(), async (req, res, next) => {
// POST /post/clothes
try {
const cloth = await Cloth.postClothbyReq(req);
// 생략
// 모든 카테고리에 대해 조건을 걸어서 적합한 테이블에 저장을 해야한다
// 코드량이 너무 길어질뿐 아니라, 추가되는 테이블이 있다면 더 유지보수가 힘들것이다
if(req.body.items.categori === 'outer'){
const outer = await Outer.postOuterbyReq(req)
// 일대일 관계에서 자동으로 사용할 수 있는 메서드 setTable 이다.
await Cloth.setOuter(outer)
}else if(req.body.items.categori === 'Shirt'){
const shirt = await Shirt.postShirtbyReq(req)
await Cloth.setShrit(shirt)
}else if(req.body.items.categori === 'Pant'){
const pant = await Pant.postPantbyReq(req)
await Cloth.setPant(pant)
...
}
// 생략
} catch (err) {
console.error(err);
next(err);
}
});
// 개별 카테고리의 메서드 예시
// Outer.js
static postOuterbyReq = async (req) => {
const result = await this.create({
shoulder: req.body.items.categoriItem.shoulder,
arm: req.body.items.categoriItem.arm,
totalLength: req.body.items.categoriItem.totalLength,
chest: req.body.items.categoriItem.chest,
});
return result;
};
...
아예 API 에서 create 해주는것보다는 코드량이 감소했다. 하지만 여전히 조건문에 의해서 모든 메서드를 API 에 작성해주고 있고, 카테고리가 늘어날수록 여전히 API 의 코드량은 많아질 것이다. 이에 새로운 객체를 생성해보자
const { Muffler, Outer, Pant, Shirt, Shoe, Top } = require("../../back/models");
const getCatagori = {
Top: {
post: async function (cloth, req) {
const top = await Top.postTopbyReq(req);
await cloth.setTop(top);
},
},
Outer: {
post: async function (cloth, req) {
const outer = await Outer.postOuterbyReq(req);
await cloth.setOuter(outer);
},
},
Shirt: {
post: async function (cloth, req) {
const shirt = await Shirt.postShirtbyReq(req);
await cloth.setShirt(shirt);
},
},
Pant: {
post: async function (cloth, req) {
const pant = await Pant.postPantbyReq(req);
await cloth.setPant(pant);
},
},
Shoe: {
post: async function (cloth, req) {
const shoe = await Shoe.postShoesbyReq(req);
await cloth.setShoe(shoe);
},
},
Muffler: {
post: async function (cloth, req) {
const muffler = await Muffler.postMufflerbyReq(req);
await cloth.setMuffler(muffler);
},
},
};
module.exports = getCatagori;
객체 getCategori 를 생성하고, 각 key 값을 카테고리로 지정해주자. key 를 통해 value 값을 호출하는데, 비동기 함수가 호출이 된다. 각 카테고리의 테이블의 내부 메서드가 실행이 되고(즉 카테고리 실측 데이터가 테이블에 저장이 되고), Cloth 테이블과 연결을 시켜준다.
즉, 카테고리에 따라 key 로 접근을 하면 그 카테고리에 해당하는 실측값이 카테고리 테이블에 저장이 되는것이다. 이렇게 설정하면 API 가 확실히 간소해진다.
router.post("/clothes", isLoggedIn, upload.none(), async (req, res, next) => {
// POST /post/clothes
try {
const cloth = await Cloth.postClothbyReq(req);
// 생략
}
// getCategori 객체 key 값에 접근을 한다. 그 중 post 에 대한 value(함수)를 호출
// 인자로 req 를 넘겨준다
getCatagori[req.body.items.categori]["post"](cloth, req);
const reverseId = await Cloth.findOne({
where: { id: cloth.id },
});
res.status(200).json(reverseId);
} catch (err) {
console.error(err);
next(err);
}
});
이제 카테고리가 2배 3배 늘어난다 하더라도, API 를 변경할 필요는 없어졌다. 개인적으로 훨씬 깔끔해진 것 같다. 실제 데이터를 저장하여 mysql 에서 확인해보면 잘 들어와있음을 확인할 수 있었다.
의류 수정하기
데이터를 저장하는것은 그래도 그리 어렵게 느껴지진 않았다. 테이블을 잘 지정해줘서 create 해주면 되는것이기 때문이다.
하지만 특정 의류의 데이터를 수정하는 것은 단순히 patch 를 통해 할 수 있다고 생각들진 않는다. 코드를 작성하기 전에 먼저 머리속에서 단계를 생각해보았다
- 우선 업데이트 해주고 싶은 특정 id 의 의류를 찾아야 한다
- 의류를 찾았다면 이제 기존 데이터에 바뀐 데이터를 붙여넣어준다
여기까지는 그냥 patch 와 큰 차이점을 느끼지 못했는데, 좀 더 생각해보니 걸리는 사항들이 존재하였고, 그래서 단계에 추가하여 다시 생각해보았다
- 업데이트 해주고 싶은 특정 id 의 의류를 찾는다
- 의류를 찾았다면 기존 데이터에 바뀐 데이터를 붙여넣어준다
- 여기서 이미지 테이블과 카테고리 테이블은 좀 더 상황에 따라 다르게 접근해야한다. 우선 이미지부터 살펴보자
- 만일 기존 이미지가 3개 존재한다고 가정해보겠다. 우리는 이중 2개를 지우기로 하고 새로운 이미지 1개를 추가했다고 가정한다
- 번호로 좀 더 쉽게 예를 들자면 기존 [1, 2, 3] 이미지 중에서 2를 삭제하고 3을 추가하는 과정이다. [1.4]
- 기존은 [1, 2, 3] 이며, 새롭게 patch 를 통해 들어오는 이미지는 [1, 4] 이다.
- 1번 사진은 동일하기에 수정할 필요가 없을 것이다. 반면 2와 3번 이미지는 기본 테이블에서 삭제를 해야한다
- 그리고 4번 이미지는 새롭게 추가를 해야한다
- 즉, patch 해서 오는 이미지 중 기존 이미지들과의 비교를 통해 변경이 필요 없는 부분은 그대로 두면서, 삭제해야 할 이미지와 추가해야 할 이미지를 선별해야한다.
- 다음은 카테고리를 살펴보도록 하자
- 만일 기존에 Outer 로 저장하고 그에 대한 실측값을 테이블에 저장하였는데, 분류를 잘못한것을 알게 되었다
- 따라서 카테고리를 Top 으로 변경하고 실측값은 그대로 유지한다고 하자
- Outer 와 Top 의 테이블은 개별적이기에, 이 부분은 patch 가 아니라 delete 와 create 가 이루어져야 한다.
- 즉, 카테고리가 변경되었는지 아닌지에 따라 Patch / Delete, Create 가 결정이 되어야 한다
- 종합해보면 이미지와 카테고리를 제외한 나머지 정보는 기존처럼 Patch 를 통해 업데이트 해주면 된다 생각한다
- 하지만 이미지의 경우 기존 데이터와 변경된 데이터간의 변경 부분을 Delete, Create 해야하며
- 카테고리 역시 카테고리의 변화 유뮤에 따라 Patch 인지 Delete, Create 인지 결정이 되어야 할 것이다.
생각이 어느정도 정리가 되었으니, 이제 이를 코드로서 구현만 하면 될 것이다. (말처럼 쉽게되면 좋겠다지만)
가장 기본적으로 Patch 에 대한 부분을 먼저 구현해주자
router.patch("/clothes/:clothId", isLoggedIn, async (req, res, next) => {
try {
//params 로 clothid 를 가져올 수 있다. 이를 통해 변경할 의류를 찾아보자
const cloth = await Cloth.findOne({
where: { id: req.params.clothId },
// 이 의류와 연관된 카테고리 테이블을 순회하면서 한 테이블을 include 한다
include: [Outer, Top, Pant, Shirt, Shoe, Muffler, Image],
});
if (!cloth) {
return res.status(403).send("의류가 존재하지 않습니다.");
}
// update 메서드를 활용해서 req 에 담겨있는 내용으로 기존 데이터를 업데이트 한다.
await Cloth.update(
{
productName: req.body.items.productName,
description: req.body.items.description,
price: req.body.items.price,
color: req.body.items.color,
categori: req.body.items.categori,
purchaseDay: req.body.items.purchaseDay,
preference: req.body.items.preference,
UserId: req.user.id,
},
{
where: { id: req.params.clothId },
}
);
// categori 에 따른 업데이트
// image 업데이트
res.status(200).send("데이터를 수정하였습니다");
} catch (error) {
console.error(error);
next(error);
}
});
해당 의류를 먼저 Clothid 를 기반으로 find 하자. 이후 req 에 담겨있는 수정데이터를 기반으로 업데이트를 진행한다.
이미지와 카테고리를 제외한 나머지 부분은 여기서 다 업데이트가 이루어진다.
이제 우선 카테고리를 살펴보도록 하자. 앞에서 정리하였듯이 카테고리는 변경이 되느냐와 아니냐에 따라 update 일지 delete, create 일지 결정이 되어야 한다. 조건식이니 조건문을 작성해주고, 그에 맞게 update 와 delete, create 를 설정해주자
또한 유의할점은 새로 create 를 할 경우 기존 Cloth 데이터를 create 할 때와는 달리, foreign key 인 clothId 를 같이 기입해주어야 한다. 처음 데이터를 생성할 때는 자동으로 생성되는 setTeble 메서드로 자동으로 foreign key 를 설정 할 수 있었지만, 지금은 카테고리 테이블 내에서만 create 를 진행하는것이기 때문에 clothId 를 같이 기입해주어야 한다. 이렇게 되면 기존 custom method 를 그대로 활용할 수 없으니 한가지 더 method 를 생성해주도록 하자. 그리고 update 부분 역시 메서드로 만들어주면 좀 더 getCategori 객체에서 쉽게 관리할 수 있을 것이다.
// Outer.js
static postOuterWithClothId = async (req, id) => {
await this.create({
shoulder: req.body.items.categoriItem.shoulder,
arm: req.body.items.categoriItem.arm,
totalLength: req.body.items.categoriItem.totalLength,
chest: req.body.items.categoriItem.chest,
ClothId: id,
});
};
static updateOuterbyReq = async (req, id) => {
await this.update(
{
shoulder: req.body.items.categoriItem.shoulder,
arm: req.body.items.categoriItem.arm,
totalLength: req.body.items.categoriItem.totalLength,
chest: req.body.items.categoriItem.chest,
},
{
where: { ClothId: id },
}
);
};
위 코드는 Outer.js 에서 생성한 2가지 메서드이며, 다른 카테고리 테이블에서도 동일하게 메서드를 만들어주자
다 만들었다면 getCategori 객체에 추가해주자
const getCatagori = {
Top: {
post: async function (cloth, req) {
const top = await Top.postTopbyReq(req);
await cloth.setTop(top);
},
postWithId: async function (req, clothId) {
await Top.postTopWithClothId(req, clothId);
},
update: async function (req, clothId) {
await Top.updateTopbyReq(req, clothId);
},
},
Outer: {
post: async function (cloth, req) {
const outer = await Outer.postOuterbyReq(req);
await cloth.setOuter(outer);
},
postWithId: async function (req, clothId) {
await Outer.postOuterWithClothId(req, clothId);
},
update: async function (req, clothId) {
await Outer.updateOuterbyReq(req, clothId);
},
},
Shirt: {
// 생략
},
Pant: {
// 생략
},
Shoe: {
// 생략
},
Muffler: {
// 생략
},
};
update 부분과 create 부분이 모두 구현이 되었고, 이제 카테고리 부분을 마저 작성해보자
router.patch("/clothes/:clothId", isLoggedIn, async (req, res, next) => {
try {
//params 로 clothid 를 가져올 수 있다. 이를 통해 변경할 의류를 찾아보자
const cloth = await Cloth.findOne({
where: { id: req.params.clothId },
// 이 의류와 연관된 카테고리 테이블을 순회하면서 한 테이블을 include 한다
include: [Outer, Top, Pant, Shirt, Shoe, Muffler, Image],
});
if (!cloth) {
return res.status(403).send("의류가 존재하지 않습니다.");
}
// update 메서드를 활용해서 req 에 담겨있는 내용으로 기존 데이터를 업데이트 한다.
// 생략
// categori 에 따른 업데이트
// 만일 카테고리가 변했다면
if (req.body.items.categori !== cloth.categori) {
// 테이블 중에서 cloth.categori 에 해당하는 테이블을 불러온다. 이후 clothId 에 해당하는 데이터 삭제
await db[cloth.categori].destroy({ where: { ClothId: req.params.clothId } });
// 이후 clothId 를 포함해서 저장하는 postWithId 메서드를 호출해서 저장해주자
getCatagori[req.body.items.categori]["postWithId"](cloth, req);
// 아니라면 그냥 update 메서드를 호출해서 업데이트만 해주면 된다
} else if (req.body.items.categori === cloth.categori) {
getCatagori[req.body.items.categori]["update"](req, req.params.clothId);
}
// image 업데이트
res.status(200).send("데이터를 수정하였습니다");
} catch (error) {
console.error(error);
next(error);
}
});
주석에서 설명하는 것처럼 조건에 따라 카테고리 데이터를 업데이트 해줄 수 있게 되었다.
다음은 이제 이미지 수정을 구현해보자.
위에 계획했던 방향대로 구현해주면 되는데, 이미지는 특성상 update 를 사용할 일은 없다. 파일 자체가 변화하는 것이기 때문이다.
router.patch("/clothes/:clothId", isLoggedIn, async (req, res, next) => {
try {
const cloth = await Cloth.findOne({
where: { id: req.params.clothId },
include: [Outer, Top, Pant, Shirt, Shoe, Muffler, Image],
});
if (!cloth) {
return res.status(403).send("의류가 존재하지 않습니다.");
}
await Cloth.update(
// 생략
);
// categori 에 따른 업데이트
if (req.body.items.categori !== cloth.categori) {
await db[cloth.categori].destroy({ where: { ClothId: req.params.clothId } });
getCatagori[req.body.items.categori]["postWithId"](cloth, req);
} else if (req.body.items.categori === cloth.categori) {
getCatagori[req.body.items.categori]["update"](req, req.params.clothId);
}
// image 업데이트
if (req.body.items.image) {
// 현 존재하는 이미지를 검색해서 찾는다.
const existingImages = await Image.findAll({ where: { ClothId: req.params.clothId } });
console.log("existingImages", existingImages); // [1, 2, 3]
// 새롭게 전달받은 이미지를 구조에 맞게 변형하여 배열에 넣어준다
const filenameArray = req.body.items.image.map((v) => {
return { src: v.src, visionSearch: v.visionSearch };
});
console.log("filenameArray", filenameArray); // [1, 4]
// 이미지 주소를 통해 구별해야하기에 src 만 뽑아준다(비교용)
const filenameArrayKey = filenameArray.map((v) => v.src);
// 지울 이미지를 찾는다. 방법은 존재하는 이미지 하나하나 전달받은 이미지 배열에 포함되는지 확인한다
// 예를 들어 2 는 [1, 4] 에 포함되지 않으니 지워야 할 이미지이다
const imagesToRemove = existingImages.filter((img) => !filenameArrayKey.includes(img.src));
console.log("imageToRemove", imagesToRemove); // [2, 3]
// 2,3 을 지워준다
await Promise.all(imagesToRemove.map((imgSrc) => Image.destroy({ where: { src: imgSrc.dataValues.src } })));
// 추가해야 할 이미지를 골라주자. 겹치는 이미지는 [1, 4] 에서 각각 [1, 2, 3] 에 하나라도 같은게 없는지 확인한다
const imagesToAdd = filenameArray.filter((img) => !existingImages.some((ei) => ei.dataValues.src === img.src));
console.log("imagesToAdd", imagesToAdd); // 4
// 이미지를 추가해준다 주의할점은 clothId 도 추가해주자
await Promise.all(imagesToAdd.map((image) => Image.create({ src: image.src, visionSearch: image.visionSearch, ClothId: req.params.clothId })));
}
res.status(200).send("데이터를 수정하였습니다");
} catch (error) {
console.error(error);
next(error);
}
});
결국 이미지는 지워야 할 이미지와 추가해야할 이미지를 선별하여 삭제하고 추가해주면 된다. 추가할 때 유의점은 clothId 를 같이 넣어주는것이며 이제 정상적으로 잘 수정이 되는지 확인해보자
이미지를 추가하는것부터 해서 다양한 시도를 해봤을 때 정상적으로 잘 수정되는것을 확인하였다.
ItemForm 컴포넌트를 구현하면서..
항상 계획은 금방금방 세우고, 일정 역시 한 3일 정도 잡고 구현하면 될 것이라 생각했었는데, 일주일 이상이 걸리는 과정을 보고나서 자신을 믿으면 안되겠다라는 생각이 들었다(일정에 관해선)
프로젝트를 계획할 때 가장 핵심이 되는 부분이 의류를 추가하는 기능이라고 생각하였고, 그래서인지 발생할 수 있는 여러 상황에 대해서 충분히 고려해보고 구현하였다 생각이 든다. 물론 이렇게 고민하여도 오류들은 언제 그랬냐는듯이 계속 나타나기도 하였고, 매번 그러한 오류들을 고치면서 지금의 코드까지 오게 된 것 같다.
간략하게 고민했던 사항들을 간단하게 요약해보자면
- 입력폼을 더 간단하게 관리할 수 있는 방법을 찾아보자
- react-hook-form 을 활용
- watch를 활용하여 카테고리별 입력폼을 렌더하기
- 데이터를 수정할 때 의류에 대한 정보가 input 창이 미리 렌더가 되어있어야 한다
- react-hook-form 의 reset 을 활용하기와 singleItem 상태값에 따른 구독 값 결정하기
- 사진을 업로드 할 때, 저장하려는 카테고리내 적합성 여부를 판단하는 기능을 구현하고 싶다
- Vision AI 를 통해 객체 분석을 하여 클라이언트에서 preview 해준다
- 이미지를 드래그 방식과 버튼 방식으로 업로드 할 수 있어야 한다
- 웹 DragEvent API 를 활용하여 설정
위와 같이 요약이 될 것이다.
다시 리뷰할 수록 부족한 부분들이 보이겠지만, 그건 추후에 해결해보도록 하자!

'Practice' 카테고리의 다른 글
[Closet] SWR Mutate 를 통한 캐시 갱신 (0) | 2023.05.06 |
---|---|
[Closet]Cursor-based-pagination 이 효율은 좋지만.. (0) | 2023.05.01 |
[Closet]React-Hook-Form의 Reset (1) | 2023.04.30 |
[Closet]이미지 파일을 Multer 와 Vision AI로 처리해보자 (0) | 2023.04.30 |
[Closet] 이미지 업로드 시 Drag and Drop을 활용해보기 (0) | 2023.04.29 |