ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,38 @@
+// import dotenv from 'dotenv';
+const dotenv = require('dotenv');
+dotenv.config();
+
+const {
+    DB_DEV_USER,
+    DB_DEV_PASSWORD,
+    DB_DEV_DATABASE,
+    DB_DEV_HOST,
+    DB_PRODUCTION_USER,
+    DB_PRODUCTION_PASSWORD,
+    DB_PRODUCTION_DATABASE,
+    DB_PRODUCTION_HOST,
+} = process.env;
+
+module.exports = {
+    development: {
+        username: DB_DEV_USER,
+        password: DB_DEV_PASSWORD,
+        database: DB_DEV_DATABASE,
+        host: DB_DEV_HOST,
+        dialect: 'mysql',
+    },
+    test: {
+        username: DB_DEV_USER,
+        password: DB_DEV_PASSWORD,
+        database: DB_DEV_DATABASE,
+        host: DB_DEV_HOST,
+        dialect: 'mysql',
+    },
+    production: {
+        username: DB_PRODUCTION_USER,
+        password: DB_PRODUCTION_PASSWORD,
+        database: DB_PRODUCTION_DATABASE,
+        host: DB_PRODUCTION_HOST,
+        dialect: 'mysql',
+    },
+};

리뷰: 불필요한 주석으로 보입니다~
@@ -0,0 +1,38 @@
+// import dotenv from 'dotenv';
+const dotenv = require('dotenv');
+dotenv.config();
+
+const {
+    DB_DEV_USER,
+    DB_DEV_PASSWORD,
+    DB_DEV_DATABASE,
+    DB_DEV_HOST,
+    DB_PRODUCTION_USER,
+    DB_PRODUCTION_PASSWORD,
+    DB_PRODUCTION_DATABASE,
+    DB_PRODUCTION_HOST,
+} = process.env;
+
+module.exports = {
+    development: {
+        username: DB_DEV_USER,
+        password: DB_DEV_PASSWORD,
+        database: DB_DEV_DATABASE,
+        host: DB_DEV_HOST,
+        dialect: 'mysql',
+    },
+    test: {
+        username: DB_DEV_USER,
+        password: DB_DEV_PASSWORD,
+        database: DB_DEV_DATABASE,
+        host: DB_DEV_HOST,
+        dialect: 'mysql',
+    },
+    production: {
+        username: DB_PRODUCTION_USER,
+        password: DB_PRODUCTION_PASSWORD,
+        database: DB_PRODUCTION_DATABASE,
+        host: DB_PRODUCTION_HOST,
+        dialect: 'mysql',
+    },
+};

리뷰: 좋은 습관입니다 👍
@@ -0,0 +1,19 @@
+FROM node:11.11.0
+
+# 앱 디렉터리 생성
+WORKDIR /usr/src/app
+
+# 앱 의존성 설치
+# 가능한 경우(npm@5+) package.json과 package-lock.json을 모두 복사하기 위해
+# 와일드카드를 사용
+COPY package*.json ./
+
+RUN npm install
+# 프로덕션을 위한 코드를 빌드하는 경우
+# RUN npm ci --only=production
+
+# 앱 소스 추가
+COPY . .
+
+EXPOSE 4000
+CMD [ "npm", "start" ]
\ No newline at end of file

리뷰: `RUN npm ci --only=production`로 치환하는게 더 바람직해보이네요 :)
@@ -0,0 +1,38 @@
+// import dotenv from 'dotenv';
+const dotenv = require('dotenv');
+dotenv.config();
+
+const {
+    DB_DEV_USER,
+    DB_DEV_PASSWORD,
+    DB_DEV_DATABASE,
+    DB_DEV_HOST,
+    DB_PRODUCTION_USER,
+    DB_PRODUCTION_PASSWORD,
+    DB_PRODUCTION_DATABASE,
+    DB_PRODUCTION_HOST,
+} = process.env;
+
+module.exports = {
+    development: {
+        username: DB_DEV_USER,
+        password: DB_DEV_PASSWORD,
+        database: DB_DEV_DATABASE,
+        host: DB_DEV_HOST,
+        dialect: 'mysql',
+    },
+    test: {
+        username: DB_DEV_USER,
+        password: DB_DEV_PASSWORD,
+        database: DB_DEV_DATABASE,
+        host: DB_DEV_HOST,
+        dialect: 'mysql',
+    },
+    production: {
+        username: DB_PRODUCTION_USER,
+        password: DB_PRODUCTION_PASSWORD,
+        database: DB_PRODUCTION_DATABASE,
+        host: DB_PRODUCTION_HOST,
+        dialect: 'mysql',
+    },
+};

