ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,36 @@
+package com.doubleowner.revibe.global.common.service;
+
+import com.doubleowner.revibe.global.exception.CommonException;
+import com.doubleowner.revibe.global.exception.errorCode.ErrorCode;
+import com.doubleowner.revibe.global.util.S3Uploader;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+
+@Service
+@RequiredArgsConstructor
+public class ImageService {
+    private final S3Uploader s3Uploader;
+
+    public String UploadImage(String image, MultipartFile file) {
+        try {
+            if (image != null) {
+                s3Uploader.deleteImage(image);
+            }
+            return s3Uploader.upload(file);
+        } catch (IOException e) {
+            throw new CommonException(ErrorCode.FAILED_UPLOAD_IMAGE);
+        }
+    }
+
+    public void deleteImage(String image) {
+        try {
+            s3Uploader.deleteImage(image);
+        } catch (IOException e) {
+            throw new CommonException(ErrorCode.FAILED_DELETE_IMAGE);
+        }
+    }
+
+}

리뷰: _:hammer_and_wrench: Refactor suggestion_

**파일 유효성 검사가 필요합니다**

`file` 파라미터에 대한 null 체크와 파일 크기, 타입 등의 유효성 검사가 누락되어 있습니다. 잘못된 입력으로 인한 예외를 방지하기 위해 검증 로직이 필요합니다.

다음과 같은 검증 로직 추가를 제안합니다:

```diff
     public String UploadImage(String image, MultipartFile file) {
+        if (file == null || file.isEmpty()) {
+            throw new CommonException(ErrorCode.INVALID_FILE);
+        }
+        validateFileSize(file);
+        validateFileType(file);
         try {
```

추가로 다음과 같은 검증 메서드 구현이 필요합니다:

```java
private void validateFileSize(MultipartFile file) {
    long maxSize = 5 * 1024 * 1024; // 5MB
    if (file.getSize() > maxSize) {
        throw new CommonException(ErrorCode.FILE_TOO_LARGE);
    }
}

private void validateFileType(MultipartFile file) {
    String contentType = file.getContentType();
    if (contentType == null || !contentType.startsWith("image/")) {
        throw new CommonException(ErrorCode.INVALID_FILE_TYPE);
    }
}
```

<!-- This is an auto-generated comment by CodeRabbit -->
@@ -0,0 +1,36 @@
+package com.doubleowner.revibe.global.common.service;
+
+import com.doubleowner.revibe.global.exception.CommonException;
+import com.doubleowner.revibe.global.exception.errorCode.ErrorCode;
+import com.doubleowner.revibe.global.util.S3Uploader;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+
+@Service
+@RequiredArgsConstructor
+public class ImageService {
+    private final S3Uploader s3Uploader;
+
+    public String UploadImage(String image, MultipartFile file) {
+        try {
+            if (image != null) {
+                s3Uploader.deleteImage(image);
+            }
+            return s3Uploader.upload(file);
+        } catch (IOException e) {
+            throw new CommonException(ErrorCode.FAILED_UPLOAD_IMAGE);
+        }
+    }
+
+    public void deleteImage(String image) {
+        try {
+            s3Uploader.deleteImage(image);
+        } catch (IOException e) {
+            throw new CommonException(ErrorCode.FAILED_DELETE_IMAGE);
+        }
+    }
+
+}

리뷰: _:hammer_and_wrench: Refactor suggestion_

**메서드 이름이 Java 네이밍 컨벤션을 위반합니다**

`UploadImage` 메서드 이름이 카멜 케이스 규칙을 따르지 않습니다. Java 메서드 이름은 소문자로 시작해야 합니다.

다음과 같이 수정하는 것을 제안합니다:

