Skip to content
Snippets Groups Projects
Commit cc0f58a5 authored by Lee WooChang's avatar Lee WooChang
Browse files

Merge branch 'feat/parts' into 'main'

Parts 추가

See merge request !10
parents 5fa6cfd1 0888fe4b
Branches
No related tags found
1 merge request!10Parts 추가
Pipeline #10581 passed
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: '기록방식',
},
};
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;
},
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 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]);
},
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;
import { Router } from 'express'; import { Router } from 'express';
import { wrapAsync } from '../utils.js';
import { ReportableError } from '../errors.js';
import PartService from '../services/partService.js';
const partRouter = Router(); const partRouter = Router();
partRouter.get('/', (req, res) => { partRouter.get(
return res.send({ message: 'parts thigns' }); '/',
}); wrapAsync(async (req, res) => {
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);
})
);
partRouter.get(
'/:id',
wrapAsync(async (req, res) => {
const { id } = req.params;
if (!id) throw ReportableError(400, 'id 를 지정해주세요');
const part = await PartService.getById(+id, true);
return res.sendResponse('', 200, part);
})
);
export default partRouter; export default partRouter;
import { columnMapping } from '../constants/columnMapping.js';
import { ReportableError } from '../errors.js';
import PartRepository from '../repositories/partRepository.js';
const PartService = {
cleanEntity(entity) {
return entity;
},
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, '해당 부품을 찾을 수 없습니다');
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);
},
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 ReportableError(400, '파트 타입이 필요합니다.');
}
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;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment