Skip to content

[최은영] sprint2 #49

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
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
7 changes: 5 additions & 2 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
for (let i = 0; i < 5; i++) {
console.log('Hello world!');
}

## 요구사항

Expand All @@ -17,5 +20,5 @@
![image](이미지url)

## 멘토에게
- 셀프 코드 리뷰를 통해 질문 이어가겠습니다.
-
- 뭘 배웠는지 뭘 했는지 아직 모르겠습니다. 뭘 해야하는지도요...
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

막막할때는 하나씩 해보시는건 어떨까요 ?

은영님처럼 처음 개발을 배우는 사람뿐아니라, 실무에 있는 사람이나, 고연차에 사람들도 막막할때가 있습니다.
그럴때는 조금은 멀리 떨어져서 생각해보거나, 하나하나 차근차근 생각해보는 게 저한테는 도움이 되더라고요 !

- PR 하는 방법 헷갈려서 늦게 올립니다. 죄송합니다.
89 changes: 89 additions & 0 deletions .github/sprint-misson-2/ArticleService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// 기본 API 주소 설정
const Base_URL = 'https://panda-market-api-crud.vercel.app';

// 게시글 리스트를 가져오는 함수 (page, pageSize, keyword 쿼리 파라미터를 이용)
export function getArticleList(params = { page: 1, pageSize: 10, keyword: '' }) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

params 에 대한 Object 활용 및 디폴트 선언을 한 점 아주 훌륭합니다 !


// URL 객체를 생성하여 쿼리 파라미터를 처리
const url = new URL('${BASE_URL}/articles');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

template literal 을 활용하여 좋은 코드를 작성했습니다 너무좋습니다!
더 나아간다면,

  1. Base_URL 을 다른 파일로 분리하여, import 했다면 좋았을 것 같습니다
  2. 변수명을 아예 BASE_URL 이런식으로 했다면 가독성이 더 좋았을 듯 합니다


// params 객체에서 page, pageSize, keyword 정보를 쿼리스트링에 추가
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== '') url.searchParams.append(key, value);
});

// fetch로 GET 요청 전달
return fetch(url)
.then(res => {
// 응답이 실패(2XX 범위 밖)할 시, err.massage 출력한 후 err를 throw
if (!res.ok) throw new Error('목록 조회 실패: ${res.status}');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

주석과 코드가 조금 다른 듯 합니다.
단순히 ok = 2xx 대는 아닙니다!
따라서 header의 httpStatusCode를 활용하여 했다면 더 좋았을 듯 합니다 !

return res.json();
})
.catch(err => console.error('getArticleList 에러:', err.message));
}

// getArticle() GET 메소드를 사용
// 특정 게시글 하나를 조회하는 함수
export function getArticle(articleId) {
// fetch를 이용해 GET 요청 전달
return fetch('${BASE_URL}/articles/${articleId}')
.then(res => {
if (!res.ok) throw new Error('게시글 조회 실패: ${res.status}');
return res.json();
})
.catch(err => console.error('getArticle 에러:', err.message));
}

// createArticle() POST 메소드를 사용
// Request Body에 title, content, image를 포함한 게시글을 생성하는 함수
export function createArticle({ title, content, image }) {
// fetch로 POST 요청 전달
return fetch('${BASE_URL}/articles', {
method: 'POST', // POST 메서드 사용
headers: { 'Content-Type': 'application/json' }, // 요청에 Json 데이터를 알림.
body: JSON.stringify({
title,
content,
image,
}),
})
.then(res => {
if (!res.ok) throw new Error('생성 실패: ${res.status');
return res.json();
})
.catch(err => console.error('createArticle 에러:', err.message));
}

