Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
c951615
fix: 탈퇴 회원 닉네임 재사용 불가 버그 수정
5solbin Apr 21, 2026
60b5bf2
fix : 닉네임 중복 수정 테스트 코드
5solbin Apr 21, 2026
33fd438
Merge pull request #133 from 5solbin/fix/#132-nickname_duplicate
5solbin Apr 21, 2026
bb69073
feat: 포인트 시스템 복원
5solbin Apr 21, 2026
859daf2
feat : 포인트 랭킹관련 기능 삭제
5solbin Apr 22, 2026
7f6f7b4
feat : 포인트 지급 방식 정책에 맞게 수정, 테스트 코드 수정
5solbin Apr 22, 2026
0f54459
feat : 회원 프로필응답에 포인트 필드 추가 및 테스트 코드 추가
5solbin Apr 22, 2026
3d1c752
feat : 멤버의 포인트 히스토리 확인 기능 추가
5solbin Apr 22, 2026
6b29bdb
feat : 시큐리티 접근 설정 변경
5solbin Apr 22, 2026
f4791bc
Merge pull request #134 from 5solbin/feat/#117-point_system
Qbooo Apr 24, 2026
7ca91f9
test: 서비스 레이어 테스트 케이스 보강
5solbin Apr 28, 2026
02a3bfd
fix : 핫이슈, 급상승 조회 로직 개선
5solbin Apr 28, 2026
f2b09b6
fix: getHotIssueVote() fetchJoin 제거
5solbin Apr 29, 2026
4d77207
fix : getTrendingVote() @Transactional 제거
5solbin Apr 29, 2026
d2c0b56
fix: getHotissueVote() @Transactional 제거
5solbin Apr 29, 2026
951956a
fix : getVoteDetailById() System.out.println 제거
5solbin Apr 29, 2026
0acb9e4
fix : processVote() 필요 없는 주석 제거
5solbin Apr 29, 2026
1813577
fix : processVote() 필요 없는 주석 제거
5solbin Apr 29, 2026
b1ccc9f
Merge pull request #135 from 5solbin/fix/#125-service_code_refactor
5solbin May 6, 2026
96cee2a
chore: test dev deployment
5solbin May 7, 2026
5c31c36
Merge pull request #137 from 5solbin/dev
5solbin May 7, 2026
e182804
Update deploy-dev.yml to include AWS env variables
5solbin May 7, 2026
0c00af1
chore: retry dev deployment
5solbin May 7, 2026
87e1277
Merge pull request #138 from 5solbin/dev
5solbin May 7, 2026
fc39bb6
chore: retry dev deployment on new aws account
5solbin May 7, 2026
a33b789
Merge pull request #139 from 5solbin/dev
5solbin May 7, 2026
36764cc
fix : SwaggerConfig 도메인 설정 변경
5solbin May 11, 2026
dd3f41c
fix : 새로운 도메인 security 접근 설정 추가
5solbin May 11, 2026
893d063
Merge pull request #141 from 5solbin/dev
5solbin May 11, 2026
028d3a7
feat: 칭호 도메인 추가
5solbin May 12, 2026
b7c52b9
feat: 칭호 서비스 및 저장소 파일 추가
5solbin May 12, 2026
9c7bff8
feat: 프로필 응답에 장착 칭호 추가
5solbin May 12, 2026
b852502
feat: 칭호 장착 서비스 추가
5solbin May 12, 2026
5f5a564
feat: 회원 칭호 목록 API 추가
5solbin May 13, 2026
d66be8e
chore: 칭호 API CORS 허용 설정 추가
5solbin May 13, 2026
10f629e
feat : 칭호 목록 응답에서 색상 필드 제거
5solbin May 13, 2026
fb1e241
feat : 칭호 도메인에서 색상 필드 제거
5solbin May 13, 2026
0996127
feat: 칭호 구매 서비스 메서드 추가
5solbin May 14, 2026
f486dd3
feat: 칭호 구매 API 추가
5solbin May 14, 2026
1c62b56
feat: 관리자 칭호 생성 API 추가
5solbin May 14, 2026
bb0a501
feat: 관리자 칭호 수정 API 추가
5solbin May 14, 2026
eaa0304
feat: 관리자 칭호 목록 조회 API 추가
5solbin May 15, 2026
a95d86b
Merge pull request #143 from 5solbin/dev
5solbin May 15, 2026
0ec36a0
fix: 투표 목록 조회 커서 페이징 정합성 및 N+1 쿼리 개선
5solbin May 18, 2026
4634fe9
fix: 투표 목록 조회 요청 파라미터 검증 추가
5solbin May 18, 2026
40fc446
Merge pull request #145 from 5solbin/fix/#144-updateVoteList
5solbin May 19, 2026
fcb690b
feat : 포인트 사용 내역
5solbin May 19, 2026
bd04246
fix: 포인트 내역 응답에 잔여 재화 추가
5solbin May 19, 2026
4be239a
Merge pull request #146 from 5solbin/feat/#140-title
5solbin May 19, 2026
4e80793
feat : 댓글 조회 응답 값에 칭호 추가
5solbin May 20, 2026
0585a88
feat : 게시물 조회 응답 값에 회원 칭호 추가
5solbin May 20, 2026
1b7859f
Merge pull request #148 from 5solbin/feat/#140-title
5solbin May 20, 2026
29ce811
fix : 회원가입 시 point type 저장으로 생기는 오류 수정
5solbin May 20, 2026
0878a9b
feat : 기본 칭호 자동 장착 로직 추가
5solbin May 21, 2026
8551c58
Merge pull request #1 from 5solbin/feat/#140-title
5solbin May 21, 2026
70adb11
Merge branch 'main' into dev
5solbin May 22, 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
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,20 @@ public CorsConfigurationSource corsConfigurationSource() {
"https://valanserver.store:8081",
"https://valanserver.store:8082"
));
configuration.setAllowedOriginPatterns(Arrays.asList(
"http://localhost:*",
"http://127.0.0.1:*"
));

configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
configuration.setMaxAge(3600L);

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/member/titles", configuration);
source.registerCorsConfiguration("/member/titles/**", configuration);
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
}
109 changes: 108 additions & 1 deletion src/main/java/com/valanse/valanse/controller/MemberController.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,28 @@
import com.valanse.valanse.dto.MemberProfile.MemberMyPageResponse;
import com.valanse.valanse.dto.MemberProfile.MemberProfileRequest;
import com.valanse.valanse.dto.MemberProfile.MemberProfileResponse;
import com.valanse.valanse.dto.PointHistory.PointHistoryResponse;
import com.valanse.valanse.dto.Title.TitleCreateRequest;
import com.valanse.valanse.dto.Title.TitleCreateResponse;
import com.valanse.valanse.dto.Title.TitleAdminResponse;
import com.valanse.valanse.dto.Title.TitleDeleteResponse;
import com.valanse.valanse.dto.Title.TitleEquipResponse;
import com.valanse.valanse.dto.Title.TitleListResponse;
import com.valanse.valanse.dto.Title.TitlePurchaseResponse;
import com.valanse.valanse.dto.Title.TitleUpdateRequest;
import com.valanse.valanse.dto.Title.TitleUpdateResponse;
import com.valanse.valanse.service.MemberProfileService.MemberProfileService;
import com.valanse.valanse.service.PointService.PointService;
import com.valanse.valanse.service.TitleService.TitleService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Tag(name = "회원 정보 API", description = "프로필 조회 등 회원 정보 관련 기능")
Expand All @@ -20,6 +34,8 @@
public class MemberController {

private final MemberProfileService memberProfileService;
private final PointService pointService;
private final TitleService titleService;

@Operation(
summary = "회원 프로필 정보 저장",
Expand Down Expand Up @@ -92,4 +108,95 @@ public ResponseEntity<MemberMyPageResponse> getMyProfile() {
MemberMyPageResponse response = memberProfileService.getMyProfile();
return ResponseEntity.ok(response);
}
}

@Operation(
summary = "포인트 지급 내역 조회",
description = "현재 로그인한 회원의 포인트 지급 내역을 조회합니다. 최신순으로 정렬되어 반환됩니다."
)
@GetMapping("/point-history")
public ResponseEntity<PointHistoryResponse> getPointHistory() {
Long userId = Long.parseLong(SecurityContextHolder.getContext().getAuthentication().getName());
PointHistoryResponse response = pointService.getPointHistory(userId);
return ResponseEntity.ok(response);
}

@Operation(
summary = "칭호 선택 목록 조회",
description = "현재 로그인한 회원 기준으로 기본, 보유, 미보유 칭호를 분리해서 조회합니다."
)
@GetMapping("/titles")
public ResponseEntity<TitleListResponse> getTitles() {
Long userId = Long.parseLong(SecurityContextHolder.getContext().getAuthentication().getName());
TitleListResponse response = titleService.getTitleList(userId);
return ResponseEntity.ok(response);
}

@Operation(
summary = "관리자 칭호 목록 조회",
description = "관리자 권한으로 잠김/보유 여부와 상관없이 칭호 마스터 데이터 목록을 조회합니다."
)
@GetMapping("/titles/admin")
public ResponseEntity<List<TitleAdminResponse>> getTitlesForAdmin() {
Long userId = Long.parseLong(SecurityContextHolder.getContext().getAuthentication().getName());
List<TitleAdminResponse> response = titleService.getTitleListForAdmin(userId);
return ResponseEntity.ok(response);
}

@Operation(
summary = "관리자 칭호 생성",
description = "관리자 권한으로 새로운 칭호 마스터 데이터를 생성합니다."
)
@PostMapping("/titles")
public ResponseEntity<TitleCreateResponse> createTitle(@RequestBody TitleCreateRequest request) {
Long userId = Long.parseLong(SecurityContextHolder.getContext().getAuthentication().getName());
TitleCreateResponse response = titleService.createTitle(userId, request);
return ResponseEntity.ok(response);
}

@Operation(
summary = "관리자 칭호 수정",
description = "관리자 권한으로 칭호 마스터 데이터를 수정합니다."
)
@PatchMapping("/titles/{titleId}")
public ResponseEntity<TitleUpdateResponse> updateTitle(
@PathVariable Long titleId,
@RequestBody TitleUpdateRequest request
) {
Long userId = Long.parseLong(SecurityContextHolder.getContext().getAuthentication().getName());
TitleUpdateResponse response = titleService.updateTitle(userId, titleId, request);
return ResponseEntity.ok(response);
}

@Operation(
summary = "관리자 칭호 삭제",
description = "관리자 권한으로 칭호를 비활성화합니다. 삭제 대상 칭호를 장착 중인 회원은 활성 기본 칭호로 변경됩니다."
)
@DeleteMapping("/titles/{titleId}")
public ResponseEntity<TitleDeleteResponse> deleteTitle(@PathVariable Long titleId) {
Long userId = Long.parseLong(SecurityContextHolder.getContext().getAuthentication().getName());
TitleDeleteResponse response = titleService.deleteTitle(userId, titleId);
return ResponseEntity.ok(response);
}

@Operation(
summary = "칭호 장착",
description = "현재 로그인한 회원이 보유한 칭호를 대표 칭호로 선택합니다."
)
@PostMapping("/titles/{titleId}/equip")
public ResponseEntity<TitleEquipResponse> equipTitle(@PathVariable Long titleId) {
Long userId = Long.parseLong(SecurityContextHolder.getContext().getAuthentication().getName());
TitleEquipResponse response = titleService.equipTitle(userId, titleId);
return ResponseEntity.ok(response);
}

@Operation(
summary = "칭호 구매",
description = "현재 로그인한 회원이 포인트로 구매 가능한 칭호를 구매합니다."
)
@PostMapping("/titles/{titleId}/purchase")
public ResponseEntity<TitlePurchaseResponse> purchaseTitle(@PathVariable Long titleId) {
Long userId = Long.parseLong(SecurityContextHolder.getContext().getAuthentication().getName());
TitlePurchaseResponse response = titleService.purchaseTitle(userId, titleId);
return ResponseEntity.ok(response);
}
}
27 changes: 27 additions & 0 deletions src/main/java/com/valanse/valanse/domain/MemberProfile.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

