Skip to content

Commit

Permalink
Merge branch 'main' into Feature/#152-해커톤_페이지_구현
Browse files Browse the repository at this point in the history
  • Loading branch information
amaran-th authored Aug 25, 2024
2 parents c5c8cfd + 05aac5c commit a6ee7c5
Show file tree
Hide file tree
Showing 88 changed files with 3,503 additions and 459 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/back-build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ jobs:

steps:
- uses: actions/checkout@v4

- name: Checkout submodules
uses: actions/checkout@v4
with:
submodules: recursive
token: ${{ secrets.ACTION_TOKEN }}

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
/.idea/
.iml
.iml

**/node_modules
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[submodule "config"]
path = backend/src/main/resources/config
url = https://github.com/SW-CSS/config.git
branch = main
8 changes: 8 additions & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,21 @@ dependencies {
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 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.apache.poi:poi:5.2.3'
implementation 'org.apache.poi:poi-ooxml:5.2.3'
implementation 'commons-io:commons-io'
implementation 'org.springframework.boot:spring-boot-starter-data-redis-reactive'
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'io.projectreactor:reactor-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

// restdocs
Expand Down
106 changes: 106 additions & 0 deletions backend/src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ include::{snippets}/student-find-all/http-response.adoc[]
.Response Body
include::{snippets}/student-find-all/response-fields.adoc[]

=== `PATCH`: 비밀번호 수정

.HTTP Request
include::{snippets}/member-change-password/http-request.adoc[]

.Request Body
include::{snippets}/member-change-password/request-fields.adoc[]

.HTTP Response
include::{snippets}/member-change-password/http-response.adoc[]

== 학과

=== `GET`: 단과대학 목록 조회
Expand Down Expand Up @@ -222,3 +233,98 @@ include::{snippets}/download-score-file/query-parameters.adoc[]

.HTTP Response
include::{snippets}/download-score-file/http-response.adoc[]

== 인증

=== `POST`: 회원가입

.HTTP Request
include::{snippets}/auth-sign-up/http-request.adoc[]

.Request Body
include::{snippets}/auth-sign-up/request-fields.adoc[]

.HTTP Response
include::{snippets}/auth-sign-up/http-response.adoc[]

=== `POST`: 부산대 메일 인증 코드 발송

.HTTP Request
include::{snippets}/auth-send-auth-code/http-request.adoc[]

.Request Body
include::{snippets}/auth-send-auth-code/request-fields.adoc[]

.HTTP Response
include::{snippets}/auth-send-auth-code/http-response.adoc[]

.Response Body
include::{snippets}/auth-send-auth-code/response-fields.adoc[]

=== `GET`: 이메일 중복 확인

.HTTP Request
include::{snippets}/auth-check-duplicate-email/http-request.adoc[]

.Path Parameters
include::{snippets}/auth-check-duplicate-email/query-parameters.adoc[]

.HTTP Response
include::{snippets}/auth-check-duplicate-email/http-response.adoc[]

.Response Body
include::{snippets}/auth-check-duplicate-email/response-fields.adoc[]

=== `GET`: 학번 중복 확인

.HTTP Request
include::{snippets}/auth-check-duplicate-student-id/http-request.adoc[]

.Path Parameters
include::{snippets}/auth-check-duplicate-student-id/query-parameters.adoc[]

.HTTP Response
include::{snippets}/auth-check-duplicate-student-id/http-response.adoc[]

.Response Body
include::{snippets}/auth-check-duplicate-student-id/response-fields.adoc[]

=== `GET`: 전화번호 중복 확인

.HTTP Request
include::{snippets}/auth-check-duplicate-phone-number/http-request.adoc[]

.Path Parameters
include::{snippets}/auth-check-duplicate-phone-number/query-parameters.adoc[]

.HTTP Response
include::{snippets}/auth-check-duplicate-phone-number/http-response.adoc[]

.Response Body
include::{snippets}/auth-check-duplicate-phone-number/response-fields.adoc[]

=== `POST`: 로그인

.HTTP Request
include::{snippets}/auth-sign-in/http-request.adoc[]

.Request Body
include::{snippets}/auth-sign-in/request-fields.adoc[]

.HTTP Response
include::{snippets}/auth-sign-in/http-response.adoc[]

.Response Body
include::{snippets}/auth-sign-in/response-fields.adoc[]

=== `PATCH`: 임시 비밀번호 발급

.HTTP Request
include::{snippets}/auth-reset-password/http-request.adoc[]

.Request Body
include::{snippets}/auth-reset-password/request-fields.adoc[]

.HTTP Response
include::{snippets}/auth-reset-password/http-response.adoc[]

9 changes: 5 additions & 4 deletions backend/src/main/java/sw_css/SwCssApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;

@SpringBootApplication
@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
public class SwCssApplication {

public static void main(String[] args) {
SpringApplication.run(SwCssApplication.class, args);
}
public static void main(String[] args) {
SpringApplication.run(SwCssApplication.class, args);
}

}
36 changes: 36 additions & 0 deletions backend/src/main/java/sw_css/auth/api/SignInController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package sw_css.auth.api;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import sw_css.auth.application.AuthSignInService;
import sw_css.auth.application.dto.request.ResetPasswordRequest;
import sw_css.auth.application.dto.request.SignInRequest;
import sw_css.auth.application.dto.response.SignInResponse;

@Validated
@RequestMapping("/sign-in")
@RestController
@RequiredArgsConstructor
public class SignInController {

private final AuthSignInService authSignInService;

@PostMapping
public ResponseEntity<SignInResponse> signIn(@RequestBody @Valid SignInRequest request) {
return ResponseEntity.ok(authSignInService.signIn(request.email(), request.password()));
}

@PatchMapping("/reset-password")
public ResponseEntity<Void> resetPassword(
@RequestBody @Valid ResetPasswordRequest request) {
authSignInService.resetPassword(request.email(), request.name());
return ResponseEntity.noContent().build();
}
}
60 changes: 60 additions & 0 deletions backend/src/main/java/sw_css/auth/api/SignUpController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package sw_css.auth.api;


import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import java.net.URI;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import sw_css.auth.application.AuthEmailService;
import sw_css.auth.application.AuthSignUpService;
import sw_css.auth.application.dto.request.SendAuthCodeRequest;
import sw_css.auth.application.dto.request.SignUpRequest;
import sw_css.auth.application.dto.response.CheckDuplicateResponse;
import sw_css.auth.application.dto.response.SendAuthCodeResponse;

@Validated
@RequestMapping("/sign-up")
@RestController
@RequiredArgsConstructor
public class SignUpController {

private final AuthSignUpService authSignUpService;
private final AuthEmailService authEmailService;

@PostMapping
public ResponseEntity<Void> signUp(@RequestBody @Valid SignUpRequest request) {
long memberId = authSignUpService.signUp(request);
return ResponseEntity.created(URI.create("/members/" + memberId)).build();
}

@PostMapping("/send-auth-code")
public ResponseEntity<SendAuthCodeResponse> sendAuthCode(@RequestBody @Valid SendAuthCodeRequest request) {
return ResponseEntity.ok(authEmailService.emailAuth(request.email()));
}

@GetMapping("/exists/email")
public ResponseEntity<CheckDuplicateResponse> checkDuplicateEmail(
@RequestParam(value = "email") @NotBlank final String email) {
return ResponseEntity.ok(authSignUpService.isDuplicateEmail(email));
}

@GetMapping("/exists/student-id")
public ResponseEntity<CheckDuplicateResponse> checkDuplicateStudentId(
@RequestParam(value = "student_id") @NotBlank final String studentId) {
return ResponseEntity.ok(authSignUpService.isDuplicateStudentId(studentId));
}

@GetMapping("/exists/phone-number")
public ResponseEntity<CheckDuplicateResponse> checkDuplicatePhoneNumber(
@RequestParam(value = "phone_number") @NotBlank final String phoneNumber) {
return ResponseEntity.ok(authSignUpService.isDuplicatePhoneNumber(phoneNumber));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package sw_css.auth.application;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import sw_css.member.domain.repository.MemberRepository;
import sw_css.member.domain.repository.StudentMemberRepository;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class AuthCheckDuplicateService {
private final MemberRepository memberRepository;
private final StudentMemberRepository studentMemberRepository;

public boolean isDuplicateEmail(String email) {
return memberRepository.existsByEmail(email);
}

public boolean isDuplicateStudentID(String studentIdStr) {
Long studentId = Long.parseLong(studentIdStr);
return studentMemberRepository.existsById(studentId);
}

public boolean isDuplicatePhoneNumber(String phoneNumber) {
return memberRepository.existsByPhoneNumber(phoneNumber);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package sw_css.auth.application;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import sw_css.auth.application.dto.response.SendAuthCodeResponse;
import sw_css.auth.domain.EmailAuthRedis;
import sw_css.auth.domain.repository.EmailAuthRedisRepository;
import sw_css.auth.exception.AuthException;
import sw_css.auth.exception.AuthExceptionType;
import sw_css.utils.MailUtil;

@Service
@RequiredArgsConstructor
public class AuthEmailService {
public static final int EMAIL_EXPIRED_SECONDS = 600; // 10분
public static final int AUTH_CODE_LENGTH = 10;

private static final Random RANDOM = new Random();

private final MailUtil mailUtil;
private final EmailAuthRedisRepository emailAuthRedisRepository;
private final AuthCheckDuplicateService authCheckDuplicateService;

public SendAuthCodeResponse emailAuth(String email) {
checkIsDuplicateEmail(email);

String authCode = generateRandomAuthCode();
emailAuthRedisRepository.save(EmailAuthRedis.of(email, authCode));
sendAuthCode(email, authCode);
return SendAuthCodeResponse.from(EMAIL_EXPIRED_SECONDS);
}

public void sendNewPassword(String email, String password) {
List<String> toUserList = new ArrayList<>(List.of(email));
String subject = "[부산대학교] SW역량강화플랫폼 임시 비밀번호 발송 메일입니다.";
String text = "SW역량강화플랫폼 임시 비밀번호는 " + password + " 입니다.";
mailUtil.sendMail(toUserList, subject, text);
}

private static String generateRandomAuthCode() {
char leftLimit = '0';
char rightLimit = 'z';

return RANDOM.ints(leftLimit, rightLimit + 1)
.filter(i -> Character.isAlphabetic(i) || Character.isDigit(i))
.limit(AuthEmailService.AUTH_CODE_LENGTH)
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
.toString();
}

private void sendAuthCode(String email, String authCode) {
List<String> toUserList = new ArrayList<>(List.of(email));
String subject = "[부산대학교] SW역량강화플랫폼 인증코드 발송 메일입니다.";
String text = "SW역량강화플랫폼 인증코드는 " + authCode + " 입니다.";
mailUtil.sendMail(toUserList, subject, text);
}

private void checkIsDuplicateEmail(String email) {
if (authCheckDuplicateService.isDuplicateEmail(email)) {
throw new AuthException(AuthExceptionType.MEMBER_EMAIL_DUPLICATE);
}
}
}
Loading

0 comments on commit a6ee7c5

Please sign in to comment.