ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,91 @@
+package christmas.domain;
+
+import christmas.domain.menu.Dessert;
+import christmas.domain.menu.Drink;
+import christmas.domain.menu.Entree;
+import christmas.domain.menu.Menu;
+
+import java.util.Map;
+
+import static christmas.constant.Number.*;
+import static christmas.constant.message.OutputMessage.*;
+
+public record Event(Date date, Order order, Price price) {
+    public int d_Day() {
+        if (price.price() < TEN_THOUSAND || date.date() > CHRISTMAS) {
+            return ZERO;
+        }
+        return THOUSAND + (date.date() - MIN_DATE) * HUNDRED;
+    }
+
+    public int weekday() {
+        if (price.price() < TEN_THOUSAND || (date.date() % SEVEN == ONE || date.date() % SEVEN == TWO)) {
+            return ZERO;
+        }
+        int count = ZERO;
+        for (Map.Entry<Menu, Integer> entry : order.order().entrySet()) {
+            Menu menu = entry.getKey();
+            Integer i = entry.getValue();
+            try {
+                Dessert.valueOf(menu.name());
+            } catch (IllegalArgumentException e) {
+                continue;
+            }
+            count += i;
+        }
+        return YEAR * count;
+    }
+
+    public int weekend() {
+        if (price.price() < TEN_THOUSAND || (date.date() % SEVEN != ONE && date.date() % SEVEN != TWO)) {
+            return ZERO;
+        }
+        int count = ZERO;
+        for (Map.Entry<Menu, Integer> entry : order.order().entrySet()) {
+            Menu menu = entry.getKey();
+            Integer i = entry.getValue();
+            try {
+                Entree.valueOf(menu.name());
+            } catch (IllegalArgumentException e) {
+                continue;
+            }
+            count += i;
+        }
+        return YEAR * count;
+    }
+
+    public int special() {
+        if (price.price() < TEN_THOUSAND || (date.date() % SEVEN != THREE && date.date() != CHRISTMAS)) {
+            return ZERO;
+        }
+        return THOUSAND;
+    }
+
+    public int freeGift() {
+        if (price.price() > FREE_GIFT) {
+            return Drink.샴페인.getPrice();
+        }
+        return ZERO;
+    }
+
+    public String badge() {
+        if (allBenefit() >= SANTA) {
+            return SANTA_BADGE;
+        }
+        if (allBenefit() >= TREE) {
+            return TREE_BADGE;
+        }
+        if (allBenefit() >= STAR) {
+            return STAR_BADGE;
+        }
+        return NOTHING;
+    }
+
+    public int allBenefit() {
+        return d_Day() + weekday() + weekend() + special() + freeGift();
+    }
+
+    public int afterDiscount() {
+        return d_Day() + weekday() + weekend() + special();
+    }
+}

리뷰: Date, Event, Order, Price 모두 Record로 만들어주신 이유는 뭘까요?
@@ -0,0 +1,52 @@
+import React, { Component } from 'react';
+
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faHeart } from '@fortawesome/free-regular-svg-icons';
+
+import './LikeBtn.scss';
+
+class LikeBtn extends Component {
+  constructor(props) {
+    super(props);
+    const { isLiked } = this.props;
+    this.state = { isLikeBtnClicked: isLiked };
+  }
+
+  handleLikeBtnColor = () => {
+    const { isLikeBtnClicked } = this.state;
+    const { whoseBtn, handler, userId, categoryTitle, coffeeName } = this.props;
+    this.setState({ isLikeBtnClicked: !isLikeBtnClicked });
+
+    switch (whoseBtn) {
+      case 'product':
+        handler(!isLikeBtnClicked);
+        break;
+      case 'review':
+        handler(userId, !isLikeBtnClicked);
+        break;
+      case 'coffeeCard':
+        handler(categoryTitle, coffeeName, !isLikeBtnClicked);
+        break;
+      default:
+        break;
+    }
+  };
+
+  render() {
+    const { isLikeBtnClicked } = this.state;
+    const { handleLikeBtnColor } = this;
+    return (
+      <div className="like-btn-wrap">
+        <button
+          className={isLikeBtnClicked ? 'like-wrap clicked' : 'like-wrap'}
+          id="product-like"
+          onClick={handleLikeBtnColor}
+        >
+          <FontAwesomeIcon icon={faHeart} />
+        </button>
+      </div>
+    );
+  }
+}
+
+export default LikeBtn;