리뷰: 환경변수를 .env파일에 `DEV_`와 `PRODUCTION` 구분을 두어 저장하는 것보다
환경변수명을 일치시키고(=dev, prod 구분없이)
추가로 `.env.test`, `.env.dev` 와 같은 파일을 만들어 관리하는걸 추천드립니다.

https://github.com/motdotla/dotenv#should-i-have-multiple-env-files
@@ -0,0 +1,38 @@
+// import dotenv from 'dotenv';
+const dotenv = require('dotenv');
+dotenv.config();
+
+const {
+    DB_DEV_USER,
+    DB_DEV_PASSWORD,
+    DB_DEV_DATABASE,
+    DB_DEV_HOST,
+    DB_PRODUCTION_USER,
+    DB_PRODUCTION_PASSWORD,
+    DB_PRODUCTION_DATABASE,
+    DB_PRODUCTION_HOST,
+} = process.env;
+
+module.exports = {
+    development: {
+        username: DB_DEV_USER,
+        password: DB_DEV_PASSWORD,
+        database: DB_DEV_DATABASE,
+        host: DB_DEV_HOST,
+        dialect: 'mysql',
+    },
+    test: {
+        username: DB_DEV_USER,
+        password: DB_DEV_PASSWORD,
+        database: DB_DEV_DATABASE,
+        host: DB_DEV_HOST,
+        dialect: 'mysql',
+    },
+    production: {
+        username: DB_PRODUCTION_USER,
+        password: DB_PRODUCTION_PASSWORD,
+        database: DB_PRODUCTION_DATABASE,
+        host: DB_PRODUCTION_HOST,
+        dialect: 'mysql',
+    },
+};

리뷰: 이 라인이 꼭 주석처리 되어야하는지 확인해보시면 좋을 것 같습니다
@@ -0,0 +1,31 @@
+'use strict';
+module.exports = {
+    up: (queryInterface, Sequelize) => {
+        return queryInterface.createTable('room_options', {
+            bed: {
+                type: Sequelize.INTEGER,
+            },
+            bedroom: {
+                type: Sequelize.INTEGER,
+            },
+            bathroom: {
+                type: Sequelize.INTEGER,
+            },
+            free_parking: {
+                type: Sequelize.BOOLEAN,
+            },
+            wifi: {
+                type: Sequelize.BOOLEAN,
+            },
+            kitchen: {
+                type: Sequelize.BOOLEAN,
+            },
+            washer: {
+                type: Sequelize.BOOLEAN,
+            },
+        });
+    },
+    down: (queryInterface, Sequelize) => {
+        return queryInterface.dropTable('room_options');
+    },
+};

리뷰: 'use strict'를 꼭 써야하나요? 
만약 꼭 필요하다면, 이 라인이 없는 파일도 있던데 주석으로 설명을 달아놓으면 좋을 것 같네요 :)
@@ -0,0 +1,37 @@
+'use strict';
+
+module.exports = {
+    up: (queryInterface, Sequelize) => {
+        return queryInterface.bulkInsert(
+            'users',
+            [
+                {
+                    name: '일지수',
+                    email: 'init@init.com',
+                    password: 'password',
+                    salt: 'salt',
+                    is_super_host: false,
+                },
+                {
+                    name: '이지수',
+                    email: 'init2@init.com',
+                    password: 'password',
+                    salt: 'salt',
+                    is_super_host: false,
+                },
+                {
+                    name: '삼지수',
+                    email: 'init3@init.com',
+                    password: 'password',
+                    salt: 'salt',
+                    is_super_host: false,
+                },
+            ],
+            {},
+        );
+    },
+
+    down: (queryInterface, Sequelize) => {
+        return queryInterface.bulkDelete('users', null, {});
+    },
+};

