Skip to content

Commit

Permalink
Merge pull request #10 from BBack-BBoo-Team/사용자_로그인_회원가입_시혁
Browse files Browse the repository at this point in the history
[서버] 사용자 정보 조회 및 저장 API
  • Loading branch information
Si-Hyeak-KANG authored Jan 30, 2024
2 parents 566e329 + 3978343 commit 232f255
Show file tree
Hide file tree
Showing 13 changed files with 409 additions and 16 deletions.
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ repositories {
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.google.code.gson:gson'

annotationProcessor 'org.projectlombok:lombok'
compileOnly 'org.projectlombok:lombok'
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/com/book/app/infra/config/JpaConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.book.app.infra.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@Configuration
public class JpaConfig {

}
73 changes: 73 additions & 0 deletions src/main/java/com/book/app/modules/account/Account.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.book.app.modules.account;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import org.springframework.format.annotation.DateTimeFormat;

import java.time.LocalDateTime;
import java.util.Objects;

@ToString
@Getter
@EntityListeners(AuditingEntityListener.class)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "ACCOUNT")
@Entity
public class Account {

@Id
@Column(name = "ACCOUNT_ID")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "UID", unique = true, updatable = false, nullable = false)
private String uid; // Supabase 제공

@Column(name = "EMAIL", unique = true, nullable = false, length = 100)
private String email;

@Column(name = "NICKNAME", unique = true, nullable = false, length = 20)
private String nickname;

@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
@CreatedDate
@Column(name = "JOIN_AT", nullable = false, updatable = false)
private LocalDateTime joinAt;

@Column(name = "PROFILE_IMG")
private String profileImg;

private Account(String uid, String email, String nickname) {
this.uid = uid;
this.email = email;
this.nickname = nickname;
}

public static Account of(String uid, String email, String nickname) {
return new Account(uid, email, nickname);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Account account)) return false;
return id != null && id.equals(account.id);
}

@Override
public int hashCode() {
return Objects.hash(id);
}

public String getProfileImg() {
return this.profileImg == null
? ""
: this.profileImg;
}
}

46 changes: 46 additions & 0 deletions src/main/java/com/book/app/modules/account/AccountController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.book.app.modules.account;

import com.book.app.modules.account.dto.AccountInfoDto;
import com.book.app.modules.account.dto.SignUpDto;
import com.book.app.modules.account.service.AccountService;
import com.book.app.modules.global.response.SuccessResponseDto;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

@Slf4j
@Validated
@RequiredArgsConstructor
@RequestMapping("/accounts")
@RestController
public class AccountController {

private final AccountService accountService;

// 사용자 정보 저장
@PostMapping("/sign-up")
public ResponseEntity<SuccessResponseDto> signUp(@RequestBody @Valid SignUpDto info) {
log.info("sign-up info = {}",info);
Account newAccount = accountService.saveSignUpInfo(info.toEntity());
log.info("newAccount = {}", newAccount);
SuccessResponseDto<AccountInfoDto> result
= new SuccessResponseDto<>("account", AccountInfoDto.from(newAccount));
return new ResponseEntity<>(result, HttpStatus.CREATED);
}

// 사용자 정보 조회
@GetMapping("/info/{uid}")
public ResponseEntity<SuccessResponseDto> getInfoAccount(@PathVariable("uid") String uid) {
log.info("uid = {}", uid);
Account getAccount = accountService.getAccountByUid(uid);
log.info("getAccount = {} ", getAccount);
SuccessResponseDto<AccountInfoDto> result
= new SuccessResponseDto<>("account", AccountInfoDto.from(getAccount));
return new ResponseEntity<>(result, HttpStatus.OK);
}

}
12 changes: 12 additions & 0 deletions src/main/java/com/book/app/modules/account/AccountRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.book.app.modules.account;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface AccountRepository extends JpaRepository<Account, Long> {
Optional<Account> findByNickname(String nickname);
Optional<Account> findByUid(String uid);
}
42 changes: 42 additions & 0 deletions src/main/java/com/book/app/modules/account/dto/AccountInfoDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.book.app.modules.account.dto;

import com.book.app.modules.account.Account;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;

import java.time.LocalDateTime;

