Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
{
"name": "Node.js & TypeScript",
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm",

// Prettier 패키지를 컨테이너에 '글로벌'로 설치합니다.
"postCreateCommand": "npm install -g prettier",

"customizations": {
"vscode": {
"extensions": [
// VS Code Prettier 확장 (esbenp.prettier-vscode)
"esbenp.prettier-vscode"
],
"settings": {
// 파일을 저장할 때 자동 포맷팅 활성화
"editor.formatOnSave": true,
// 기본 포맷터로 Prettier 설정
"editor.defaultFormatter": "esbenp.prettier-vscode",

// 💡 추가된 설정: Prettier 확장이 로컬이 아닌 글로벌 모듈을 찾도록 지시
"prettier.resolveGlobalModules": true
}
}
}
}
31 changes: 31 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# 의존성
/node_modules
/package-lock.json
/yarn.lock
/npm-debug.log*
/yarn-debug.log*
/yarn-error.log*
/pnpm-debug.log*

# 빌드 출력 (TypeScript 컴파일 결과)
/dist
/build
/out
/es
/lib

# TypeScript 캐시
/.tsbuildinfo

# 환경 변수 및 보안 파일
.env
.env.local
.env.*.local

# 운영체제/에디터 파일
.DS_Store
Thumbs.db

# VS Code 설정
.vscode
/generated/prisma
116 changes: 116 additions & 0 deletions ArticleService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// ArticleService.js
import axios from "axios";
import { Article } from "./main.js";

const BASE_URL = "https://panda-market-api-crud.vercel.app";

/**
* 아티클 리스트 조회
* @param {Object} params { page, pageSize, keyword }
* @returns {Promise<Article[]>}
*/
export async function getArticleList(params = {}) {
try {
const response = await axios.get(`${BASE_URL}/articles`, { params });
// 응답 구조가 { list: [ {...} ], totalCount: … } 라는 가정
const articles = response.data.list.map((item) => {
// 생성일이 있을수도 있고 없을수도 있으므로 기본값 처리
const article = new Article(
item.title,
item.content,
item.writer || "알 수 없음"
);
return article;
});
return articles;
} catch (error) {
console.error("getArticleList 에러:", error.message);
throw error;
}
}

/**
* 단일 아티클 조회
* @param {string|number} id
* @returns {Promise<Article>}
*/
export async function getArticle(id) {
try {
const response = await axios.get(`${BASE_URL}/articles/${id}`);
const { title, content, writer } = response.data;
const article = new Article(title, content, writer || "알 수 없음");
return article;
} catch (error) {
console.error("getArticle 에러:", error.message);
throw error;
}
}

/**
* 아티클 생성
* @param {Object} data { title, content, image }
* @returns {Promise<Article|null>}
*/
export async function createArticle(data) {
try {
// 반드시 image 필드를 배열로
const body = {
title: data.title,
content: data.content,
image: Array.isArray(data.image) ? data.image : [data.image],
};
const response = await axios.post(`${BASE_URL}/articles`, body);
const { title, content, writer } = response.data;
const article = new Article(title, content, writer || "알 수 없음");
console.log("createArticle 성공:", response.status);
return article;
} catch (error) {
const status = error.response ? error.response.status : "N/A";
console.error(`createArticle 실패 (상태 코드: ${status}):`, error.message);
return null;
}
}

/**
* 아티클 수정
* @param {string|number} id
* @param {Object} data { title?, content?, image? }
* @returns {Promise<Article|null>}
*/
export async function patchArticle(id, data) {
try {
const body = {};
if (data.title !== undefined) body.title = data.title;
if (data.content !== undefined) body.content = data.content;
if (data.image !== undefined) {
body.image = Array.isArray(data.image) ? data.image : [data.image];
}
const response = await axios.patch(`${BASE_URL}/articles/${id}`, body);
const { title, content, writer } = response.data;
const article = new Article(title, content, writer || "알 수 없음");
console.log("patchArticle 성공:", response.status);
return article;
} catch (error) {
const status = error.response ? error.response.status : "N/A";
console.error(`patchArticle 실패 (상태 코드: ${status}):`, error.message);
return null;
}
}