리뷰: mock 데이터지만 작명센스 👍
@@ -0,0 +1,38 @@
+// import dotenv from 'dotenv';
+const dotenv = require('dotenv');
+dotenv.config();
+
+const {
+    DB_DEV_USER,
+    DB_DEV_PASSWORD,
+    DB_DEV_DATABASE,
+    DB_DEV_HOST,
+    DB_PRODUCTION_USER,
+    DB_PRODUCTION_PASSWORD,
+    DB_PRODUCTION_DATABASE,
+    DB_PRODUCTION_HOST,
+} = process.env;
+
+module.exports = {
+    development: {
+        username: DB_DEV_USER,
+        password: DB_DEV_PASSWORD,
+        database: DB_DEV_DATABASE,
+        host: DB_DEV_HOST,
+        dialect: 'mysql',
+    },
+    test: {
+        username: DB_DEV_USER,
+        password: DB_DEV_PASSWORD,
+        database: DB_DEV_DATABASE,
+        host: DB_DEV_HOST,
+        dialect: 'mysql',
+    },
+    production: {
+        username: DB_PRODUCTION_USER,
+        password: DB_PRODUCTION_PASSWORD,
+        database: DB_PRODUCTION_DATABASE,
+        host: DB_PRODUCTION_HOST,
+        dialect: 'mysql',
+    },
+};

리뷰: 중복되는 코드는 가능하면 줄이는 것이 좋습니다. 왜냐하면 지금은 코드가 많지 않아서 문제가 없다고 느낄 수도 있지만, 이후 복잡도가 증가하면 코드를 수정할 때 유지보수의 측면에서 버그가 발생할 수 있는 가능성이 높아지기 때문입니다. `DRY(Don't Repeat Yourself)` 원칙은 이후로도 항상 기억하고 코드를 작성하는 것이 바람직할 것 같습니다.
@@ -0,0 +1,37 @@
+import fs from 'fs';
+import path from 'path';
+import Sequelize from 'sequelize';
+import configs from '../../config/database.js';
+
+const basename = path.basename(__filename);
+const env = process.env.NODE_ENV || 'development';
+const config = configs[env];
+
+const db = {};
+
+let sequelize;
+if (config.use_env_variable) {
+    sequelize = new Sequelize(process.env[config.use_env_variable], config);
+} else {
+    sequelize = new Sequelize(config.database, config.username, config.password, config);
+}
+
+fs.readdirSync(__dirname)
+    .filter(file => {
+        return file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js';
+    })
+    .forEach(file => {
+        const model = sequelize['import'](path.join(__dirname, file));
+        db[model.name] = model;
+    });
+
+Object.keys(db).forEach(modelName => {
+    if (db[modelName].associate) {
+        db[modelName].associate(db);
+    }
+});
+
+db.sequelize = sequelize;
+db.Sequelize = Sequelize;
+
+module.exports = db;

리뷰: db 객체의 필드값이 이렇게 비슷하면 이후에 헷갈리기 쉬울 것 같습니다. 변수 명을 조금 더 명확하게 구분을 해 주는 것도 좋을 것 같아요.
@@ -0,0 +1,28 @@
+'use strict';
+
+module.exports = {
+    up: (queryInterface, Sequelize) => {
+        return queryInterface.bulkInsert(
+            'room_types',
+            [
+                {
+                    name: '집 전체',
+                },
+                {
+                    name: '개인실',
+                },
+                {
+                    name: '호텔 객실',
+                },
+                {
+                    name: '다인실',
+                },
+            ],
+            {},
+        );
+    },
+
+    down: (queryInterface, Sequelize) => {
+        return queryInterface.bulkDelete('room_types', null, {});
+    },
+};