// patchArticle() PARCH 메소드를 사용
// 게시글을 수정하는 함수
export function patchArticle(articleId, { title, content, image }) {
// fetch로 PATCH 요청 전달
return fetch('${BASE_URL}/articles/${articleId}', {
method: 'PATCH', // PATCH 메서드 사용
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ //수정 내용을 JSON으로 전달
title,
content,
image,
}),
})
.then(res => {
if (!res.ok) throw new Error('수정 실패: ${res.status}');
return res.json();
})
.catch(err => console,error('patchArticle 에러:', err.message));
}
Comment on lines +62 to +75
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indent (들여쓰기) 를 맞추어 가독성을 신경썼다면 더 좋았을 것 같습니다 !


//deleteArticle() : DELETE 메소드 사용
// 특정 게시글을 삭제하는 함수
export function deleteArticle(articleId) {
// fetch로 DELETE 요청 전달
return fetch('${BASE_URL}/articles/${articleId}', {
method: 'DELETE', // DELETE 메서드 사용
})
.then(res => {
if (!res.ok) throw new Error('삭제 실패: ${res.ststus}');
return res.text(); // 삭제 성공 할 시 텍스트로 응답
})
.catch(err => console.error('deleteArticle 에러:', err.message));
}
44 changes: 44 additions & 0 deletions .github/sprint-misson-2/Classes.js
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

클래스는 이렇게 한 곳에 모으기 보다는 관점에 따라서 분리했다면 더 좋았을 것 같습니다!
products.js
articles.js
와 같이요 !

Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// 상품 클래스 정의
export class Product {
// 상품 정보 객체 속성 세팅
constructor(name, description, price, tags = [], images = []) {
this.name = name; //상품명
this.description = description; //상품 설명
this.price = price; //상품 가격
this.tags = tags; //상품 해시태그 배열
this.images = images; //상품 이미지 배열
this.favorite = 0; //찜하기 수 (초기값 0)
}
Comment on lines +4 to +11
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# 문법을 통해, 변수들을 private 화 하여, 캡슐화를 진행해보는건 어땠을까요 ~?

this.#name = name



// favorite 메서드 찜하기 수를 1씩 증가
favorite () {
this.favoriteCount += 1; //favoriteCount 값을 1씩 증가
}
}

// Product를 상속하는 ElectronicProduct 클래스
export class ElectronicProduct extends Product {
constructor(name, description, price, tags = [], images = [], manufacturer = '')
{
super(name, description, price, tags, images); // product 속성
this.manufacturer = manufacturer; // 제조사 정보 추가 저장
}
}

// 게시글 클레스 정의 Article 클래스
export class Article {
// 게시글 정보 객체 속성
constructor(title, content, writer) {
this.title = title; // 게시글 제목
this.content = content; // 게시글 내용
this.writer = writer; // 작성자 이름
this.likeCount = 0; // 좋아요 수 (초기값 0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

비즈니스적 관점에서 초기 생성시에 0 을 지정해주는 것 좋습니다.

너무 잘하셔서 더 나아간 점을 추가로 피드백 드리자면,
초기 생성시에는 0이 맞으나, 나중에 Database에 들어간 값이거나, 어디서 불러온 것이라면 초기에 0이 아닐 수 있으므로, 생성자에 받는것이 오히려 좋을 수 있습니다 !

this.createdAt = new Date(); // 객체 생성 시점 현재시간 저장
}

// 좋아요 수 1씩 증가시키는 like 메서드
like() {
this.likeCount += 1; // 호출 할 경우 likeCount가 1 증가
}
}
101 changes: 101 additions & 0 deletions .github/sprint-misson-2/ProductService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
const BASE_URL = 'https://panda-market-api-crud.vercel.app';

// getProductList: 상품 리스트를 받아오는 함수 (page, pageSize, keyword 쿼리 파라미터를 이용)
export async function getProductList({ page = 1, pageSize = 10, keyword = '' } = {}) {
try {
const url = new URL(`${BASE_URL}/products`);
if (page) url.searchParams.append('page', page);
if (pageSize) url.searchParams.append('pageSize', pageSize);
if (keyword) url.searchParams.append('keyword', keyword);

const res = await fetch(url);

if (!res.ok) {
console.error('상품 리스트 조회 실패:', res.status);
return null;
}

return await res.json();
} catch (err) {
console.error('getProductList 에러:', err.message);
return null;
}
}