/**
* 아티클 삭제
* @param {string|number} id
* @returns {Promise<boolean>} 성공하면 true
*/
export async function deleteArticle(id) {
try {
const response = await axios.delete(`${BASE_URL}/articles/${id}`);
console.log("deleteArticle 성공:", response.status);
return true;
} catch (error) {
const status = error.response ? error.response.status : "N/A";
console.error(`deleteArticle 실패 (상태 코드: ${status}):`, error.message);
return false;
}
}

48 changes: 48 additions & 0 deletions ProductService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import axios from "axios";
import { Product, ElectronicProduct } from "./main.js";

const BASE_URL = "https://panda-market-api-crud.vercel.app";

export function validateProduct(productData) {
if (!productData) throw new Error("productData가 제공되지 않았습니다.");
const { name, description, price, tags, images } = productData;

const missingFields = [];
if (!name) missingFields.push("name");
if (!description) missingFields.push("description");
if (price === undefined || price === null) missingFields.push("price");
if (!tags) missingFields.push("tags");
if (!images) missingFields.push("images");
if (missingFields.length > 0)
throw new Error(`필수 필드가 누락되었습니다: ${missingFields.join(", ")}`);

if (typeof name !== "string") throw new Error("name은 문자열이어야 합니다.");
if (typeof description !== "string")
throw new Error("description은 문자열이어야 합니다.");
if (typeof price !== "number" || price < 0)
throw new Error("price는 0 이상의 숫자여야 합니다.");
if (!Array.isArray(tags)) throw new Error("tags는 배열이어야 합니다.");
if (!Array.isArray(images)) throw new Error("images는 배열이어야 합니다.");
}

export async function getProductList(params = {}) {
try {
const response = await axios.get(`${BASE_URL}/products`, { params });
const products = response.data.list.map((p) =>
p.tags.includes("전자제품")
? new ElectronicProduct(
p.name,
p.description,
p.price,
p.tags,
p.images,
p.manufacturer
)
: new Product(p.name, p.description, p.price, p.tags, p.images)
);
return products;
} catch (error) {
console.error("상품 리스트 조회 실패:", error.message);
return [];
}
}
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{1팀}

팀원 구성

- 신영석
- 서석규
- 이창호
- 강희성
- 이요한

기술 스택

- Backend: Express.js, PrismaORM
- Database: PostgresSQL
- etc: Git & Github, Discord, Notion
96 changes: 96 additions & 0 deletions algorithm/sorts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// 선택 정렬
function selectionSort(arr) {
for (let i = 0; i < arr.length - 1; i++) {
let minIndex = i;

for (let j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}

[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
}
}

// 삽입 정렬
function insertionSort(arr) {
for (let i = 1; i < arr.length; i++) {
let current = arr[i];
let j = i - 1;

while (j >= 0 && arr[j] > current) {
arr[j + 1] = arr[j];
j--;
}

arr[j + 1] = current;
}
}

// 병합 정렬
function mergeSort(arr) {
if (arr.length <= 1) return arr;

const middle = Math.floor(arr.length / 2);

const left = mergeSort(arr.slice(0, middle));
const right = mergeSort(arr.slice(middle));

return merge(left, right);
}

function merge(left, right) {
const result = [];

let leftIndex = 0;
let rightIndex = 0;

while (leftIndex < left.length && rightIndex < right.length) {
if (left[leftIndex] < right[rightIndex]) {
result.push(left[leftIndex]);
leftIndex++;
} else {
result.push(right[rightIndex]);
rightIndex++;
}
}

return result
.concat(left.slice(leftIndex))
.concat(right.slice(rightIndex));
}

// 퀵 정렬
function quickSort(arr, left = 0, right = arr.length - 1) {
if (left >= right) return;

const pivotIndex = partition(arr, left, right);

quickSort(arr, left, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, right);
}

function partition(arr, left, right) {
const pivot = arr[right];

let i = left - 1;

for (let j = left; j < right; j++) {
if (arr[j] < pivot) {
i++;
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}

[arr[i + 1], arr[right]] = [arr[right], arr[i + 1]];

return i + 1;
}

module.exports = {
selectionSort,
insertionSort,
mergeSort,
quickSort,
};
Loading