Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
cd60ff6
[FIX/#413] Pageable 객체에 ParameterObject 어노테이션 추가
eeeeeaaan Aug 14, 2026
95a85eb
[FEAT/#406] 주변 장소 조회 API에 카테고리 및 학생회 필터링 추가
BAEK0111 Aug 20, 2026
735d823
[FEAT/#406] getUsablePartnership 및 getStores 카테고리 필터링 추가
BAEK0111 Aug 20, 2026
fc6d3b0
[FEAT/#406] adminId 필터링을 레포지토리 쿼리 레벨로 이동
BAEK0111 Aug 20, 2026
3facadb
[REFACTOR/#406] getStores 내 adminName 조회 방식 개선
BAEK0111 Aug 22, 2026
e16eeb0
Merge pull request #430 from ASSU-dev/feat/#406-partnership-category
BAEK0111 Aug 22, 2026
ae2660c
[FEAT/#432] 주변 가게 조회 응답에 storeCategory 추가
BAEK0111 Aug 29, 2026
7c8c837
Merge pull request #433 from ASSU-dev/feat/#432-map-api-response-add-…
BAEK0111 Aug 31, 2026
fb0a409
[FEAT/#434] 홈 추천 제휴 조회 API 구현
BAEK0111 Aug 31, 2026
470c497
Merge pull request #435 from ASSU-dev/feat/#434-home-recommend-partne…
BAEK0111 Aug 31, 2026
8e94d1a
[FIX/#436] 리다이렉트 권한 삭제
eeeeeaaan Aug 31, 2026
9ca9022
Merge pull request #437 from ASSU-dev/fix/#436-qr-redirect-authorize
eeeeeaaan Aug 31, 2026
51ac687
Merge branch 'develop' into fix/#413
eeeeeaaan Aug 31, 2026
3e90d45
Merge pull request #414 from ASSU-dev/fix/#413
eeeeeaaan Aug 31, 2026
48cb6ad
[FEAT/#434] 홈 캐러셀 추천 제휴 및 이용 가능 제휴 조회 API 개선
BAEK0111 Aug 31, 2026
67f9686
[FEAT/#434] 가게 상세 조회 API 구현
BAEK0111 Aug 31, 2026
1463b7b
Merge pull request #439 from ASSU-dev/feat/#434-home-recommend-partne…
BAEK0111 Aug 31, 2026
94c3a6b
[FEAT/#434] 홈 캐러셀 추천 제휴 응답에 belonging 필드 추가
BAEK0111 Aug 31, 2026
87337dc
Merge pull request #440 from ASSU-dev/feat/#434-home-recommend-partne…
BAEK0111 Aug 31, 2026
e7c2924
[FEAT/#441] 학생 홈 화면 추천 조회 API, 백오피스 큐레이션 제목 수정 및 전체 설정 API, 백오피스 서브 …
kimyw1018 Sep 1, 2026
ff65f8c
[FEAT/#441] 컴파일 에러 수정
kimyw1018 Sep 1, 2026
4154e9d
[FEAT/#441] import 수정
kimyw1018 Sep 1, 2026
2570b01
[FIX/#441] 필드에 example 누락 보완
kimyw1018 Sep 1, 2026
dfb59fb
[FIX/#441] 집합 순서 검증 추가
kimyw1018 Sep 1, 2026
aab4873
[FIX/#441] 들여쓰기 통일
kimyw1018 Sep 1, 2026
c6c62c0
Merge pull request #442 from ASSU-dev/feat/#441-home-recommend
kimyw1018 Sep 1, 2026
103eb4a
[FEAT/#444] 학생 홈 제휴 API의 partnerProfileUrl presigned URL 누락 수정
BAEK0111 Sep 1, 2026
820c463
Merge pull request #445 from ASSU-dev/feat/#444-presigned-url
BAEK0111 Sep 1, 2026
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,14 @@ src/main/resources/application-prod.yml
src/main/resources/firebase/

### Claude Code ###
CLAUDE.md

### macOS ###
.DS_Store
**/.DS_Store

### Node ###
node_modules/


/loadtest
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.assu.server.domain.backoffice.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.assu.server.domain.backoffice.annotation.BackofficeAudited;
import com.assu.server.domain.backoffice.dto.BackofficeCurationTitleUpdateRequestDTO;
import com.assu.server.domain.backoffice.dto.BackofficeHomeAutoRecommendResponseDTO;
import com.assu.server.domain.backoffice.dto.BackofficeHomeCurationResponseDTO;
import com.assu.server.domain.backoffice.dto.BackofficeHomeCurationUpdateRequestDTO;
import com.assu.server.domain.backoffice.service.BackofficeHomeCurationService;
import com.assu.server.global.apiPayload.BaseResponse;
import com.assu.server.global.apiPayload.code.status.SuccessStatus;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@Tag(name = "Backoffice Home Curation", description = "백오피스 학생 홈 큐레이션 관리 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/backoffice/home/curation")
@PreAuthorize("hasRole('BACKOFFICE')")
public class BackofficeHomeCurationController {

private final BackofficeHomeCurationService backofficeHomeCurationService;

@BackofficeAudited(action = "HOME_CURATION_READ")
@Operation(
summary = "홈 화면 큐레이션 현재 설정 조회 API (백오피스용)",
description = "현재 백오피스에 등록되어 학생 홈에 노출 중인 큐레이션 설정(제목, 상단 추천 업체, 2개 그룹 x 각 2개 업체)을 가져옵니다.\n\n" +
"**Response:**\n" +
" - 성공 시 200(OK)과 `BackofficeHomeCurationResponseDTO` 반환"
)
@GetMapping
public BaseResponse<BackofficeHomeCurationResponseDTO> getHomeCuration() {
return BaseResponse.onSuccess(SuccessStatus._OK, backofficeHomeCurationService.getHomeCuration());
}

@BackofficeAudited(action = "HOME_CURATION_UPDATE")
@Operation(
summary = "전체 큐레이션 및 추천 업체 직접 저장 API (백오피스용)",
description = "상단 추천 업체 및 2개 그룹(각 그룹당 2개 매장)을 직접 지정하여 저장합니다.\n\n" +
"**Request Body:**\n" +
" - `title`: 큐레이션 섹션 제목\n" +
" - `featuredStoreId`: 상단 추천 업체 ID\n" +
" - `featuredDiscountContent`: 상단 추천 할인 문구 (선택)\n" +
" - `curationLists`: 2개 그룹(각 그룹당 2개 상점) 정보\n\n" +
"**Response:**\n" +
" - 성공 시 200(OK)과 수정된 `BackofficeHomeCurationResponseDTO` 반환"
)
@PutMapping
public BaseResponse<BackofficeHomeCurationResponseDTO> updateHomeCuration(
@RequestBody @Valid BackofficeHomeCurationUpdateRequestDTO request
) {
return BaseResponse.onSuccess(SuccessStatus._OK, backofficeHomeCurationService.updateHomeCuration(request));
}

@BackofficeAudited(action = "HOME_CURATION_TITLE_UPDATE")
@Operation(
summary = "큐레이션 섹션 제목 단독 수정 API (백오피스용)",
description = "추천 업체 목록은 그대로 두고 제목 텍스트만 변경할 때 사용합니다.\n\n" +
"**Request Body:**\n" +
" - `title`: 수정할 큐레이션 섹션 제목 (예: {name}님을 위한 특별 혜택)\n\n" +
"**Response:**\n" +
" - 성공 시 200(OK)과 수정된 `BackofficeHomeCurationResponseDTO` 반환"
)
@PatchMapping("/title")
public BaseResponse<BackofficeHomeCurationResponseDTO> updateCurationTitle(
@RequestBody @Valid BackofficeCurationTitleUpdateRequestDTO request
) {
return BaseResponse.onSuccess(SuccessStatus._OK, backofficeHomeCurationService.updateCurationTitle(request));
}

@BackofficeAudited(action = "HOME_CURATION_AUTO_RECOMMEND_READ")
@Operation(
summary = "추천 업체 자동 추출 API (백오피스 서브 기능 버튼용)",
description = "평점 및 활성 매장 기반으로 상단 추천 1곳 + 2개 그룹(각 2곳)을 자동 선별합니다. 백오피스 UI의 [자동 추천 불러오기] 버튼 클릭 시 호출하여 폼에 자동 입력해 주는 용도입니다.\n\n" +
"**Response:**\n" +
" - 성공 시 200(OK)과 `BackofficeHomeAutoRecommendResponseDTO` 반환"
)
@GetMapping("/auto-recommend")
public BaseResponse<BackofficeHomeAutoRecommendResponseDTO> getAutoRecommendedCuration() {
return BaseResponse.onSuccess(SuccessStatus._OK, backofficeHomeCurationService.getAutoRecommendedCuration());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
Expand Down Expand Up @@ -53,7 +54,7 @@ public BaseResponse<PageResponseDTO<BackofficeMemberSummaryDTO>> listMembers(
@RequestParam(required = false) UserRole role,
@RequestParam(required = false) ActivationStatus status,
@RequestParam(required = false) Boolean deleted,
@PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable
@ParameterObject @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable
) {
return BaseResponse.onSuccess(
SuccessStatus._OK,
Expand All @@ -73,7 +74,7 @@ public BaseResponse<PageResponseDTO<BackofficeMemberSummaryDTO>> listMembers(
)
@GetMapping("/deleted")
public BaseResponse<PageResponseDTO<BackofficeMemberSummaryDTO>> listDeletedMembers(
@PageableDefault(size = 20, sort = "deletedAt", direction = Sort.Direction.DESC) Pageable pageable
@ParameterObject @PageableDefault(size = 20, sort = "deletedAt", direction = Sort.Direction.DESC) Pageable pageable
) {
return BaseResponse.onSuccess(
SuccessStatus._OK,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.assu.server.domain.backoffice.controller;

import org.springdoc.core.annotations.ParameterObject;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
Expand Down Expand Up @@ -66,7 +67,7 @@ public class BackofficePaperController {
)
@GetMapping
public BaseResponse<Page<WritePartnershipResponseDTO>> getPapers(
@PageableDefault(size = 10, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable
@ParameterObject @PageableDefault(size = 10, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable
) {
return BaseResponse.onSuccess(SuccessStatus._OK, backofficePaperService.getPapers(pageable));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.assu.server.domain.backoffice.controller;

import org.springdoc.core.annotations.ParameterObject;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
Expand Down Expand Up @@ -60,7 +61,7 @@ public class BackofficePartnershipController {
)
@GetMapping
public BaseResponse<Page<WritePartnershipResponseDTO>> getPartnerships(
@PageableDefault(size = 10, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable
@ParameterObject @PageableDefault(size = 10, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable
) {
return BaseResponse.onSuccess(SuccessStatus._OK, backofficePartnershipService.getPartnerships(pageable));
}
Expand Down Expand Up @@ -98,7 +99,7 @@ public BaseResponse<Page<WritePartnershipResponseDTO>> getPartnerships(
@GetMapping("/admin/{adminId}")
public BaseResponse<Page<WritePartnershipResponseDTO>> getPartnershipsByAdmin(
@PathVariable @Parameter(description = "학생회 ID", required = true) Long adminId,
@PageableDefault(size = 10, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable
@ParameterObject @PageableDefault(size = 10, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable
) {
return BaseResponse.onSuccess(SuccessStatus._OK, backofficePartnershipService.getPartnershipsByAdmin(adminId, pageable));
}
Expand Down Expand Up @@ -136,7 +137,7 @@ public BaseResponse<Page<WritePartnershipResponseDTO>> getPartnershipsByAdmin(
@GetMapping("/store/{storeId}")
public BaseResponse<Page<WritePartnershipResponseDTO>> getPartnershipsByStore(
@PathVariable @Parameter(description = "가게 ID", required = true) Long storeId,
@PageableDefault(size = 10, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable
@ParameterObject @PageableDefault(size = 10, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable
) {
return BaseResponse.onSuccess(SuccessStatus._OK, backofficePartnershipService.getPartnershipsByStore(storeId, pageable));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.assu.server.domain.backoffice.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;

public record BackofficeCurationTitleUpdateRequestDTO(
@NotBlank
@Schema(description = "수정할 큐레이션 섹션 제목 ({name} 플레이스홀더 사용 가능)", example = "{name}님을 위한 제휴")
String title
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.assu.server.domain.backoffice.dto;

import io.swagger.v3.oas.annotations.media.Schema;

import java.util.List;

public record BackofficeHomeAutoRecommendResponseDTO(
@Schema(description = "추천 기본 섹션 제목", example = "{name}님을 위한 제휴")
String suggestedTitle,

@Schema(
description = "자동 추출된 상단 추천 업체",
example = "{\"storeId\": 1, \"storeName\": \"더진국 숭실대점\", \"discountContent\": \"전 메뉴 1,000원 할인 또는 음료수 증정\", \"storeCategory\": \"RESTAURANT\"}"
)
AutoFeaturedDTO featuredRecommendation,

@Schema(
description = "자동 추출된 추천 큐레이션 2개 그룹 (각 2개 업체)",
example = "[{\"groupIndex\": 1, \"groupTitle\": \"추천 제휴 1\", \"stores\": [{\"storeId\": 10, \"storeName\": \"가게 1\", \"discountContent\": \"10% 할인\", \"storeCategory\": \"RESTAURANT\", \"sortOrder\": 1}, {\"storeId\": 11, \"storeName\": \"가게 2\", \"discountContent\": \"음료 증정\", \"storeCategory\": \"CAFE\", \"sortOrder\": 2}]}, {\"groupIndex\": 2, \"groupTitle\": \"추천 제휴 2\", \"stores\": [{\"storeId\": 20, \"storeName\": \"가게 3\", \"discountContent\": \"500원 할인\", \"storeCategory\": \"PUB\", \"sortOrder\": 1}, {\"storeId\": 21, \"storeName\": \"가게 4\", \"discountContent\": \"사이드 무료\", \"storeCategory\": \"RESTAURANT\", \"sortOrder\": 2}]}]"
)
List<AutoGroupDTO> curationLists
) {

public record AutoFeaturedDTO(
@Schema(description = "추천 업체 ID", example = "1")
Long storeId,

@Schema(description = "추천 업체 이름", example = "더진국 숭실대점")
String storeName,

@Schema(description = "추천 할인 내용", example = "전 메뉴 1,000원 할인 또는 음료수 증정")
String discountContent,

@Schema(description = "업체 카테고리", example = "RESTAURANT")
String storeCategory
) {
public static AutoFeaturedDTO of(
Long storeId,
String storeName,
String discountContent,
String storeCategory
) {
return new AutoFeaturedDTO(storeId, storeName, discountContent, storeCategory);
}
}

public record AutoGroupDTO(
@Schema(description = "그룹 인덱스 (1 또는 2)", example = "1")
Integer groupIndex,

@Schema(description = "그룹 소제목", example = "추천 제휴 1")
String groupTitle,

@Schema(
description = "추천 업체 2개 목록",
example = "[{\"storeId\": 10, \"storeName\": \"가게 1\", \"discountContent\": \"10% 할인\", \"storeCategory\": \"RESTAURANT\", \"sortOrder\": 1}, {\"storeId\": 11, \"storeName\": \"가게 2\", \"discountContent\": \"음료 증정\", \"storeCategory\": \"CAFE\", \"sortOrder\": 2}]"
)
List<AutoStoreDTO> stores
) {
public static AutoGroupDTO of(
Integer groupIndex,
String groupTitle,
List<AutoStoreDTO> stores
) {
return new AutoGroupDTO(groupIndex, groupTitle, stores);
}
}

public record AutoStoreDTO(
@Schema(description = "가게 ID", example = "2")
Long storeId,

@Schema(description = "가게 이름", example = "스타벅스 숭실대점")
String storeName,

@Schema(description = "자동 추출된 할인 내용", example = "아메리카노 사이즈업")
String discountContent,

@Schema(description = "가게 카테고리", example = "CAFE")
String storeCategory,

@Schema(description = "노출 순서", example = "1")
Integer sortOrder
) {
public static AutoStoreDTO of(
Long storeId,
String storeName,
String discountContent,
String storeCategory,
Integer sortOrder
) {
return new AutoStoreDTO(storeId, storeName, discountContent, storeCategory, sortOrder);
}
}

public static BackofficeHomeAutoRecommendResponseDTO of(
String suggestedTitle,
AutoFeaturedDTO featuredRecommendation,
List<AutoGroupDTO> curationLists
) {
return new BackofficeHomeAutoRecommendResponseDTO(suggestedTitle, featuredRecommendation, curationLists);
}
}
Loading
Loading