리뷰: react부터 wecode에서는 클래스명도 카멜케이스로 통일하자고 했던 것 같습니다...?
@@ -0,0 +1,103 @@
+{
+  "footerList": [
+    {
+      "id": 1,
+      "title": "COMPANY",
+      "contents": [
+        {
+          "id": 1,
+          "contentName": "한눈에 보기"
+        },
+        {
+          "id": 2,
+          "contentName": "위벅스 사명"
+        },
+        {
+          "id": 3,
+          "contentName": "위벅스 소개"
+        },
+        {
+          "id": 4,
+          "contentName": "국내 뉴스룸"
+        },
+        {
+          "id": 5,
+          "contentName": "세계의 위벅스"
+        },
+        {
+          "id": 6,
+          "contentName": "글로벌 뉴스룸"
+        }
+      ]
+    },
+    {
+      "id": 2,
+      "title": "CORPORATE_SALES",
+      "contents": [
+        {
+          "id": 1,
+          "contentName": "단체 및 기업 구매 안내"
+        }
+      ]
+    },
+    {
+      "id": 3,
+      "title": "PARTNERSHIP",
+      "contents": [
+        {
+          "id": 1,
+          "contentName": "글로벌 뉴스룸"
+        },
+        {
+          "id": 2,
+          "contentName": "신규 입점 제의"
+        }
+      ]
+    },
+    {
+      "id": 4,
+      "title": "ONLINE COMMUNITY",
+      "contents": [
+        {
+          "id": 1,
+          "contentName": "페이스북"
+        },
+        {
+          "id": 2,
+          "contentName": "트위터"
+        },
+        {
+          "id": 3,
+          "contentName": "유튜브"
+        },
+        {
+          "id": 4,
+          "contentName": "블로그"
+        },
+        {
+          "id": 5,
+          "contentName": "인스타그램"
+        },
+        {
+          "id": 6,
+          "contentName": "페이스북"
+        }
+      ]
+    },
+    {
+      "id": 5,
+      "title": "RECRUIT",
+      "contents": [
+        {
+          "id": 1,
+          "contentName": "채용 소개"
+        },
+        {
+          "id": 2,
+          "contentName": "채용 지원하기"
+        }
+      ]
+    },
+    { "id": 6, "title": "WEBUKCS", "contents": [] }
+  ]
+}
\ No newline at end of file

리뷰: footer까지 json으로 하셨네요! 부지런하십니다,,👍🏼
@@ -0,0 +1,44 @@
+{
+  "menus": [
+    {
+      "id": 1,
+      "parentId": 0,
+      "level": 1,
+      "name": "홈",
+      "path": "/",
+      "children": null
+    },
+    {
+      "id": 2,
+      "parentId": 1,
+      "level": 2,
+      "name": "MENU",
+      "path": "/menu/",
+      "children": null
+    },
+    {
+      "id": 3,
+      "parentId": 2,
+      "level": 3,
+      "name": "음료",
+      "path": "/list-junbeom",
+      "children": null
+    },
+    {
+      "id": 4,
+      "parentId": 3,
+      "level": 4,
+      "name": "콜드브루",
+      "path": "/menu/drink/coldbrew/",
+      "children": null
+    },
+    {
+      "id": 5,
+      "parentId": 4,
+      "level": 5,
+      "name": "시그니처 더 블랙 콜드 브루",
+      "path": "/detail-junbeom",
+      "children": null
+    }
+  ]
+}
\ No newline at end of file

