From 73a69b7cbb9dfb58f18f10ff701f57eca9bcd013 Mon Sep 17 00:00:00 2001
From: Eunhak Lee <lee@enak.kr>
Date: Sat, 30 Nov 2024 16:50:25 +0900
Subject: [PATCH 1/8] style: eslint --fix

---
 src/errors.js                         | 2 +-
 src/index.js                          | 5 ++---
 src/middlewares/responseMiddleware.js | 3 +--
 3 files changed, 4 insertions(+), 6 deletions(-)

diff --git a/src/errors.js b/src/errors.js
index f40194a..4aa7ef8 100644
--- a/src/errors.js
+++ b/src/errors.js
@@ -4,4 +4,4 @@ export class ReportableError extends Error {
     this.message = message;
     this.statusCode = statusCode;
   }
-}
\ No newline at end of file
+}
diff --git a/src/index.js b/src/index.js
index 55d8546..27de7a5 100644
--- a/src/index.js
+++ b/src/index.js
@@ -3,6 +3,7 @@ import express from 'express';
 import apiRouter from './routes/index.js';
 import pool from './db.js';
 import responseMiddleware from './middlewares/responseMiddleware.js';
+import { ReportableError } from './errors.js';
 
 const app = express();
 app.use(express.json());
@@ -10,6 +11,7 @@ app.use(express.json());
 app.use(responseMiddleware);
 app.use('/api', apiRouter);
 
+// eslint-disable-next-line no-unused-vars
 app.use((err, req, res, next) => {
   if (err instanceof ReportableError) {
     const { statusCode, message } = err;
@@ -20,7 +22,6 @@ app.use((err, req, res, next) => {
   }
 });
 
-
 const PORT = +process.env.PORT || 8000;
 
 console.log('Connecting to database');
@@ -32,5 +33,3 @@ pool.connect().then(async (conn) => {
     console.log(`Listening on PORT ${PORT}`);
   });
 });
-
-
diff --git a/src/middlewares/responseMiddleware.js b/src/middlewares/responseMiddleware.js
index 9d64db2..9b91e6e 100644
--- a/src/middlewares/responseMiddleware.js
+++ b/src/middlewares/responseMiddleware.js
@@ -9,5 +9,4 @@ const responseMiddleware = (req, res, next) => {
   next();
 };
 
-export default responseMiddleware
-
+export default responseMiddleware;
-- 
GitLab


From 92a19e40279698073302082957263e661baf42bb Mon Sep 17 00:00:00 2001
From: Eunhak Lee <lee@enak.kr>
Date: Sat, 30 Nov 2024 17:41:40 +0900
Subject: [PATCH 2/8] =?UTF-8?q?feat:=20parts=20repo,=20service=20=EB=8F=84?=
 =?UTF-8?q?=EC=9E=85?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/repositories/partRepository.js | 22 ++++++++++++++++++++++
 src/services/partService.js        | 27 +++++++++++++++++++++++++++
 2 files changed, 49 insertions(+)
 create mode 100644 src/repositories/partRepository.js
 create mode 100644 src/services/partService.js

diff --git a/src/repositories/partRepository.js b/src/repositories/partRepository.js
new file mode 100644
index 0000000..43f5c04
--- /dev/null
+++ b/src/repositories/partRepository.js
@@ -0,0 +1,22 @@
+import pool from '../db.js';
+
+const PartRepository = {
+  async findById(id) {
+    const resp = await pool.query(
+      `SELECT type, name, image_url FROM parts WHERE id = $1`,
+      [id]
+    );
+    const [part] = resp.rows;
+    return part;
+  },
+  async findMetaByTypeAndId(type, id) {
+    const resp = await pool.query(
+      `SELECT * FROM part_info_${type} WHERE part_id = $1;`,
+      [id]
+    );
+    const [info] = resp.rows;
+    return info;
+  },
+};
+
+export default PartRepository;
diff --git a/src/services/partService.js b/src/services/partService.js
new file mode 100644
index 0000000..a326299
--- /dev/null
+++ b/src/services/partService.js
@@ -0,0 +1,27 @@
+import { ReportableError } from '../errors.js';
+import PartRepository from '../repositories/partRepository.js';
+
+const PartService = {
+  cleanEntity(entity) {
+    return entity;
+  },
+  async getById(id) {
+    if (!id || id <= 0)
+      throw new ReportableError(400, '올바르지 않은 id 입니다.');
+
+    const part = await PartRepository.findById(id);
+    if (!part) throw new ReportableError(404, '해당 부품을 찾을 수 없습니다');
+
+    // eslint-disable-next-line no-unused-vars
+    const { part_id, ...infos } = await PartRepository.findMetaByTypeAndId(
+      part.type.toLowerCase(),
+      id
+    );
+    const description = Object.values(infos).join(' / ');
+    part['description'] = description;
+
+    return this.cleanEntity(part);
+  },
+};
+
+export default PartService;
-- 
GitLab


