Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 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
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ out/
.env
.terraform
*.pem
/src/main/resources/firebase
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import ita.tinybite.domain.auth.entity.JwtTokenProvider;
import ita.tinybite.domain.chat.entity.ChatRoom;
import ita.tinybite.domain.party.dto.request.PartyCreateRequest;
import ita.tinybite.domain.party.dto.request.PartyUpdateRequest;
import ita.tinybite.domain.party.dto.response.ChatRoomResponse;
import ita.tinybite.domain.party.dto.response.PartyDetailResponse;
import ita.tinybite.domain.party.dto.response.PartyListResponse;
Expand All @@ -19,14 +20,9 @@
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.ErrorResponse;
import org.springframework.web.bind.annotation.*;

import java.util.List;

Expand Down Expand Up @@ -272,8 +268,6 @@ public ResponseEntity<List<PartyParticipant>> getPendingParticipants(
return ResponseEntity.ok(participants);
}



/**
* 파티 목록 조회 (홈 화면)
*/
Expand All @@ -287,17 +281,12 @@ public ResponseEntity<PartyListResponse> getPartyList(
example = "ALL",
schema = @Schema(allowableValues = {"ALL", "DELIVERY", "GROCERY", "HOUSEHOLD"})
)
@RequestParam(defaultValue = "ALL") PartyCategory category,

@Parameter(description = "사용자 위도", required = true, example = "37.4979")
@RequestParam String latitude,

@Parameter(description = "사용자 경도", required = true, example = "127.0276")
@RequestParam String longitude) {
@RequestParam(defaultValue = "ALL") PartyCategory category
) {
Long userId = jwtTokenProvider.getUserId(token);

PartyListResponse response = partyService.getPartyList(
userId, category, latitude, longitude);
userId, category);

return ResponseEntity.ok(response);
}
Expand Down Expand Up @@ -330,12 +319,11 @@ public ResponseEntity<PartyListResponse> getPartyList(
@GetMapping("/{partyId}")
public ResponseEntity<PartyDetailResponse> getPartyDetail(
@PathVariable Long partyId,
@RequestHeader("Authorization") String token,
@RequestParam Double latitude,
@RequestParam Double longitude) {
@RequestHeader("Authorization") String token
) {
Long userId = jwtTokenProvider.getUserId(token);

PartyDetailResponse response = partyService.getPartyDetail(partyId, userId, latitude, longitude);
PartyDetailResponse response = partyService.getPartyDetail(partyId, userId);
return ResponseEntity.ok(response);
}

Expand Down Expand Up @@ -374,4 +362,90 @@ public ResponseEntity<Long> createParty(
return ResponseEntity.ok(partyId);
}

/**
* 파티 수정
*/
@Operation(
summary = "파티 수정",
description = """
파티 정보를 수정합니다.

**수정 권한**
- 파티 호스트만 수정 가능

**수정 가능 범위**
- 승인된 파티원이 없을 때: 모든 항목 수정 가능
- 승인된 파티원이 있을 때: 설명, 이미지만 수정 가능 (가격, 인원, 수령 정보 수정 불가)
"""
)
@ApiResponses({
@ApiResponse(
responseCode = "200",
description = "파티 수정 성공"
),
@ApiResponse(
responseCode = "400",
description = "잘못된 요청 (수정 권한 없음, 유효하지 않은 데이터)",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))
),
@ApiResponse(
responseCode = "404",
description = "파티를 찾을 수 없음",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))
)
})
@PatchMapping("/{partyId}")
public ResponseEntity<Void> updateParty(
@PathVariable Long partyId,
@AuthenticationPrincipal Long userId,
@RequestBody PartyUpdateRequest request) {

partyService.updateParty(partyId, userId, request);
return ResponseEntity.ok().build();
}

