Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/#27 #30

Merged
merged 9 commits into from
Aug 17, 2024
258 changes: 185 additions & 73 deletions docs/index.html

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/docs/asciidoc/group.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ endif::[]

operation::group-register[snippets='http-request,http-response']

[#_그룹_단건_조회_모달창용]
=== 그룹 단건 조회 [모달창용]

operation::group-get-basic[snippets='path-parameters,http-request,http-response']

[#_그룹_단건_조회_상세페이지용]
=== 그룹 단건 조회 [상세페이지용]

operation::group-get-detail[snippets='path-parameters,path-parameters,http-request,http-response']
Expand Down
8 changes: 8 additions & 0 deletions src/docs/asciidoc/groupParticipation.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,11 @@ endif::[]
=== 그룹 참여

operation::groups-participation[snippets='path-parameters,http-request,http-response']

=== 그룹 참여 허가

operation::groups-participation-approve[snippets='path-parameters,http-request,http-response']

=== 그룹 참여 거절

operation::groups-participation-reject[snippets='path-parameters,http-request,http-response']
2 changes: 1 addition & 1 deletion src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ endif::[]
기본적으로 모든 API는 Authorization 헤더에 액세스 토큰을 포함해야 합니다.
아래 두 API는 인증 없이 접근이 가능합니다.

<<주변 그룹 조회>>
<<주변 그룹 조회>> <<_그룹_단건_조회_모달창용>> <<_그룹_단건_조회_상세페이지용>>

include::group.adoc[]

Expand Down
8 changes: 2 additions & 6 deletions src/docs/asciidoc/jwt.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,18 @@ endif::[]

== Jwt

=== 로그인

=== Request
=== 소셜 로그인

[source]
----
http://localhost:8080/oauth2/authorization/kakao
----

=== Response

리프레쉬 토큰은 쿠키로, 액세스 토큰은 바디로 응답합니다.

=== 액세스 토큰 재발급

operation::jwt-reissue[snippets='path-parameters,http-request,http-response']
operation::jwt-reissue[snippets='http-request,http-response']

=== 액세스 토큰 재발급 예외

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public enum ErrorCode {
LOCATION_NOT_FOUND("308", "주변 모각코가 존재하지 않습니다."),
VERIFICATION_DUPLICATED("309", "이미 인증이 완료된 회원입니다."),
PARTICIPANT_DUPLICATED("310", "중복된 회원입니다."),
EXCEED_GROUP_CAPACITY("311", "그룹 수용 인원을 초과합니다.");
EXCEED_GROUP_CAPACITY("311", "그룹 수용 인원을 초과합니다."),
PARTICIPATION_NOT_FOUND("312", "참여 요청을 조회할 수 없습니다.");

private final String code;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ public static ResourceNotFoundException memberNotFound() {
return new ResourceNotFoundException(ErrorCode.MEMBER_NOT_FOUND);
}

public static ResourceNotFoundException participationNotFound() {
return new ResourceNotFoundException(ErrorCode.PARTICIPATION_NOT_FOUND);
}

public static ResourceNotFoundException groupNotFound() {
return new ResourceNotFoundException(ErrorCode.GROUP_NOT_FOUND);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import lombok.RequiredArgsConstructor;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -21,8 +22,26 @@ public class GroupParticipationController {
private final GroupParticipationService groupParticipationService;

@PostMapping("/{groupId}/participation")
@ResponseStatus(HttpStatus.NO_CONTENT)
@ResponseStatus(HttpStatus.CREATED)
public void participate(@PathVariable final Long groupId, @AuthMember final Member member) {
groupParticipationService.participate(groupId, member);
}

@PostMapping("/{groupId}/participation/{memberId}")
@ResponseStatus(HttpStatus.CREATED)
public void approve(
@PathVariable final Long groupId,
@PathVariable final Long memberId,
@AuthMember final Member host) {
groupParticipationService.approve(groupId, memberId, host);
}

@PatchMapping("/{groupId}/participation/{memberId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void reject(
@PathVariable final Long groupId,
@PathVariable final Long memberId,
@AuthMember final Member host) {
ohksj77 marked this conversation as resolved.
Show resolved Hide resolved
groupParticipationService.reject(groupId, memberId, host);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.amorgakco.backend.groupparticipation.domain;

import com.amorgakco.backend.global.BaseTime;
import com.amorgakco.backend.global.exception.IllegalAccessException;
import com.amorgakco.backend.group.domain.Group;
import com.amorgakco.backend.member.domain.Member;
import com.amorgakco.backend.participant.domain.Participant;

import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
Expand Down Expand Up @@ -31,15 +33,30 @@ public class GroupParticipation extends BaseTime {

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn
private Member member;
private Member participant;

@Enumerated(EnumType.STRING)
private ParticipationStatus participationStatus;

@Builder
public GroupParticipation(final Group group, final Member member) {
this.group = group;
this.member = member;
this.participant = member;
this.participationStatus = ParticipationStatus.PENDING;
}

public void approve(final Member host) {
if (group.isNotGroupHost(host.getId())) {
throw IllegalAccessException.noAuthorityForGroup();
}
participationStatus = ParticipationStatus.APPROVED;
group.addParticipants(new Participant(participant));
}

public void reject(final Member host) {
if (group.isNotGroupHost(host.getId())) {
throw IllegalAccessException.noAuthorityForGroup();
}
participationStatus = ParticipationStatus.REJECTED;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@
import com.amorgakco.backend.groupparticipation.domain.GroupParticipation;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface GroupParticipationRepository extends JpaRepository<GroupParticipation, Long> {}
public interface GroupParticipationRepository extends JpaRepository<GroupParticipation, Long> {
@Query(
"select gp from GroupParticipation gp join fetch gp.group where gp.group.id = :groupId and gp.participant.id =:memberId")
Optional<GroupParticipation> findByGroupIdAndMemberId(
@Param("groupId") Long groupId, @Param("memberId") Long memberId);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.amorgakco.backend.groupparticipation.service;

import com.amorgakco.backend.global.exception.ResourceNotFoundException;
import com.amorgakco.backend.group.domain.Group;
import com.amorgakco.backend.group.service.GroupService;
import com.amorgakco.backend.groupparticipation.domain.GroupParticipation;
Expand Down Expand Up @@ -34,7 +35,33 @@ public void participate(final Long groupId, final Member member) {
groupParticipationMapper.toGroupParticipation(group, member);
groupParticipationRepository.save(groupParticipation);
final NotificationRequest notificationRequest =
NotificationCreator.groupJoiningNotification(member, group.getHost());
NotificationCreator.participationNotification(member, group.getHost());
notificationPublisher.sendSmsAndFcmWebPush(notificationRequest);
}

@Transactional
public void approve(final Long groupId, final Long memberId, final Member host) {
final GroupParticipation groupParticipation =
groupParticipationRepository
.findByGroupIdAndMemberId(groupId, memberId)
.orElseThrow(ResourceNotFoundException::participationNotFound);
groupParticipation.approve(host);
final NotificationRequest notificationRequest =
NotificationCreator.participationApproveNotification(
host, groupParticipation.getParticipant());
notificationPublisher.sendSmsAndFcmWebPush(notificationRequest);
}

@Transactional
public void reject(final Long groupId, final Long memberId, final Member host) {
final GroupParticipation groupParticipation =
groupParticipationRepository
.findByGroupIdAndMemberId(groupId, memberId)
.orElseThrow(ResourceNotFoundException::participationNotFound);
groupParticipation.reject(host);
final NotificationRequest notificationRequest =
NotificationCreator.participationRejectNotification(
host, groupParticipation.getParticipant());
notificationPublisher.sendFcmWebPush(notificationRequest);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,18 @@
@RestController
@RequiredArgsConstructor
@Slf4j
@RequestMapping("/token")
@RequestMapping("/tokens")
public class JwtController {

private final JwtService jwtService;
private final JwtCookieLoader jwtCookieLoader;

@PostMapping("/{memberId}")
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public AccessTokenResponse reissueAccessToken(
@CookieValue(value = "refresh-token") final String refreshToken,
@PathVariable final String memberId,
final HttpServletResponse response) {
final MemberJwt memberJwt = jwtService.reissue(refreshToken, memberId);
final MemberJwt memberJwt = jwtService.reissue(refreshToken);
jwtCookieLoader.loadCookie(response, memberJwt.refreshToken());
return new AccessTokenResponse(memberJwt.accessToken());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,11 @@ public class JwtService {
private final JwtCreator jwtCreator;
private final RefreshTokenRepository refreshTokenRepository;

public MemberJwt reissue(final String refreshToken, final String requestMemberId) {
public MemberJwt reissue(final String refreshToken) {
final RefreshToken savedRefreshToken = findRefreshTokenFromRedis(refreshToken);
final String savedMemberId = savedRefreshToken.getMemberId();
if (jwtValidator.areBothNotEqual(savedMemberId, requestMemberId)) {
throw JwtAuthenticationException.loginAgain();
}
refreshTokenRepository.delete(savedRefreshToken);
return createAndSaveMemberToken(requestMemberId);
return createAndSaveMemberToken(savedMemberId);
}

private RefreshToken findRefreshTokenFromRedis(final String token) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/notification")
@RequestMapping("/notifications")
@RequiredArgsConstructor
public class NotificationController {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
@RequiredArgsConstructor
@Getter
public enum NotificationTitle {
GROUP_JOINING_REQUEST("참여요청");
PARTICIPATION_REQUEST("참여 요청"),
PARTICIPATION_APPROVED("참여 허가"),
PARTICIPATION_REJECTED("참여 거절");

private final String title;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,35 @@
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class NotificationCreator {

public static NotificationRequest groupJoiningNotification(
public static NotificationRequest participationNotification(
final Member sender, final Member receiver) {
return NotificationRequest.builder()
.sender(sender)
.receiver(receiver)
.notificationTitle(NotificationTitle.GROUP_JOINING_REQUEST)
.content(sender.getNickname() + "님 께서 모각코에 참여하길 원합니다.")
.notificationTitle(NotificationTitle.PARTICIPATION_REQUEST)
.content(sender.getNickname() + "님께서 모각코에 참여하길 원합니다.")
.build();
}

public static NotificationRequest participationApproveNotification(
final Member sender, final Member receiver) {
return NotificationRequest.builder()
.sender(sender)
.receiver(receiver)
.notificationTitle(NotificationTitle.PARTICIPATION_APPROVED)
.content(
sender.getNickname() + "님께서" + receiver.getNickname() + "의 모각코 참여를 허가했습니다.")
songhaechan marked this conversation as resolved.
Show resolved Hide resolved
.build();
}

public static NotificationRequest participationRejectNotification(
final Member sender, final Member receiver) {
return NotificationRequest.builder()
.sender(sender)
.receiver(receiver)
.notificationTitle(NotificationTitle.PARTICIPATION_REJECTED)
.content(
sender.getNickname() + "님께서" + receiver.getNickname() + "의 모각코 참여를 거절했습니다.")
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public NotificationMessageResponse toNotificationMessageResponse(
final Slice<Notification> notificationSlice) {
return NotificationMessageResponse.builder()
.page(notificationSlice.getNumber())
.elementSize(notificationSlice.getSize())
.elementSize(notificationSlice.getContent().size())
.hasNext(notificationSlice.hasNext())
.notificationMessages(getNotificationMessages(notificationSlice.getContent()))
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,18 @@
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ParticipantService {

private static final Integer PAGE_SIZE = 10;
private final ParticipantRepository participantRepository;
private final ParticipantMapper participantMapper;

@Transactional
public void verifyParticipantLocation(
final LocationVerificationRequest request, final Long memberId) {
final Participant participant =
Expand Down
Loading
Loading