리뷰: 객실을 붙여서 써야 하는데 오타인 것 같네요
@@ -0,0 +1,13 @@
+NODE_ENV=
+
+DB_DEV_USER=
+DB_DEV_PASSWORD=
+DB_DEV_DATABASE=
+DB_DEV_HOST=
+
+DB_PRODUCTION_USER=
+DB_PRODUCTION_PASSWORD=
+DB_PRODUCTION_DATABASE=
+DB_PRODUCTION_HOST=
+
+TOKEN_SECRET_KEY=
\ No newline at end of file

리뷰: .env 파일은 실제로 사용할 때는 민감한 정보들이 많이 있을 수 있으니 .dev.env 형태로 github에서 보이지 않게 해주는 것도 보안상 좋을 것 같아요.
@@ -0,0 +1,37 @@
+import fs from 'fs';
+import path from 'path';
+import Sequelize from 'sequelize';
+import configs from '../../config/database.js';
+
+const basename = path.basename(__filename);
+const env = process.env.NODE_ENV || 'development';
+const config = configs[env];
+
+const db = {};
+
+let sequelize;
+if (config.use_env_variable) {
+    sequelize = new Sequelize(process.env[config.use_env_variable], config);
+} else {
+    sequelize = new Sequelize(config.database, config.username, config.password, config);
+}
+
+fs.readdirSync(__dirname)
+    .filter(file => {
+        return file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js';
+    })
+    .forEach(file => {
+        const model = sequelize['import'](path.join(__dirname, file));
+        db[model.name] = model;
+    });
+
+Object.keys(db).forEach(modelName => {
+    if (db[modelName].associate) {
+        db[modelName].associate(db);
+    }
+});
+
+db.sequelize = sequelize;
+db.Sequelize = Sequelize;
+
+module.exports = db;

리뷰: 한 줄이라도 만약 한 눈에 보기에 조금 복잡하다는 느낌이 든다면 별도의 함수 형태로 외부에 빼는 것도 좋을 것 같아요. 이후에 조건이 더 붙는다면 그렇게 하는 것을 권장합니다. 다른 개발자가 코드를 이해하는데 훨씬 더 도움이 될 것입니다.
@@ -0,0 +1,41 @@
+INSERT INTO board_entity (board_name)
+VALUES
+  ('자유 게시판'),
+  ('개발 게시판'),
+  ('일상 게시판'),
+  ('사건사고 게시판');
+
+INSERT INTO article_entity (title, content, password, board_id)
+VALUES
+  ('숙대입구역 맛집 추천', '숙대입구역 근처에 있는 평화남영 식당이 맛있어요!', '1234', 1),
+  ('연애 프로그램을 보면서..', '개발자들을 위한 예능 프로그램이 있었으면 좋겠다.. 아 근데 개발자들이 카메라 앞에 설리가 없지 ㅋㅋㅋㅋ', '1234', 1),
+  ('GPT 3.5를 쓰면서..', '닌 그냥 코딩 하지 말아라 속 터진다.', '1234', 2),
+  ('JS와 Java를 공부하니까', '너무 헷갈려요 ㅋㅋㅋ 매개변수 넣을 때 자꾸 타입을 안씀;;', '1234', 2),
+  ('네이버 블로그 style 코드 리뷰', '오늘은 코드 리뷰를 알아볼게요! 저도 참 궁금하네요!', '1234', 2),
+  ('요즘 근황', '밥 먹어 코딩 해 또 밥 먹어 코딩 해 유튜브 봐 또 코딩 해', '1234', 3),
+  ('요즘 근황', '밥밥밥밥 오늘 저녁은 제육볶음~~', '1234', 3),
+  ('사건사고? 그런건 내 사전에 없다.', '왜냐하면 코딩을 한 줄도 안치기 때문이지 하하하!', '1234', 4),
+  ('야 에러 뜨는 놈들 봐라 ㅋㅋㅋ', '에러? 아니 컴퓨터 너가 틀리고 내가 맞어', '1234', 4);
+
+INSERT INTO comment_entity(content, password, article_id)
+VALUES
+  ('평화남영 거기 맛있어요!', '1234', 1),
+  ('인테리어가 이뻐요!', '1234', 1),
+  ('개발자를 위한 예능이라니.. 아아 꿈을 꾸었습니다..', '1234', 2),
+  ('아주 행복한 꿈이었습니다..', '1234', 2),
+  ('근데 왜 우는 것이냐', '1234', 2),
+  ('그건 이뤄질 수 없는 꿈이기 때문입니다.', '1234', 2),
+  ('GPT는 오타 잡기 최고라고 ㅋㅋㅋㅋ', '1234', 3),
+  ('백엔드의 근본은 Java이지 ㅋㅋㅋㅋ', '1234', 4),
+  ('js는 근본이 없다 아입니꺼 근본이! ', '1234', 4),
+  ('이왕 이렇게 된거 코틀린으로 간다.', '1234', 4),
+  ('실패하면 타입스크립트 성공하면 동적언어 최고아닙니까!.', '1234', 4),
+  ('타입스크립트도 Java랑 타입 쓰는 방식이 다름 ㅋㅋㅋㅋ 수고요.', '1234', 4),
+  ('비밀 댓글입니다.', '1234', 5),
+  ('비밀 댓글입니다.', '1234', 5),
+  ('개발 공부하면 원래 반쯤 미쳐가나요?', '1234', 5),
+  ('일상 알고리즘 폼 미쳤다', '1234', 6),
+  ('너 아래 글 쓴 놈이지?', '1234', 7),
+  ('개발 공부하면 원래 반쯤 미치나요?', '1234', 7),
+  ('그런 방법이 있었네 ㅋㅋㅋㅋㅋㅋ', '1234', 8),
+  ('404 Not Found라고? 그럼 찾어!', '1234', 9);