리뷰: 저는 제가 list로 돌아가고 싶어서 menu에만 Link 처리를 해두었는데 path까지 디테일까지 설정하셨네요! 저도 좀더 디테일하게 수정해봐야겠습니다. 좋은 정보 감사합니다 :)
@@ -0,0 +1,48 @@
+import React, { Component } from 'react';
+
+import FooterMenuTitle from './FooterMenuTitle';
+import FooterMenuContent from './FooterMenuContent';
+
+import './Footer.scss';
+
+class Footer extends Component {
+  constructor(props) {
+    super(props);
+    this.state = { footerList: [] };
+  }
+
+  componentDidMount() {
+    fetch('http://localhost:3000/data/footerListMockData.json')
+      .then(res => res.json())
+      .then(data => {
+        this.setState({ footerList: data.footerList });
+      });
+  }
+
+  render() {
+    const { footerList } = this.state;
+    return (
+      <footer className="Footer">
+        <div className="footer-menus">
+          {footerList.map(data => {
+            return (
+              <ul key={data.id}>
+                <FooterMenuTitle title={data.title} />
+                {data.contents.map(content => {
+                  return (
+                    <FooterMenuContent
+                      key={content.id}
+                      content={content.contentName}
+                    />
+                  );
+                })}
+              </ul>
+            );
+          })}
+        </div>
+      </footer>
+    );
+  }
+}
+
+export default Footer;

리뷰: import 코딩 컨벤션이 따로 있는지 모르겠으나, 팀끼리 의논해서 더 가독성 좋은 쪽으로 줄바꿈을 적용하면 좋을 것 같다는 생각입니다 :)
@@ -0,0 +1,35 @@
+@import '../../Styles/variables.scss';
+
+.Footer {
+  width: 100%;
+  background-color: #2c2a29;
+
+  .footer-menus {
+    display: flex;
+    justify-content: space-around;
+    flex-wrap: wrap;
+    margin: 20px auto;
+    padding: 10px 50px;
+    color: white;
+
+    li {
+      list-style: none;
+    }
+
+    a {
+      color: $theme-color;
+      text-decoration: none;
+    }
+
+    .footer-menu-title {
+      margin-bottom: 15px;
+      font-size: 15px;
+      font-weight: 100;
+    }
+
+    .footer-menu-content {
+      margin-bottom: 6px;
+      font-size: 12px;
+    }
+  }
+}

리뷰: sass variable을 어디에 적용해야 할지 몰라 고민하고 있었는데 참고 많이 하겠습니다!! 감사합니다 :)
@@ -0,0 +1,164 @@
+@import '../../../Styles/variables.scss';
+@import '../../../Styles/font.scss';
+
+.Detail {
+  @extend %flex-column;
+  align-items: center;
+  flex-wrap: wrap;
+  padding-top: 3rem;
+
+  .detail-section {
+    @extend %flex-column;
+    width: 60%;
+    margin-top: 30px;
+
+    .category-title {
+      font-size: 25px;
+      font-weight: 700;
+    }
+  }
+
+  .detail-category {
+    position: relative;
+    height: 80px;
+    margin-bottom: 20px;
+    padding: 10px;
+
+    .category-nav {
+      position: absolute;
+      right: 0;
+      bottom: 0;
+      width: 400px;
+    }
+  }
+
+  .detail-content {
+    @extend %flex-between;
+    flex-wrap: wrap;
+  }
+
+  .detail-img-wrap {
+    max-width: 45%;
+    min-width: 300px;
+    padding: 10px;
+
+    img {
+      width: 100%;
+    }
+  }
+
+  .detail-text-wrap {
+    max-width: 55%;
+    padding: 10px;
+  }
+
+  .detail-text {
+    position: relative;
+
+    .product-name-kr {
+      padding-bottom: 18px;
+      margin-bottom: 20px;
+      color: #222;
+      border-bottom: 2px solid #333;
+      font-size: 25px;
+      font-weight: 400;
+    }
+
+    .product-name-en {
+      color: #666;
+      font-size: 16px;
+    }
+
+    .like-btn-wrap {
+      position: absolute;
+      top: 0;
+      right: 0;
+
+      .like-wrap {
+        font-size: 25px;
+      }
+    }
+
+    .product-description {
+      margin-bottom: 20px;
+      font-size: 16px;
+      white-space: pre-wrap;
+    }
+  }
+
+  .product-info {
+    @extend %flex-column;
+
+    .product-info-head {
+      @extend %flex-between;
+      border-top: 1px solid $division-color;
+      border-bottom: 1px solid $division-color;
+
+      .info-head-text {
+        color: #222;
+        font-size: 18px;
+        padding: 10px;
+        margin: 10px;
+      }
+    }
+
+    .product-info-content {
+      @extend %flex-center;
+      margin: 10px;
+      padding: 10px;
+
+      table {
+        width: 100%;
+
+        tr {
+          height: 30px;
+        }
+
+        td {
+          width: 25%;
+        }
+
+        .align-right {
+          text-align: right;
+        }
+
+        .padding-right {
+          padding-right: 10px;
+        }
+
+        .dashed-border {
+          padding-left: 10px;
+          border-left: 1px dashed $division-color;
+        }
+      }
+    }
+
+    .allergens-wrap {
+      margin: 10px;
+      padding: 10px;
+      font-size: 18px;
+      background-color: $division-color;
+    }
+  }
+
+  .detail-review {
+    margin-bottom: 30px;
+
+    .review-title {
+      padding: 20px 0px;
+      border-bottom: 1px solid $division-color;
+    }
+
+    ul {
+      padding: 20px 0px;
+      list-style: none;
+    }
+
+    .review-input {
+      width: 100%;
+      padding: 10px;
+      background-color: $division-color;
+      border: none;
+    }
+  }
+}