From d0fb2c5e4309406b2e0c263b6935054512c5ec00 Mon Sep 17 00:00:00 2001
From: Eunhak Lee <lee@enak.kr>
Date: Sat, 30 Nov 2024 17:42:04 +0900
Subject: [PATCH 3/8] =?UTF-8?q?feat:=20=EB=B6=80=ED=92=88=20id=EB=A1=9C=20?=
 =?UTF-8?q?=EA=B2=80=EC=83=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/routes/parts.js | 23 ++++++++++++++++++++---
 1 file changed, 20 insertions(+), 3 deletions(-)

diff --git a/src/routes/parts.js b/src/routes/parts.js
index 2ea1b2c..64f03d8 100644
--- a/src/routes/parts.js
+++ b/src/routes/parts.js
@@ -1,9 +1,26 @@
 import { Router } from 'express';
+import { wrapAsync } from '../utils.js';
+import { ReportableError } from '../errors.js';
+import PartService from '../services/partService.js';
 
 const partRouter = Router();
 
-partRouter.get('/', (req, res) => {
-  return res.send({ message: 'parts thigns' });
-});
+partRouter.get(
+  '/',
+  wrapAsync(async (req, res) => {
+    return res.send({ message: 'parts thigns' });
+  })
+);
+
+partRouter.get(
+  '/:id',
+  wrapAsync(async (req, res) => {
+    const { id } = req.params;
+    if (!id) throw ReportableError(400, 'id 를 지정해주세요');
+
+    const part = await PartService.getById(+id);
+    return res.sendResponse('', 200, part);
+  })
+);
 
 export default partRouter;
-- 
GitLab


From 1262fd3f0909c39af6eab78353462c27bcb48111 Mon Sep 17 00:00:00 2001
From: Eunhak Lee <lee@enak.kr>
Date: Sat, 30 Nov 2024 17:44:50 +0900
Subject: [PATCH 4/8] =?UTF-8?q?chore:=20=EB=B6=80=ED=92=88=EC=9D=84=20id?=
 =?UTF-8?q?=20=EB=A1=9C=20=EA=B0=80=EC=A0=B8=EC=98=AC=20=EB=95=8C=20?=
 =?UTF-8?q?=EC=84=A4=EB=AA=85=EA=B9=8C=EC=A7=80=20=EA=B0=80=EC=A0=B8?=
 =?UTF-8?q?=EC=98=AC=EC=A7=80=20=EC=97=AC=EB=B6=80=20flag?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/routes/parts.js         |  2 +-
 src/services/partService.js | 18 ++++++++++--------
 2 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/src/routes/parts.js b/src/routes/parts.js
index 64f03d8..3de6960 100644
--- a/src/routes/parts.js
+++ b/src/routes/parts.js
@@ -18,7 +18,7 @@ partRouter.get(
     const { id } = req.params;
     if (!id) throw ReportableError(400, 'id 를 지정해주세요');
 
-    const part = await PartService.getById(+id);
+    const part = await PartService.getById(+id, true);
     return res.sendResponse('', 200, part);
   })
 );