@Builder
@AllArgsConstructor
@NoArgsConstructor
Expand Down Expand Up @@ -43,6 +46,13 @@ public class MemberProfile extends BaseEntity {

private String mbti;

@Builder.Default
private long point = 0L;

@Builder.Default
@OneToMany(mappedBy = "memberProfile", cascade = CascadeType.ALL)
private List<MemberProfileTitle> memberProfileTitles = new ArrayList<>();

public void update(String nickname, Gender gender, Age age, MbtiIe mbtiIe, MbtiTf mbtiTf, String mbti) {
this.nickname = nickname;
this.gender = gender;
Expand All @@ -52,4 +62,21 @@ public void update(String nickname, Gender gender, Age age, MbtiIe mbtiIe, MbtiT
this.mbti = mbti;
}

public void addPoint(long amount) {
this.point += amount;
}

public boolean hasEnoughPoint(long amount) {
return this.point >= amount;
}

public void subtractPoint(long amount) {
if (amount < 0) {
throw new IllegalArgumentException("차감할 포인트는 0 이상이어야 합니다.");
}
if (!hasEnoughPoint(amount)) {
throw new IllegalArgumentException("포인트가 부족합니다.");
}
this.point -= amount;
}
}
59 changes: 59 additions & 0 deletions src/main/java/com/valanse/valanse/domain/MemberProfileTitle.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.valanse.valanse.domain;

import com.valanse.valanse.domain.common.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.PrePersist;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Entity
public class MemberProfileTitle extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_profile_id", nullable = false)
private MemberProfile memberProfile;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "title_id", nullable = false)
private Title title;

@Builder.Default
@Column(nullable = false)
private boolean equipped = false;

private LocalDateTime acquiredAt;

@PrePersist
public void prePersist() {
if (acquiredAt == null) {
acquiredAt = LocalDateTime.now();
}
}

public void equip() {
this.equipped = true;
}

public void unequip() {
this.equipped = false;
}
}
44 changes: 44 additions & 0 deletions src/main/java/com/valanse/valanse/domain/PointHistory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.valanse.valanse.domain;

import com.valanse.valanse.domain.enums.PointType;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;

import java.time.LocalDateTime;

@Entity
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PointHistory {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;

private Long amount;

@Enumerated(EnumType.STRING)
@JdbcTypeCode(SqlTypes.VARCHAR)
@Column(nullable = false, length = 30)
private PointType type;

private Long remainingPoint;

private LocalDateTime createdAt;

@PrePersist
public void prePersist() {
this.createdAt = LocalDateTime.now();
}
}
Loading
Loading