Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
bce7dbc
feat : 닉네임 중복 체크 (nickname unique), 인증코드 검사 예외처리 추가
yyytir777 Dec 8, 2025
3573613
fix : user_id IDENTITY strategy & dev redis host 이름변경 (localhost -> r…
yyytir777 Dec 8, 2025
08cfb63
test코드 생성 & swagger url 삭제 & 환경변수 중복 삭제
yyytir777 Dec 8, 2025
ac96fa0
fix : 엔드포인트 추가
yyytir777 Dec 9, 2025
43e75ed
Merge pull request #27 from tinybite-2025/feature/13-user
yyytir777 Dec 9, 2025
a70941b
Feature/26 notification (#29)
marshmallowing Dec 11, 2025
cbbf597
feat : google login 구현 완료
yyytir777 Dec 11, 2025
d03fd30
fix : main push 시에만 workflow trigger
yyytir777 Dec 11, 2025
00cc289
Merge branch 'main' of https://github.com/tinybite-2025/tinybite-serv…
yyytir777 Dec 11, 2025
729ae0a
Feature/#28 google apple login
yyytir777 Dec 16, 2025
b5c29db
Merge branch 'main' into develop
yyytir777 Dec 16, 2025
2e5da55
Merge branch 'develop' of https://github.com/tinybite-2025/tinybite-s…
yyytir777 Dec 18, 2025
1fbd896
merge main into develop : main의 핫픽스 변경사항 develop에 반영
yyytir777 Dec 18, 2025
dd9771a
workflow 줄바꿈 에러 수정
yyytir777 Dec 18, 2025
38de611
hotifx : 에러 핸들링 수정 및 무중단 배포 삭제 (리소스 너무 많이 먹음)
yyytir777 Dec 19, 2025
91b8cba
main의 핫픽스 develop에 반영
yyytir777 Dec 22, 2025
fbc5e90
수정사항 반영 (API 인증 관련, db schema, 예외 처리 등..)
yyytir777 Dec 23, 2025
7b9fb7b
main브랜치 핫픽스 반영
yyytir777 Dec 24, 2025
bbb080f
Feature/35 term (#38)
yyytir777 Dec 24, 2025
4c352aa
fix : docker compose 명령어 수정
yyytir777 Dec 24, 2025
27dfcbb
Feature : 파티 기능 (#42)
milowon Dec 27, 2025
0de3a43
Merge branch 'main' into develop
milowon Dec 31, 2025
2f27dee
hotfix : url parser 경로 제거
milowon Dec 31, 2025
6eb0d8d
Merge branch 'main' of https://github.com/tinybite-2025/tinybite-serv…
milowon Jan 1, 2026
3f69bc1
Merge branch 'main' of https://github.com/tinybite-2025/tinybite-serv…
milowon Jan 2, 2026
45191a9
hotfix : 파티 거리 계산 로직 임시 주석 처리
milowon Jan 2, 2026
3a9a6e6
Merge branch 'main' of https://github.com/tinybite-2025/tinybite-serv…
milowon Jan 2, 2026
b2ca1f4
hotfix : 파티 수정, 삭제 controller 추가
milowon Jan 2, 2026
38ab16c
hotfix : 선택 값들이 존재할때만 넣도록 수정
milowon Jan 2, 2026
037d2e4
hotfix : 위도, 경도 로직 삭제
milowon Jan 2, 2026
9d305f8
Feat : 마이페이지 참여중인 파티 조회 (#50)
milowon Jan 2, 2026
1cee657
hotfix : user service에 transactional 어노테이션 추가
milowon Jan 2, 2026
5213214
Merge branch 'main' of https://github.com/tinybite-2025/tinybite-serv…
milowon Jan 2, 2026
ed3a399
hotfix : 참여중 파티 조회 반환 형식 통일
milowon Jan 2, 2026
374f720
hotfix : 파티 생성, 조회 시, 거리 계산 로직 반영
milowon Jan 2, 2026
4ddfa39
Hotfix: 유저 좌표 입력 requestParam 형식으로 변경
milowon Jan 2, 2026
42bc4d4
Merge branch 'main' into develop
milowon Jan 2, 2026
9ee078a
Merge branch 'main' into develop
milowon Jan 2, 2026
3be9d38
hotfix : 누락된 swagger 문서 수정사항 반영
milowon Jan 2, 2026
89bbef3
Merge branch 'main' into develop
milowon Jan 2, 2026
b281a29
feat : 회원 탈퇴 및 재가입 방지, 검증 (#65)
milowon Jan 2, 2026
35a62b7
fix : 파티 수정 버그 픽스 (#67)
milowon Jan 2, 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
11 changes: 6 additions & 5 deletions src/main/java/ita/tinybite/domain/auth/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,17 @@ public class AuthService {
@Transactional
public AuthResponse kakaoSignup(KakaoSignupRequest request) {
// 카카오 API로 유저 정보 조회
KakaoUserInfo kakaoUser = kakaoApiClient.getUserInfo(request.getCode());
// KakaoUserInfo kakaoUser = kakaoApiClient.getUserInfo(request.getCode());

// 이메일 중복 체크
if (userRepository.findByEmail(kakaoUser.getKakaoAccount().getEmail()).isPresent()) {
throw new RuntimeException("이미 가입된 이메일입니다.");
}
// if (userRepository.findByEmail(kakaoUser.getKakaoAccount().getEmail()).isPresent()) {
// throw new RuntimeException("이미 가입된 이메일입니다.");
// }

// User 엔티티 생성 및 저장
User user = User.builder()
.email(kakaoUser.getKakaoAccount().getEmail())
// .email(kakaoUser.getKakaoAccount().getEmail())
.email("ace312@gmail.com")
.nickname(request.getNickname())
.location(request.getLocation())
.type(LoginType.KAKAO)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,12 +310,10 @@ public ResponseEntity<PartyListResponse> getPartyList(
@GetMapping("/{partyId}")
public ResponseEntity<PartyDetailResponse> getPartyDetail(
@PathVariable Long partyId,
@RequestHeader("Authorization") String token,
@Parameter(hidden = true) @AuthenticationPrincipal Long userId,
@RequestParam(required = false) Double userLat,
@RequestParam(required = false) Double userLon
) {
Long userId = jwtTokenProvider.getUserId(token);

PartyDetailResponse response = partyService.getPartyDetail(partyId, userId,userLat,userLon);
return ResponseEntity.ok(response);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package ita.tinybite.domain.party.dto.request;

import ita.tinybite.domain.party.entity.PickupLocation;
import ita.tinybite.domain.party.enums.PartyCategory;
import jakarta.validation.constraints.*;
import lombok.*;

Expand All @@ -23,10 +25,9 @@ public class PartyUpdateRequest {
private Integer maxParticipants;

@Size(max = 30, message = "수령 장소는 최대 30자까지 입력 가능합니다")
private String pickupLocation;
private PickupLocation pickupLocation;

private Double latitude;
private Double longitude;
private PartyCategory category;

// @Pattern(regexp = "^(https?://)?.*", message = "올바른 URL 형식으로 입력해주세요")
private String productLink;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ public static PartyCardResponse from(Party party, int currentParticipants, boole
.build();
}
private static String getThumbnailImage(Party party) {
if (party.getImage() != null && !party.getImage().isEmpty()) {
return party.getImage();
if (party.getImages() != null && !party.getImages().isEmpty()) {
return party.getImages().get(0);
}
return "/images/default-party-thumbnail.jpg"; // 기본 이미지
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public class PartyDetailResponse {
// 설명
private String description;

private String thumbnailImage;

// 이미지 (최대 5장)
private List<String> images;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
@AllArgsConstructor
@Builder
public class ProductLink {
private String thumbnailImage;
private String productName;
// private String thumbnailImage;
// private String productName;
private String url;
}
9 changes: 4 additions & 5 deletions src/main/java/ita/tinybite/domain/party/entity/Party.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class Party {
private String thumbnailImage; // 섬네일 이미지 URL

@Column(length = 500)
private String image; // 이미지 URL
private List<String> images; // 이미지 URL

@Column(nullable = false)
private Integer price; // 가격
Expand Down Expand Up @@ -132,8 +132,7 @@ public String getTimeAgo() {
}

public void updateAllFields(String title, Integer price, Integer maxParticipants,
PickupLocation pickupLocation, Double latitude, Double longitude,
String productLink, String description, List<String> images) {
PickupLocation pickupLocation, String productLink, String description, List<String> images) {
this.title = title != null ? title : this.title;
this.price = price != null ? price : this.price;
this.maxParticipants = maxParticipants != null ? maxParticipants : this.maxParticipants;
Expand All @@ -150,7 +149,7 @@ public void updateAllFields(String title, Integer price, Integer maxParticipants
this.description = description != null ? description : this.description;

if (images != null && !images.isEmpty()) {
this.image = images.get(0);
this.images = images;
this.thumbnailImage = images.get(0);
}
}
Expand All @@ -159,7 +158,7 @@ public void updateLimitedFields(String description, List<String> images) {
this.description = description != null ? description : this.description;

if (images != null && !images.isEmpty()) {
this.image = images.get(0);
this.images = images;
this.thumbnailImage = images.get(0);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,28 @@ int countByPartyIdAndStatus(
@Param("status") ParticipantStatus status
);

/**
* 사용자가 참여중인 파티 개수 조회 (호스트 + 참가자)
*/
@Query("SELECT COUNT(DISTINCT pp.party.id) " +
"FROM PartyParticipant pp " +
"WHERE pp.user.userId = :userId " +
"AND pp.party.status IN :activeStatuses " +
"AND pp.status = :participantStatus")
long countActivePartiesByUserId(
@Param("userId") Long userId,
@Param("activeStatuses") List<PartyStatus> activeStatuses,
@Param("participantStatus") ParticipantStatus participantStatus
);

/**
* 사용자가 호스트인 활성 파티 개수
*/
@Query("SELECT COUNT(p) FROM Party p " +
"WHERE p.host.userId = :userId " +
"AND p.status IN :activeStatuses")
long countActivePartiesByHostId(
@Param("userId") Long userId,
@Param("activeStatuses") List<PartyStatus> activeStatuses
);
}
45 changes: 34 additions & 11 deletions src/main/java/ita/tinybite/domain/party/service/PartyService.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public Long createParty(Long userId, PartyCreateRequest request) {
.pickupLatitude(request.getPickupLocation().getPickupLatitude())
.pickupLongitude(request.getPickupLocation().getPickupLongitude())
.build())
.image(getImageIfPresent(request.getImages()))
.images(getImagesIfPresent(request.getImages()))
.thumbnailImage(getThumbnailIfPresent(request.getImages(), request.getCategory()))
.link(getLinkIfValid(request.getProductLink(), request.getCategory()))
.description(getDescriptionIfPresent(request.getDescription()))
Expand Down Expand Up @@ -299,10 +299,10 @@ private PartyDetailResponse convertToDetailResponse(Party party, double distance
int pricePerPerson = party.getPrice() / party.getMaxParticipants();

// 이미지 파싱
List<String> images = new ArrayList<>();
if (party.getImage() != null && !party.getImage().isEmpty()) {
images = List.of(party.getImage());
}
// List<String> images = new ArrayList<>();
// if (party.getImages() != null && !party.getImages().isEmpty()) {
// images = List.of(party.getImages());
// }

return PartyDetailResponse.builder()
.partyId(party.getId())
Expand All @@ -315,6 +315,7 @@ private PartyDetailResponse convertToDetailResponse(Party party, double distance
.profileImage(party.getHost().getProfileImage())
.build())
.pickupLocation(party.getPickupLocation())
.thumbnailImage(party.getThumbnailImage())
.distance(formatDistanceIfExists(distance))
.currentParticipants(currentCount)
.maxParticipants(party.getMaxParticipants())
Expand All @@ -326,7 +327,7 @@ private PartyDetailResponse convertToDetailResponse(Party party, double distance
.url(party.getLink())
.build() : null)
.description(party.getDescription())
.images(images)
.images(party.getImages())
.isClosed(party.getIsClosed())
.isParticipating(isParticipating)
.build();
Expand Down Expand Up @@ -357,17 +358,39 @@ public void updateParty(Long partyId, Long userId, PartyUpdateRequest request) {
request.getTitle(),
request.getTotalPrice(),
request.getMaxParticipants(),
new PickupLocation(request.getPickupLocation(), request.getLatitude(), request.getLongitude()),
getPickUpLocationIfExists(request,party),
// new PickupLocation(request.getPickupLocation()),
request.getLatitude(),
request.getLongitude(),
request.getProductLink(),
request.getDescription(),
request.getImages()
);
}
}

private PickupLocation getPickUpLocationIfExists(PartyUpdateRequest request, Party currentParty) {
if (request.getPickupLocation() == null) {
return currentParty.getPickupLocation();
}
PickupLocation requestPickup = request.getPickupLocation();
PickupLocation currentPickup = currentParty.getPickupLocation();

// 각 필드별로 새 값이 있으면 사용, 없으면 기존 값 유지
String place = requestPickup.getPlace() != null
? requestPickup.getPlace()
: (currentPickup != null ? currentPickup.getPlace() : "");

Double latitude = requestPickup.getPickupLatitude() != null
? requestPickup.getPickupLatitude()
: (currentPickup != null ? currentPickup.getPickupLatitude() : null);

Double longitude = requestPickup.getPickupLongitude() != null
? requestPickup.getPickupLongitude()
: (currentPickup != null ? currentPickup.getPickupLongitude() : null);

return new PickupLocation(place, latitude, longitude);

}

@Transactional
public void deleteParty(Long partyId, Long userId) {
Party party = partyRepository.findById(partyId)
Expand Down Expand Up @@ -617,8 +640,8 @@ private void checkAndCloseIfFull(Party party) {
}

// 헬퍼 메서드들
private String getImageIfPresent(List<String> images) {
return (images != null && !images.isEmpty()) ? images.get(0) : null;
private List<String> getImagesIfPresent(List<String> images) {
return (images != null && !images.isEmpty()) ? images : null;
}

private String getThumbnailIfPresent(List<String> images, PartyCategory category) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package ita.tinybite.domain.user.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import ita.tinybite.domain.party.dto.response.PartyCardResponse;
import ita.tinybite.domain.user.dto.req.UpdateUserReqDto;
import ita.tinybite.domain.user.dto.res.RejoinValidationResponse;
import ita.tinybite.domain.user.dto.res.UserResDto;
import ita.tinybite.domain.user.dto.res.WithDrawValidationResponse;
import ita.tinybite.domain.user.service.UserService;
import ita.tinybite.global.response.APIResponse;
import jakarta.validation.Valid;
Expand Down Expand Up @@ -67,17 +70,47 @@ public APIResponse<?> updateLocation(@RequestParam(defaultValue = "37.3623504988
return success();
}

@Operation(
summary = "회원 탈퇴 가능 여부 확인",
description = "진행 중인 파티가 있는지 확인하여 탈퇴 가능 여부를 반환합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "확인 성공"),
@ApiResponse(responseCode = "401", description = "인증 실패")
})
@GetMapping("/me/withdrawal/validate")
public APIResponse<WithDrawValidationResponse> validateWithdrawal(
@Parameter(hidden = true) @AuthenticationPrincipal Long userId) {
WithDrawValidationResponse response = userService.validateWithdrawal(userId);
return success(response);
}

@Operation(summary = "회원 탈퇴", description = "현재 로그인한 사용자를 삭제합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "탈퇴 성공"),
@ApiResponse(responseCode = "401", description = "인증 실패")
})
@DeleteMapping("/me")
public APIResponse<?> deleteUser() {
userService.deleteUser();
public APIResponse<?> deleteUser(@AuthenticationPrincipal Long userId) {
userService.deleteUser(userId);
return success();
}

@Operation(
summary = "재가입 가능 여부 확인",
description = "탈퇴 후 30일 이내인지 확인합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "확인 성공")
})
@GetMapping("/rejoin/validate")
public APIResponse<RejoinValidationResponse> validateRejoin(
@Parameter(description = "이메일", required = true)
@RequestParam String email) {
RejoinValidationResponse response = userService.validateRejoin(email);
return success(response);
}

@Operation(summary = "활성 파티 목록 조회", description = "사용자가 참여 중인 활성 파티 목록을 조회합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package ita.tinybite.domain.user.dto.res;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

import java.time.LocalDateTime;

@Getter
@AllArgsConstructor
@Builder
public class RejoinValidationResponse {
private boolean canRejoin;
private Long daysRemaining;
private LocalDateTime canRejoinAt;
private String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package ita.tinybite.domain.user.dto.res;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Getter
@AllArgsConstructor
@Builder
public class WithDrawValidationResponse {
private boolean canWithdraw;
private long activePartyCount;
private long hostPartyCount;
private long participantPartyCount;
private String message;
}
15 changes: 15 additions & 0 deletions src/main/java/ita/tinybite/domain/user/entity/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import lombok.*;
import org.hibernate.annotations.Comment;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

Expand Down Expand Up @@ -49,6 +50,8 @@ public class User extends BaseEntity {
@Column(length = 100)
private String location;

private LocalDateTime withdrawAt;

@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<UserTermAgreement> agreements = new ArrayList<>();;

Expand All @@ -72,4 +75,16 @@ public void updateSignupInfo(GoogleAndAppleSignupRequest req, String email, Logi
public void addTerms(List<UserTermAgreement> agreements) {
this.agreements.addAll(agreements);
}

public void withdraw() {
this.nickname = "탈퇴한 사용자";
this.profileImage = "/images/default-profile.jpg";
this.status = UserStatus.WITHDRAW;
this.withdrawAt = LocalDateTime.now();
}

// 탈퇴 여부 확인
public boolean isWithdrawn() {
return this.status == UserStatus.WITHDRAW;
}
}
Loading