/**
* 단일 사용자 데이터
* @uid : Supabase에서 제공하는 사용자 식별자
* @email : 로그인을 위한 이메일
* @nickname : 사용자 별칭
* @joinAt : DB 저장 날짜 및 시간
* @profileImg : 프로필 이미지. null 이라면, length 0인 문자열 "" 반환
*/
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class AccountInfoDto {

private String uid;
private String email;
private String nickname;

@JsonProperty("join_at")
private LocalDateTime joinAt;

@JsonProperty("profile_img")
private String profileImg;

public static AccountInfoDto from(Account account) {
return AccountInfoDto.builder()
.uid(account.getUid())
.email(account.getEmail())
.nickname(account.getNickname())
.joinAt(account.getJoinAt())
.profileImg(account.getProfileImg())
.build();
}
}
40 changes: 40 additions & 0 deletions src/main/java/com/book/app/modules/account/dto/SignUpDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.book.app.modules.account.dto;

import com.book.app.modules.account.Account;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.*;
import org.hibernate.validator.constraints.Length;

/**
* 회원가입 데이터
* @uid : Supabase에서 제공하는 사용자 식별자
* @email : 로그인을 위한 이메일
* @nickname : 사용자 별칭
*/
@ToString
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class SignUpDto {

@NotBlank(message = "uid를 입력해주세요.")
private String uid;

@Email(message = "이메일 형식에 맞춰주세요.")
@NotBlank(message = "이메일을 입력해주세요.")
private String email;

/**
* 2~20자 길이의 한글, 영어(대소문자), 숫자(0-9) 만 허용
*/
@NotBlank(message = "닉네임을 입력해주세요.")
@Length(min = 2, max = 20, message = "닉네임 길이는 2자 이상 20자 이하로 작성해주세요.")
@Pattern(regexp = "^[ㄱ-ㅎ가-힣A-Za-z0-9]{2,20}$", message = "닉네임은 한글, 영어(대소문자), 숫자만 허용하며 2자에서 20자 사이어야 합니다.")
private String nickname;

public Account toEntity() {
return Account.of(this.uid, this.email, this.nickname);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.book.app.modules.account.service;

import com.book.app.modules.account.Account;

/**
* 사용자 CRUD에 해당하는 기능을 포함합니다.
*/
public interface AccountService {

/**
* 회원가입을 하는 사용자의 정보(uid, email, nickname)를 DB에 저장합니다.
* 중복된 닉네임이 있는지 검증합니다.
*/
Account saveSignUpInfo(Account account);

/**
* uid에 해당하는 사용자 정보 조회
* @param uid
* @return Account
*/
Account getAccountByUid(String uid);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.book.app.modules.account.service;

import com.book.app.modules.account.Account;
import com.book.app.modules.account.AccountRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.crossstore.ChangeSetPersister;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.NoSuchElementException;

@RequiredArgsConstructor
@Transactional(readOnly = true)
@Service
public class AccountServiceImpl implements AccountService {

private final AccountRepository accountRepository;

@Override
@Transactional
public Account saveSignUpInfo(Account account) {
verifyExistsNickname(account);
return accountRepository.save(account);
}

@Override
public Account getAccountByUid(String uid) {
return accountRepository.findByUid(uid).orElseThrow(() -> new NoSuchElementException("uid에 해당하는 사용자를 찾을 수 없습니다."));
}

private void verifyExistsNickname(Account account) {
accountRepository.findByNickname(account.getNickname()).ifPresent(e -> {
throw new IllegalArgumentException("중복된 닉네임입니다.");
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.book.app.modules.global.response;

import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class SuccessResponseDto<T> {

private String status;
private String domain;
private T data;

public SuccessResponseDto(String domain, T data) {
this.status = "success";
this.domain = domain;
this.data = data;
}
}
15 changes: 0 additions & 15 deletions src/test/java/com/book/app/SebadogBookServerApplicationTests.java

This file was deleted.

20 changes: 20 additions & 0 deletions src/test/java/com/book/app/infra/MockMvcTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.book.app.infra;

import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.transaction.annotation.Transactional;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@ActiveProfiles("test")
@Transactional
@SpringBootTest
@AutoConfigureMockMvc
public @interface MockMvcTest {
}
Loading

0 comments on commit 232f255

Please sign in to comment.