diff --git a/.github/workflows/version-check.yml b/.github/workflows/version-check.yml index 7a60c385b..470c7463c 100644 --- a/.github/workflows/version-check.yml +++ b/.github/workflows/version-check.yml @@ -1,11 +1,13 @@ -name: Version Check & Release PR +name: Version Tag Sync on: push: branches: [main] + paths: + - '**/VERSION' concurrency: - group: version-check + group: version-tag-sync cancel-in-progress: true permissions: @@ -13,7 +15,57 @@ permissions: pull-requests: write jobs: - check-and-create-pr: + create-tag: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - module: product + version_file: bottlenote-product-api/VERSION + - module: admin + version_file: bottlenote-admin-api/VERSION + - module: batch + version_file: bottlenote-batch/VERSION + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Check VERSION file changed + id: version-change + run: | + if git diff --name-only HEAD~1 HEAD | grep -q "${{ matrix.version_file }}"; then + VERSION=$(cat "${{ matrix.version_file }}" | tr -d '[:space:]') + echo "changed=true" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "${{ matrix.module }} VERSION changed: $VERSION" + else + echo "changed=false" >> "$GITHUB_OUTPUT" + echo "${{ matrix.module }} VERSION not changed, skipping" + fi + + - name: Create and push tag + if: steps.version-change.outputs.changed == 'true' + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + VERSION="${{ steps.version-change.outputs.version }}" + MODULE="${{ matrix.module }}" + TAG_NAME="${MODULE}/v${VERSION}" + + if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then + echo "Tag $TAG_NAME already exists, skipping" + else + git tag -a "$TAG_NAME" -m "Release ${MODULE} v${VERSION}" + git push origin "$TAG_NAME" + echo "Created and pushed tag: $TAG_NAME" + fi + + create-release-pr: runs-on: ubuntu-latest strategy: fail-fast: false @@ -82,7 +134,7 @@ jobs: id: existing-version run: | PR_TITLE="${{ steps.existing-pr.outputs.pr_title }}" - EXISTING_VERSION=$(echo "$PR_TITLE" | grep -oP 'v\K[0-9]+\.[0-9]+\.[0-9]+' || echo "0.0.0") + EXISTING_VERSION=$(echo "$PR_TITLE" | grep -oP 'v\K[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?' || echo "0.0.0") echo "version=$EXISTING_VERSION" >> "$GITHUB_OUTPUT" echo "Existing PR version: $EXISTING_VERSION" @@ -94,7 +146,7 @@ jobs: OLD_VERSION="${{ steps.existing-version.outputs.version }}" version_to_int() { - echo "$1" | awk -F. '{ printf("%d%03d%03d", $1, $2, $3) }' + echo "$1" | awk -F'[-.]' '{ printf("%d%03d%03d%03d", $1, $2, $3, (NF>=4 ? $4 : 0)) }' } NEW_INT=$(version_to_int "$NEW_VERSION") @@ -142,7 +194,7 @@ jobs: - **Auto-generated release PR** --- - This PR was automatically created by version-check workflow. + This PR was automatically created by version-tag-sync workflow. EOF ) @@ -173,7 +225,7 @@ jobs: - **Auto-generated release PR** --- - This PR was automatically created by version-check workflow. + This PR was automatically created by version-tag-sync workflow. EOF ) diff --git a/bottlenote-admin-api/VERSION b/bottlenote-admin-api/VERSION index 3cd22829f..b0f3d96f8 100644 --- a/bottlenote-admin-api/VERSION +++ b/bottlenote-admin-api/VERSION @@ -1 +1 @@ -1.0.7-3 +1.0.8 diff --git a/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/request/AdminBannerCreateRequest.java b/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/request/AdminBannerCreateRequest.java index b4a78a64c..63ff407f2 100644 --- a/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/request/AdminBannerCreateRequest.java +++ b/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/request/AdminBannerCreateRequest.java @@ -11,18 +11,18 @@ import lombok.Builder; public record AdminBannerCreateRequest( - @NotBlank(message = "배너명은 필수입니다.") String name, - @Pattern(regexp = "^#[0-9a-fA-F]{6}$", message = "HEX 색상 형식이 올바르지 않습니다.") String nameFontColor, - @Size(max = 50, message = "배너 설명은 최대 50자까지 가능합니다.") String descriptionA, - @Size(max = 50, message = "배너 설명은 최대 50자까지 가능합니다.") String descriptionB, - @Pattern(regexp = "^#[0-9a-fA-F]{6}$", message = "HEX 색상 형식이 올바르지 않습니다.") + @NotBlank(message = "BANNER_NAME_REQUIRED") String name, + @Pattern(regexp = "^#[0-9a-fA-F]{6}$", message = "INVALID_HEX_COLOR_FORMAT") String nameFontColor, + @Size(max = 50, message = "BANNER_DESCRIPTION_MAX_SIZE") String descriptionA, + @Size(max = 50, message = "BANNER_DESCRIPTION_MAX_SIZE") String descriptionB, + @Pattern(regexp = "^#[0-9a-fA-F]{6}$", message = "INVALID_HEX_COLOR_FORMAT") String descriptionFontColor, - @NotBlank(message = "이미지 URL은 필수입니다.") String imageUrl, + @NotBlank(message = "BANNER_IMAGE_URL_REQUIRED") String imageUrl, TextPosition textPosition, Boolean isExternalUrl, String targetUrl, - @NotNull(message = "배너 유형은 필수입니다.") BannerType bannerType, - @Min(value = 0, message = "정렬 순서는 0 이상이어야 합니다.") Integer sortOrder, + @NotNull(message = "BANNER_TYPE_REQUIRED") BannerType bannerType, + @Min(value = 0, message = "BANNER_SORT_ORDER_MINIMUM") Integer sortOrder, LocalDateTime startDate, LocalDateTime endDate) { diff --git a/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/request/AdminBannerSortOrderRequest.java b/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/request/AdminBannerSortOrderRequest.java index c6bbf3bdd..6a43f8b22 100644 --- a/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/request/AdminBannerSortOrderRequest.java +++ b/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/request/AdminBannerSortOrderRequest.java @@ -4,5 +4,5 @@ import jakarta.validation.constraints.NotNull; public record AdminBannerSortOrderRequest( - @NotNull(message = "정렬 순서는 필수입니다.") @Min(value = 0, message = "정렬 순서는 0 이상이어야 합니다.") + @NotNull(message = "BANNER_SORT_ORDER_REQUIRED") @Min(value = 0, message = "BANNER_SORT_ORDER_MINIMUM") Integer sortOrder) {} diff --git a/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/request/AdminBannerStatusRequest.java b/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/request/AdminBannerStatusRequest.java index 4106ba841..cd9367f46 100644 --- a/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/request/AdminBannerStatusRequest.java +++ b/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/request/AdminBannerStatusRequest.java @@ -2,4 +2,4 @@ import jakarta.validation.constraints.NotNull; -public record AdminBannerStatusRequest(@NotNull(message = "활성화 상태는 필수입니다.") Boolean isActive) {} +public record AdminBannerStatusRequest(@NotNull(message = "BANNER_IS_ACTIVE_REQUIRED") Boolean isActive) {} diff --git a/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/request/AdminBannerUpdateRequest.java b/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/request/AdminBannerUpdateRequest.java index 73394f4ae..c374a358c 100644 --- a/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/request/AdminBannerUpdateRequest.java +++ b/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/request/AdminBannerUpdateRequest.java @@ -10,19 +10,19 @@ import java.time.LocalDateTime; public record AdminBannerUpdateRequest( - @NotBlank(message = "배너명은 필수입니다.") String name, - @Pattern(regexp = "^#[0-9a-fA-F]{6}$", message = "HEX 색상 형식이 올바르지 않습니다.") String nameFontColor, - @Size(max = 50, message = "배너 설명은 최대 50자까지 가능합니다.") String descriptionA, - @Size(max = 50, message = "배너 설명은 최대 50자까지 가능합니다.") String descriptionB, - @Pattern(regexp = "^#[0-9a-fA-F]{6}$", message = "HEX 색상 형식이 올바르지 않습니다.") + @NotBlank(message = "BANNER_NAME_REQUIRED") String name, + @Pattern(regexp = "^#[0-9a-fA-F]{6}$", message = "INVALID_HEX_COLOR_FORMAT") String nameFontColor, + @Size(max = 50, message = "BANNER_DESCRIPTION_MAX_SIZE") String descriptionA, + @Size(max = 50, message = "BANNER_DESCRIPTION_MAX_SIZE") String descriptionB, + @Pattern(regexp = "^#[0-9a-fA-F]{6}$", message = "INVALID_HEX_COLOR_FORMAT") String descriptionFontColor, - @NotBlank(message = "이미지 URL은 필수입니다.") String imageUrl, + @NotBlank(message = "BANNER_IMAGE_URL_REQUIRED") String imageUrl, TextPosition textPosition, Boolean isExternalUrl, String targetUrl, - @NotNull(message = "배너 유형은 필수입니다.") BannerType bannerType, - @NotNull(message = "정렬 순서는 필수입니다.") @Min(value = 0, message = "정렬 순서는 0 이상이어야 합니다.") + @NotNull(message = "BANNER_TYPE_REQUIRED") BannerType bannerType, + @NotNull(message = "BANNER_SORT_ORDER_REQUIRED") @Min(value = 0, message = "BANNER_SORT_ORDER_MINIMUM") Integer sortOrder, LocalDateTime startDate, LocalDateTime endDate, - @NotNull(message = "활성화 상태는 필수입니다.") Boolean isActive) {} + @NotNull(message = "BANNER_IS_ACTIVE_REQUIRED") Boolean isActive) {} diff --git a/bottlenote-mono/src/main/java/app/bottlenote/global/exception/custom/code/ValidExceptionCode.java b/bottlenote-mono/src/main/java/app/bottlenote/global/exception/custom/code/ValidExceptionCode.java index 2bf73c495..aeaf63a1b 100644 --- a/bottlenote-mono/src/main/java/app/bottlenote/global/exception/custom/code/ValidExceptionCode.java +++ b/bottlenote-mono/src/main/java/app/bottlenote/global/exception/custom/code/ValidExceptionCode.java @@ -80,7 +80,17 @@ public enum ValidExceptionCode implements ExceptionCode { // HELP HELP_TITLE_REQUIRED(HttpStatus.BAD_REQUEST, "문의글 제목은 필수입니다."), HELP_CONTENT_REQUIRED(HttpStatus.BAD_REQUEST, "문의글 내용은 필수입니다."), - REQUIRED_HELP_TYPE(HttpStatus.BAD_REQUEST, "문의 유형은 필수입니다.(WHISKEY, REVIEW, USER, ETC)"); + REQUIRED_HELP_TYPE(HttpStatus.BAD_REQUEST, "문의 유형은 필수입니다.(WHISKEY, REVIEW, USER, ETC)"), + + // BANNER + BANNER_NAME_REQUIRED(HttpStatus.BAD_REQUEST, "배너명은 필수입니다."), + INVALID_HEX_COLOR_FORMAT(HttpStatus.BAD_REQUEST, "HEX 색상 형식이 올바르지 않습니다."), + BANNER_DESCRIPTION_MAX_SIZE(HttpStatus.BAD_REQUEST, "배너 설명은 최대 50자까지 가능합니다."), + BANNER_IMAGE_URL_REQUIRED(HttpStatus.BAD_REQUEST, "이미지 URL은 필수입니다."), + BANNER_TYPE_REQUIRED(HttpStatus.BAD_REQUEST, "배너 유형은 필수입니다."), + BANNER_SORT_ORDER_REQUIRED(HttpStatus.BAD_REQUEST, "정렬 순서는 필수입니다."), + BANNER_SORT_ORDER_MINIMUM(HttpStatus.BAD_REQUEST, "정렬 순서는 0 이상이어야 합니다."), + BANNER_IS_ACTIVE_REQUIRED(HttpStatus.BAD_REQUEST, "활성화 상태는 필수입니다."); private final HttpStatus httpStatus; private String message; diff --git a/bottlenote-product-api/VERSION b/bottlenote-product-api/VERSION index af0b7ddbf..3cd22829f 100644 --- a/bottlenote-product-api/VERSION +++ b/bottlenote-product-api/VERSION @@ -1 +1 @@ -1.0.6 +1.0.7-3 diff --git a/git.environment-variables b/git.environment-variables index 9a3e14d6f..1581bb6e2 160000 --- a/git.environment-variables +++ b/git.environment-variables @@ -1 +1 @@ -Subproject commit 9a3e14d6fa2057c6ef3570016840ef249422ffa7 +Subproject commit 1581bb6e200e599be9eed5c86a4506b69bf90399 diff --git "a/http/admin/04_\353\260\260\353\204\210\352\264\200\353\246\254/\353\260\260\353\204\210.http" "b/http/admin/04_\353\260\260\353\204\210\352\264\200\353\246\254/\353\260\260\353\204\210.http" new file mode 100644 index 000000000..d720e612f --- /dev/null +++ "b/http/admin/04_\353\260\260\353\204\210\352\264\200\353\246\254/\353\260\260\353\204\210.http" @@ -0,0 +1,77 @@ +### 배너 목록 조회 +GET {{host}}/banners?cursor=0&pageSize=10 +Authorization: Bearer {{accessToken}} + +### 배너 상세 조회 +@bannerId = 1 +GET {{host}}/banners/{{bannerId}} +Authorization: Bearer {{accessToken}} + +### 배너 생성 +POST {{host}}/banners +Authorization: Bearer {{accessToken}} +Content-Type: application/json + +{ + "name": "새 배너", + "nameFontColor": "#ffffff", + "descriptionA": "배너 설명A", + "descriptionB": "배너 설명B", + "descriptionFontColor": "#ffffff", + "imageUrl": "https://example.com/banner.jpg", + "textPosition": "RT", + "isExternalUrl": false, + "targetUrl": "https://www.instagram.com/bottle_note_official", + "bannerType": "CURATION", + "sortOrder": 0, + "startDate": "2026-02-11T00:00:00", + "endDate": "2026-02-12T00:00:00" +} + +### 배너 수정 +@bannerId = 1 +PUT {{host}}/banners/{{bannerId}} +Authorization: Bearer {{accessToken}} +Content-Type: application/json + +{ + "name": "수정된 배너", + "nameFontColor": "#000000", + "descriptionA": "수정된 설명A", + "descriptionB": "수정된 설명B", + "descriptionFontColor": "#000000", + "imageUrl": "https://example.com/banner-updated.jpg", + "textPosition": "RT", + "isExternalUrl": false, + "targetUrl": "https://www.instagram.com/bottle_note_official", + "bannerType": "CURATION", + "sortOrder": 1, + "startDate": "2026-02-11T00:00:00", + "endDate": "2026-02-28T00:00:00", + "isActive": true +} + +### 배너 삭제 +@bannerId = 1 +DELETE {{host}}/banners/{{bannerId}} +Authorization: Bearer {{accessToken}} + +### 배너 활성화 상태 변경 +@bannerId = 1 +PATCH {{host}}/banners/{{bannerId}}/status +Authorization: Bearer {{accessToken}} +Content-Type: application/json + +{ + "isActive": true +} + +### 배너 정렬 순서 변경 +@bannerId = 1 +PATCH {{host}}/banners/{{bannerId}}/sort-order +Authorization: Bearer {{accessToken}} +Content-Type: application/json + +{ + "sortOrder": 0 +}