/**
* 파티 삭제
*/
@Operation(
summary = "파티 삭제",
description = """
파티를 삭제합니다.

**삭제 권한**
- 파티 호스트만 삭제 가능

**삭제 제한**
- 승인된 파티원이 있는 경우 삭제 불가능
- 승인된 파티원이 없을 때만 삭제 가능

**삭제 시 처리**
- 관련 채팅방 비활성화
- 대기 중인 참가 신청 모두 삭제
"""
)
@ApiResponses({
@ApiResponse(
responseCode = "204",
description = "파티 삭제 성공"
),
@ApiResponse(
responseCode = "400",
description = "잘못된 요청 (삭제 권한 없음, 승인된 파티원 존재)",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))
),
@ApiResponse(
responseCode = "404",
description = "파티를 찾을 수 없음",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))
)
})
@DeleteMapping("/{partyId}")
public ResponseEntity<Void> deleteParty(
@PathVariable Long partyId,
@AuthenticationPrincipal Long userId) {

partyService.deleteParty(partyId, userId);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class PartyUpdateRequest {
private Double latitude;
private Double longitude;

@Pattern(regexp = "^(https?://)?.*", message = "올바른 URL 형식으로 입력해주세요")
// @Pattern(regexp = "^(https?://)?.*", message = "올바른 URL 형식으로 입력해주세요")
private String productLink;

// 항상 수정 가능한 필드
Expand Down
11 changes: 10 additions & 1 deletion src/main/java/ita/tinybite/domain/party/entity/Party.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ public class Party {
private LocalDateTime createdAt; // 등록시간

@UpdateTimestamp
@Column(nullable = false)
private LocalDateTime updatedAt;

private LocalDateTime closedAt;
Expand All @@ -89,6 +88,16 @@ public class Party {
@Builder.Default
private List<PartyParticipant> participants = new ArrayList<>(); // 파티 참여 유저

/**
* 참여자 수 증가
*/
public void incrementParticipants() {
if (this.currentParticipants >= this.maxParticipants) {
throw new IllegalStateException("파티 인원이 가득 찼습니다");
}
this.currentParticipants++;
}

public String getTimeAgo() {
LocalDateTime now = LocalDateTime.now();

Expand Down
50 changes: 40 additions & 10 deletions src/main/java/ita/tinybite/domain/party/service/PartyService.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,11 @@ public Long createParty(Long userId, PartyCreateRequest request) {
.maxParticipants(request.getMaxParticipants())
.pickupLocation(PickupLocation.builder()
.place(request.getPickupLocation().getPlace())
// .pickupLatitude(request.getPickupLocation().getPickupLatitude())
// .pickupLongitude(request.getPickupLocation().getPickupLongitude())
.build())
.image(request.getImages().get(0))
.thumbnailImage(thumbnailImage)
.link(request.getProductLink())
.description(request.getDescription())
.image(getImageIfPresent(request.getImages()))
.thumbnailImage(getThumbnailIfPresent(request.getImages(), request.getCategory()))
.link(getLinkIfValid(request.getProductLink(), request.getCategory()))
.description(getDescriptionIfPresent(request.getDescription()))
.currentParticipants(1)
.status(PartyStatus.RECRUITING)
.isClosed(false)
Expand Down Expand Up @@ -106,8 +104,7 @@ public Long createParty(Long userId, PartyCreateRequest request) {
/**
* 파티 목록 조회 (홈 화면)
*/
public PartyListResponse getPartyList(Long userId, PartyCategory category,
String userLat, String userLon) {
public PartyListResponse getPartyList(Long userId, PartyCategory category) {
User user = null;
if (userId != null) {
user = userRepository.findById(userId).orElse(null);
Expand Down Expand Up @@ -185,7 +182,7 @@ public PartyListResponse getPartyList(Long userId, PartyCategory category,
/**
* 파티 상세 조회
*/
public PartyDetailResponse getPartyDetail(Long partyId, Long userId, Double userLat, Double userLon) {
public PartyDetailResponse getPartyDetail(Long partyId, Long userId) {
Party party = partyRepository.findById(partyId)
.orElseThrow(() -> new IllegalArgumentException("파티를 찾을 수 없습니다"));

Expand Down Expand Up @@ -390,7 +387,7 @@ public void deleteParty(Long partyId, Long userId) {
*/
@Transactional
public void approveParticipant(Long partyId, Long participantId, Long hostId) {
Party party = partyRepository.findById(partyId)
Party party = partyRepository.findByIdWithHost(partyId)
.orElseThrow(() -> new IllegalArgumentException("파티를 찾을 수 없습니다"));

// 파티장 권한 확인
Expand All @@ -401,9 +398,18 @@ public void approveParticipant(Long partyId, Long participantId, Long hostId) {
PartyParticipant participant = partyParticipantRepository.findById(participantId)
.orElseThrow(() -> new IllegalArgumentException("참여 신청을 찾을 수 없습니다"));


// 현재 인원이 최대 인원을 초과하는지 검증
if (party.getCurrentParticipants() >= party.getMaxParticipants()) {
throw new IllegalStateException("파티 인원이 가득 찼습니다");
}

// 승인 처리
participant.approve();

// 파티 현재 참여자 수 증가
party.incrementParticipants();

// 단체 채팅방 조회 또는 생성
ChatRoom groupChatRoom = getOrCreateGroupChatRoom(party);

Expand Down Expand Up @@ -601,5 +607,29 @@ private void checkAndCloseIfFull(Party party) {
party.close();
}
}

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

private String getThumbnailIfPresent(List<String> images, PartyCategory category) {
if (images != null && !images.isEmpty()) {
return images.get(0);
}
return null;
}

private String getLinkIfValid(String link, PartyCategory category) {
if (link != null && !link.isBlank()) {
validateProductLink(category, link);
return link;
}
return null;
}

private String getDescriptionIfPresent(String description) {
return (description != null && !description.isBlank()) ? description : null;
}
}