diff --git a/src/services/partService.js b/src/services/partService.js
index a326299..d9dbe01 100644
--- a/src/services/partService.js
+++ b/src/services/partService.js
@@ -5,20 +5,22 @@ const PartService = {
   cleanEntity(entity) {
     return entity;
   },
-  async getById(id) {
+  async getById(id, detail = false) {
     if (!id || id <= 0)
       throw new ReportableError(400, '올바르지 않은 id 입니다.');
 
     const part = await PartRepository.findById(id);
     if (!part) throw new ReportableError(404, '해당 부품을 찾을 수 없습니다');
 
-    // eslint-disable-next-line no-unused-vars
-    const { part_id, ...infos } = await PartRepository.findMetaByTypeAndId(
-      part.type.toLowerCase(),
-      id
-    );
-    const description = Object.values(infos).join(' / ');
-    part['description'] = description;
+    if (detail) {
+      // eslint-disable-next-line no-unused-vars
+      const { part_id, ...infos } = await PartRepository.findMetaByTypeAndId(
+        part.type.toLowerCase(),
+        id
+      );
+      const description = Object.values(infos).join(' / ');
+      part['description'] = description;
+    }
 
     return this.cleanEntity(part);
   },
-- 
GitLab


From aec0f1c1744f44bcebc333fa4c942a591ec632b0 Mon Sep 17 00:00:00 2001
From: LeeWxx <dnckd0903@naver.com>
Date: Mon, 2 Dec 2024 00:12:28 +0900
Subject: [PATCH 5/8] =?UTF-8?q?feat:=20part=20=EB=9D=BC=EC=9A=B0=ED=8A=B8?=
 =?UTF-8?q?=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/routes/parts.js | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/src/routes/parts.js b/src/routes/parts.js
index 3de6960..a6a9287 100644
--- a/src/routes/parts.js
+++ b/src/routes/parts.js
@@ -8,7 +8,19 @@ const partRouter = Router();
 partRouter.get(
   '/',
   wrapAsync(async (req, res) => {
-    return res.send({ message: 'parts thigns' });
+    const { partType, filters } = req.query;
+
+    const parts = await PartService.getParts(partType, filters);
+
+    return res.sendResponse('', 200, { parts });
+  })
+);
+
+partRouter.get(
+  '/filters',
+  wrapAsync(async (req, res) => {
+    const filters = await PartService.getFilters();
+    return res.sendResponse('', 200, filters);
   })
 );
 
-- 
GitLab


From 193efb2f5097fc68bd7ca7c25ff5797943119daf Mon Sep 17 00:00:00 2001
From: LeeWxx <dnckd0903@naver.com>
Date: Mon, 2 Dec 2024 00:15:03 +0900
Subject: [PATCH 6/8] =?UTF-8?q?feat:=20part=20=EC=84=9C=EB=B9=84=EC=8A=A4?=
 =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/constants/columnMapping.js | 58 ++++++++++++++++++++++++++++++++++
 src/services/partService.js    | 50 +++++++++++++++++++++++++++++
 2 files changed, 108 insertions(+)
 create mode 100644 src/constants/columnMapping.js

diff --git a/src/constants/columnMapping.js b/src/constants/columnMapping.js
new file mode 100644
index 0000000..9997dea
--- /dev/null
+++ b/src/constants/columnMapping.js
@@ -0,0 +1,58 @@
+export const columnMapping = {
+  cpu: {
+    family_type: 'CPU 종류',
+    socket_type: '소켓 구분',
+    core_count: '코어 수',
+    thread_count: '스레드 수',
+    base_clock: '기본 클럭',
+    max_clock: '최대 클럭',
+    mem_type: '메모리 규격',
+    tdp: 'TDP',
+  },
+  gpu: {
+    chipset_manufacturer: '칩셋 제조사',
+    family_type: '제품 시리즈',
+    chipset: '칩셋',
+    vram_type: '메모리 종류',
+    vram_size: '메모리 용량',
+    interface: '인터페이스',
+    max_monitor_count: '모니터 지원',
+    power_consumption: '사용 전력',
+  },
+  ram: {
+    usage_type: '사용 장치',
+    form_factor: '메모리 규격',
+    size: '메모리 용량',
+    generation: '제품 분류',
+    base_clock: '동작 클럭(대역폭)',
+    package_count: '램 개수',
+  },
+  mb: {
+    board_type: '제품 분류',
+    cpu_socket: 'CPU 소켓',
+    cpu_chipset: '세부 칩셋',
+    power_phase: '전원부',
+    ram_type: '메모리 종류',
+    ram_speed: '메모리 속도',
+    ram_slot_count: '메모리 슬롯 수',
+    form_factor: '폼팩터',
+  },
+  ssd: {
+    interface: '인터페이스',
+    size: '용량',
+    form_factor: '폼팩터',
+    nand_type: '메모리 타입',
+    dram_type_size: 'DRAM (유형과 용량)',
+    protocol: '프로토콜',
+  },
+  hdd: {
+    usage_type: '제품 분류',
+    disk_standard_size: '디스크 크기',
+    disk_size: '디스크 용량',
+    interface: '인터페이스',
+    buffer_size: '버퍼 용량',
+    rpm: '회전 수',
+    max_speed: '전송 속도',
+    access_method: '기록방식',
+  },
+};
diff --git a/src/services/partService.js b/src/services/partService.js
index d9dbe01..2e8d58c 100644
--- a/src/services/partService.js
+++ b/src/services/partService.js
@@ -1,3 +1,4 @@
+import { columnMapping } from '../constants/columnMapping.js';
 import { ReportableError } from '../errors.js';
 import PartRepository from '../repositories/partRepository.js';
 
@@ -24,6 +25,55 @@ const PartService = {
 
     return this.cleanEntity(part);
   },
+  async getFilters() {
+    const types = Object.keys(columnMapping);
+    const filters = {};
+
+    for (const type of types) {
+      const columns = await PartRepository.getColumnsByType(type);
+
+      filters[type.toUpperCase()] = {};
+      for (const column of columns) {
+        const koreanName = columnMapping[type]?.[column];
+        if (koreanName) {
+          const filterValues =
+            await PartRepository.getFilterDataByTypeAndColumn(type, column);
+
+          filters[type.toUpperCase()][koreanName] = {
+            column,
+            values: filterValues,
+          };
+        }
+      }
+    }
+
+    return filters;
+  },
+
+  async getParts(partType, filters) {
+    if (!partType) {
+      throw new Error('파트 타입이 필요합니다.');
+    }
+
+    let parsedFilters;
+    try {
+      parsedFilters = filters ? JSON.parse(filters) : {};
+    } catch {
+      throw new ReportableError(400, '잘못된 JSON 형태입니다.');
+    }
+
+    const whereClauses = Object.entries(parsedFilters)
+      .map(([key], index) => `${key} = $${index + 1}`)
+      .join(' AND ');
+    const queryValues = Object.values(parsedFilters);
+
+    const parts = await PartRepository.getPartsByFilters(
+      partType,
+      whereClauses,
+      queryValues
+    );
+    return parts;
+  },
 };
 
 export default PartService;
-- 
GitLab


From 7bdca9999f09e4ab2d9a7917e2fcd63f3d37d22d Mon Sep 17 00:00:00 2001
From: LeeWxx <dnckd0903@naver.com>
Date: Mon, 2 Dec 2024 00:15:35 +0900
Subject: [PATCH 7/8] =?UTF-8?q?feat:=20partRepository=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/repositories/partRepository.js | 39 ++++++++++++++++++++++++++++++
 1 file changed, 39 insertions(+)

diff --git a/src/repositories/partRepository.js b/src/repositories/partRepository.js
index 43f5c04..e5553f0 100644
--- a/src/repositories/partRepository.js
+++ b/src/repositories/partRepository.js
@@ -17,6 +17,45 @@ const PartRepository = {
     const [info] = resp.rows;
     return info;
   },
+  async getColumnsByType(type) {
+    const resp = await pool.query(
+      `
+      SELECT column_name 
+      FROM information_schema.columns 
+      WHERE table_name = $1
+      ORDER BY ordinal_position;
+      `,
+      [`part_info_${type}`.toLowerCase()]
+    );
+    return resp.rows.map((row) => row.column_name);
+  },
+
+  async getFilterDataByTypeAndColumn(type, column) {
+    const query = `
+      SELECT DISTINCT ${column}
+      FROM part_info_${type}
+      WHERE ${column} IS NOT NULL
+      ORDER BY ${column}
+    `;
+    const resp = await pool.query(query);
+    return resp.rows.map((row) => row[column]);
+  },
+
+  async getPartsByFilters(partType, whereClauses, queryValues) {
+    const query = `
+  SELECT 
+    parts.id AS partId, 
+    parts.name, 
+    parts.image_url
+  FROM part_info_${partType.toLowerCase()} 
+  INNER JOIN parts 
+  ON part_info_${partType.toLowerCase()}.part_id = parts.id
+  ${whereClauses ? `WHERE ${whereClauses}` : ''}
+  LIMIT 20;
+`;
+    const result = await pool.query(query, queryValues);
+    return result.rows;
+  },
 };
 
 export default PartRepository;
