Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ dependencies {

//Mail
implementation'org.springframework.boot:spring-boot-starter-mail'

//chat
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'

//S3
implementation platform("io.awspring.cloud:spring-cloud-aws-dependencies:3.3.0")
implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3'

}

test {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
@Getter
public class CustomUser implements UserDetails {

private final String memberId;
private final Long memberId;

public CustomUser(String memberId) {
public CustomUser(Long memberId) {
this.memberId = memberId;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ public class CustomUserService implements UserDetailsService {
@Transactional(readOnly = true)
public CustomUser loadUserByUsername(String username) throws UsernameNotFoundException {
memberService.findById(Long.valueOf(username));
return new CustomUser(username);
return new CustomUser(Long.parseLong(username));
}
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
package org.example.plzdrawing.api.auth.repository;

import static org.example.plzdrawing.common.redis.RedisKeyPrefix.EMAIL_AUTH_NUMBER;
import static org.example.plzdrawing.common.redis.RedisKeyPrefix.REISSUE_PREFIX;

import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;

@Component
@Repository
@RequiredArgsConstructor
public class AuthCodeRedisRepository {

@Value("${spring.mail.auth-code-expiration}")
private Long EXPIRATION;

private final StringRedisTemplate redisTemplate;
private final String PREFIX = "AuthNumber:";
private final String REISSUE_PREFIX = "Reissue:";
private final RedisTemplate<String, String> redisTemplate;

public void saveAuthNumber(String key, String emailAuthNumber) {
redisTemplate.opsForValue()
.set(PREFIX + key, emailAuthNumber, EXPIRATION, TimeUnit.MILLISECONDS);
.set(EMAIL_AUTH_NUMBER + key, emailAuthNumber, EXPIRATION, TimeUnit.MILLISECONDS);
}
public String findEmailAuthNumberByKey(String key) {
return redisTemplate.opsForValue().get(PREFIX + key);
return redisTemplate.opsForValue().get(EMAIL_AUTH_NUMBER + key);
}

public void saveReissueAuthNumber(String key, String reissueAuthNumber) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,17 @@
import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;

@Component
@RequiredArgsConstructor
@Repository
@RequiredArgsConstructor
public class RefreshTokenRedisRepository {

@Value("${jwt.refresh-expiration}")
private Long EXPIRATION;

private final StringRedisTemplate redisTemplate;
private final RedisTemplate<String, String> redisTemplate;

public void saveRefreshToken(String memberId, String jti, String refreshToken) {
String redisId = createRedisId(memberId, jti);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.example.plzdrawing.api.auth.service.strategy;

import static org.example.plzdrawing.api.member.exception.MemberErrorCode.MEMBER_NOT_FOUND;
import static org.example.plzdrawing.api.member.exception.MemberErrorCode.PASSWORD_INCORRECT;

import lombok.RequiredArgsConstructor;
import org.example.plzdrawing.api.auth.dto.request.LoginRequest;
import org.example.plzdrawing.api.auth.dto.request.SignUpRequest;
import org.example.plzdrawing.api.auth.dto.response.LoginResponse;
import org.example.plzdrawing.api.auth.dto.response.SignUpResponse;
import org.example.plzdrawing.common.exception.RestApiException;
import org.example.plzdrawing.domain.member.Member;
import org.example.plzdrawing.domain.member.MemberRepository;
import org.example.plzdrawing.domain.member.Provider;
import org.example.plzdrawing.util.jwt.TokenService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional
public class EmailService implements AuthService {

private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
private final TokenService tokenService;

@Override
public LoginResponse login(LoginRequest request) {
Member member = memberRepository.findByEmailAndProvider(request.getEmail(),
request.getProvider()).orElseThrow(()->new RestApiException(MEMBER_NOT_FOUND.getErrorCode()));

validatePassword(request, member);

String accessToken = tokenService.createAccessToken(member.getId());
String refreshToken = tokenService.createRefreshToken(member.getId());

return new LoginResponse(accessToken, refreshToken);
}

@Override
public SignUpResponse signUp(SignUpRequest request) {
String encodedPassword = passwordEncoder.encode(request.getPassword());
Member member = Member.builder()
.email(request.getEmail())
.password(encodedPassword)
.provider(request.getProvider())
.nickname(request.getNickName())
.build();

Long savedId = memberRepository.save(member).getId();
return new SignUpResponse(savedId);
}

@Override
public Provider getProviderName() {
return Provider.EMAIL;
}

private void validatePassword(LoginRequest request, Member member) {
if (!isPasswordMatching(request.getPassword(), member.getPassword())) {
throw new RestApiException(PASSWORD_INCORRECT.getErrorCode());
}
}

private boolean isPasswordMatching(String inputPassword, String savedPassword) {
return passwordEncoder.matches(inputPassword, savedPassword);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ public LoginResponse login(LoginRequest request) {

validatePassword(request, member);

String accessToken = tokenService.createAccessToken(String.valueOf(member.getId()));
String refreshToken = tokenService.createRefreshToken(String.valueOf(member.getId()));
String accessToken = tokenService.createAccessToken(member.getId());
String refreshToken = tokenService.createRefreshToken(member.getId());

return new LoginResponse(accessToken, refreshToken);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.example.plzdrawing.api.chat.controller;

import jakarta.validation.Valid;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import lombok.RequiredArgsConstructor;
import org.example.plzdrawing.api.chat.dto.request.ChatDto;
import org.example.plzdrawing.api.chat.dto.response.ResponseChat;
import org.example.plzdrawing.api.chat.service.ChatService;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/chat")
public class ChatController {

private final ChatService chatService;

@MessageMapping("/v1/chat") //app/v1/chat
public void sendMessage(@Payload @Valid ChatDto chatDto) {
chatService.saveMessage(chatDto, Timestamp.valueOf(LocalDateTime.now()));
}

@GetMapping("/{chatRoomId}")
public ResponseEntity<Page<ResponseChat>> getChats(@PathVariable("chatRoomId") String chatRoomId,
@RequestParam(name = "pageNum", defaultValue = "0") int pageNum) {
Page<ResponseChat> chatPage = chatService.getChats(chatRoomId, pageNum);

//TODO 검증 1회만
return ResponseEntity.ok(chatPage);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.example.plzdrawing.api.chat.dto.converter;

import java.sql.Timestamp;
import org.example.plzdrawing.api.chat.dto.request.ChatDto;
import org.example.plzdrawing.api.chat.dto.response.ResponseChat;
import org.example.plzdrawing.domain.chat.Chat;

public class ChatConverter {

public static Chat toEntity(ChatDto dto, Timestamp sendTime) {
return Chat.builder()
.chatRoomId(dto.getChatRoomId())
.senderId(dto.getSenderId())
.message(dto.getMessage())
.messageType(dto.getMessageType())
.fileUrl(dto.getFileUrl())
.fileName(dto.getFileName())
.fileSize(dto.getFileSize())
.mimeType(dto.getMimeType())
.timestamp(sendTime)
.build();
}

public static ResponseChat fromEntity(Chat chat) {
return new ResponseChat(
chat.getChatId(),
chat.getChatRoomId(),
chat.getSenderId(),
chat.getMessage(),
chat.getTimestamp(),
chat.getMessageType(),
chat.getFileUrl(),
chat.getFileName(),
chat.getFileSize(),
chat.getMimeType()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.example.plzdrawing.api.chat.dto.request;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Positive;
import lombok.Getter;
import org.example.plzdrawing.api.chat.dto.validation.ValidChatDto;
import org.example.plzdrawing.domain.chat.MessageType;

@Getter
@ValidChatDto
public class ChatDto {

@Positive
private String chatRoomId;

@Positive
private Long senderId;

@NotBlank
private String message;

@NotBlank
private MessageType messageType;

private String fileUrl;
private String fileName;
private Long fileSize;
private String mimeType;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.example.plzdrawing.api.chat.dto.response;

import java.sql.Timestamp;
import lombok.AllArgsConstructor;
import org.example.plzdrawing.domain.chat.MessageType;

@AllArgsConstructor
public class ResponseChat {

private String chatId;
private String chatRoomId;
private Long senderId;
private String message;
private Timestamp timestamp;
private MessageType messageType;
private String FileUrl;
private String FileName;
private Long FileSize;
private String mimeType;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.example.plzdrawing.api.chat.dto.validation;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.example.plzdrawing.api.chat.dto.request.ChatDto;
import org.example.plzdrawing.domain.chat.MessageType;

public class ChatDtoValidator implements ConstraintValidator<ValidChatDto, ChatDto> {

@Override
public boolean isValid(ChatDto chatDto, ConstraintValidatorContext context) {
if (chatDto.getMessageType() == MessageType.TEXT) {
return true;
}

boolean valid = true;
context.disableDefaultConstraintViolation();

if (isEmpty(chatDto.getFileUrl())) {
addViolation(context, "fileUrl", "파일 전송 시 fileUrl은 필수입니다.");
valid = false;
}
if (isEmpty(chatDto.getFileName())) {
addViolation(context, "fileName", "파일 전송 시 fileName은 필수입니다.");
valid = false;
}
if (chatDto.getFileSize() == null || chatDto.getFileSize() <= 0) {
addViolation(context, "fileSize", "파일 전송 시 fileSize는 필수입니다.");
valid = false;
}
if (isEmpty(chatDto.getMimeType())) {
addViolation(context, "mimeType", "파일 전송 시 mimeType은 필수입니다.");
valid = false;
}
return valid;
}

private boolean isEmpty(String value) {
return value == null || value.trim().isEmpty();
}

private void addViolation(ConstraintValidatorContext context, String property, String message) {
context.buildConstraintViolationWithTemplate(message)
.addPropertyNode(property)
.addConstraintViolation();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.example.plzdrawing.api.chat.dto.validation;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Documented
@Constraint(validatedBy = ChatDtoValidator.class)
@Target({ElementType.TYPE})
@Retention(RUNTIME)
public @interface ValidChatDto {
String message() default "잘못된 요청입니다.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Loading
Loading