Skip to content

Commit

Permalink
YEL-88 [feat] 푸시 알림 구현
Browse files Browse the repository at this point in the history
YEL-88 [feat] 푸시 알림 구현
  • Loading branch information
devkwonsehoon authored Aug 6, 2023
2 parents 9cf84bd + cd80145 commit dba21a7
Show file tree
Hide file tree
Showing 35 changed files with 516 additions and 67 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/deploy-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,12 @@ jobs:
cd ./src/main/resources
touch ./application.yml
echo "$APPLICATION" > ./application.yml
touch ./firebase_key.json
echo "$FIREBASE_JSON" > ./firebase_key.json
sed -i 's/#/"/g' ./firebase_key.json
env:
APPLICATION: ${{ secrets.APPLICATION_TEST }}
FIREBASE_JSON: ${{ secrets.FIREBASE_JSON }}
shell: bash

- name: 🐘 Gradle로 빌드 실행
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,12 @@ jobs:
cd ./src/main/resources
touch ./application.yml
echo "$APPLICATION" > ./application.yml
touch ./firebase_key.json
echo "$FIREBASE_JSON" > ./firebase_key.json
sed -i 's/#/"/g' ./firebase_key.json
env:
APPLICATION: ${{ secrets.APPLICATION }}
FIREBASE_JSON: ${{ secrets.FIREBASE_JSON }}
shell: bash

- name: 🐘 Gradle로 빌드 실행
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ out/
### config ###
application.yml
application-dev.yml
yello-*-firebase-*.json
firebase*.json