리뷰: yaml설정 create라 테스트용 데이터 전부 생성해놓은 섬세함에 놀랐습니다.
@@ -0,0 +1,21 @@
+package com.subject.board.entity;
+
+import jakarta.persistence.*;
+import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+@Getter
+@Setter
+@Entity
+public class BoardEntity {
+  @Id
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  private Long id;
+  private String boardName;
+  @OneToMany(mappedBy = "board")
+  @OrderBy("id DESC") // article의 id를 내림차순으로 정렬
+  private List<ArticleEntity> articles;
+}

리뷰: entity에서 article의 id를 정렬해 부가적인 코드의 추가 없이 간단하게 게시글을 정렬할 수 있었네요. 배우고갑니다.
@@ -0,0 +1,58 @@
+package com.subject.board.comment;
+
+import com.subject.board.article.ArticleRepository;
+import com.subject.board.entity.ArticleEntity;
+import com.subject.board.entity.CommentEntity;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+@Service
+@RequiredArgsConstructor
+public class CommentService {
+  private final ArticleRepository articleRepository;
+  private final CommentRepository commentRepository;
+
+  // Create
+  public void create(
+    String content,
+    String password,
+    Long articleId) {
+    Optional<ArticleEntity> article = articleRepository.findById(articleId);
+
+    CommentEntity comment = new CommentEntity();
+    comment.setContent(content);
+    comment.setPassword(password);
+    comment.setArticle(article.orElse(null));
+    commentRepository.save(comment);
+  }
+
+  // Read
+  // 게시글별 댓글 조회
+  public List<CommentEntity> findByArticleId(Long articleId) {
+    return commentRepository.findByArticleId(articleId);
+  }
+
+  public CommentEntity readOne(Long commentId) {
+    return commentRepository.findById(commentId)
+      .orElse(null);
+  }
+
+  // Delete
+  public void delete(Long commentId) {
+    commentRepository.deleteById(commentId);
+  }
+
+  // 비밀번호 확인
+  public Boolean checkPassword(
+    Long commentId,
+    String inputPassword
+  ) {
+    String passwrod = readOne(commentId).getPassword();
+    if (passwrod.equals(inputPassword)) {
+      return true;
+    } else return false;
+  }
+}

