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

RELEASE 2024/07/25 20:39:00 #156

Merged
merged 6 commits into from
Jul 25, 2024
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ dependencies {
// AWS
implementation platform("io.awspring.cloud:spring-cloud-aws-dependencies:3.0.1")
implementation 'io.awspring.cloud:spring-cloud-aws-starter-sqs'
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

}

tasks.named('bootBuildImage') {
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/io/sobok/SobokSobok/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ public class SecurityConfig {
"/auth/login",
"/auth/refresh",
"/user",
"/pill/count/**"
"/pill/count/**",
"/sticker/image"
};

private final JwtProvider jwtProvider;
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/io/sobok/SobokSobok/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ public enum ErrorCode {
UNREGISTERED_STICKER(HttpStatus.NOT_FOUND, "๋“ฑ๋ก๋˜์ง€ ์•Š์€ ์Šคํ‹ฐ์ปค์ž…๋‹ˆ๋‹ค."),
ALREADY_SEND_STICKER(HttpStatus.CONFLICT, "์ด๋ฏธ ์Šคํ‹ฐ์ปค๋ฅผ ์ „์†กํ–ˆ์Šต๋‹ˆ๋‹ค."),
UNREGISTERED_LIKE_SCHEDULE(HttpStatus.NOT_FOUND, "์Šคํ‹ฐ์ปค ์ „์†ก๊ธฐ๋ก์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."),
NOT_FOUND_SAVE_IMAGE_EXCEPTION(HttpStatus.NOT_FOUND, "์ด๋ฏธ์ง€ ์ €์žฅ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."),
NOT_FOUND_IMAGE_EXCEPTION(HttpStatus.NOT_FOUND, "์ด๋ฏธ์ง€ ์ด๋ฆ„์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."),
INVALID_MULTIPART_EXTENSION_EXCEPTION(HttpStatus.BAD_REQUEST, "ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ํƒ€์ž…์˜ ํŒŒ์ผ์ž…๋‹ˆ๋‹ค.")
;

private final HttpStatus code;
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/io/sobok/SobokSobok/exception/SuccessCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public enum SuccessCode {
GET_PILL_INFO_SUCCESS(HttpStatus.OK, "์•ฝ ์ •๋ณด ์กฐํšŒ์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค."),
SEND_PILL_SUCCESS(HttpStatus.CREATED, "์•ฝ ์ „์†ก์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค."),
DELETE_PILL_SUCCESS(HttpStatus.OK, "์•ฝ ์‚ญ์ œ์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค."),
UPDATE_PILL_SUCCESS(HttpStatus.OK, "์•ฝ ์ˆ˜์ •์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค."),

// schedule
CHECK_PILL_SCHEDULE_SUCCESS(HttpStatus.OK, "๋ณต์šฉ ์™„๋ฃŒ ์ฒดํฌ์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค."),
Expand All @@ -53,6 +54,7 @@ public enum SuccessCode {
SEND_STICKER_SUCCESS(HttpStatus.OK, "์Šคํ‹ฐ์ปค ์ „์†ก์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค."),
UPDATE_STICKER_SUCCESS(HttpStatus.OK, "๋ณด๋‚ธ ์Šคํ‹ฐ์ปค ์ˆ˜์ •์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค."),
GET_RECEIVED_STICKER_SUCCESS(HttpStatus.OK, "๋ฐ›์€ ์Šคํ‹ฐ์ปค ์ „์ฒด ์กฐํšŒ์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค."),
UPLOAD_STICKER_IMAGE_SUCCESS(HttpStatus.OK, "์Šคํ‹ฐ์ปค ์ด๋ฏธ์ง€ ๋“ฑ๋ก์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค."),
;

private final HttpStatus code;
Expand Down
91 changes: 91 additions & 0 deletions src/main/java/io/sobok/SobokSobok/external/aws/s3/S3Service.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package io.sobok.SobokSobok.external.aws.s3;


import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import io.sobok.SobokSobok.exception.ErrorCode;
import io.sobok.SobokSobok.exception.model.BadRequestException;
import io.sobok.SobokSobok.exception.model.NotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.UUID;
import javax.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

@Service
@RequiredArgsConstructor
public class S3Service {

private AmazonS3 amazonS3;

@Value("${spring.cloud.aws.credentials.access-key}")
private String accessKey;

@Value("${spring.cloud.aws.credentials.secret-key}")
private String secretKey;

@Value("${spring.cloud.aws.s3.bucket}")
private String bucket;

@Value("${spring.cloud.aws.region.static}")
private String region;

@PostConstruct
public void amazonS3Client() {
BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
amazonS3 = AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.build();
}

public String uploadImage(MultipartFile multipartFile, String folder) {
String fileName = createFileName(multipartFile.getOriginalFilename());
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(multipartFile.getSize());
objectMetadata.setContentType(multipartFile.getContentType());
try (InputStream inputStream = multipartFile.getInputStream()) {
amazonS3.putObject(
new PutObjectRequest(bucket + "/" + folder + "/image", fileName, inputStream,
objectMetadata)
.withCannedAcl(CannedAccessControlList.PublicRead));
return amazonS3.getUrl(bucket + "/" + folder + "/image", fileName).toString();
} catch (IOException e) {
throw new NotFoundException(ErrorCode.NOT_FOUND_SAVE_IMAGE_EXCEPTION);
}
}

// ํŒŒ์ผ๋ช… (์ค‘๋ณต ๋ฐฉ์ง€)
private String createFileName(String fileName) {
return UUID.randomUUID().toString().concat(getFileExtension(fileName));
}

// ํŒŒ์ผ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
private String getFileExtension(String fileName) {
if (fileName.length() == 0) {
throw new NotFoundException(ErrorCode.NOT_FOUND_IMAGE_EXCEPTION);
}
ArrayList<String> fileValidate = new ArrayList<>();
fileValidate.add(".jpg");
fileValidate.add(".jpeg");
fileValidate.add(".png");
fileValidate.add(".JPG");
fileValidate.add(".JPEG");
fileValidate.add(".PNG");
String idxFileName = fileName.substring(fileName.lastIndexOf("."));
if (!fileValidate.contains(idxFileName)) {
throw new BadRequestException(ErrorCode.INVALID_MULTIPART_EXTENSION_EXCEPTION);
}
return fileName.substring(fileName.lastIndexOf("."));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

import com.google.firebase.messaging.*;
import io.sobok.SobokSobok.auth.application.util.UserServiceUtil;
import io.sobok.SobokSobok.auth.domain.Platform;
import io.sobok.SobokSobok.auth.domain.User;
import io.sobok.SobokSobok.auth.infrastructure.UserRepository;
import io.sobok.SobokSobok.exception.ErrorCode;
import io.sobok.SobokSobok.exception.model.BadRequestException;
import io.sobok.SobokSobok.external.firebase.dto.PushNotificationRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -19,78 +22,43 @@ public class FCMPushService {
private final UserRepository userRepository;

public void sendNotificationByToken(PushNotificationRequest request) {
User user = UserServiceUtil.findUserById(userRepository, request.userId());

Message message = Message.builder()
.setToken(user.getDeviceToken())
.setNotification(
Notification.builder()
.setTitle(request.title())
.setBody(request.body())
.build()
)
.setAndroidConfig(
AndroidConfig.builder()
.setNotification(
AndroidNotification.builder()
.setTitle(request.title())
.setBody(request.body())
.setClickAction("push_click")
.build()
)
.build()
)
.setApnsConfig(
ApnsConfig.builder()
.setAps(Aps.builder()
.setCategory("push_click")
.build())
.build()
)
.putData("title", request.title())
.putData("body", request.body() == null ? "" : request.body())
.putData("type", request.type())
.build();

sendMessageToFirebase(message, user.getId());
sendNotification(request, null);
}

public void sendNotificationByTokenWithFriendData(PushNotificationRequest request) {
sendNotification(request, request.data().get("friendId"));
}

private void sendNotification(PushNotificationRequest request, String friendId) {
User user = UserServiceUtil.findUserById(userRepository, request.userId());
Message.Builder messageBuilder;
if (user.getPlatform().equals(Platform.ANDROID)) {
messageBuilder = Message.builder()
.setToken(user.getDeviceToken())
.putData("title", request.title())
.putData("body", request.body() == null ? "" : request.body())
.putData("type", request.type());
} else if (user.getPlatform().equals(Platform.iOS)) {
messageBuilder = Message.builder()
.setToken(user.getDeviceToken())
.setNotification(buildNotification(request))
.putData("type", request.type());
} else {
throw new BadRequestException(ErrorCode.INVALID_PLATFORM);
}

Message message = Message.builder()
.setToken(user.getDeviceToken())
.setNotification(
Notification.builder()
.setTitle(request.title())
.setBody(request.body())
.build()
)
.setAndroidConfig(
AndroidConfig.builder()
.setNotification(
AndroidNotification.builder()
.setTitle(request.title())
.setBody(request.body())
.setClickAction("push_click")
.build()
)
.build()
)
.setApnsConfig(
ApnsConfig.builder()
.setAps(Aps.builder()
.setCategory("push_click")
.build())
.build()
)
.putData("title", request.title())
.putData("body", request.body() == null ? "" : request.body())
.putData("type", request.type())
.putData("friendId", request.data().get("friendId"))
.build();
if (friendId != null) {
messageBuilder.putData("friendId", friendId);
}

sendMessageToFirebase(message, user.getId());
sendMessageToFirebase(messageBuilder.build(), user.getId());
}

private Notification buildNotification(PushNotificationRequest request) {
return Notification.builder()
.setTitle(request.title())
.setBody(request.body())
.build();
}

private void sendMessageToFirebase(Message message, Long userId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.sobok.SobokSobok.pill.ui.dto.PillListResponse;
import io.sobok.SobokSobok.pill.ui.dto.PillRequest;
import io.sobok.SobokSobok.pill.ui.dto.PillResponse;
import io.sobok.SobokSobok.pill.ui.dto.PillUpdateRequest;
import io.sobok.SobokSobok.utils.PillUtil;
import lombok.RequiredArgsConstructor;
import io.sobok.SobokSobok.pill.infrastructure.PillQueryRepository;
Expand All @@ -30,6 +31,7 @@
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -152,6 +154,41 @@ public Integer getPillCount(Long userId) {
return pillQueryRepository.getPillCount(userId);
}

@Transactional
public void updatePill(Long userId, Long pillId, PillUpdateRequest request) {
UserServiceUtil.existsUserById(userRepository, userId);
Pill pill = PillServiceUtil.findPillById(pillRepository, pillId);
boolean isTimeListChanged = false;

if (!pill.isPillUser(userId)) {
throw new ForbiddenException(ErrorCode.UNAUTHORIZED_PILL);
}

if (!pill.getPillName().equals(request.pillName())) {
pill.updatePillName(request.pillName());
}

List<String> timeList = Arrays.asList(request.timeList());
if (!timeList.equals(pillScheduleQueryRepository.getPillScheduleTime(pillId))) {
isTimeListChanged = true;
}

if (PillUtil.checkChangePillSchedule(pill, request) || isTimeListChanged) {
pillScheduleRepository.deleteAllByPillId(pillId);
LocalDate[] scheduleDate = PillUtil.getScheduleDateList(request.startDate(), request.endDate(), request.day().split(", "));
for (LocalDate date : scheduleDate) {
for (String time : request.timeList()) {
pillScheduleRepository.save(PillSchedule.builder()
.scheduleDate(date)
.scheduleTime(time)
.pillId(pillId)
.build()
);
}
}
}
}

@Transactional
public void deletePill(Long userId, Long pillId) {

Expand Down
4 changes: 4 additions & 0 deletions src/main/java/io/sobok/SobokSobok/pill/domain/Pill.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ public void receivePill(Long userId) {
this.userId = userId;
}

public void updatePillName(String newPillName) {
this.pillName = newPillName;
}

public boolean isPillUser(Long userId) {
return Objects.equals(this.userId, userId);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
package io.sobok.SobokSobok.pill.infrastructure;

import com.querydsl.core.Tuple;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.CaseBuilder;
import com.querydsl.jpa.impl.JPAQueryFactory;
import io.sobok.SobokSobok.pill.domain.PillSchedule;
import io.sobok.SobokSobok.pill.domain.QPill;
import io.sobok.SobokSobok.pill.domain.QPillSchedule;
import io.sobok.SobokSobok.pill.ui.dto.DateScheduleResponse;
import io.sobok.SobokSobok.pill.ui.dto.MonthScheduleResponse;
import io.sobok.SobokSobok.pill.ui.dto.PillScheduleInfo;
import io.sobok.SobokSobok.pill.ui.dto.StickerInfo;
import io.sobok.SobokSobok.sticker.domain.QLikeSchedule;
import io.sobok.SobokSobok.sticker.domain.QSticker;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
Expand Down Expand Up @@ -106,17 +104,22 @@ public List<DateScheduleResponse> getDateSchedule(Long userId, LocalDate date) {
.where(pillSchedule.scheduleDate.eq(date), pillSchedule.scheduleTime.eq(time))
.fetch();

Map<Long, List<Long>> stickerIdMap = pillScheduleIds.stream()
Map<Long, List<StickerInfo>> stickerIdMap = pillScheduleIds.stream()
.collect(Collectors.toMap(id -> id, id -> queryFactory
.select(likeSchedule.stickerId)
.select(likeSchedule.stickerId, likeSchedule.id)
.from(likeSchedule)
.where(likeSchedule.scheduleId.eq(id))
.fetch()));
.fetch().stream()
.map(tuple -> StickerInfo.builder()
.stickerId(tuple.get(0, Long.class))
.likeScheduleId(tuple.get(1, Long.class))
.build()
).collect(Collectors.toList())));

// ๊ฒฐ๊ณผ ๋งคํ•‘
List<PillScheduleInfo> pillScheduleInfoList = pillScheduleIds.stream()
.flatMap(scheduleId -> {
List<Long> stickerIds = stickerIdMap.getOrDefault(scheduleId, Collections.emptyList());
List<StickerInfo> stickerIds = stickerIdMap.getOrDefault(scheduleId, Collections.emptyList());
return queryFactory
.select(
pillSchedule.id,
Expand Down
Loading
Loading