-- 
GitLab


From 0888fe4bf7fe4f9e1fc6b85ab79948a24dc045b0 Mon Sep 17 00:00:00 2001
From: LeeWxx <dnckd0903@naver.com>
Date: Mon, 2 Dec 2024 21:36:31 +0900
Subject: [PATCH 8/8] =?UTF-8?q?feat:=20=EC=8B=A4=EC=A0=9C=20=EC=9C=A0?=
 =?UTF-8?q?=EC=A0=80=EA=B0=80=20=EA=B0=80=EC=A7=80=EA=B3=A0=20=EC=9E=88?=
 =?UTF-8?q?=EB=8A=94=20=EB=B6=80=ED=92=88=EB=A7=8C=EC=9D=84=20=ED=95=84?=
 =?UTF-8?q?=ED=84=B0=EB=A1=9C=20=EB=B0=98=ED=99=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/repositories/partRepository.js | 11 ++++++-----
 src/routes/parts.js                |  2 --
 src/services/partService.js        |  2 +-
 3 files changed, 7 insertions(+), 8 deletions(-)

diff --git a/src/repositories/partRepository.js b/src/repositories/partRepository.js
index e5553f0..4e453fa 100644
--- a/src/repositories/partRepository.js
+++ b/src/repositories/partRepository.js
@@ -32,11 +32,12 @@ const PartRepository = {
 
   async getFilterDataByTypeAndColumn(type, column) {
     const query = `
-      SELECT DISTINCT ${column}
-      FROM part_info_${type}
-      WHERE ${column} IS NOT NULL
-      ORDER BY ${column}
-    `;
+    SELECT DISTINCT p.${column}
+    FROM relations r
+    JOIN part_info_${type} p ON r.part_id = p.part_id -- id 대신 part_id 사용
+    WHERE p.${column} IS NOT NULL
+    ORDER BY p.${column}
+  `;
     const resp = await pool.query(query);
     return resp.rows.map((row) => row[column]);
   },
diff --git a/src/routes/parts.js b/src/routes/parts.js
index a6a9287..b995962 100644
--- a/src/routes/parts.js
+++ b/src/routes/parts.js
@@ -9,9 +9,7 @@ partRouter.get(
   '/',
   wrapAsync(async (req, res) => {
     const { partType, filters } = req.query;
-
     const parts = await PartService.getParts(partType, filters);
-
     return res.sendResponse('', 200, { parts });
   })
 );
diff --git a/src/services/partService.js b/src/services/partService.js
index 2e8d58c..0fa797d 100644
--- a/src/services/partService.js
+++ b/src/services/partService.js
@@ -52,7 +52,7 @@ const PartService = {
 
   async getParts(partType, filters) {
     if (!partType) {
-      throw new Error('파트 타입이 필요합니다.');
+      throw new ReportableError(400, '파트 타입이 필요합니다.');
     }
 
     let parsedFilters;
-- 
GitLab