리뷰: password 오타있습니다.
return password.equals(inputPassword); 로 줄일수 있을것 같습니다.
@@ -0,0 +1,41 @@
+INSERT INTO board_entity (board_name)
+VALUES
+  ('자유 게시판'),
+  ('개발 게시판'),
+  ('일상 게시판'),
+  ('사건사고 게시판');
+
+INSERT INTO article_entity (title, content, password, board_id)
+VALUES
+  ('숙대입구역 맛집 추천', '숙대입구역 근처에 있는 평화남영 식당이 맛있어요!', '1234', 1),
+  ('연애 프로그램을 보면서..', '개발자들을 위한 예능 프로그램이 있었으면 좋겠다.. 아 근데 개발자들이 카메라 앞에 설리가 없지 ㅋㅋㅋㅋ', '1234', 1),
+  ('GPT 3.5를 쓰면서..', '닌 그냥 코딩 하지 말아라 속 터진다.', '1234', 2),
+  ('JS와 Java를 공부하니까', '너무 헷갈려요 ㅋㅋㅋ 매개변수 넣을 때 자꾸 타입을 안씀;;', '1234', 2),
+  ('네이버 블로그 style 코드 리뷰', '오늘은 코드 리뷰를 알아볼게요! 저도 참 궁금하네요!', '1234', 2),
+  ('요즘 근황', '밥 먹어 코딩 해 또 밥 먹어 코딩 해 유튜브 봐 또 코딩 해', '1234', 3),
+  ('요즘 근황', '밥밥밥밥 오늘 저녁은 제육볶음~~', '1234', 3),
+  ('사건사고? 그런건 내 사전에 없다.', '왜냐하면 코딩을 한 줄도 안치기 때문이지 하하하!', '1234', 4),
+  ('야 에러 뜨는 놈들 봐라 ㅋㅋㅋ', '에러? 아니 컴퓨터 너가 틀리고 내가 맞어', '1234', 4);
+
+INSERT INTO comment_entity(content, password, article_id)
+VALUES
+  ('평화남영 거기 맛있어요!', '1234', 1),
+  ('인테리어가 이뻐요!', '1234', 1),
+  ('개발자를 위한 예능이라니.. 아아 꿈을 꾸었습니다..', '1234', 2),
+  ('아주 행복한 꿈이었습니다..', '1234', 2),
+  ('근데 왜 우는 것이냐', '1234', 2),
+  ('그건 이뤄질 수 없는 꿈이기 때문입니다.', '1234', 2),
+  ('GPT는 오타 잡기 최고라고 ㅋㅋㅋㅋ', '1234', 3),
+  ('백엔드의 근본은 Java이지 ㅋㅋㅋㅋ', '1234', 4),
+  ('js는 근본이 없다 아입니꺼 근본이! ', '1234', 4),
+  ('이왕 이렇게 된거 코틀린으로 간다.', '1234', 4),
+  ('실패하면 타입스크립트 성공하면 동적언어 최고아닙니까!.', '1234', 4),
+  ('타입스크립트도 Java랑 타입 쓰는 방식이 다름 ㅋㅋㅋㅋ 수고요.', '1234', 4),
+  ('비밀 댓글입니다.', '1234', 5),
+  ('비밀 댓글입니다.', '1234', 5),
+  ('개발 공부하면 원래 반쯤 미쳐가나요?', '1234', 5),
+  ('일상 알고리즘 폼 미쳤다', '1234', 6),
+  ('너 아래 글 쓴 놈이지?', '1234', 7),
+  ('개발 공부하면 원래 반쯤 미치나요?', '1234', 7),
+  ('그런 방법이 있었네 ㅋㅋㅋㅋㅋㅋ', '1234', 8),
+  ('404 Not Found라고? 그럼 찾어!', '1234', 9);