리뷰: 디테일 페이지에는 대부분 camelCase이 적용이 되어 있지 않은 것 같습니다.
@@ -0,0 +1,44 @@
+{
+  "menus": [
+    {
+      "id": 1,
+      "parentId": 0,
+      "level": 1,
+      "name": "홈",
+      "path": "/",
+      "children": null
+    },
+    {
+      "id": 2,
+      "parentId": 1,
+      "level": 2,
+      "name": "MENU",
+      "path": "/menu/",
+      "children": null
+    },
+    {
+      "id": 3,
+      "parentId": 2,
+      "level": 3,
+      "name": "음료",
+      "path": "/list-junbeom",
+      "children": null
+    },
+    {
+      "id": 4,
+      "parentId": 3,
+      "level": 4,
+      "name": "콜드브루",
+      "path": "/menu/drink/coldbrew/",
+      "children": null
+    },
+    {
+      "id": 5,
+      "parentId": 4,
+      "level": 5,
+      "name": "시그니처 더 블랙 콜드 브루",
+      "path": "/detail-junbeom",
+      "children": null
+    }
+  ]
+}
\ No newline at end of file

리뷰: 엄청 꼼꼼히 작성하셨네요. 대단합니다!! :)
@@ -0,0 +1,52 @@
+import React, { Component } from 'react';
+
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faHeart } from '@fortawesome/free-regular-svg-icons';
+
+import './LikeBtn.scss';
+
+class LikeBtn extends Component {
+  constructor(props) {
+    super(props);
+    const { isLiked } = this.props;
+    this.state = { isLikeBtnClicked: isLiked };
+  }
+
+  handleLikeBtnColor = () => {
+    const { isLikeBtnClicked } = this.state;
+    const { whoseBtn, handler, userId, categoryTitle, coffeeName } = this.props;
+    this.setState({ isLikeBtnClicked: !isLikeBtnClicked });
+
+    switch (whoseBtn) {
+      case 'product':
+        handler(!isLikeBtnClicked);
+        break;
+      case 'review':
+        handler(userId, !isLikeBtnClicked);
+        break;
+      case 'coffeeCard':
+        handler(categoryTitle, coffeeName, !isLikeBtnClicked);
+        break;
+      default:
+        break;
+    }
+  };
+
+  render() {
+    const { isLikeBtnClicked } = this.state;
+    const { handleLikeBtnColor } = this;
+    return (
+      <div className="like-btn-wrap">
+        <button
+          className={isLikeBtnClicked ? 'like-wrap clicked' : 'like-wrap'}
+          id="product-like"
+          onClick={handleLikeBtnColor}
+        >
+          <FontAwesomeIcon icon={faHeart} />
+        </button>
+      </div>
+    );
+  }
+}
+
+export default LikeBtn;