// getProduct: 특정 상품 하나를 조회하는 함수
export async function getProduct(productId) {
try {
const res = await fetch(`${BASE_URL}/products/${productId}`);

if (!res.ok) {
console.error('상품 조회 실패:', res.status);
return null;
}

return await res.json();
} catch (err) {
console.error('getProduct 에러:', err.message);
return null;
}
}

// createProduct: 요청 body에 name, description, price, tags, images 포함하여 상품 생성
export async function createProduct({ name, description, price, tags, images }) {
try {
const res = await fetch(`${BASE_URL}/products`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, description, price, tags, images }),
});

if (!res.ok) {
console.error('상품 생성 실패:', res.status);
return null;
}

return await res.json();
} catch (err) {
console.error('createProduct 에러:', err.message);
return null;
}
}

// patchProduct: 특정 상품(productId)의 이름, 설명, 가격, 태그, 이미지를 수정하는 함수
export async function patchProduct(productId, { name, description, price, tags, images }) {
try {
const res = await fetch(`${BASE_URL}/products/${productId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, description, price, tags, images }),
});

if (!res.ok) {
console.error('상품 수정 실패:', res.status);
return null;
}

return await res.json();
} catch (err) {
console.error('patchProduct 에러:', err.message);
return null;
}
}

// deleteProduct: 특정 상품(productId)을 삭제하는 함수
export async function deleteProduct(productId) {
try {
const res = await fetch(`${BASE_URL}/products/${productId}`, {
method: 'DELETE',
});

if (!res.ok) {
console.error('상품 삭제 실패:', res.status);
return false;
}

return true;
} catch (err) {
console.error('deleteProduct 에러:', err.message);
return false;
}
}
51 changes: 51 additions & 0 deletions .github/sprint-misson-2/main.js
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전체적으로 indent가 잘 돼있지 않아 코드가 가독성이 떨어지는 것 같습니다!
이부분을 직접 수정하거나,
추후에는 eslint를 활용해보는것도 좋을 것 같습니다 !

Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Product, ElectronicProduct, Article } from './Classes.js';
import { getProductList, getProduct, createProduct, patchProduct, deleteProduct } from './ProductService.js';
import { getArticleList, getArticle, createArticle, patchArticle, deleteArticle } from './ArticleService.js';

async function loadProducts() {
const response = await getProductList({ page: 1, pageSize: 10 });
const products = [];
if (response && response.data) {
for (const item of response.data) {
if (item.tags && item.tags.includes('전자제품')) {
products.push(new ElectronicProduct(item.name, item.description, item.price, item.tags, item.images, item.manufacturer || ''));
} else {
products.push(new Product(item.name, item.description, item.price, item.tags, item.images));
}
}
}
console.log('만들어진 상품 인스턴스 배열:', products);
return products;
}

function testArticleService() {
getArticleList({ page: 1, pageSize: 3, keyword: '' }).then(console.log);
getArticle(1).then(console.log);
createArticle({ title: '테스트', content: '내용입니다', image: '' }).then(console.log);
patchArticle(1, { title: '수정', content: '수정된 내용', image: '' }).then(console.log);
deleteArticle(2).then(console.log);
}

async function testProductService() {
console.log(await getProductList({ page: 1, pageSize: 3, keyword: '' }));
console.log(await getProduct(1));
console.log(await createProduct({
name: '테스트 상품',
description: '테스트 설명',
price: 10000,
tags: ['테스트'],
images: [''],
}));
console.log(await patchProduct(1, {
name: '수정상품',
description: '수정 설명',
price: 13000,
tags: ['수정'],
images: [''],
}));
console.log(await deleteProduct(1));
}

loadProducts();
testArticleService();
testProductService();