리뷰: 데이터가 들어있어서 테스트하기 좋았습니다.
@@ -0,0 +1,54 @@
+package christmas.constant;
+
+public final class CommentConstants {
+    public static final String EVENT_INFORMATION_MESSAGE = """
+            우테코 식당의 최대 이벤트! 12월 이벤트를 시작합니다!
+            최대 3번의 중복 할인과 우테코가 준비한 두 개의 깜짝 선물을 받아 가세요!
+            * 모든 이벤트는 타 이벤트와 중복 적용이 가능합니다.
+            
+            크리스마스 디데이를 같이 세어보아요.
+            최대 3400원 할인! 1일 1000원으로 시작하여 25일까지 100원씩 증가하는 할인!
+            크리스마스의 선물을 느껴보세요!
+            * 25일까지
+            
+            지나가는 2023년을 위한, 2,023원 할인 이벤트!
+            금요일, 토요일에는 메인 메뉴 1개당 2,023원을 할인합니다!
+            다른 날 방문해도 섭섭해하지 마세요.
+            일요일부터 목요일까지는 달콤한 디저트를 1개당 2,023원의 혜택을 받아보세요.
+            
+            별이 뜨는 날에는 행운이 찾아올 거야
+            달력에 별이 뜬 날에는 1,000원의 추가할인이 제공됩니다!
+            
+            특별한 날을 더 특별하게, 샴페인과 함께하는 우아한 시간
+            총 주문 금액 12만 원 이상 시 샴페인을 제공해 드립니다.
+            * 할인 전 금액 기준
+            
+            1석 2조, 혜택도 받고, 새해선물도 받자!
+            혜택 금액에 따라 받는 이벤트 배지로 1월 새해 선물 받아 가세요! 선물은 새해이벤트에 공개될 예정입니다.
+            * 5천 원 이상 : 별, 1만 원 이상 : 트리, 2만 원 이상 : 산타
+            
+            * 총 주문 금액 10,000원 이상부터 이벤트가 적용됩니다.
+            * 음료만 주문 시, 주문할 수 없습니다.
+            * 메뉴는 한 번에 최대 20개까지만 주문할 수 있습니다.
+            
+            ------------------------ 우테코 식당 메뉴 ------------------------
+            
+            <애피타이저>
+            양송이수프(6,000), 타파스(5,500), 시저샐러드(8,000)
+            
+            <메인>
+            티본스테이크(55,000), 바비큐립(54,000), 해산물파스타(35,000), 크리스마스파스타(25,000)
+            
+            <디저트>
+            초코케이크(15,000), 아이스크림(5,000)
+            
+            <음료>
+            제로콜라(3,000), 레드와인(60,000), 샴페인(25,000)
+            
+            ---------------------------------------------------------------       
+            안녕하세요! 우테코 식당 12월 이벤트 플래너입니다.
+            """;
+
+    private CommentConstants() {
+    }
+}
\ No newline at end of file

리뷰: 엇 이건 혹시 사용되는 않는 메시지일까요!?
@@ -0,0 +1,11 @@
+package christmas.domain.discount;
+
+import christmas.constant.DiscountPolicyName;
+import christmas.service.dto.OrderDto;
+
+public interface DiscountPolicy {
+
+    int discount(final OrderDto order);
+
+    DiscountPolicyName getDiscountPolicyName();
+}
\ No newline at end of file