리뷰: 버튼의 좋아요 상태를 버튼이 가지고 있으면 항상 좋아요가 false겠죠? 이미 좋아요 한 컴포넌트를 표현할 수 없습니다. 따라서 props로 받아와야 합니다.

이렇게 바뀌면 메서드 들도 조금 수정하셔야 할겁니다!
@@ -0,0 +1,93 @@
+import Util from './util/Util.js';
+import MenuValidation from './validation/MenuValidation.js';
+import { PRICE, APPETIZER, MAIN, DESSERT, BEVERAGE, PRICE_CONSTANT } from './constants/constant.js';
+
+class Menu {
+  #main = { ...MAIN };
+
+  #dessert = { ...DESSERT };
+
+  #beverage = { ...BEVERAGE };
+
+  #appetizer = { ...APPETIZER };
+
+  #totalPrice = 0;
+
+  constructor(menu) {
+    const data = Util.countMethod(menu);
+    this.#validate(data);
+    this.#calculateMenuAndPrice(data);
+  }
+
+  #validate(data) {
+    MenuValidation.validateMenuAndMenuCount(data);
+    MenuValidation.validateDuplicate(data);
+    MenuValidation.validateOnlyBeverage({ ...this.#appetizer, ...this.#main, ...this.#dessert }, data);
+  }
+
+  #calculateMenuAndPrice(data) {
+    data.forEach(([food, count]) => {
+      if (this.#appetizer[food] !== undefined) this.#appetizer[food] += count;
+      if (this.#dessert[food] !== undefined) this.#dessert[food] += count;
+      if (this.#main[food] !== undefined) this.#main[food] += count;
+      if (this.#beverage[food] !== undefined) this.#beverage[food] += count;
+      this.#totalPrice += PRICE[food] * count;
+    });
+  }
+
+  /**
+   * 총 주문한 디저트의 개수
+   * @returns {number} totalDessert
+   */
+  calcultaeTotalDessert() {
+    return Util.extractValuesAndSum(this.#dessert);
+  }
+
+  /**
+   * 총 주문한 메인 메뉴의 개수
+   * @returns {number} totalMainMenu
+   */
+  calcultaeTotalMain() {
+    return Util.extractValuesAndSum(this.#main);
+  }
+
+  /**
+   * 에피타이저, 메인메뉴, 디저트, 음료 중 1개이상 주문한 메뉴만 추려서 반환
+   * @returns {{[key:string]: number}} buyingMenu
+   */
+  calculateBuyingMenu() {
+    const totalFood = { ...this.#appetizer, ...this.#main, ...this.#dessert, ...this.#beverage };
+    const foodNames = Util.extractKeys(totalFood);
+    const buyingMenu = {};
+    foodNames.forEach((food) => {
+      if (totalFood[food]) buyingMenu[food] = totalFood[food];
+    });
+    return buyingMenu;
+  }
+
+  /**
+   * 12만원 이상 구매 여부 반환
+   * @returns {boolean} isPresentedAmount
+   */
+  isPresentedAmount() {
+    return this.#totalPrice >= PRICE_CONSTANT.presentedAmountPrice;
+  }
+
+  /**
+   * 총 구입 금액이 기준 금액 이상 여부에 따라서 이벤트 혜택 여부를 확인합니다.
+   * @returns {boolean} 이벤트 대상 여부를 확인합니다.
+   */
+  isEvent() {
+    return this.#totalPrice >= 10000;
+  }
+
+  /**
+   * 할인전 총 금액 반환
+   * @returns {number} totalPrice
+   */
+  getTotalPrice() {
+    return this.#totalPrice;
+  }
+}
+
+export default Menu;

리뷰: 필드의 개수가 많은데 이렇게 두신 이유가 있으실까요?
@@ -0,0 +1,93 @@
+import Util from './util/Util.js';
+import MenuValidation from './validation/MenuValidation.js';
+import { PRICE, APPETIZER, MAIN, DESSERT, BEVERAGE, PRICE_CONSTANT } from './constants/constant.js';
+
+class Menu {
+  #main = { ...MAIN };
+
+  #dessert = { ...DESSERT };
+
+  #beverage = { ...BEVERAGE };
+
+  #appetizer = { ...APPETIZER };
+
+  #totalPrice = 0;
+
+  constructor(menu) {
+    const data = Util.countMethod(menu);
+    this.#validate(data);
+    this.#calculateMenuAndPrice(data);
+  }
+
+  #validate(data) {
+    MenuValidation.validateMenuAndMenuCount(data);
+    MenuValidation.validateDuplicate(data);
+    MenuValidation.validateOnlyBeverage({ ...this.#appetizer, ...this.#main, ...this.#dessert }, data);
+  }
+
+  #calculateMenuAndPrice(data) {
+    data.forEach(([food, count]) => {
+      if (this.#appetizer[food] !== undefined) this.#appetizer[food] += count;
+      if (this.#dessert[food] !== undefined) this.#dessert[food] += count;
+      if (this.#main[food] !== undefined) this.#main[food] += count;
+      if (this.#beverage[food] !== undefined) this.#beverage[food] += count;
+      this.#totalPrice += PRICE[food] * count;
+    });
+  }
+
+  /**
+   * 총 주문한 디저트의 개수
+   * @returns {number} totalDessert
+   */
+  calcultaeTotalDessert() {
+    return Util.extractValuesAndSum(this.#dessert);
+  }
+
+  /**
+   * 총 주문한 메인 메뉴의 개수
+   * @returns {number} totalMainMenu
+   */
+  calcultaeTotalMain() {
+    return Util.extractValuesAndSum(this.#main);
+  }
+
+  /**
+   * 에피타이저, 메인메뉴, 디저트, 음료 중 1개이상 주문한 메뉴만 추려서 반환
+   * @returns {{[key:string]: number}} buyingMenu
+   */
+  calculateBuyingMenu() {
+    const totalFood = { ...this.#appetizer, ...this.#main, ...this.#dessert, ...this.#beverage };
+    const foodNames = Util.extractKeys(totalFood);
+    const buyingMenu = {};
+    foodNames.forEach((food) => {
+      if (totalFood[food]) buyingMenu[food] = totalFood[food];
+    });
+    return buyingMenu;
+  }
+
+  /**
+   * 12만원 이상 구매 여부 반환
+   * @returns {boolean} isPresentedAmount
+   */
+  isPresentedAmount() {
+    return this.#totalPrice >= PRICE_CONSTANT.presentedAmountPrice;
+  }
+
+  /**
+   * 총 구입 금액이 기준 금액 이상 여부에 따라서 이벤트 혜택 여부를 확인합니다.
+   * @returns {boolean} 이벤트 대상 여부를 확인합니다.
+   */
+  isEvent() {
+    return this.#totalPrice >= 10000;
+  }
+
+  /**
+   * 할인전 총 금액 반환
+   * @returns {number} totalPrice
+   */
+  getTotalPrice() {
+    return this.#totalPrice;
+  }
+}
+
+export default Menu;

리뷰: Data는 불용어라고 들었는데 전부 소문자이면 상관이 없나요?
@@ -0,0 +1,70 @@
+import ReservationDateValidation from './validation/ReservationDateValidation.js';
+import { DAY_CONSTANT, PRICE_CONSTANT } from './constants/constant.js';
+
+class ReservationDate {
+  #date;
+
+  constructor(date) {
+    const dateToNumber = Number(date);
+    this.#validate(dateToNumber);
+    this.#date = dateToNumber;
+  }
+
+  #validate(date) {
+    ReservationDateValidation.validateNumber(date);
+    ReservationDateValidation.validateUnderAndOver(date);
+  }
+
+  #getDate() {
+    const index = this.#date % DAY_CONSTANT.days.length;
+    const day = DAY_CONSTANT.days[index];
+    return day;
+  }
+
+  /**
+   * 해당 요일 이벤트 혜택 시작 문구 반환
+   * @returns {string} dateString
+   */
+  makeDateString() {
+    return `12월 ${this.#date}일에 우테코 식장에서 받을 이벤트 혜택 미리 보기!`;
+  }
+
+  /**
+   * 크리스마스 디데이 할인 금액 출력
+   * @returns {number} christmasDiscount
+   */
+  calculateChristmasDiscount() {
+    if (this.#date <= DAY_CONSTANT.christmas) {
+      return (this.#date - DAY_CONSTANT.startDay) * 100 + PRICE_CONSTANT.christmasBaseDiscountPrice;
+    }
+    return 0;
+  }
+
+  /**
+   * 주말 여부 확인
+   * @returns {boolean} isWeekend
+   */
+  isWeekend() {
+    const day = this.#getDate();
+    return day === DAY_CONSTANT.days[1] || day === DAY_CONSTANT.days[2];
+  }
+
+  /**
+   * 평일 여부 확인
+   * @returns {boolean} isWeekday
+   */
+  isWeekday() {
+    const day = this.#getDate();
+    return !(day === DAY_CONSTANT.days[1]) && !(day === DAY_CONSTANT.days[2]);
+  }
+
+  /**
+   * 해당 요일이 스페셜 할인이 포함되는 요일인지 확인
+   * @returns {boolean} isSpecialDiscount
+   */
+  isSpecialDiscount() {
+    return DAY_CONSTANT.specialDiscountDays.includes(this.#date);
+  }
+}
+
+export default ReservationDate;

리뷰: 이 클래스는 도메인인가요..?
getDate() 메서드는 접근제한을 두시고 이 메서드에서는 객체의 상태값을 그대로 내보내시는 이유가 궁금해요..🤔
@@ -0,0 +1,58 @@
+package com.codesquad.dust4.api;
+
+import com.codesquad.dust4.domain.DustForecast;
+import com.codesquad.dust4.dto.DustInfoByStationDto;
+import com.codesquad.dust4.dto.ForecastResponseDto;
+import com.codesquad.dust4.dto.LocationReturnDto;
+import com.codesquad.dust4.service.DustStatusPublicApi;
+import com.codesquad.dust4.service.ForecastPublicApi;
+import com.codesquad.dust4.utils.DustMockDataUtil;
+import com.codesquad.dust4.utils.LocationConverterUtil;
+
+import java.net.URISyntaxException;
+import java.util.concurrent.ExecutionException;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.IOException;
+
+@RestController
+public class DustAPIController {
+
+  private static final Logger logger = LoggerFactory.getLogger(DustAPIController.class);
+
+  @GetMapping("/dust-status")
+
+  public ResponseEntity<DustInfoByStationDto> getDustInfo(@RequestParam("latitude") String EPSGlatitude, @RequestParam("longitude") String EPSGlongitude)
+      throws IOException, ExecutionException, InterruptedException {
+    logger.info("longitude: {}, latitude: {}", EPSGlongitude, EPSGlatitude);
+
+    LocationReturnDto locationReturnDto = LocationConverterUtil.locationConverter(EPSGlongitude, EPSGlatitude);
+
+    String tmX = locationReturnDto.getLongitude();
+    String tmY = locationReturnDto.getLatitude();
+
+    DustInfoByStationDto dustInfoByStationDto = DustStatusPublicApi.dustStatus(tmX, tmY);
+
+    return new ResponseEntity<>(dustInfoByStationDto, HttpStatus.OK);
+  }
+
+  @GetMapping("/forecast")
+  public ResponseEntity<ForecastResponseDto> enlistDustForecast() throws URISyntaxException, JsonProcessingException {
+
+    logger.info("dust forecast");
+
+    DustForecast forecast = ForecastPublicApi.forecast();
+    ForecastResponseDto responseDto = new ForecastResponseDto();
+    responseDto.setContent(forecast);
+
+    return new ResponseEntity<>(responseDto, HttpStatus.OK);
+  }
+}

리뷰: 불필요한 개행이 있네요!