```diff
-    public String UploadImage(String image, MultipartFile file) {
+    public String uploadImage(String image, MultipartFile file) {
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    public String uploadImage(String image, MultipartFile file) {
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
@@ -0,0 +1,36 @@
+package com.doubleowner.revibe.global.common.service;
+
+import com.doubleowner.revibe.global.exception.CommonException;
+import com.doubleowner.revibe.global.exception.errorCode.ErrorCode;
+import com.doubleowner.revibe.global.util.S3Uploader;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+
+@Service
+@RequiredArgsConstructor
+public class ImageService {
+    private final S3Uploader s3Uploader;
+
+    public String UploadImage(String image, MultipartFile file) {
+        try {
+            if (image != null) {
+                s3Uploader.deleteImage(image);
+            }
+            return s3Uploader.upload(file);
+        } catch (IOException e) {
+            throw new CommonException(ErrorCode.FAILED_UPLOAD_IMAGE);
+        }
+    }
+
+    public void deleteImage(String image) {
+        try {
+            s3Uploader.deleteImage(image);
+        } catch (IOException e) {
+            throw new CommonException(ErrorCode.FAILED_DELETE_IMAGE);
+        }
+    }
+
+}

리뷰: _:hammer_and_wrench: Refactor suggestion_

**이미지 경로 유효성 검사가 필요합니다**

`image` 파라미터에 대한 null 체크와 경로 유효성 검사가 누락되어 있습니다. 잘못된 이미지 경로로 인한 불필요한 S3 호출을 방지해야 합니다.

다음과 같이 수정하는 것을 제안합니다:

```diff
     public void deleteImage(String image) {
+        if (image == null || image.trim().isEmpty()) {
+            return;
+        }
         try {
             s3Uploader.deleteImage(image);
         } catch (IOException e) {
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    public void deleteImage(String image) {
        if (image == null || image.trim().isEmpty()) {
            return;
        }
        try {
            s3Uploader.deleteImage(image);
        } catch (IOException e) {
            throw new CommonException(ErrorCode.FAILED_DELETE_IMAGE);
        }
    }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
@@ -47,18 +47,11 @@ public class Review extends BaseTimeEntity {
     private User user;
 
 
-    public void update(UpdateReviewRequestDto updateReviewRequestDto) {
+    public void update(UpdateReviewRequestDto updateReviewRequestDto, String image) {
         this.starRate = updateReviewRequestDto.getStarRate();
         this.title = updateReviewRequestDto.getTitle();
         this.content = updateReviewRequestDto.getContent();
+        this.reviewImage = image;
 
     }
-
-    public void update(String reviewImage) {
-        this.reviewImage = reviewImage;
-    }
-
-    public void deleteImage() {
-        this.reviewImage = null;
-    }
 }

리뷰: _:hammer_and_wrench: Refactor suggestion_

**이미지 업데이트 로직 통합에 대한 피드백**

이미지 업데이트 로직을 하나의 메서드로 통합한 것은 좋은 접근입니다. 하지만 몇 가지 개선사항을 제안드립니다:

1. 입력값 검증이 누락되어 있습니다.
2. 이미지 처리 로직이 도메인 모델에 직접 노출되어 있습니다.

다음과 같이 개선해보시는 것은 어떨까요:

```diff
 public void update(UpdateReviewRequestDto updateReviewRequestDto, String image) {
+    validateUpdateRequest(updateReviewRequestDto);
     this.starRate = updateReviewRequestDto.getStarRate();
     this.title = updateReviewRequestDto.getTitle();
     this.content = updateReviewRequestDto.getContent();
     this.reviewImage = image;
 }

+private void validateUpdateRequest(UpdateReviewRequestDto dto) {
+    if (dto.getStarRate() == null || dto.getStarRate() < 1 || dto.getStarRate() > 5) {
+        throw new IllegalArgumentException("별점은 1-5 사이의 값이어야 합니다.");
+    }
+    if (dto.getTitle() == null || dto.getTitle().trim().isEmpty()) {
+        throw new IllegalArgumentException("제목은 필수 입력값입니다.");
+    }
+    if (dto.getContent() == null || dto.getContent().trim().isEmpty()) {
+        throw new IllegalArgumentException("내용은 필수 입력값입니다.");
+    }
+}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    public void update(UpdateReviewRequestDto updateReviewRequestDto, String image) {
        validateUpdateRequest(updateReviewRequestDto);
        this.starRate = updateReviewRequestDto.getStarRate();
        this.title = updateReviewRequestDto.getTitle();
        this.content = updateReviewRequestDto.getContent();
        this.reviewImage = image;
    }

    private void validateUpdateRequest(UpdateReviewRequestDto dto) {
        if (dto.getStarRate() == null || dto.getStarRate() < 1 || dto.getStarRate() > 5) {
            throw new IllegalArgumentException("별점은 1-5 사이의 값이어야 합니다.");
        }
        if (dto.getTitle() == null || dto.getTitle().trim().isEmpty()) {
            throw new IllegalArgumentException("제목은 필수 입력값입니다.");
        }
        if (dto.getContent() == null || dto.getContent().trim().isEmpty()) {
            throw new IllegalArgumentException("내용은 필수 입력값입니다.");
        }
    }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
@@ -1,6 +1,9 @@
 package bridge;
 
+import bridge.domain.constant.MoveCommand;
 import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 /**
  * 다리의 길이를 입력 받아서 다리를 생성해주는 역할을 한다.
@@ -18,6 +21,10 @@ public BridgeMaker(BridgeNumberGenerator bridgeNumberGenerator) {
      * @return 입력받은 길이에 해당하는 다리 모양. 위 칸이면 "U", 아래 칸이면 "D"로 표현해야 한다.
      */
     public List<String> makeBridge(int size) {
-        return null;
+        return IntStream
+                .generate(bridgeNumberGenerator::generate)
+                .limit(size)
+                .mapToObj(MoveCommand::convertCommand)
+                .collect(Collectors.toList());
     }
 }

리뷰: 이 부분 객체지향적으로 설계를 잘하셨네요!! 가독성 아주 굿입니다
@@ -0,0 +1,95 @@
+package bridge.controller;
+
+import bridge.domain.model.Bridge;
+import bridge.domain.model.BridgeGame;
+import bridge.domain.constant.MoveCommand;
+import bridge.domain.constant.RetryCommand;
+import bridge.domain.constant.Status;
+import bridge.view.InputView;
+import bridge.view.OutputView;
+
+public class BridgeController {
+
+    private final InputView inputView;
+    private final OutputView outputView;
+    private final BridgeGame bridgeGame;
+
+    public BridgeController() {
+        this.inputView = new InputView();
+        this.outputView = new OutputView();
+        this.bridgeGame = new BridgeGame();
+    }
+
+    public void crossBridge() {
+        outputView.printStartMessage();
+        Bridge bridge = createBridge();
+        startCrossingTheBridge(bridge);
+    }
+
+    private void startCrossingTheBridge(Bridge bridge) {
+        int count = 1;
+        Status status;
+        while (true) {
+            status = startGame(bridge);
+            if (status == Status.FAIL) {
+                if (restartGame() == RetryCommand.RETRY) {
+                    count++;
+                    continue;
+                }
+            }
+            break;
+        }
+        outputView.printResult(count, status, bridgeGame);
+    }
+
+    private RetryCommand restartGame() {
+        outputView.printRestart();
+        while (true) {
+            try {
+                String command = inputView.readGameCommand();
+                return RetryCommand.from(command);
+            } catch (IllegalArgumentException e) {
+                outputView.printError(e);
+            }
+        }
+    }
+
+    private Status startGame(Bridge bridge) {
+        Status status = Status.SUCCESS;
+        for (int idx = 0; idx < bridge.getSize(); idx++) {
+            MoveCommand moveCommand = getMoveCommand();
+            status = bridgeGame.move(idx, moveCommand, bridge);
+            outputView.printMap(bridgeGame);
+            if (bridgeGame.retry(status)) {
+                break;
+            }
+        }
+        return status;
+    }
+
+    private Bridge createBridge() {
+        outputView.printInputSize();
+        while (true) {
+            try {
+                int bridgeSize = inputView.readBridgeSize();
+                Bridge bridge = Bridge.createBridge(bridgeSize);
+                bridge.setBridge(bridgeSize);
+                return bridge;
+            } catch (IllegalArgumentException e) {
+                outputView.printError(e);
+            }
+        }
+    }
+
+    private MoveCommand getMoveCommand() {
+        outputView.printInputMoveCommand();
+        while (true) {
+            try {
+                String command = inputView.readMoving();
+                return MoveCommand.from(command);
+            } catch (IllegalArgumentException e) {
+                outputView.printError(e);
+            }
+        }
+    }
+}

리뷰: depth가 3이네요 ㅜㅜ 함수를 분리하셔야할거 같습니다
@@ -0,0 +1,41 @@
+package bridge.domain.model;
+
+import bridge.domain.constant.MoveCommand;
+import bridge.domain.constant.Status;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class GameDashboard {
+
+    private final List<Status> upResult = new ArrayList<>();
+    private final List<Status> downResult = new ArrayList<>();
+
+    public void updateResult(Status status, MoveCommand moveCommand) {
+        if (status == Status.SUCCESS) {
+            checkResult(status, moveCommand);
+        }
+        if (status == Status.FAIL) {
+            checkResult(status, moveCommand);
+        }
+    }
+
+    private void checkResult(Status status, MoveCommand moveCommand) {
+        if (moveCommand == MoveCommand.UP) {
+            upResult.add(status);
+            downResult.add(Status.SPACE);
+        }
+        if (moveCommand == MoveCommand.DOWN) {
+            upResult.add(Status.SPACE);
+            downResult.add(status);
+        }
+    }
+
+    public List<Status> getUpResult() {
+        return Collections.unmodifiableList(upResult);
+    }
+
+    public List<Status> getDownResult() {
+        return Collections.unmodifiableList(downResult);
+    }
+}

리뷰: 이 부분 좋네요! 저도 Map 을 외부로 뺄지 말지 고민하다가 안뺐는데 뺀 후에 Player가 가지고 있는 기능들을 옮겨주는게 더 좋았을거 같습니다.
@@ -0,0 +1,95 @@
+package bridge.controller;
+
+import bridge.domain.model.Bridge;
+import bridge.domain.model.BridgeGame;
+import bridge.domain.constant.MoveCommand;
+import bridge.domain.constant.RetryCommand;
+import bridge.domain.constant.Status;
+import bridge.view.InputView;
+import bridge.view.OutputView;
+
+public class BridgeController {
+
+    private final InputView inputView;
+    private final OutputView outputView;
+    private final BridgeGame bridgeGame;
+
+    public BridgeController() {
+        this.inputView = new InputView();
+        this.outputView = new OutputView();
+        this.bridgeGame = new BridgeGame();
+    }
+
+    public void crossBridge() {
+        outputView.printStartMessage();
+        Bridge bridge = createBridge();
+        startCrossingTheBridge(bridge);
+    }
+
+    private void startCrossingTheBridge(Bridge bridge) {
+        int count = 1;
+        Status status;
+        while (true) {
+            status = startGame(bridge);
+            if (status == Status.FAIL) {
+                if (restartGame() == RetryCommand.RETRY) {
+                    count++;
+                    continue;
+                }
+            }
+            break;
+        }
+        outputView.printResult(count, status, bridgeGame);
+    }
+
+    private RetryCommand restartGame() {
+        outputView.printRestart();
+        while (true) {
+            try {
+                String command = inputView.readGameCommand();
+                return RetryCommand.from(command);
+            } catch (IllegalArgumentException e) {
+                outputView.printError(e);
+            }
+        }
+    }
+
+    private Status startGame(Bridge bridge) {
+        Status status = Status.SUCCESS;
+        for (int idx = 0; idx < bridge.getSize(); idx++) {
+            MoveCommand moveCommand = getMoveCommand();
+            status = bridgeGame.move(idx, moveCommand, bridge);
+            outputView.printMap(bridgeGame);
+            if (bridgeGame.retry(status)) {
+                break;
+            }
+        }
+        return status;
+    }
+
+    private Bridge createBridge() {
+        outputView.printInputSize();
+        while (true) {
+            try {
+                int bridgeSize = inputView.readBridgeSize();
+                Bridge bridge = Bridge.createBridge(bridgeSize);
+                bridge.setBridge(bridgeSize);
+                return bridge;
+            } catch (IllegalArgumentException e) {
+                outputView.printError(e);
+            }
+        }
+    }
+
+    private MoveCommand getMoveCommand() {
+        outputView.printInputMoveCommand();
+        while (true) {
+            try {
+                String command = inputView.readMoving();
+                return MoveCommand.from(command);
+            } catch (IllegalArgumentException e) {
+                outputView.printError(e);
+            }
+        }
+    }
+}

리뷰: 이 부분을 BridgeGame에서 진행하는 방법은 없었을까요???
@@ -0,0 +1,64 @@
+package bridge.view;
+
+import static bridge.view.ViewMessage.*;
+
+import bridge.domain.model.BridgeGame;
+import bridge.domain.model.GameDashboard;
+import bridge.domain.constant.Status;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 사용자에게 게임 진행 상황과 결과를 출력하는 역할을 한다.
+ */
+public class OutputView {
+
+    public void printError(IllegalArgumentException e) {
+        System.out.println(e.getMessage());
+    }
+
+    public void printStartMessage() {
+        System.out.println(START_MESSAGE.getMessage());
+    }
+
+    public void printInputSize() {
+        System.out.println(INPUT_BRIDGE_SIZE_MESSAGE.getMessage());
+    }
+
+    public void printInputMoveCommand() {
+        System.out.println(INPUT_MOVE_COMMAND_MESSAGE.getMessage());
+    }
+
+    public void printRestart() {
+        System.out.println(INPUT_RETRY_COMMAND_MESSAGE.getMessage());
+    }
+
+    /**
+     * 현재까지 이동한 다리의 상태를 정해진 형식에 맞춰 출력한다.
+     * <p>
+     * 출력을 위해 필요한 메서드의 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다.
+     */
+    public void printMap(BridgeGame bridgeGame) {
+        GameDashboard dashboard = bridgeGame.getGameDashboard();
+        System.out.printf(RESULT_FORMAT.getMessage(), convertToString(dashboard.getUpResult()));
+        System.out.printf(RESULT_FORMAT.getMessage(), convertToString(dashboard.getDownResult()));
+    }
+
+    /**
+     * 게임의 최종 결과를 정해진 형식에 맞춰 출력한다.
+     * <p>
+     * 출력을 위해 필요한 메서드의 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다.
+     */
+    public void printResult(int count, Status status, BridgeGame bridgeGame) {
+        System.out.println(RESULT_MESSAGE.getMessage());
+        printMap(bridgeGame);
+        System.out.printf(SUCCESS_OR_FAIL.getMessage(), status.getMessage());
+        System.out.printf(TOTAL_ATTEMPTS_COUNT.getMessage(), count);
+    }
+
+    private String convertToString(List<Status> result) {
+        List<String> convertResult = result.stream().map(Status::getStatus)
+                .collect(Collectors.toList());
+        return String.join(DELIMITER.getMessage(), convertResult);
+    }
+}

리뷰: 매개값을 BridgeGame이 아닌 GameDashBoard로 받았으면 어땠을까요???
@@ -0,0 +1,23 @@
+package bridge.view;
+
+public enum ViewMessage {
+    START_MESSAGE("다리 건너기 게임을 시작합니다."),
+    INPUT_BRIDGE_SIZE_MESSAGE("다리의 길이를 입력해주세요."),
+    INPUT_MOVE_COMMAND_MESSAGE("이동할 칸을 선택해주세요. (위: U, 아래: D)"),
+    INPUT_RETRY_COMMAND_MESSAGE("게임을 다시 시도할지 여부를 입력해주세요. (재시도: R, 종료: Q)"),
+    RESULT_MESSAGE("최종 게임 결과"),
+    SUCCESS_OR_FAIL("게임 성공 여부: %s%n"),
+    TOTAL_ATTEMPTS_COUNT("총 시도한 횟수: %d%n"),
+    DELIMITER(" | "),
+    RESULT_FORMAT("[ %s ]%n");
+
+    private final String message;
+
+    ViewMessage(String message) {
+        this.message = message;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+}

리뷰: StringJoiner 를 쓰면 해당 부분을 한번에 해결할 수 있더라구요!
@@ -0,0 +1,21 @@
+import { EOL as LINE_SEPARATOR } from 'os';
+import { TIME, UNITS } from './system.js';
+
+export const OUTPUT_MESSAGES = Object.freeze({
+  intro: `안녕하세요! 우테코 식당 ${TIME.month}${UNITS.month} 이벤트 플래너입니다.`,
+  outro: `일에 우테코 식당에서 받을 이벤트 혜택 미리 보기!`,
+});
+
+export const INPUT_MESSAGES = Object.freeze({
+  date: `${TIME.month}${UNITS.month} 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!)${LINE_SEPARATOR}`,
+  order: `주문하실 메뉴를 메뉴와 개수를 알려 주세요. (e.g. 해산물파스타-2,레드와인-1,초코케이크-1)${LINE_SEPARATOR}`,
+});
+
+export const ERROR_MESSAGES = Object.freeze({
+  prefix: '[ERROR]',
+  invalidDate: '유효하지 않은 날짜입니다. 다시 입력해 주세요.',
+  invalidMenu: '유효하지 않은 주문입니다. 다시 입력해 주세요.',
+  exceedQuantity:
+    '메뉴는 한 번에 최대 20개까지만 주문할 수 있습니다. 다시 입력해 주세요.',
+  onlyBeverage: '음료만 주문할 수 없습니다. 다시 입력해 주세요.',
+});

리뷰: 이벤트를 진행하는 달이 바뀌는 것을 고려해 `TIME.month` 상수를 따로 생성한 점이 인상 깊어요!
`12월 이벤트 참여 고객의 5%가 내년 1월 새해 이벤트에 재참여하는 것`
이메일의 이러한 문장이 저도 눈에 밟히긴 했는데 그냥 기능 구현에 집중하느라 잊어버렸거든요..
정말 섬세하게 고민하시면서 구현하신 것 같습니다! 감탄만 나오네요 😮
@@ -0,0 +1,28 @@
+export const TIME = Object.freeze({
+  year: '2023',
+  month: '12',
+  dateFormat: `2023-12-`,
+});
+
+export const PROMOTION_TITLES = Object.freeze({
+  1: '새해',
+  12: '크리스마스',
+});
+
+export const SYMBOLS = Object.freeze({
+  comma: ',',
+  dash: '-',
+  colon: ':',
+  blank: '',
+});
+
+export const UNITS = Object.freeze({
+  quantity: '개',
+  won: '원',
+  month: '월',
+  year: '년',
+});
+
+export const NONE = '없음';
+
+export const FILE_PATH = 'src/data/dec-promotion.json';

리뷰: 확장을 고려하셨군요! 배우고 갑니다 👍
실제 회사였다면 이벤트 기획자가 너무 좋아할 것 같아요!
@@ -0,0 +1,28 @@
+export const TIME = Object.freeze({
+  year: '2023',
+  month: '12',
+  dateFormat: `2023-12-`,
+});
+
+export const PROMOTION_TITLES = Object.freeze({
+  1: '새해',
+  12: '크리스마스',
+});
+
+export const SYMBOLS = Object.freeze({
+  comma: ',',
+  dash: '-',
+  colon: ':',
+  blank: '',
+});
+
+export const UNITS = Object.freeze({
+  quantity: '개',
+  won: '원',
+  month: '월',
+  year: '년',
+});
+
+export const NONE = '없음';
+
+export const FILE_PATH = 'src/data/dec-promotion.json';

리뷰: 그냥 감탄만 나오네요... 할말이 없네요... 최종 코테 이런분이 가시는구나...