Skip to content

Commit

Permalink
Merge pull request Team-TenTen#150 from Team-TenTen/Feature/get-notif…
Browse files Browse the repository at this point in the history
…ications

Feature/get notifications - 알림 조회 기능 구현
  • Loading branch information
KimMinheee committed Nov 26, 2023
2 parents 8e46239 + 56e4c95 commit 65ff009
Show file tree
Hide file tree
Showing 18 changed files with 464 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
.requestMatchers("/spaces/search/me").authenticated()
.requestMatchers("/members/profile").authenticated()
.requestMatchers("/spaces/favorites/me").authenticated()
.requestMatchers("/notifications/invitations").authenticated()
.requestMatchers(HttpMethod.GET).permitAll() // 임시로 풀어준 것 운영시에는 허용 주소 관리
.requestMatchers("/members/join").permitAll()
.requestMatchers("/members/emails/**").permitAll()
Expand Down Expand Up @@ -104,7 +105,7 @@ public static class CustomAuthenticationEntryPoint implements AuthenticationEntr

@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
AuthenticationException authException) throws IOException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.tenten.linkhub.domain.notification.controller;

import com.tenten.linkhub.domain.auth.MemberDetails;
import com.tenten.linkhub.domain.notification.controller.dto.SpaceInviteNotificationGetApiRequest;
import com.tenten.linkhub.domain.notification.controller.dto.SpaceInviteNotificationGetApiResponses;
import com.tenten.linkhub.domain.notification.controller.mapper.NotificationApiMapper;
import com.tenten.linkhub.domain.notification.service.NotificationService;
import com.tenten.linkhub.domain.notification.service.dto.SpaceInviteNotificationGetRequest;
import com.tenten.linkhub.domain.notification.service.dto.SpaceInviteNotificationGetResponses;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "Notification", description = "notification 템플릿 API Document")
@RequiredArgsConstructor
@RestController
@RequestMapping("/notifications")
public class NotificationController {
private final NotificationService notificationService;
private final NotificationApiMapper apiMapper;

/**
* 스페이스 초대 알림 조회 API
*/
@Operation(
summary = "스페이스 초대 알림 조회 API ", description = "[JWT 필요] - pageNumber, pageSize를 받아 스페이스 초대 알림 내역을 조회합니다. ",
responses = {
@ApiResponse(responseCode = "200", description = "초대 알림 조회가 성공적으로 완료되었습니다.")
})
@GetMapping(value = "/invitations")
public ResponseEntity<SpaceInviteNotificationGetApiResponses> getInviteNotifications(
@ModelAttribute SpaceInviteNotificationGetApiRequest request,
@AuthenticationPrincipal MemberDetails memberDetails
) {
PageRequest pageRequest = PageRequest.of(
request.pageNumber(),
request.pageSize()
);

SpaceInviteNotificationGetRequest serviceRequest = apiMapper.toSpaceInvitationGetRequest(
pageRequest,
memberDetails.memberId()
);

SpaceInviteNotificationGetResponses responses = notificationService.getSpaceInvitations(serviceRequest);
SpaceInviteNotificationGetApiResponses apiResponses = SpaceInviteNotificationGetApiResponses.from(responses);

return ResponseEntity
.ok()
.body(apiResponses);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.tenten.linkhub.domain.notification.controller.dto;

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

public record SpaceInviteNotificationGetApiRequest(
@Schema(title = "페이지 번호", example = "1")
Integer pageNumber,

@Schema(title = "페이지 크기", example = "10")
Integer pageSize
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.tenten.linkhub.domain.notification.controller.dto;

import com.tenten.linkhub.domain.notification.model.NotificationType;

public record SpaceInviteNotificationGetApiResponse(
Long notificationId,
Long invitingMemberId,
Long spaceId,
String invitingMemberName,
String spaceName,
NotificationType notificationType,
boolean isAccepted
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.tenten.linkhub.domain.notification.controller.dto;

import com.tenten.linkhub.domain.notification.service.dto.SpaceInviteNotificationGetResponses;
import com.tenten.linkhub.global.util.PageMetaData;
import org.springframework.data.domain.Slice;

import java.util.List;

public record SpaceInviteNotificationGetApiResponses(
List<SpaceInviteNotificationGetApiResponse> responses,
PageMetaData pageMetaData
) {
public static SpaceInviteNotificationGetApiResponses from(SpaceInviteNotificationGetResponses responses) {
Slice<SpaceInviteNotificationGetApiResponse> invitationResponses = responses.responses()
.map(i -> new SpaceInviteNotificationGetApiResponse(
i.notificationId(),
i.invitingMemberId(),
i.spaceId(),
i.invitingMemberName(),
i.spaceName(),
i.notificationType(),
i.isAccepted()
));

PageMetaData pageMetaData = new PageMetaData(
invitationResponses.hasNext(),
invitationResponses.getSize(),
invitationResponses.getNumber());

return new SpaceInviteNotificationGetApiResponses(
invitationResponses.getContent(),
pageMetaData
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.tenten.linkhub.domain.notification.controller.mapper;

import com.tenten.linkhub.domain.notification.service.dto.SpaceInviteNotificationGetRequest;
import org.mapstruct.InjectionStrategy;
import org.mapstruct.Mapper;
import org.springframework.data.domain.Pageable;

@Mapper(componentModel = "spring",
injectionStrategy = InjectionStrategy.CONSTRUCTOR
)
public interface NotificationApiMapper {
SpaceInviteNotificationGetRequest toSpaceInvitationGetRequest(Pageable pageable, Long memberId);
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package com.tenten.linkhub.domain.notification.repository;

import com.tenten.linkhub.domain.notification.model.Notification;
import com.tenten.linkhub.domain.notification.repository.dto.NotificationGetQueryCondition;
import com.tenten.linkhub.domain.notification.repository.dto.SpaceInvitationNotificationGetDto;
import com.tenten.linkhub.domain.notification.repository.querydsl.NotificationQueryDslRepository;
import com.tenten.linkhub.global.exception.DataNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Slice;
import org.springframework.stereotype.Repository;

@RequiredArgsConstructor
@Repository
public class DefaultNotificationRepository implements NotificationRepository {

private final NotificationJpaRepository notificationJpaRepository;
private final NotificationQueryDslRepository notificationQueryDslRepository;

@Override
public Notification getById(Long notificationId) {
Expand All @@ -21,5 +26,10 @@ public Notification getById(Long notificationId) {
public Notification save(Notification notification) {
return notificationJpaRepository.save(notification);
}
}

@Override
public Slice<SpaceInvitationNotificationGetDto> getInviteNotifications(NotificationGetQueryCondition condition) {
return notificationQueryDslRepository.getSpaceInvitationNotifications(condition);
}

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package com.tenten.linkhub.domain.notification.repository;

import com.tenten.linkhub.domain.notification.model.Notification;
import com.tenten.linkhub.domain.notification.repository.dto.NotificationGetQueryCondition;
import com.tenten.linkhub.domain.notification.repository.dto.SpaceInvitationNotificationGetDto;
import org.springframework.data.domain.Slice;

public interface NotificationRepository {
Notification getById(Long notificationId);

Slice<SpaceInvitationNotificationGetDto> getInviteNotifications(NotificationGetQueryCondition condition);

Notification save(Notification notification);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.tenten.linkhub.domain.notification.repository.dto;

import org.springframework.data.domain.Pageable;

public record NotificationGetQueryCondition(
Long memberId,
Pageable pageable
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.tenten.linkhub.domain.notification.repository.dto;

import com.querydsl.core.annotations.QueryProjection;
import com.tenten.linkhub.domain.notification.model.NotificationType;

public record SpaceInvitationNotificationGetDto(
Long notificationId,
Long invitingMemberId,
Long spaceId,
String invitingMemberName,
String spaceName,
NotificationType notificationType,
boolean isAccepted
) {
@QueryProjection
public SpaceInvitationNotificationGetDto(Long notificationId,
Long invitingMemberId,
Long spaceId,
String invitingMemberName,
String spaceName,
NotificationType notificationType,
boolean isAccepted) {
this.notificationId = notificationId;
this.invitingMemberId = invitingMemberId;
this.spaceId = spaceId;
this.invitingMemberName = invitingMemberName;
this.spaceName = spaceName;
this.notificationType = notificationType;
this.isAccepted = isAccepted;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.tenten.linkhub.domain.notification.repository.querydsl;

import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.tenten.linkhub.domain.notification.repository.dto.NotificationGetQueryCondition;
import com.tenten.linkhub.domain.notification.repository.dto.QSpaceInvitationNotificationGetDto;
import com.tenten.linkhub.domain.notification.repository.dto.SpaceInvitationNotificationGetDto;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.stereotype.Repository;

import java.util.List;

import static com.tenten.linkhub.domain.member.model.QMember.member;
import static com.tenten.linkhub.domain.notification.model.QNotification.notification;
import static com.tenten.linkhub.domain.space.model.space.QInvitation.invitation;
import static com.tenten.linkhub.domain.space.model.space.QSpace.space;

@Repository
public class NotificationQueryDslRepository {
private final JPAQueryFactory jpaQueryFactory;

public NotificationQueryDslRepository(JPAQueryFactory jpaQueryFactory) {
this.jpaQueryFactory = jpaQueryFactory;

}

public Slice<SpaceInvitationNotificationGetDto> getSpaceInvitationNotifications(NotificationGetQueryCondition condition) {
List<SpaceInvitationNotificationGetDto> notificationGetDtos = jpaQueryFactory
.select(new QSpaceInvitationNotificationGetDto(
notification.id,
notification.senderId,
invitation.space.id,
member.nickname,
space.spaceName,
notification.notificationType,
invitation.isAccepted
))
.from(notification)
.join(invitation).on(invitation.notificationId.eq(notification.id))
.join(invitation.space, space)
.join(member).on(checkMemberJoinCondition(condition.memberId()))
.offset(condition.pageable().getOffset())
.limit(condition.pageable().getPageSize() + 1)
.fetch();

boolean hasNext = false;

if (notificationGetDtos.size() > condition.pageable().getPageSize()) {
notificationGetDtos.remove(condition.pageable().getPageSize());
hasNext = true;
}

return new SliceImpl<>(notificationGetDtos, condition.pageable(), hasNext);

}

BooleanExpression checkMemberJoinCondition(Long memberId) {
BooleanExpression joinMemberCondition = notification.senderId.eq(member.id);
BooleanExpression isSameMemberId = notification.recipientId.eq(memberId);

return joinMemberCondition.and(isSameMemberId);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,23 @@
import com.tenten.linkhub.domain.notification.model.Notification;
import com.tenten.linkhub.domain.notification.model.NotificationType;
import com.tenten.linkhub.domain.notification.repository.NotificationRepository;
import com.tenten.linkhub.domain.notification.repository.dto.SpaceInvitationNotificationGetDto;
import com.tenten.linkhub.domain.notification.service.dto.NotificationCreateRequest;
import com.tenten.linkhub.domain.notification.service.dto.SpaceInviteNotificationGetRequest;
import com.tenten.linkhub.domain.notification.service.dto.SpaceInviteNotificationGetResponses;
import com.tenten.linkhub.domain.notification.service.mapper.NotificationMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Slice;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

private final NotificationRepository notificationRepository;
private final NotificationMapper notificationMapper;

@Transactional
public void changeIsCheckedAsTrue(Long notificationId, Long memberId) {
Expand All @@ -21,6 +28,13 @@ public void changeIsCheckedAsTrue(Long notificationId, Long memberId) {
notification.changeIsCheckedAsTrue(memberId);
}

public SpaceInviteNotificationGetResponses getSpaceInvitations(SpaceInviteNotificationGetRequest request) {
Slice<SpaceInvitationNotificationGetDto> notificationGetDtos = notificationRepository.getInviteNotifications(notificationMapper.toQueryCondition(request));

return SpaceInviteNotificationGetResponses.from(notificationGetDtos);
}

@Transactional
public Long createNotification(NotificationCreateRequest request) {

Notification notification = new Notification(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.tenten.linkhub.domain.notification.service.dto;

import org.springframework.data.domain.Pageable;

public record SpaceInviteNotificationGetRequest(
Pageable pageable,
Long memberId
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.tenten.linkhub.domain.notification.service.dto;

import com.tenten.linkhub.domain.notification.model.NotificationType;

public record SpaceInviteNotificationGetResponse(
Long notificationId,
Long invitingMemberId,
Long spaceId,
String invitingMemberName,
String spaceName,
NotificationType notificationType,
boolean isAccepted
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.tenten.linkhub.domain.notification.service.dto;

import com.tenten.linkhub.domain.notification.repository.dto.SpaceInvitationNotificationGetDto;
import org.springframework.data.domain.Slice;

public record SpaceInviteNotificationGetResponses(
Slice<SpaceInviteNotificationGetResponse> responses
) {
public static SpaceInviteNotificationGetResponses from(Slice<SpaceInvitationNotificationGetDto> notificationGetDtos) {
Slice<SpaceInviteNotificationGetResponse> responseList = notificationGetDtos.map(
n -> new SpaceInviteNotificationGetResponse(
n.notificationId(),
n.invitingMemberId(),
n.spaceId(),
n.invitingMemberName(),
n.spaceName(),
n.notificationType(),
n.isAccepted()
)
);

return new SpaceInviteNotificationGetResponses(responseList);
}
}
Loading

0 comments on commit 65ff009

Please sign in to comment.