### monitoring ###
monitoring/prometheus/volume
Expand Down
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ dependencies {

runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j:8.0.31'

// FCM
implementation 'com.google.firebase:firebase-admin:9.2.0'

}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
public record SignUpRequest(
@NotNull Social social,
@NotNull String uuid,
@NotNull String deviceToken,
@NotNull @Email String email,
@NotNull String profileImage,
@NotNull Long groupId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public OAuthResponse oauthLogin(OAuthRequest oAuthRequest) {
final ResponseEntity<KakaoTokenInfo> response = RestUtil.getKakaoTokenInfo(
oAuthRequest.accessToken());

if (response.getStatusCode() == BAD_REQUEST || response.getStatusCode() == UNAUTHORIZED) {
if (response.getStatusCode()==BAD_REQUEST || response.getStatusCode()==UNAUTHORIZED) {
throw new OAuthException(OAUTH_TOKEN_EXCEPTION);
}

Expand Down Expand Up @@ -133,6 +133,7 @@ public User signUpUser(SignUpRequest signUpRequest) {
School group = schoolRepository.getById(signUpRequest.groupId());

final User newSignInUser = userRepository.save(User.of(signUpRequest, group));
tokenValueOperations.setDeviceToken(newSignInUser.getUuid(), signUpRequest.deviceToken());
return newSignInUser;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.yello.server.global.common.SuccessCode;
import com.yello.server.global.common.annotation.AccessTokenUser;
import com.yello.server.global.common.dto.BaseResponse;
import com.yello.server.infrastructure.firebase.service.NotificationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
Expand Down Expand Up @@ -43,6 +44,7 @@
public class FriendController {

private final FriendService friendService;
private final NotificationService notificationService;

@Operation(summary = "친구 추가하기 API", responses = {
@ApiResponse(
Expand All @@ -55,7 +57,8 @@ public BaseResponse addFriend(
@Parameter(name = "targetId", description = "친구 신청할 상대 유저의 아이디 값 입니다.")
@Valid @PathVariable Long targetId,
@AccessTokenUser User user) {
friendService.addFriend(user.getId(), targetId);
val data = friendService.addFriend(user.getId(), targetId);
notificationService.sendFriendNotification(data);
return BaseResponse.success(ADD_FRIEND_SUCCESS);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.yello.server.domain.user.repository.UserRepository;
import com.yello.server.domain.vote.repository.VoteRepository;
import com.yello.server.global.common.factory.PaginationFactory;
import com.yello.server.infrastructure.firebase.service.NotificationService;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
Expand All @@ -38,6 +39,7 @@ public class FriendService {
private final FriendRepository friendRepository;
private final UserRepository userRepository;
private final VoteRepository voteRepository;
private final NotificationService notificationService;

public FriendsResponse findAllFriends(Pageable pageable, Long userId) {
final Page<Friend> friendsData = friendRepository.findAllFriendsByUserId(pageable, userId);
Expand All @@ -54,7 +56,7 @@ public FriendsResponse findAllFriends(Pageable pageable, Long userId) {
}

@Transactional
public void addFriend(Long userId, Long targetId) {
public Friend addFriend(Long userId, Long targetId) {
final User target = userRepository.getById(targetId);
final User user = userRepository.getById(userId);

Expand All @@ -64,8 +66,9 @@ public void addFriend(Long userId, Long targetId) {
throw new FriendException(EXIST_FRIEND_EXCEPTION);
}

friendRepository.save(Friend.createFriend(user, target));
Friend friend = friendRepository.save(Friend.createFriend(user, target));
friendRepository.save(Friend.createFriend(target, user));
return friend;
}

public List<FriendShuffleResponse> findShuffledFriend(Long userId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.yello.server.global.common.SuccessCode;
import com.yello.server.global.common.annotation.AccessTokenUser;
import com.yello.server.global.common.dto.BaseResponse;
import com.yello.server.infrastructure.firebase.service.NotificationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
Expand All @@ -47,6 +48,7 @@
public class VoteController {

private final VoteService voteService;
private final NotificationService notificationService;

@Operation(summary = "내 투표 전체 조회 API", responses = {
@ApiResponse(
Expand Down Expand Up @@ -141,7 +143,8 @@ public BaseResponse<VoteCreateResponse> createVote(
@RequestBody CreateVoteRequest request
) {
val data = voteService.createVote(user.getId(), request);
return BaseResponse.success(CREATE_VOTE_SUCCESS, data);
data.votes().forEach(notificationService::sendYelloNotification);
return BaseResponse.success(CREATE_VOTE_SUCCESS, data.toOnlyPoint());
}

@Operation(summary = "투표 이름 부분 조회 API", responses = {
Expand All @@ -155,6 +158,7 @@ public BaseResponse<RevealNameResponse> revealNameHint(
@PathVariable Long voteId
) {
val data = voteService.revealNameHint(user.getId(), voteId);

return BaseResponse.success(SuccessCode.REVEAL_NAME_HINT_SUCCESS, data);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
package com.yello.server.domain.vote.dto.response;

import com.yello.server.domain.vote.entity.Vote;
import java.util.List;
import lombok.Builder;

@Builder
public record VoteCreateResponse(
Integer point
Integer point,
List<Vote> votes
) {

public static VoteCreateResponse of(Integer point) {
public static VoteCreateResponse of(Integer point, List<Vote> votes) {
return VoteCreateResponse.builder()
.point(point)
.votes(votes)
.build();
}

public VoteCreateResponse toOnlyPoint() {
return VoteCreateResponse.builder()
.point(point)
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import com.yello.server.domain.vote.exception.VoteNotFoundException;
import com.yello.server.domain.vote.repository.VoteRepository;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -140,21 +141,25 @@ public VoteAvailableResponse checkVoteAvailable(Long userId) {

@Transactional
public VoteCreateResponse createVote(Long userId, CreateVoteRequest request) {
List<Vote> votes = new ArrayList<>();
final User sender = userRepository.getById(userId);

final List<VoteAnswer> voteAnswerList = request.voteAnswerList();
IntStream.range(0, voteAnswerList.size())
.forEach(index -> {
if (index > 0 && voteAnswerList.get(index - 1).questionId() == voteAnswerList.get(index)
.questionId()) {
VoteAnswer answer = voteAnswerList.get(index);

if (index > 0 && voteAnswerList.get(index - 1).questionId().equals(answer.questionId())) {
throw new VoteForbiddenException(DUPLICATE_VOTE_EXCEPTION);
}

User receiver = userRepository.getById(voteAnswerList.get(index).friendId());
Question question = questionRepository.findById(voteAnswerList.get(index).questionId());
Vote newVote = Vote.createVote(voteAnswerList.get(index).keywordName(), sender, receiver,
question, voteAnswerList.get(index).colorIndex());
voteRepository.save(newVote);
User receiver = userRepository.getById(answer.friendId());
Question question = questionRepository.findById(answer.questionId());
Vote newVote = Vote.createVote(answer.keywordName(), sender, receiver,
question, answer.colorIndex());

Vote savedVote = voteRepository.save(newVote);
votes.add(savedVote);
});

final Optional<Cooldown> cooldown = cooldownRepository.findByUserId(sender.getId());
Expand All @@ -165,7 +170,7 @@ public VoteCreateResponse createVote(Long userId, CreateVoteRequest request) {
}

sender.plusPoint(request.totalPoint());
return VoteCreateResponse.of(sender.getPoint());
return VoteCreateResponse.of(sender.getPoint(), votes);
}

@Transactional
Expand All @@ -177,7 +182,7 @@ public RevealNameResponse revealNameHint(Long userId, Long voteId) {
}

final Vote vote = voteRepository.getById(voteId);
if (vote.getNameHint() != NAME_HINT_DEFAULT) {
if (vote.getNameHint()!=NAME_HINT_DEFAULT) {
throw new VoteNotFoundException(INVALID_VOTE_EXCEPTION);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public enum ErrorCode {
NOT_FOUND_VOTE_EXCEPTION(NOT_FOUND, "존재하지 않는 투표입니다."),
NOT_FOUND_QUESTION_EXCEPTION(NOT_FOUND, "존재하지 않는 질문입니다."),
NOT_FOUND_FRIEND_EXCEPTION(NOT_FOUND, "존재하지 않는 친구이거나 친구 관계가 아닙니다."),
REDIS_NOT_FOUND_UUID(NOT_FOUND, "uuid에 해당하는 디바이스 토큰 정보를 찾을 수 없습니다."),

/**
* 409 CONFLICT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.yello.server.domain.vote.exception.VoteNotFoundException;
import com.yello.server.global.common.dto.BaseResponse;
import com.yello.server.infrastructure.redis.exception.RedisException;
import com.yello.server.infrastructure.redis.exception.RedisNotFoundException;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.http.converter.HttpMessageNotReadableException;
Expand Down Expand Up @@ -120,7 +121,8 @@ public ResponseEntity<BaseResponse> ForbiddenException(CustomException exception
VoteNotFoundException.class,
GroupNotFoundException.class,
FriendNotFoundException.class,
QuestionNotFoundException.class
QuestionNotFoundException.class,
RedisNotFoundException.class
})
public ResponseEntity<BaseResponse> NotFoundException(CustomException exception) {
return ResponseEntity.status(NOT_FOUND)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.yello.server.infrastructure.firebase.configuration;

import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import javax.annotation.PostConstruct;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

@Log4j2
@Configuration
public class FirebaseConfiguration {

@Value("${firebase.config-path}")
private String firebaseConfigPath;

@Value("${firebase.scope}")
private String scope;

@PostConstruct
public void initialize() throws IOException {
ClassPathResource resource = new ClassPathResource(firebaseConfigPath);

try (InputStream inputStream = resource.getInputStream()) {
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(inputStream))
.build();

if (FirebaseApp.getApps().isEmpty()) {
FirebaseApp.initializeApp(options);
log.info("Successfully firebase application initialized!");
}
} catch (FileNotFoundException e) {
log.error("Firebase ServiceAccountKey FileNotFoundException" + e.getMessage());
} catch (IOException e) {
log.error("FirebaseOptions IOException" + e.getMessage());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.yello.server.infrastructure.firebase.dto.request;

import com.google.firebase.messaging.Notification;
import com.yello.server.domain.user.entity.Gender;
import com.yello.server.domain.user.entity.User;
import java.text.MessageFormat;
import lombok.Builder;

@Builder
public record NotificationMessage(
String title,
String message
) {

public static NotificationMessage toVoteAvailableNotificationContent() {
return NotificationMessage.builder()
.title("친구에게 쪽지 보내고 포인트 받기")
.message("대기시간이 다 지났어요. 친구들에게 투표해봐요!")
.build();
}

public static NotificationMessage toFriendNotificationContent(User user) {
return NotificationMessage.builder()
.title(MessageFormat.format("{0}님이 회원님과 친구가 되었어요", user.getName()))
.message("친구와 쪽지를 주고받아 보세요!")
.build();
}

public static NotificationMessage toYelloNotificationContent(User user) {
final String target = Gender.MALE.getIntial().equals(user.getGender().getIntial()) ? "남학생" : "여학생";
return NotificationMessage.builder()
.title(MessageFormat.format("{0}이 쪽지를 보냈어요!", target))
.message("나는 너가 ...")
.build();
}

public Notification toNotification() {
return Notification.builder()
.setTitle(title)
.setBody(message)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.yello.server.infrastructure.firebase.manager;

import com.google.firebase.messaging.Message;
import com.google.firebase.messaging.Notification;

public interface FCMManager {

void send(Message message);

Message createMessage(String deviceToken, Notification notification);

Message createMessage(String deviceToken, Notification notification, String path);

}
Loading

0 comments on commit dba21a7

Please sign in to comment.