Skip to content

Commit

Permalink
Merge pull request #71 from AndLetgo/develop
Browse files Browse the repository at this point in the history
🚀 [DEPLOY]: 11차 배포
  • Loading branch information
gyehwan24 authored Jan 12, 2024
2 parents 4de2f08 + f017635 commit e542b7d
Show file tree
Hide file tree
Showing 24 changed files with 264 additions and 79 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ jobs:
touch ./src/main/resources/forbidden-words.txt
echo "${{ secrets.FORBIDDEN_TXT }}" | base64 --decode > src/main/resources/forbidden-words.txt
touch ./src/main/resources/services-account.json
echo "${{ secrets.SERVICES_ACCOUNT_JSON }}" | base64 --decode > src/main/resources/services-account.json
#추가
- name: Make Gradle Wrapper script executable
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ out/
/src/main/resources/application-auth.yml

/src/main/resources/forbidden-words.txt
/src/main/resources/services-account.json
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ dependencies {
// test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'

//fcm
implementation 'com.google.firebase:firebase-admin:6.8.1'
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.2.2'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import depth.jeonsilog.domain.exhibition.domain.Exhibition;
import depth.jeonsilog.domain.exhibition.domain.OperatingKeyword;
import depth.jeonsilog.domain.exhibition.domain.repository.ExhibitionRepository;
import depth.jeonsilog.domain.fcm.application.FcmService;
import depth.jeonsilog.domain.follow.domain.Follow;
import depth.jeonsilog.domain.follow.domain.repository.FollowRepository;
import depth.jeonsilog.domain.interest.domain.Interest;
Expand All @@ -24,7 +25,6 @@
import depth.jeonsilog.global.payload.Message;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
Expand All @@ -33,6 +33,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
Expand All @@ -53,6 +54,7 @@ public class AlarmService {
private final UserRepository userRepository;
private final ExhibitionRepository exhibitionRepository;
private final UserService userService;
private final FcmService fcmService;

private static String BEFORE_7DAYS = "전시 시작까지 7일 남았어요";
private static String BEFORE_3DAYS = "전시 시작까지 3일 남았어요";
Expand Down Expand Up @@ -156,67 +158,83 @@ public ResponseEntity<?> checkAlarm(UserPrincipal userPrincipal, Long alarmId) {

// TODO: 팔로잉한 사람의 new 감상평 -> 알림 생성
@Transactional
public void makeReviewAlarm(Review review) {
public void makeReviewAlarm(Review review) throws IOException {
List<Follow> follows = followRepository.findAllByFollow(review.getUser());
for (Follow follow : follows) {
User receiver = follow.getUser();
Alarm alarm = Alarm.builder()
.user(follow.getUser())
.user(receiver)
.senderId(follow.getFollow().getId())
.alarmType(AlarmType.REVIEW)
.targetId(review.getId())
.clickId(review.getId())
.isChecked(false)
.build();
alarmRepository.save(alarm);

if (!receiver.getIsRecvActive() || receiver.getFcmToken() == null) return;
fcmService.send(receiver.getFcmToken(), "전시로그", follow.getFollow().getNickname() + " 님이 감상평 남겼어요");
}
}

// 팔로잉한 사람의 new 별점 -> 알림 생성
// TODO: 팔로잉한 사람의 new 별점 -> 알림 생성
@Transactional
public void makeRatingAlarm(Rating rating) {
public void makeRatingAlarm(Rating rating) throws IOException {
List<Follow> follows = followRepository.findAllByFollow(rating.getUser());
for (Follow follow : follows) {
User receiver = follow.getUser();
Alarm alarm = Alarm.builder()
.user(follow.getUser())
.user(receiver)
.senderId(follow.getFollow().getId())
.alarmType(AlarmType.RATING)
.targetId(rating.getId())
.clickId(rating.getExhibition().getId())
.isChecked(false)
.build();
alarmRepository.save(alarm);

if (!receiver.getIsRecvActive() || receiver.getFcmToken() == null) return;
fcmService.send(receiver.getFcmToken(), "전시로그", follow.getFollow().getNickname() + " 님이 별점을 남겼어요");
}
}

// 나의 감상평에 달린 댓글 -> 알림 생성
// TODO: 나의 감상평에 달린 댓글 -> 알림 생성
@Transactional
public void makeReplyAlarm(Reply reply) {
public void makeReplyAlarm(Reply reply) throws IOException {
User receiver = reply.getReview().getUser();
Alarm alarm = Alarm.builder()
.user(reply.getReview().getUser())
.user(receiver)
.senderId(reply.getUser().getId())
.alarmType(AlarmType.REPLY)
.targetId(reply.getId())
.clickId(reply.getReview().getId())
.isChecked(false)
.build();
alarmRepository.save(alarm);

if (!receiver.getIsRecvActive() || receiver.getFcmToken() == null) return;
fcmService.send(receiver.getFcmToken(), "전시로그", reply.getUser().getNickname() + " 님이 댓글을 남겼어요");
}

// 나를 팔로우 -> 알림 생성
// TODO: 나를 팔로우 -> 알림 생성
@Transactional
public void makeFollowAlarm(Follow follow) {
public void makeFollowAlarm(Follow follow) throws IOException {
User receiver = follow.getFollow();
Alarm alarm = Alarm.builder()
.user(follow.getFollow())
.user(receiver)
.senderId(follow.getUser().getId())
.alarmType(AlarmType.FOLLOW)
.targetId(follow.getId())
.clickId(follow.getUser().getId())
.isChecked(false)
.build();
alarmRepository.save(alarm);

if (!receiver.getIsRecvActive() || receiver.getFcmToken() == null) return;
fcmService.send(receiver.getFcmToken(), "전시로그", follow.getUser().getNickname() + " 님이 나를 팔로우해요");
}

// 관심 전시회 시작 전 -> 알림 생성
// TODO: 관심 전시회 시작 전 -> 알림 생성
@Transactional
@Scheduled(cron = "0 0 9 * * *") // 오전 9시에 실행
public void makeExhibitionAlarm() {
Expand All @@ -226,24 +244,28 @@ public void makeExhibitionAlarm() {
log.info("현재 날짜: " + currentDate);
for (Interest interest : interests) {
Exhibition exhibition = interest.getExhibition();
User receiver = interest.getUser();

LocalDate targetDate = LocalDate.parse(exhibition.getStartDate(), DateTimeFormatter.ofPattern("yyyyMMdd"));
log.info("전시회 시작 날짜: " + targetDate);

Alarm alarm = null;
long daysDifference = ChronoUnit.DAYS.between(currentDate, targetDate);
if (daysDifference == 7) {
if (checkDuplicateAlarm(interest.getUser().getId(), interest.getExhibition().getId(), BEFORE_7DAYS)) continue;
if (checkDuplicateAlarm(receiver.getId(), exhibition.getId(), BEFORE_7DAYS)) continue;
alarm = AlarmConverter.toExhibitionAlarm(interest, BEFORE_7DAYS);
} else if (daysDifference == 3) {
if (checkDuplicateAlarm(interest.getUser().getId(), interest.getExhibition().getId(), BEFORE_3DAYS)) continue;
if (checkDuplicateAlarm(receiver.getId(), exhibition.getId(), BEFORE_3DAYS)) continue;
alarm = AlarmConverter.toExhibitionAlarm(interest, BEFORE_3DAYS);
} else if (daysDifference == 1) {
if (checkDuplicateAlarm(interest.getUser().getId(), interest.getExhibition().getId(), BEFORE_1DAYS)) continue;
if (checkDuplicateAlarm(receiver.getId(), exhibition.getId(), BEFORE_1DAYS)) continue;
alarm = AlarmConverter.toExhibitionAlarm(interest, BEFORE_1DAYS);
}
assert alarm != null;
alarmRepository.save(alarm);

if (!receiver.getIsRecvExhibition() || receiver.getFcmToken() == null) return;
fcmService.send(receiver.getFcmToken(), "전시로그", "[" + exhibition.getName() + "]\n" + alarm.getContents());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public static User toUser(AuthRequestDto.SignUpReq signUpReq, PasswordEncoder pa
.provider(Provider.KAKAO)
.role(Role.USER)
.isOpen(true)
.isRecvFollowing(true)
.isRecvExhibition(true)
.isRecvActive(true)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ public ResponseEntity<?> updateExhibitionDetail(UserPrincipal userPrincipal, Exh
String storedFileName = null;

if (updateExhibitionDetailReq.getIsImageChange()) { // 이미지를 변경하는 경우
if (!img.isEmpty()) { // 이미지 삭제가 아닌 이미지를 변경하거나 추가하는 경우
if (img != null) { // 이미지 삭제가 아닌 이미지를 변경하거나 추가하는 경우
storedFileName = s3Uploader.upload(img, DIRNAME);
}
// 기존 포스터 이미지가 s3에 있으면, 이미지 삭제 / 없으면(OPEN API 포스터 이미지 or NULL의 경우) 말고
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package depth.jeonsilog.domain.fcm.application;

import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.json.JSONObject;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;

@Service
@RequiredArgsConstructor
@Slf4j
public class FcmService {

// API_URL은 메세지 전송을 위해 요청하는 주소이다. {프로젝트 ID}넣기
private static final String API_URL = "https://fcm.googleapis.com/v1/projects/jeonsilog-fd54e/messages:send";
private static final String MESSAGING_SCOPE = "https://www.googleapis.com/auth/firebase.messaging";
private static final String[] SCOPES = { MESSAGING_SCOPE };

//AccessToken 발급 받기. -> Header에 포함하여 푸시 알림 요청
private static String getAccessToken() throws IOException {
ClassPathResource resource = new ClassPathResource("services-account.json");
GoogleCredential googleCredential = GoogleCredential
.fromStream(new FileInputStream(resource.getFile()))
.createScoped(Arrays.asList(SCOPES));
googleCredential.refreshToken();
log.info("액세스 토큰 발급: " + googleCredential.getAccessToken());
return googleCredential.getAccessToken();
}

public void send(String fcmToken, String title, String body) {

// 1. create message body
JSONObject jsonValue = new JSONObject();
jsonValue.put("title", title);
jsonValue.put("body", body);

JSONObject jsonData = new JSONObject();
jsonData.put("token", fcmToken);
jsonData.put("data", jsonValue);

JSONObject jsonMessage = new JSONObject();
jsonMessage.put("message", jsonData);

// 2. create token & send push
try {
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.addHeader("Authorization", "Bearer " + getAccessToken())
.addHeader("Content-Type", "application/json; UTF-8")
.url(API_URL)
.post(RequestBody.create(jsonMessage.toString(), MediaType.parse("application/json")))
.build();
Response response = okHttpClient.newCall(request).execute();

log.info("### response str : " + response.toString());
log.info("### response result : " + response.isSuccessful());
} catch (Exception e) {
e.printStackTrace();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
import depth.jeonsilog.global.payload.ApiResponse;
import depth.jeonsilog.global.payload.Message;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
Expand All @@ -35,7 +35,7 @@ public class FollowService {

// 팔로우하기
@Transactional
public ResponseEntity<?> follow(UserPrincipal userPrincipal, Long userId) {
public ResponseEntity<?> follow(UserPrincipal userPrincipal, Long userId) throws IOException {

User findUser = userService.validateUserByToken(userPrincipal);
User followUser = userService.validateUserById(userId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;

@Tag(name = "Follow API", description = "Follow 관련 API입니다.")
@RequiredArgsConstructor
@RestController
Expand All @@ -36,7 +38,7 @@ public class FollowController {
public ResponseEntity<?> follow(
@Parameter(description = "Access Token을 입력해주세요.", required = true) @CurrentUser UserPrincipal userPrincipal,
@Parameter(description = "팔로우할 유저의 ID를 입력해주세요.", required = true) @PathVariable(value = "userId") Long userId
) {
) throws IOException {
return followService.follow(userPrincipal, userId);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import depth.jeonsilog.domain.rating.domain.repository.RatingRepository;
import depth.jeonsilog.domain.rating.dto.RatingRequestDto;
import depth.jeonsilog.domain.rating.dto.RatingResponseDto;
import depth.jeonsilog.domain.review.domain.Review;
import depth.jeonsilog.domain.user.application.UserService;
import depth.jeonsilog.domain.user.domain.User;
import depth.jeonsilog.global.DefaultAssert;
Expand All @@ -23,6 +22,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.util.List;
import java.util.Optional;

Expand All @@ -39,7 +39,7 @@ public class RatingService {

// 별점 등록
@Transactional
public ResponseEntity<?> registerRating(UserPrincipal userPrincipal, RatingRequestDto.RatingReq ratingReq) {
public ResponseEntity<?> registerRating(UserPrincipal userPrincipal, RatingRequestDto.RatingReq ratingReq) throws IOException {
User findUser = userService.validateUserByToken(userPrincipal);
Exhibition exhibition = exhibitionService.validateExhibitionById(ratingReq.getExhibitionId());

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package depth.jeonsilog.domain.rating.presentation;

import depth.jeonsilog.domain.place.dto.PlaceResponseDto;
import depth.jeonsilog.domain.rating.application.RatingService;
import depth.jeonsilog.domain.rating.dto.RatingRequestDto;
import depth.jeonsilog.domain.rating.dto.RatingResponseDto;
Expand All @@ -10,7 +9,6 @@
import depth.jeonsilog.global.payload.Message;
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;
Expand All @@ -21,6 +19,8 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;

@Tag(name = "Rating API", description = "Rating 관련 API입니다.")
@RestController
@RequiredArgsConstructor
Expand All @@ -38,7 +38,7 @@ public class RatingController {
public ResponseEntity<?> registerInterest(
@Parameter(description = "Access Token을 입력해주세요.", required = true) @CurrentUser UserPrincipal userPrincipal,
@Parameter(description = "Schemas의 RatingReq를 참고해주세요.", required = true) @Valid @RequestBody RatingRequestDto.RatingReq ratingReq
) {
) throws IOException {
return ratingService.registerRating(userPrincipal, ratingReq);
}

Expand Down
Loading

0 comments on commit e542b7d

Please sign in to comment.