리뷰: 인터페이스를 적절하게 잘 사용하신 거 같아요! 각 할인에 대해 확장이나 변경이 쉬울 것 같아요!
@@ -0,0 +1,52 @@
+package christmas.domain.menu;
+
+import java.util.Arrays;
+
+public enum Menu {
+
+    MUSHROOM_SOUP("양송이수프", 6_000, MenuCategory.APPETIZER),
+    TAPAS("타파스", 5_500, MenuCategory.APPETIZER),
+    CAESAR_SALAD("시저샐러드", 8_000, MenuCategory.APPETIZER),
+
+    T_BONE_STEAK("티본스테이크", 55_000, MenuCategory.MAIN),
+    BBQ_RIB("바비큐립", 54_000, MenuCategory.MAIN),
+    SEAFOOD_PASTA("해산물파스타", 35_000, MenuCategory.MAIN),
+    CHRISTMAS_PASTA("크리스마스파스타", 25_000, MenuCategory.MAIN),
+
+    CHOCOLATE_CAKE("초코케이크", 15_000, MenuCategory.DESSERT),
+    ICE_CREAM("아이스크림", 5_000, MenuCategory.DESSERT),
+
+    ZERO_COLA("제로콜라", 3_000, MenuCategory.DRINK),
+    RED_WINE("레드와인", 60_000, MenuCategory.DRINK),
+    CHAMPAGNE("샴페인", 25_000, MenuCategory.DRINK);
+
+    private static final String INVALID_ORDER_MESSAGE = "유효하지 않은 주문입니다. 다시 입력해 주세요.";
+    private final String name;
+    private final int price;
+    private final MenuCategory menuCategory;
+
+    Menu(final String name, final int price, final MenuCategory menuCategory) {
+        this.name = name;
+        this.price = price;
+        this.menuCategory = menuCategory;
+    }
+
+    public static Menu findByName(final String name) {
+        return Arrays.stream(Menu.values())
+                .filter(menu -> menu.getName().equals(name))
+                .findFirst()
+                .orElseThrow(() -> new IllegalArgumentException(INVALID_ORDER_MESSAGE));
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getPrice() {
+        return price;
+    }
+
+    public MenuCategory getMenuCategory() {
+        return menuCategory;
+    }
+}
\ No newline at end of file

리뷰: 요 코드는 저랑 거의 똑같네요 ㅎㅎ 이름도 똑같은데 신기하네요 ㅋㅋㅋ
메뉴에 대한 추가적인 로직이 없어서 이렇게 메뉴를 모아놓는 게 더 가독성이 좋은 것 같아요!
@@ -0,0 +1,77 @@
+package christmas.domain.discount;
+
+import christmas.constant.DiscountConstants;
+import christmas.constant.DiscountPolicyName;
+import christmas.domain.menu.Menu;
+import christmas.domain.menu.MenuCategory;
+import christmas.service.dto.OrderDto;
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.util.Map;
+import java.util.Map.Entry;
+
+
+public class WeekendDiscountPolicy implements DiscountPolicy {
+    private static final int WEEKEND_DISCOUNT_AMOUNT = 2023;
+
+    @Override
+    public int discount(final OrderDto order) {
+        LocalDate orderDate = order.getDate();
+        if (isWithinDiscountPeriod(orderDate) && isWeekend(orderDate)) {
+            return calculateTotalDiscount(order.getMenus());
+        }
+        return DiscountConstants.NO_DISCOUNT;
+    }
+
+    @Override
+    public DiscountPolicyName getDiscountPolicyName() {
+        return DiscountPolicyName.WEEKEND_DISCOUNT;
+    }
+
+    private int calculateTotalDiscount(Map<Menu, Integer> menus) {
+        int totalDiscount = 0;
+        for (Entry<Menu, Integer> menu : menus.entrySet()) {
+            totalDiscount += calculateDiscountForMenu(menu);
+        }
+        return totalDiscount;
+    }
+
+    private int calculateDiscountForMenu(Entry<Menu, Integer> menu) {
+        if (isMainCategory(menu.getKey())) {
+            return calculateMenuDiscount(menu);
+        }
+        return 0;
+    }
+
+    private boolean isMainCategory(Menu menu) {
+        return menu.getMenuCategory() == MenuCategory.MAIN;
+    }
+
+    private int calculateMenuDiscount(Map.Entry<Menu, Integer> menu) {
+        return WEEKEND_DISCOUNT_AMOUNT * menu.getValue();
+    }
+
+    private boolean isWithinDiscountPeriod(LocalDate date) {
+        return isNotBeforeStartDate(date) && isNotAfterEndDate(date);
+    }
+
+    private boolean isNotBeforeStartDate(LocalDate date) {
+        return !date.isBefore(DiscountConstants.START_DAY_OF_MONTH);
+    }
+
+    private boolean isNotAfterEndDate(LocalDate date) {
+        return !date.isAfter(DiscountConstants.END_DAY_OF_MONTH);
+    }
+
+    private boolean isWeekend(LocalDate date) {
+        return isFriday(date) || isSaturday(date);
+    }
+
+    private boolean isFriday(LocalDate date) {
+        return date.getDayOfWeek() == DayOfWeek.FRIDAY;
+    }
+
+    private boolean isSaturday(LocalDate date) {
+        return date.getDayOfWeek() == DayOfWeek.SATURDAY;
+    }
+}
\ No newline at end of file

리뷰: DayOfWeek 사용법 저만 몰랐군요.. 되게 좋은 방법인 거 같아요! 👍👍