diff --git a/.gitignore b/.gitignore
index d843f13..0bc6eb7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -115,4 +115,11 @@ fabric.properties
hs_err_pid*
replay_pid*
-# End of https://www.toptal.com/developers/gitignore/api/java,intellij+all
\ No newline at end of file
+# End of https://www.toptal.com/developers/gitignore/api/java,intellij+all
+
+HELP.md
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
diff --git a/.gradle/8.1.1/executionHistory/executionHistory.lock b/.gradle/8.1.1/executionHistory/executionHistory.lock
index bc8695c..04d1845 100644
Binary files a/.gradle/8.1.1/executionHistory/executionHistory.lock and b/.gradle/8.1.1/executionHistory/executionHistory.lock differ
diff --git a/.gradle/8.1.1/fileHashes/fileHashes.lock b/.gradle/8.1.1/fileHashes/fileHashes.lock
index b3b2bd9..00217e3 100644
Binary files a/.gradle/8.1.1/fileHashes/fileHashes.lock and b/.gradle/8.1.1/fileHashes/fileHashes.lock differ
diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock
index a31c6f8..c364895 100644
Binary files a/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ
diff --git a/README.md b/README.md
index 0a8bbda..4c40cbe 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,268 @@
-# MiniProject
\ No newline at end of file
+
+
+
+
+
+
+
+

+
+
+
+
+
+# ์จ์ค์คํ
+### ๐ ํ๋ก์ ํธ ์๊ฐ
+ํ๋ก ํธ(React), ๋ฐฑ์๋(SpringBoot/Java) ๊ฐํ์ด ํ์
ํ์ฌ ui/ux๋ถํฐ ๋ฐฐํฌ์์
๊น์ง ์๋ฃํ ์ฌ์ด๋ํ๋ก์ ํธ์
๋๋ค.
+ํ๋ก์ ํธ๋ช
์ ์จ์ค์คํ(On&Off)์ด๋ฉฐ, ํด๋น ํ๋ก์ ํธ๋ ํด๋ผ์ด์ธํธ์ ์ผ์ ๊ด๋ฆฌ๋ฅผ ์์ฝ๊ฒ ์ ์ง๊ด๋ฆฌ ํ ์ ์์ต๋๋ค.
+
+>**๋ฐฐํฌ๋งํฌ : https://on-n-off-mini.netlify.app/**
+>
+>**๊ฐ๋ฐ๊ธฐ๊ฐ : 2023.07.24 ~ 2023.08.11**
+
+
+
+
+## ๐ ํ๋ก์ ํธ ๊ตฌ์กฐ
+
+ Table of Contents
+
+ -
+ ํ๋ก์ ํธ ์๊ฐ
+
+
+ - ํ๋ก์ ํธ ๊ตฌ์กฐ
+ - ํ๋ก์ ํธ ์ํ
+ - ํ์ ๋ฐ ์ญํ
+ -
+ ํ๋ก์ ํธ ์คํ
+
+
+ - ERD
+ - ํ
์ด๋ธ ์ค๊ณ
+ -
+ ์ฃผ์ ๊ธฐ๋ฅ
+
+
+ - API ๋ช
์ธ์
+
+
+
+(back to top)
+
+
+
+
+> **๋ก๊ทธ์ธ / ํ์๊ด๋ฆฌ**
+>
+| ๋ก๊ทธ์ธ | ํ์๊ด๋ฆฌ |
+| :------------: | :------------: |
+|  |  |
+
+
+> **๋ฉ์ธ(์ผ๋ฐ) / ๋ฉ์ธ(๊ด๋ฆฌ์)**
+>
+| ๋ฉ์ธ(์ผ๋ฐ) | ๋ฉ์ธ(๊ด๋ฆฌ์) |
+| :------------: | :------------: |
+|  |  |
+
+
+> **๊ด๋ฆฌ์**
+>
+| ๋น์งํด๊ฐ๊ด๋ฆฌ | ์ฌ์๊ด๋ฆฌ | ์ฌ์๊ด๋ฆฌ_์ธ๋ถ ์ ๋ณด |
+| :------------: | :------------: | :------------: |
+|  |  |  |
+
+
+> **ํด๊ฐ/๋น์ง**
+>
+| ํด๊ฐ/๋น์ง๊ด๋ฆฌ | ํด๊ฐ๋ฑ๋ก | ๋น์ง๋ฑ๋ก |
+| :------------: | :------------: | :------------: |
+|  | ! |  |
+
+| ๋น์ง์ ์ฒญ๋ด์ญ | ํด๊ฐ์ ์ฒญ๋ด์ญ |
+| :------------: | :------------: |
+|  |  |
+
+
+> **๊ฐ์ธ์ ๋ณด์์ **
+>
+| ๋ณ๊ฒฝ_์ ํ๋ฒํธ | ๋ณ๊ฒฝ_๋น๋ฐ๋ฒํธ | ์บ๋ฆฐ๋ |
+| :------------: | :------------: | :------------: |
+|  |  |  |
+
+(back to top)
+
+
+
+
+## โจ ํ์ ๋ฐ ์ญํ
+| ๊น์ฃผ์ | ๋ฐ์ฑ์ฑ | ํํ์ง | ๊น์ง๋ |
+| :----------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------:
+| ์ด๊ธฐ ๊ฐ๋ฐ ์ธํ
ํ์๊ฐ์
๋ก๊ทธ์ธ
๊ฐ์ธ์ ๋ณด์์
๊ด๋ฆฌ์
๋ฐฐํฌ
| ์ฐ์ฐจ ํ์ด์ง
๋น์ง ํ์ด์ง
์บ๋ฆฐ๋
๋ฐฐํฌ
| ์ฐ์ฐจ ํ์ด์ง
๋น์ง ํ์ด์ง
๊ฐ์ธ์ ๋ณด์์
์บ๋ฆฐ๋
| ํ๋ก์ ํธ ํ์ฅ
ํ๋ก์ ํธ์ธํ
๋ก๊ทธ์ธ
+
+(back to top)
+
+
+
+
+## ๐ ํ๋ก์ ํธ ์คํ(๋ฐฑ์๋)
+* ๊ฐ๋ฐํ๊ฒฝ : JAVA 11
+* IDE: IntelliJ
+* Build: Gradle
+* Framework: Spring-Boot 2.7.14
+* Database: MySQL
+* ORM: Mybatis
+* ์ฌ์ฉ๊ธฐ์
+ - Spring Web
+ - Spring Data JPA
+ - Spring DevTools
+ - Spring Security
+ - JWT 4.3.0
+ - h2database
+ - lombok
+ - Postman
+ - AWS EC2
+
+(back to top)
+
+
+
+
+## ๐ ERD
+
+
+(back to top)
+
+
+
+
+## โ ํ
์ด๋ธ ์ค๊ณ
+**1. ์ ์ **
+```
+CREATE TABLE user_tb
+(
+ id INT PRIMARY KEY AUTO_INCREMENT,
+ email VARCHAR(20) NOT NULL UNIQUE, --๋ก๊ทธ์ธ ID๋ก ์ฌ์ฉ
+ password VARCHAR(20) NOT NULL, --์ ํจ์ฑ๊ฒ์ฌ
+ username VARCHAR(20) NOT NULL, --์ค๋ช
,์ ํจ์ฑ๊ฒ์ฌ
+ phone_number VARCHAR(20) NOT NULL, --์ ํจ์ฑ๊ฒ์ฌ
+ position VARCHAR(20) NOT NULL, --์ฌ์, ์ฃผ์, ๋๋ฆฌ, ๊ณผ์ฅ, ์ฐจ์ฅ, ๋ถ์ฅ
+ roles VARCHAR(10) NOT NULL, --note: ์ผ๋ฐ, ๊ด๋ฆฌ์ / ENUM DEFAULT '์ผ๋ฐ'
+ update_date TIMESTAMP --๊ฐ์ธ์ ๋ณด ์ต์ข
์์ ์ผ/๋
ผ์
+);
+```
+**2. ๋ก๊ทธ์ธ**
+```
+CREATE TABLE login_tb
+(
+ id INT PRIMARY KEY AUTO_INCREMENT,
+ user_id INT NOT NULL,
+ ip VARCHAR(50) NOT NULL,
+ user_agent VARCHAR(10) NOT NULL,
+ login_at TIMESTAMP,
+
+ FOREIGN KEY (user_id) REFERENCES user_tb(id)
+);
+```
+**3. ์ฐ์ฐจ**
+```
+CREATE TABLE dayoff_tb
+(
+ id INT PRIMARY KEY AUTO_INCREMENT,
+ user_id INT NOT NULL,
+ num_of_dayoff FLOAT NOT NULL, --๋จ์ ์ฐ์ฐจ ์ผ์
+ start_date DATE NOT NULL, --์์์ผ
+ end_date DATE NOT NULL, --์ข
๋ฃ์ผ
+ reason VARCHAR(30) NOT NULL, --์ฌ์
+ type VARCHAR(10) NOT NULL, --์ฐ์ฐจ, ์ค์ ๋ฐ์ฐจ, ์คํ๋ฐ์ฐจ
+ status VARCHAR(10) NOT NULL, --๋๊ธฐ, ์น์ธ, ๋ฐ๋ ค ENUM DEFAULT '๋๊ธฐ'
+ apply_at DATE NOT NULL,
+ process_at DATE,
+
+ FOREIGN KEY (user_id) REFERENCES user_tb(id)
+);
+```
+**4.๋น์ง**
+```
+CREATE TABLE duty_tb
+(
+ id INT PRIMARY KEY AUTO_INCREMENT,
+ user_id INT NOT NULL,
+ date DATE NOT NULL, --๋น์ง ๋ ์ง
+ reason VARCHAR(30) NOT NULL, --์ฌ์
+ status VARCHAR(10) NOT NULL, --๋๊ธฐ, ์น์ธ, ๋ฐ๋ ค
+ apply_at DATE NOT NULL,
+ process_at DATE,
+
+ FOREIGN KEY (user_id) REFERENCES user_tb(id)
+);
+```
+(back to top)
+
+
+
+
+## ๐ ์ฃผ์ ๊ธฐ๋ฅ
+๐น ํ์๊ฐ์
+
+ - jwt์ด์ฉ, ๋น๋ฐ๋ฒํธ ๋จ๋ฐฉํฅ ์ํธํ
+ - ๊ฐ์ธ์ ๋ณด์๋๊ฒฝ์ฐ(์ด๋ฆ,ํด๋ํฐ,์ด๋ฉ์ผ) AES256์๋ฐฉํฅ ์ํธํ์ ์ฉ,๋ณตํธํ
+ - ๊ด๋ จํ๋์ DB๊ฒ์ํ ๋๋ ๊ฒ์์กฐ๊ฑด์ ์ํธํ
+
+๐น ๋ก๊ทธ์ธ
+
+ - jwt token ์ธ์ฆ๋ฐฉ์, spring security์ ์ฉ
+ - login ์ฑ๊ณต ํ ๋ง์ง๋ง ๋ก๊ทธ์ธ ์ฑ๊ณต ๋ ์ง ์
๋ฐ์ดํธ ์ ์ฉ
+ - login ์ฑ๊ณต ํ ํ์๋ฒํธ, user-agent, client ip, ์๊ฐ ๋ก๊ทธ ๋ฑ๋ก์ฒ๋ฆฌ
+ - ์ผ๋ฐ๋ก๊ทธ์ธ/๊ด๋ฆฌ์๋ก๊ทธ์ธ
+
+๐น ๊ฐ์ธ์ ๋ณด์์
+
+ - ๊ฐ์ธ์ ๋ณด ์์ ,์ญ์ , ์์ ๋ ์ผ์ ์
๋ฐ์ดํธ
+ - ๊ฐ์ธ์ฐ์ฐจ/๋น์ง ๋ฑ๋ก
+ - ๊ด๋ฆฌ์ (์ ์ ์ ๊ถํ ์ค์ ), ๋ก๊ทธ์ธ ๋ฐ๊ธ๋ jwt token๊ฒ์ฆ
+ - ๋น๋ฐ๋ฒํธ๋ณ๊ฒฝ(๋จ๋ฐฉํฅ ์ํธํ), ์ ํ๋ฒํธ๋ณ๊ฒฝ
+
+๐น ์ฐ์ฐจ/๋น์ง
+
+ - ์ ์ฒญ๋ด์ญ, ์ฌ์ฉ๋ด์ญ, ๋ฑ๋ก, ์กฐํ, ์ทจ์(crud)
+ - ์ฐ์ฐจ/๋น์ง ๊ตฌ๋ถ์ปฌ๋ผ๊ตฌ์ฑ, ํ
์ด๋ธ1๊ฐ์ค๊ณ
+ - ์ ์ฅ/์ญ์ api๊ตฌํํ์
+ - ๋ด ์ฐ์ฐจ์ผ ์
+
+๐น ๊ด๋ฆฌ์๊ธฐ๋ฅ
+
+ - ์ฐ์ฐจ ์น์ธ
+ - ์ฐ์ฐจ ๋ฐ๋ ค
+
+๐น ์ฌ์ฉ์๊ฐ๊ณต์
+
+ - ๋ฐ์ดํฐ ๋ด๋ ค์ค ์กฐํ api ๊ตฌํ
+ - ์๋ณ ์บ๋ฆฐ๋ ์ฃผ๊ฐ/์ผ๊ฐ ๋ฑ ๋ค์ํ๊ฒ ํํ
+
+
+(back to top)
+
+
+
+
+## ๐ API ๋ช
์ธ์
+
+
+(back to top)
+
+
diff --git a/build.gradle b/build.gradle
index 354ed98..517b895 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,7 @@ plugins {
}
group = 'com.example'
-version = '0.0.1-SNAPSHOT'
+version = '1.0'
java {
sourceCompatibility = '11'
@@ -32,8 +32,15 @@ dependencies {
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
+ implementation group: 'com.auth0', name: 'java-jwt', version: '4.3.0'
+ implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '3.1.4'
}
tasks.named('test') {
+ systemProperty 'file.encoding', 'UTF-8'
useJUnitPlatform()
}
+
+jar {
+ enabled = false
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/kdtbe5_miniproject/Kdtbe5MiniProjectApplication.java b/src/main/java/com/example/kdtbe5_miniproject/Kdtbe5MiniProjectApplication.java
index 735962c..99a1fda 100644
--- a/src/main/java/com/example/kdtbe5_miniproject/Kdtbe5MiniProjectApplication.java
+++ b/src/main/java/com/example/kdtbe5_miniproject/Kdtbe5MiniProjectApplication.java
@@ -2,7 +2,9 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
+@EnableJpaAuditing
@SpringBootApplication
public class Kdtbe5MiniProjectApplication {
diff --git a/src/main/java/com/example/kdtbe5_miniproject/_core/errors/MyExceptionHandler.java b/src/main/java/com/example/kdtbe5_miniproject/_core/errors/MyExceptionHandler.java
new file mode 100644
index 0000000..bfbac93
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/_core/errors/MyExceptionHandler.java
@@ -0,0 +1,48 @@
+package com.example.kdtbe5_miniproject._core.errors;
+
+import com.example.kdtbe5_miniproject._core.errors.exception.*;
+import com.example.kdtbe5_miniproject._core.util.ApiUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+
+@Slf4j
+@RequiredArgsConstructor
+@RestControllerAdvice
+public class MyExceptionHandler {
+
+ @ExceptionHandler(Exception400.class)
+ public ResponseEntity> badRequest(Exception400 e) {
+ return new ResponseEntity<>(e.body(), e.status());
+ }
+
+ @ExceptionHandler(Exception401.class)
+ public ResponseEntity> unAuthorized(Exception401 e) {
+ return new ResponseEntity<>(e.body(), e.status());
+ }
+
+ @ExceptionHandler(Exception403.class)
+ public ResponseEntity> forbidden(Exception403 e) {
+ return new ResponseEntity<>(e.body(), e.status());
+ }
+
+ @ExceptionHandler(Exception404.class)
+ public ResponseEntity> notFound(Exception404 e) {
+ return new ResponseEntity<>(e.body(), e.status());
+ }
+
+ @ExceptionHandler(Exception500.class)
+ public ResponseEntity> serverError(Exception500 e) {
+ return new ResponseEntity<>(e.body(), e.status());
+ }
+
+ @ExceptionHandler(Exception.class)
+ public ResponseEntity> unknownServerError(Exception e) {
+ ApiUtils.ApiMessageResult> apiResult = ApiUtils.error(e.getMessage());
+ return new ResponseEntity<>(apiResult, HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/_core/errors/MyValidationHandler.java b/src/main/java/com/example/kdtbe5_miniproject/_core/errors/MyValidationHandler.java
new file mode 100644
index 0000000..a80f0ab
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/_core/errors/MyValidationHandler.java
@@ -0,0 +1,38 @@
+package com.example.kdtbe5_miniproject._core.errors;
+
+import com.example.kdtbe5_miniproject._core.errors.exception.Exception400;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.Errors;
+
+@Aspect
+@Component
+public class MyValidationHandler {
+ @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
+ public void postMapping() {
+ }
+
+ @Pointcut("@annotation(org.springframework.web.bind.annotation.PutMapping)")
+ public void putMapping() {
+ }
+
+ @Before("postMapping() || putMapping()")
+ public void validationAdvice(JoinPoint jp) {
+ Object[] args = jp.getArgs();
+ for (Object arg : args) {
+ if (arg instanceof Errors) {
+ Errors errors = (Errors) arg;
+
+ if (errors.hasErrors()) {
+ throw new Exception400(
+ errors.getFieldErrors().get(0).getField(),
+ errors.getFieldErrors().get(0).getDefaultMessage()
+ );
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/DuplicatedEmailException.java b/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/DuplicatedEmailException.java
new file mode 100644
index 0000000..c1b85ed
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/DuplicatedEmailException.java
@@ -0,0 +1,8 @@
+package com.example.kdtbe5_miniproject._core.errors.exception;
+
+public class DuplicatedEmailException extends RuntimeException{
+
+ public DuplicatedEmailException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/DutyNotFoundException.java b/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/DutyNotFoundException.java
new file mode 100644
index 0000000..171da00
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/DutyNotFoundException.java
@@ -0,0 +1,7 @@
+package com.example.kdtbe5_miniproject._core.errors.exception;
+
+public class DutyNotFoundException extends RuntimeException {
+ public DutyNotFoundException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/Exception400.java b/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/Exception400.java
new file mode 100644
index 0000000..286bcfb
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/Exception400.java
@@ -0,0 +1,27 @@
+package com.example.kdtbe5_miniproject._core.errors.exception;
+
+import com.example.kdtbe5_miniproject._core.util.ApiUtils;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+
+@Getter
+public class Exception400 extends RuntimeException {
+
+ private String key;
+ private String value;
+
+ public Exception400(String key, String value) {
+ super(key + " : " + value);
+ this.key = key;
+ this.value = value;
+ }
+
+ public ApiUtils.ApiMessageResult> body() {
+ return ApiUtils.error(getMessage());
+ }
+
+ public HttpStatus status() {
+ return HttpStatus.BAD_REQUEST;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/Exception401.java b/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/Exception401.java
new file mode 100644
index 0000000..7735c53
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/Exception401.java
@@ -0,0 +1,22 @@
+package com.example.kdtbe5_miniproject._core.errors.exception;
+
+
+import com.example.kdtbe5_miniproject._core.util.ApiUtils;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+
+@Getter
+public class Exception401 extends RuntimeException {
+ public Exception401(String message) {
+ super(message);
+ }
+
+ public ApiUtils.ApiMessageResult> body() {
+ return ApiUtils.error(getMessage());
+ }
+
+ public HttpStatus status() {
+ return HttpStatus.UNAUTHORIZED;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/Exception403.java b/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/Exception403.java
new file mode 100644
index 0000000..fefcd45
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/Exception403.java
@@ -0,0 +1,21 @@
+package com.example.kdtbe5_miniproject._core.errors.exception;
+
+import com.example.kdtbe5_miniproject._core.util.ApiUtils;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+
+@Getter
+public class Exception403 extends RuntimeException {
+ public Exception403(String message) {
+ super(message);
+ }
+
+ public ApiUtils.ApiMessageResult> body() {
+ return ApiUtils.error(getMessage());
+ }
+
+ public HttpStatus status() {
+ return HttpStatus.FORBIDDEN;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/Exception404.java b/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/Exception404.java
new file mode 100644
index 0000000..3b3289e
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/Exception404.java
@@ -0,0 +1,21 @@
+package com.example.kdtbe5_miniproject._core.errors.exception;
+
+import com.example.kdtbe5_miniproject._core.util.ApiUtils;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+
+@Getter
+public class Exception404 extends RuntimeException {
+ public Exception404(String message) {
+ super(message);
+ }
+
+ public ApiUtils.ApiMessageResult> body() {
+ return ApiUtils.error(getMessage());
+ }
+
+ public HttpStatus status() {
+ return HttpStatus.NOT_FOUND;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/Exception500.java b/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/Exception500.java
new file mode 100644
index 0000000..41a25fd
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/Exception500.java
@@ -0,0 +1,23 @@
+package com.example.kdtbe5_miniproject._core.errors.exception;
+
+import com.example.kdtbe5_miniproject._core.util.ApiUtils;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+
+@Getter
+public class Exception500 extends RuntimeException {
+ public Exception500(String message) {
+ super(message);
+ }
+
+
+ public ApiUtils.ApiMessageResult> body() {
+ return ApiUtils.error(getMessage());
+ }
+
+ public HttpStatus status() {
+ return HttpStatus.INTERNAL_SERVER_ERROR;
+ }
+
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/UnCorrectPasswordException.java b/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/UnCorrectPasswordException.java
new file mode 100644
index 0000000..f3c1d81
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/UnCorrectPasswordException.java
@@ -0,0 +1,8 @@
+package com.example.kdtbe5_miniproject._core.errors.exception;
+
+public class UnCorrectPasswordException extends RuntimeException{
+
+ public UnCorrectPasswordException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/UserNotFoundException.java b/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/UserNotFoundException.java
new file mode 100644
index 0000000..91299b0
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/_core/errors/exception/UserNotFoundException.java
@@ -0,0 +1,8 @@
+package com.example.kdtbe5_miniproject._core.errors.exception;
+
+public class UserNotFoundException extends RuntimeException{
+
+ public UserNotFoundException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/_core/security/CustomUserDetails.java b/src/main/java/com/example/kdtbe5_miniproject/_core/security/CustomUserDetails.java
new file mode 100644
index 0000000..93d26ac
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/_core/security/CustomUserDetails.java
@@ -0,0 +1,70 @@
+package com.example.kdtbe5_miniproject._core.security;
+
+import com.example.kdtbe5_miniproject.user.User;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+@RequiredArgsConstructor
+@Getter
+public class CustomUserDetails implements UserDetails {
+
+ private final User user;
+
+ //TODO : UserRoles๋ฅผ Enum์ผ๋ก ๋ณ๊ฒฝํ๋ฉด์ ์ค๋ฅ๊ฐ ์๊ฒจ getAuthorities(), isAdmin() ๋ฉ์๋ ๋ด์ฉ ์์ ํ๋๋ฐ ํ์ธ ํ์ํ ๊ฒ ๊ฐ์์
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ Collection authorities = new ArrayList<>();
+ authorities.add(()-> "ROLE_"+user.getRoles().getTypeNumber());
+ return authorities;
+// return Arrays.stream(user.getRoles().split(",")).map(SimpleGrantedAuthority::new).collect(Collectors.toList());
+ }
+
+ @Override
+ public String getPassword() {
+ return user.getPassword();
+ }
+
+ @Override
+ public String getUsername() {
+ return user.getEmail();
+ }
+
+ //TODO ๋ณ๊ฒฝ ํ์ธ
+ public Long getUserId() {
+ return user.getId();
+ }
+
+ public boolean isAdmin() {
+ if(user.getRoles().getTypeNumber()==1)
+ return true;
+ return false;
+ }
+
+ @Override
+ public boolean isAccountNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isAccountNonLocked() {
+ return true;
+ }
+
+ @Override
+ public boolean isCredentialsNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/_core/security/CustomUserDetailsService.java b/src/main/java/com/example/kdtbe5_miniproject/_core/security/CustomUserDetailsService.java
new file mode 100644
index 0000000..8d5e8f4
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/_core/security/CustomUserDetailsService.java
@@ -0,0 +1,32 @@
+package com.example.kdtbe5_miniproject._core.security;
+
+import com.example.kdtbe5_miniproject.user.User;
+import com.example.kdtbe5_miniproject.user.UserRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+import java.util.Optional;
+
+@RequiredArgsConstructor
+@Service
+public class CustomUserDetailsService implements UserDetailsService {
+
+ private final UserRepository userRepository;
+
+ // login ํธ์ถ
+ @Override
+ public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
+
+ Optional userOP = userRepository.findByEmail(email);
+
+ if (userOP.isPresent()) {
+ return new CustomUserDetails(userOP.get());
+ } else {
+ return null;
+ }
+
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/_core/security/JwtAuthenticationFilter.java b/src/main/java/com/example/kdtbe5_miniproject/_core/security/JwtAuthenticationFilter.java
new file mode 100644
index 0000000..8bc0b7e
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/_core/security/JwtAuthenticationFilter.java
@@ -0,0 +1,82 @@
+package com.example.kdtbe5_miniproject._core.security;
+
+
+import com.auth0.jwt.exceptions.JWTDecodeException;
+import com.auth0.jwt.exceptions.SignatureVerificationException;
+import com.auth0.jwt.exceptions.TokenExpiredException;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.example.kdtbe5_miniproject._core.util.ApiUtils;
+import com.example.kdtbe5_miniproject.user.User;
+import com.example.kdtbe5_miniproject.user.UserRoles;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@Slf4j
+public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
+
+ public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
+ super(authenticationManager);
+ }
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
+ String prefixJwt = request.getHeader(JwtTokenProvider.HEADER);
+
+ if (prefixJwt == null) {
+ chain.doFilter(request, response);
+ return;
+ }
+
+ String jwt = prefixJwt.replace(JwtTokenProvider.TOKEN_PREFIX, "");
+
+ // TODO ๋ณ๊ฒฝ ํ์ธ
+ try {
+ DecodedJWT decodedJWT = JwtTokenProvider.verify(jwt); // ์ ์์ธ์ฆ ๋
+ Long id = decodedJWT.getClaim("id").asLong();
+ String roles = decodedJWT.getClaim("role").asString();
+
+ User user = User.builder().id(id).roles(UserRoles.valueOf(roles)).build();
+// User user = User.builder().id(id).roles(roles).build();
+ CustomUserDetails myUserDetails = new CustomUserDetails(user);
+ Authentication authentication =
+ new UsernamePasswordAuthenticationToken(
+ myUserDetails,
+ myUserDetails.getPassword(),
+ myUserDetails.getAuthorities()
+ );
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ chain.doFilter(request, response);
+ log.debug("๋๋ฒ๊ทธ : ์ธ์ฆ ๊ฐ์ฒด ๋ง๋ค์ด์ง");
+ } catch (SignatureVerificationException sve) {
+ log.error("ํ ํฐ ๊ฒ์ฆ ์คํจ");
+ AuthorizationTokenError(response, "ํ ํฐ ๊ฒ์ฆ ์คํจ");
+ } catch (TokenExpiredException tee) {
+ log.error("ํ ํฐ ๋ง๋ฃ๋จ");
+ AuthorizationTokenError(response, "ํ ํฐ ๋ง๋ฃ๋จ");
+ } catch (JWTDecodeException jde) {
+ log.error("ํ ํฐ ๋์ฝ๋ฉ ์คํจ");
+ AuthorizationTokenError(response, "ํ ํฐ ๋์ฝ๋ฉ ์คํจ");
+ }
+ }
+
+ private void AuthorizationTokenError(HttpServletResponse response, String errorMessage) throws IOException {
+ ObjectMapper objectMapper = new ObjectMapper();
+ response.setCharacterEncoding("UTF-8");
+ response.setStatus(HttpStatus.UNAUTHORIZED.value());
+ response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+ response.getWriter().write(objectMapper.writeValueAsString(ApiUtils.error(errorMessage)));
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/_core/security/JwtTokenProvider.java b/src/main/java/com/example/kdtbe5_miniproject/_core/security/JwtTokenProvider.java
new file mode 100644
index 0000000..6d82582
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/_core/security/JwtTokenProvider.java
@@ -0,0 +1,40 @@
+package com.example.kdtbe5_miniproject._core.security;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.exceptions.SignatureVerificationException;
+import com.auth0.jwt.exceptions.TokenExpiredException;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.example.kdtbe5_miniproject.user.User;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+
+
+@Component
+public class JwtTokenProvider {
+ public static final Long EXP = 1000L * 60 * 60 * 48; // 48์๊ฐ - ํ
์คํธ ํ๊ธฐ ํธํจ.
+ public static final String TOKEN_PREFIX = "Bearer "; // ์คํ์ด์ค ํ์ํจ
+ public static final String HEADER = "Authorization";
+ public static final String SECRET = "MySecretKey";
+
+ // TODO ๋ณ๊ฒฝํ์ธ
+ public static String create(User user) {
+ // ํ ํฐ์๋ ๋ฐ์ดํฐ๋ฅผ ๋ด์ ์ ์๋ค. ๋ฏผ๊ฐํ ์ ๋ณด๋ ์ถ๊ฐํ๋ฉด ์ ๋ ์๋๋ค.
+ String jwt = JWT.create()
+ .withSubject(user.getEmail())
+ .withExpiresAt(new Date(System.currentTimeMillis() + EXP))
+ .withClaim("id", user.getId())
+ .withClaim("role", user.getRoles().name())
+// .withClaim("role", user.getRoles())
+ .sign(Algorithm.HMAC512(SECRET));
+ return TOKEN_PREFIX + jwt;
+ }
+
+ public static DecodedJWT verify(String jwt) throws SignatureVerificationException, TokenExpiredException {
+ DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC512(SECRET))
+ .build().verify(jwt);
+ return decodedJWT;
+ }
+
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/_core/security/SecurityConfig.java b/src/main/java/com/example/kdtbe5_miniproject/_core/security/SecurityConfig.java
new file mode 100644
index 0000000..5301692
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/_core/security/SecurityConfig.java
@@ -0,0 +1,103 @@
+package com.example.kdtbe5_miniproject._core.security;
+
+import com.example.kdtbe5_miniproject._core.errors.exception.Exception401;
+import com.example.kdtbe5_miniproject._core.errors.exception.Exception403;
+import com.example.kdtbe5_miniproject._core.util.FilterResponseUtils;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.factory.PasswordEncoderFactories;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+
+@Configuration
+public class SecurityConfig {
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return PasswordEncoderFactories.createDelegatingPasswordEncoder();
+ }
+
+ @Bean
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
+ return authenticationConfiguration.getAuthenticationManager();
+ }
+
+ public class CustomSecurityFilterManager extends AbstractHttpConfigurer {
+ @Override
+ public void configure(HttpSecurity builder) throws Exception {
+ AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
+ builder.addFilter(new JwtAuthenticationFilter(authenticationManager));
+ super.configure(builder);
+ }
+ }
+
+ @Bean // ์ปดํผ๋ํธ ์ค์บ์์ @Bean์ด ๋ถ์ ๋ฉ์๋๊ฐ ์์ผ๋ฉด ์คํ์์ผ์ ๋ฆฌํด๋๋ ๊ฐ์ IoC์ ๋ฑ๋กํ๋ ๊น๋ฐ
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ // 1. CSRF ํด์
+ http.csrf().disable(); // postman ์ ๊ทผํด์ผ ํจ!! - CSR ํ ๋!!
+
+ // 2. iframe ๊ฑฐ๋ถ
+ http.headers().frameOptions().sameOrigin();
+
+ // 3. cors ์ฌ์ค์
+ http.cors().configurationSource(configurationSource());
+
+ // 4. jSessionId๊ฐ ์๋ต์ด ๋ ๋ ์ธ์
์ ์ญ์์ ์ฌ๋ผ์ง๋ค (์ด๊ฒ stateless)
+ http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
+
+ // 5. form ๋ก๊ธด ํด์ (UsernamePasswordAuthenticationFilter ๋นํ์ฑํ)
+ http.formLogin().disable();
+
+ // 6. ๋ก๊ทธ์ธ ์ธ์ฆ์ฐฝ์ด ๋จ์ง ์๊ฒ ๋นํ์ฑํ
+ http.httpBasic().disable();
+
+ // 7. ์ปค์คํ
ํํฐ ์ ์ฉ (์ํ๋ฆฌํฐ ํํฐ ๊ตํ)
+ http.apply(new CustomSecurityFilterManager());
+
+ // 8. ์ธ์ฆ ์คํจ ์ฒ๋ฆฌ
+ http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
+ FilterResponseUtils.unAuthorized(response, new Exception401("์ธ์ฆ๋์ง ์์์ต๋๋ค"));
+ });
+
+ // 9. ๊ถํ ์คํจ ์ฒ๋ฆฌ
+ http.exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -> {
+ FilterResponseUtils.forbidden(response, new Exception403("๊ถํ์ด ์์ต๋๋ค"));
+ });
+
+ // 11. ์ธ์ฆ, ๊ถํ ํํฐ ์ค์
+ http.authorizeRequests(
+ authorize -> authorize.antMatchers("/account/**").authenticated()
+ .antMatchers("/admin/**").access("hasRole('1')")
+ .anyRequest().permitAll()
+ );
+ return http.build();
+ }
+
+ protected void configure(HttpSecurity http)throws Exception {
+ http.logout()
+ .logoutSuccessUrl("/") //๋ก๊ทธ์์ ์ฑ๊ณต์ ๋ฆฌ๋ค์ด๋ ํธ์ฃผ์
+ .invalidateHttpSession(true) //๋ก๊ทธ์์ ์ดํ ์ธ์
์ ์ฒด ์ญ์ ์ฌ๋ถ
+ .deleteCookies("JSSIONID");
+ }
+
+ public CorsConfigurationSource configurationSource() {
+ CorsConfiguration configuration = new CorsConfiguration();
+ configuration.addAllowedHeader("*");
+ configuration.addAllowedMethod("*"); // GET, POST, PUT, DELETE (Javascript ์์ฒญ ํ์ฉ)
+ // localhost:8080 ๋ฐฑ์๋, localhost:3000 ํ๋ก ํธ์๋
+ configuration.addAllowedOriginPattern("*"); // ๋ชจ๋ IP ์ฃผ์ ํ์ฉ (ํ๋ก ํธ ์ค๋ IP๋ง ํ์ฉ react)
+ configuration.setAllowCredentials(true); // ํด๋ผ์ด์ธํธ์์ ์ฟ ํค ์์ฒญ ํ์ฉ
+ configuration.addExposedHeader("Authorization"); // ์๋ ์๋ ๋ํดํธ ์๋ค. ์ง๊ธ์ ์๋๋๋ค.
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ source.registerCorsConfiguration("/**", configuration); // /login, /board, /product/
+ return source;
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/_core/util/ApiUtils.java b/src/main/java/com/example/kdtbe5_miniproject/_core/util/ApiUtils.java
new file mode 100644
index 0000000..f0b22eb
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/_core/util/ApiUtils.java
@@ -0,0 +1,37 @@
+package com.example.kdtbe5_miniproject._core.util;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.http.HttpStatus;
+
+// ๊ณตํต ์๋ต DTO
+public class ApiUtils {
+ public static ApiDataResult success(T data) {
+ return new ApiDataResult<>(true, data);
+ }
+
+ public static ApiMessageResult success(String message) {
+ return new ApiMessageResult<>(true, message);
+ }
+
+ public static ApiMessageResult> error(String message) {
+ return new ApiMessageResult<>(false, message);
+ }
+
+ @Getter
+ @Setter
+ @AllArgsConstructor
+ public static class ApiDataResult {
+ private final boolean success;
+ private final T data;
+ }
+
+ @Getter
+ @Setter
+ @AllArgsConstructor
+ public static class ApiMessageResult {
+ private final boolean success;
+ private final String message;
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/_core/util/BaseTimeEntity.java b/src/main/java/com/example/kdtbe5_miniproject/_core/util/BaseTimeEntity.java
new file mode 100644
index 0000000..b36de28
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/_core/util/BaseTimeEntity.java
@@ -0,0 +1,19 @@
+package com.example.kdtbe5_miniproject._core.util;
+
+import lombok.Getter;
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.annotation.LastModifiedDate;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+
+import javax.persistence.EntityListeners;
+import javax.persistence.MappedSuperclass;
+import java.time.LocalDateTime;
+
+@Getter
+@MappedSuperclass
+@EntityListeners(AuditingEntityListener.class)
+public class BaseTimeEntity extends DateUtils{
+
+ @LastModifiedDate
+ private LocalDateTime updateDate; //TODO ์ด๊ฐ ์์์ ๋จ์๋ก ์์ฑ๋์ด์ ์ ์ฅ๋จ, ์์์ ์ดํ ๋ฒ๋ฆฌ๊ธฐ
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/_core/util/DBInit.java b/src/main/java/com/example/kdtbe5_miniproject/_core/util/DBInit.java
new file mode 100644
index 0000000..f48ad9a
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/_core/util/DBInit.java
@@ -0,0 +1,48 @@
+package com.example.kdtbe5_miniproject._core.util;
+
+import com.example.kdtbe5_miniproject.user.User;
+import com.example.kdtbe5_miniproject.user.UserPosition;
+import com.example.kdtbe5_miniproject.user.UserRepository;
+import com.example.kdtbe5_miniproject.user.UserRoles;
+import lombok.RequiredArgsConstructor;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Profile;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDate;
+import java.util.Arrays;
+
+@RequiredArgsConstructor
+@Component
+public class DBInit {
+
+ private final PasswordEncoder passwordEncoder;
+
+ @Profile("dev")
+ @Bean
+ CommandLineRunner initDB(UserRepository userRepository){
+ return args -> {
+ User user = User.builder()
+ .username("์ต์ํ")
+ .password(passwordEncoder.encode("0000"))
+ .email("csh@fastcampus.com")
+ .phoneNumber("01011112222")
+ .position(UserPosition.valueOf("์ฌ์"))
+ .roles(UserRoles.valueOf("์ผ๋ฐ"))
+ .joinDate(LocalDate.now())
+ .build();
+ User admin = User.builder()
+ .username("ํ์ธํฌ")
+ .password(passwordEncoder.encode("0000"))
+ .email("hsh@fastcampus.com")
+ .phoneNumber("01011112222")
+ .position(UserPosition.valueOf("๋ถ์ฅ"))
+ .roles(UserRoles.valueOf("๊ด๋ฆฌ์"))
+ .joinDate(LocalDate.now())
+ .build();
+ userRepository.saveAll(Arrays.asList(user, admin));
+ };
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/_core/util/DateUtils.java b/src/main/java/com/example/kdtbe5_miniproject/_core/util/DateUtils.java
new file mode 100644
index 0000000..6fed68d
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/_core/util/DateUtils.java
@@ -0,0 +1,10 @@
+package com.example.kdtbe5_miniproject._core.util;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+public class DateUtils {
+ public static String toStringFormat(LocalDateTime localDateTime) {
+ return localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/_core/util/EncryptUtils.java b/src/main/java/com/example/kdtbe5_miniproject/_core/util/EncryptUtils.java
new file mode 100644
index 0000000..2eb869e
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/_core/util/EncryptUtils.java
@@ -0,0 +1,38 @@
+package com.example.kdtbe5_miniproject._core.util;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.util.Base64;
+
+public class EncryptUtils {
+
+ public static String alg = "AES/CBC/PKCS5Padding";
+ private static final String key = "a1b2c3d4e5f6g7h8i9j0k9j8i7h6g5f4";
+ private static String iv = "a1b2c3d4e5f6g7h8";
+
+ public static String encrypt(String text) {
+ try {
+ Cipher cipher = Cipher.getInstance(alg);
+ SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
+ IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes());
+ cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec);
+
+ byte[] encrypted = cipher.doFinal(text.getBytes("UTF-8"));
+ return Base64.getEncoder().encodeToString(encrypted);
+ } catch (Exception e) {
+ throw new RuntimeException(); //TODO ์์ธ์ฒ๋ฆฌ custom
+ }
+ }
+
+ public static String decrypt(String cipherText) throws Exception{
+ Cipher cipher = Cipher.getInstance(alg);
+ SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(),"AES");
+ IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes());
+ cipher.init(Cipher.DECRYPT_MODE,keySpec,ivParameterSpec);
+
+ byte[] decoded = Base64.getDecoder().decode(cipherText);
+ byte[] decrypted = cipher.doFinal(decoded);
+ return new String(decrypted, "UTF-8");
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/_core/util/FilterResponseUtils.java b/src/main/java/com/example/kdtbe5_miniproject/_core/util/FilterResponseUtils.java
new file mode 100644
index 0000000..842d981
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/_core/util/FilterResponseUtils.java
@@ -0,0 +1,26 @@
+package com.example.kdtbe5_miniproject._core.util;
+
+import com.example.kdtbe5_miniproject._core.errors.exception.Exception401;
+import com.example.kdtbe5_miniproject._core.errors.exception.Exception403;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+public class FilterResponseUtils {
+ public static void unAuthorized(HttpServletResponse resp, Exception401 e) throws IOException {
+ resp.setStatus(e.status().value());
+ resp.setContentType("application/json; charset=utf-8");
+ ObjectMapper om = new ObjectMapper();
+ String responseBody = om.writeValueAsString(e.body());
+ resp.getWriter().println(responseBody);
+ }
+
+ public static void forbidden(HttpServletResponse resp, Exception403 e) throws IOException {
+ resp.setStatus(e.status().value());
+ resp.setContentType("application/json; charset=utf-8");
+ ObjectMapper om = new ObjectMapper();
+ String responseBody = om.writeValueAsString(e.body());
+ resp.getWriter().println(responseBody);
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/admin/AdminController.java b/src/main/java/com/example/kdtbe5_miniproject/admin/AdminController.java
new file mode 100644
index 0000000..0b1f839
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/admin/AdminController.java
@@ -0,0 +1,66 @@
+package com.example.kdtbe5_miniproject.admin;
+
+import com.example.kdtbe5_miniproject._core.util.ApiUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.List;
+
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/admin")
+@Slf4j
+public class AdminController {
+
+ private final AdminService adminService;
+
+ // ์น์ธ ๋๊ธฐ์ค์ธ ์ฐ์ฐจ&๋น์ง ๋ชฉ๋ก ์กฐํ
+ @GetMapping("/status")
+ public ResponseEntity> waitingStatusList() {
+
+ HashMap lists = new HashMap<>();
+ lists.put("dayOffList", adminService.findWaitingDayOff());
+ lists.put("dutyList", adminService.findWaitingDuty());
+
+ return ResponseEntity.ok().body(ApiUtils.success(lists));
+ }
+
+ // ์ ์ฒด ์ฌ์ฉ์ ์ธ๋ถ์ ๋ณด
+ @GetMapping("/users")
+ public ResponseEntity> AllUsersList() {
+ return ResponseEntity.ok().body(ApiUtils.success(adminService.findAllUsers()));
+ }
+
+ // ํน์ ์ฌ์ฉ์ ์ธ๋ถ์ ๋ณด
+ @GetMapping("/users/{userId}")
+ public ResponseEntity> userDetails(@PathVariable Long userId) {
+ return ResponseEntity.ok().body(ApiUtils.success(adminService.findUserDetail(userId)));
+ }
+
+ // ์ฐ์ฐจ ์น์ธor๋ฐ๋ ค
+ @PutMapping("/status/dayoff/{dayOffId}")
+ public ResponseEntity> dayOffModify(@PathVariable Long dayOffId, @RequestBody AdminRequest.TreatDayOffDTO request) {
+ adminService.modifyDayOffStatus(dayOffId, request);
+
+ return ResponseEntity.ok().body(ApiUtils.success(request.getStatus() + "๋์์ต๋๋ค."));
+ }
+
+ // ๋น์ง ์น์ธor๋ฐ๋ ค
+ @PutMapping("/status/duty/{dutyId}")
+ public ResponseEntity> dutyModify(@PathVariable Long dutyId, @RequestBody AdminRequest.TreatDutyDTO request) {
+ adminService.modifyDutyStatus(dutyId, request);
+
+ return ResponseEntity.ok().body(ApiUtils.success(request.getStatus() + "๋์์ต๋๋ค."));
+ }
+
+ // ์ฌ์ฉ์ ์ ๋ณด ๋ณ๊ฒฝ (์ ํ๋ฒํธ, ์ง๊ธ, ๊ถํ)
+ @PutMapping("/users/update/{userId}")
+ public ResponseEntity> userModify(@PathVariable Long userId, @RequestBody AdminRequest.UserDetailsDTO request) {
+ adminService.modifyUser(userId, request);
+
+ return ResponseEntity.ok().body(ApiUtils.success("์์ ๋์์ต๋๋ค."));
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/admin/AdminRepository.java b/src/main/java/com/example/kdtbe5_miniproject/admin/AdminRepository.java
new file mode 100644
index 0000000..ff16fcc
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/admin/AdminRepository.java
@@ -0,0 +1,104 @@
+package com.example.kdtbe5_miniproject.admin;
+
+import com.example.kdtbe5_miniproject.dayoff.DayOff;
+import com.example.kdtbe5_miniproject.dayoff.DayOffStatus;
+import com.example.kdtbe5_miniproject.duty.Duty;
+import com.example.kdtbe5_miniproject.duty.DutyStatus;
+import com.example.kdtbe5_miniproject.user.User;
+import com.example.kdtbe5_miniproject.user.UserPosition;
+import com.example.kdtbe5_miniproject.user.UserRoles;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.persistence.EntityManager;
+import javax.persistence.Query;
+import java.time.LocalDate;
+import java.util.List;
+
+@Repository
+@RequiredArgsConstructor
+public class AdminRepository {
+
+ private final EntityManager entityManager;
+
+ public List findDayOffByStatus() {
+ Query query = entityManager.createQuery(
+ "SELECT d FROM DayOff d INNER JOIN d.user u", DayOff.class);
+
+ return query.getResultList();
+ }
+
+ public List findDayOffByStatus(Long userId, DayOffStatus status) {
+ Query query = entityManager.createQuery(
+ "SELECT d.id FROM DayOff d INNER JOIN d.user u WHERE d.status = :status AND d.user.id = :userId ORDER BY d.applyAt ASC");
+ query.setParameter("status", status);
+ query.setParameter("userId", userId);
+
+ return query.getResultList();
+ }
+
+
+ public DayOff findDayOffById(Long id) {
+ Query query = entityManager.createQuery(
+ "SELECT d FROM DayOff d WHERE d.id = :id", DayOff.class);
+ query.setParameter("id", id);
+
+ return (DayOff) query.getSingleResult();
+ }
+
+ public List findDutyByStatus() {
+ Query query = entityManager.createQuery(
+ "SELECT d FROM Duty d INNER JOIN d.user u", Duty.class);
+
+ return query.getResultList();
+ }
+
+ //TODO ์ต๊ทผ ๋ ์ง๋ก ์กฐํ
+ public List findAllUsers() {
+ //๋๊ธฐ ์ํ์ธ ์ฐ์ฐจ ์ค numOfDayOff๊ฐ ๊ฐ์ฅ ๋ฎ์ ๊ฐ์ผ๋ก ๊ฐ์ ธ์ด
+ Query query = entityManager.createQuery(
+ "SELECT u From User u", User.class);
+
+ return query.getResultList();
+ }
+
+ public User findUserById(Long userId) {
+ Query query = entityManager.createQuery(
+ "SELECT u FROM User u WHERE u.id = :id", User.class);
+ query.setParameter("id", userId);
+
+ return (User) query.getSingleResult();
+ }
+
+ @Transactional
+ public void updateNumOfDayOffById(Long id, DayOffStatus status, LocalDate now) {
+ Query query = entityManager.createQuery(
+ "UPDATE DayOff SET status = :status, processAt = :now WHERE id = :id");
+ query.setParameter("id", id);
+ query.setParameter("status", status);
+ query.setParameter("now", now);
+ query.executeUpdate();
+ }
+
+ @Transactional
+ public void updateDutyById(Long id, DutyStatus status, LocalDate now) {
+ Query query = entityManager.createQuery(
+ "UPDATE Duty SET status = :status, processAt = :now WHERE id = :id");
+ query.setParameter("id", id);
+ query.setParameter("status", status);
+ query.setParameter("now", now);
+ query.executeUpdate();
+ }
+
+ @Transactional
+ public void updateUserById(Long id, String phoneNumber, UserPosition position, UserRoles roles) {
+ Query query = entityManager.createQuery(
+ "UPDATE User SET phoneNumber = :phoneNumber, position = :position, roles = : roles WHERE id = :id");
+ query.setParameter("id", id);
+ query.setParameter("phoneNumber", phoneNumber);
+ query.setParameter("position", position);
+ query.setParameter("roles", roles);
+ query.executeUpdate();
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/admin/AdminRequest.java b/src/main/java/com/example/kdtbe5_miniproject/admin/AdminRequest.java
new file mode 100644
index 0000000..b61999a
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/admin/AdminRequest.java
@@ -0,0 +1,42 @@
+package com.example.kdtbe5_miniproject.admin;
+
+import com.example.kdtbe5_miniproject.dayoff.DayOffStatus;
+import com.example.kdtbe5_miniproject.dayoff.DayOffType;
+import com.example.kdtbe5_miniproject.duty.DutyStatus;
+import com.example.kdtbe5_miniproject.user.UserPosition;
+import com.example.kdtbe5_miniproject.user.UserRoles;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.Pattern;
+import java.time.LocalDate;
+
+public class AdminRequest {
+
+ @Getter
+ @Setter
+ public static class TreatDayOffDTO {
+ @NotEmpty
+ private DayOffStatus status;
+ }
+
+ @Getter
+ @Setter
+ public static class TreatDutyDTO {
+ @NotEmpty
+ private DutyStatus status;
+ }
+
+ @Getter
+ @Setter
+ public static class UserDetailsDTO {
+ @NotEmpty
+ @Pattern(regexp = "^01(?:0|1|[6-9])\\d{7,8}$", message = "01011112222์ ๊ฐ์ ํ์์ผ๋ก ์์ฑํด์ฃผ์ธ์")
+ private String phoneNumber;
+ @NotEmpty
+ private UserPosition position;
+ @NotEmpty
+ private UserRoles roles;
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/admin/AdminResponse.java b/src/main/java/com/example/kdtbe5_miniproject/admin/AdminResponse.java
new file mode 100644
index 0000000..a34336d
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/admin/AdminResponse.java
@@ -0,0 +1,105 @@
+package com.example.kdtbe5_miniproject.admin;
+
+import com.example.kdtbe5_miniproject.dayoff.DayOff;
+import com.example.kdtbe5_miniproject.dayoff.DayOffStatus;
+import com.example.kdtbe5_miniproject.dayoff.DayOffType;
+import com.example.kdtbe5_miniproject.duty.Duty;
+import com.example.kdtbe5_miniproject.duty.DutyStatus;
+import com.example.kdtbe5_miniproject.user.User;
+import com.example.kdtbe5_miniproject.user.UserPosition;
+import com.example.kdtbe5_miniproject.user.UserRoles;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDate;
+
+public class AdminResponse {
+
+ @Getter
+ @Setter
+ public static class DayOffStatusDTO {
+ private Long id;
+ private Long userId;
+ private String userName;
+ private int position;
+ private LocalDate startDate;
+ private LocalDate endDate;
+ private String reason;
+ private int type;
+ private int status;
+
+ public DayOffStatusDTO(DayOff dayOff) {
+ this.id = dayOff.getId();
+ this.userId = dayOff.getUser().getId();
+ this.userName = dayOff.getUser().getUsername();
+ this.position = dayOff.getUser().getPosition().getTypeNumber();
+ this.startDate = dayOff.getStartDate();
+ this.endDate = dayOff.getEndDate();
+ this.reason = dayOff.getReason();
+ this.type = dayOff.getType().getTypeNumber();
+ this.status = dayOff.getStatus().getTypeNumber();
+ }
+ }
+
+ @Getter
+ @Setter
+ public static class DayOffInfoDTO {
+ private Long id;
+ private int type;
+ private Float numOfDayOff;
+ private LocalDate startDate;
+ private LocalDate endDate;
+
+ public DayOffInfoDTO(DayOff dayOff) {
+ this.id = dayOff.getId();
+ this.type = dayOff.getType().getTypeNumber();
+ this.numOfDayOff = dayOff.getNumOfDayOff();
+ this.startDate = dayOff.getStartDate();
+ this.endDate = dayOff.getEndDate();
+ }
+ }
+
+ @Getter
+ @Setter
+ public static class DutyStatusDTO {
+ private Long id;
+ private Long userId;
+ private String username;
+ private int position;
+ private LocalDate date;
+ private String reason;
+ private int status;
+
+ public DutyStatusDTO(Duty duty) {
+ this.id = duty.getId();
+ this.userId = duty.getUser().getId();
+ this.username = duty.getUser().getUsername();
+ this.position = duty.getUser().getPosition().getTypeNumber();
+ this.date = duty.getDate();
+ this.reason = duty.getReason();
+ this.status = duty.getStatus().getValue();
+ }
+ }
+
+ @Getter
+ @Setter
+ public static class UsersDTO {
+ private Long userId;
+ private String username;
+ private String email;
+ private String phoneNumber;
+ private LocalDate joinDate;
+ private int position;
+ private int roles;
+
+ public UsersDTO(User user) {
+ this.userId = user.getId();
+ this.username = user.getUsername();
+ this.email = user.getEmail();
+ this.phoneNumber = user.getPhoneNumber();
+ this.joinDate = user.getJoinDate();
+ this.position = user.getPosition().getTypeNumber();
+ this.roles = user.getRoles().getTypeNumber();
+ }
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/admin/AdminService.java b/src/main/java/com/example/kdtbe5_miniproject/admin/AdminService.java
new file mode 100644
index 0000000..4413e65
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/admin/AdminService.java
@@ -0,0 +1,69 @@
+package com.example.kdtbe5_miniproject.admin;
+
+import com.example.kdtbe5_miniproject.dayoff.DayOffStatus;
+import com.example.kdtbe5_miniproject.dayoff.DayOffType;
+import com.example.kdtbe5_miniproject.duty.DutyStatus;
+import com.example.kdtbe5_miniproject.user.User;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import javax.transaction.Transactional;
+import java.time.LocalDate;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RequiredArgsConstructor
+@Service
+public class AdminService {
+
+ private final AdminRepository adminRepository;
+
+ // ์น์ธ ๋๊ธฐ์ค์ธ ์ฐ์ฐจ ๋ชฉ๋ก
+ @Transactional
+ public List findWaitingDayOff() {
+ return adminRepository.findDayOffByStatus().stream().map(AdminResponse.DayOffStatusDTO::new).collect(Collectors.toList());
+ }
+
+ // ์น์ธ ๋๊ธฐ์ค์ธ ๋น์ง ๋ชฉ๋ก
+ @Transactional
+ public List findWaitingDuty() {
+ return adminRepository.findDutyByStatus().stream().map(AdminResponse.DutyStatusDTO::new).collect(Collectors.toList());
+ }
+
+ // ์ ์ฒด ์ฌ์ฉ์ ์ธ๋ถ์ ๋ณด
+ @Transactional
+ public List findAllUsers() {
+ return adminRepository.findAllUsers().stream().map(AdminResponse.UsersDTO::new).collect(Collectors.toList());
+ }
+
+ // ํน์ ์ฌ์ฉ์ ์ธ๋ถ์ ๋ณด
+ @Transactional
+ public AdminResponse.UsersDTO findUserDetail(Long userId) {
+ return new AdminResponse.UsersDTO(adminRepository.findUserById(userId));
+ }
+
+
+ @Transactional
+ public void modifyDayOffStatus(Long id, AdminRequest.TreatDayOffDTO request) {
+ adminRepository.updateNumOfDayOffById(id, request.getStatus(), LocalDate.now());
+ }
+
+ @Transactional
+ public List findWaitingDayOffList(Long id) {
+ Long userId = adminRepository.findDayOffById(id).getUser().getId();
+ return adminRepository.findDayOffByStatus(userId, DayOffStatus.๋๊ธฐ);
+ }
+
+ //TODO ๋ ์ง ์ค๋ณต ์์ธ
+ @Transactional
+ public void modifyDutyStatus(Long id, AdminRequest.TreatDutyDTO request) {
+ adminRepository.updateDutyById(id, request.getStatus(), LocalDate.now());
+ }
+
+ @Transactional
+ public void modifyUser(Long id, AdminRequest.UserDetailsDTO request) {
+ adminRepository.updateUserById(id, request.getPhoneNumber(), request.getPosition(), request.getRoles());
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/dayoff/DayOff.java b/src/main/java/com/example/kdtbe5_miniproject/dayoff/DayOff.java
new file mode 100644
index 0000000..7ba9c04
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/dayoff/DayOff.java
@@ -0,0 +1,50 @@
+package com.example.kdtbe5_miniproject.dayoff;
+
+import com.example.kdtbe5_miniproject.user.User;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.*;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+@Entity
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@Getter
+@Table(name = "dayoff_tb")
+public class DayOff {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ private User user;
+
+ private LocalDate startDate;
+ private LocalDate endDate;
+ private String reason;
+ private DayOffType type; //์ฐ์ฐจ(0), ์ค์ ๋ฐ์ฐจ(1), ์คํ๋ฐ์ฐจ(2)
+ private DayOffStatus status; //๋๊ธฐ(0), ์น์ธ(1), ๋ฐ๋ ค(2)
+ private Float numOfDayOff;
+ private LocalDate applyAt;
+ private LocalDate processAt;
+
+ public float getUsedDayOff() {
+ if (this.status != null && (this.status == DayOffStatus.valueOf("์น์ธ") || this.status == DayOffStatus.valueOf("๋๊ธฐ"))) {
+ return this.numOfDayOff;
+ } else {
+ return 0f;
+ }
+ }
+
+ public void setStatus(DayOffStatus status) {
+ this.status = status;
+ }
+ public void setNumOfDayOff(float numOfDayOff) {
+ this.numOfDayOff = numOfDayOff;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/kdtbe5_miniproject/dayoff/DayOffController.java b/src/main/java/com/example/kdtbe5_miniproject/dayoff/DayOffController.java
new file mode 100644
index 0000000..ce79b84
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/dayoff/DayOffController.java
@@ -0,0 +1,51 @@
+package com.example.kdtbe5_miniproject.dayoff;
+
+import com.example.kdtbe5_miniproject._core.security.CustomUserDetails;
+import com.example.kdtbe5_miniproject._core.util.ApiUtils;
+import com.example.kdtbe5_miniproject.user.User;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+
+@RequestMapping("/mypage")
+@RequiredArgsConstructor
+@RestController
+public class DayOffController {
+ private final DayOffService dayOffService;
+
+ // ์ฐ์ฐจ ์ ์ฒญ
+ @PostMapping("/dayoff/register")
+ public ResponseEntity> registerDayOff(@RequestBody @Valid DayOffRequest.RegisterDTO registerDTO, @AuthenticationPrincipal CustomUserDetails customUserDetails) {
+ User user = customUserDetails.getUser();
+ dayOffService.registerDayOff(user.getId(), registerDTO);
+ return ResponseEntity.ok().body(ApiUtils.success("์ฐ์ฐจ ์ ์ฒญ์ด ์๋ฃ๋์์ต๋๋ค."));
+ }
+
+ // ๋์ ์ฐ์ฐจ ๊ฐ์
+ @GetMapping("/dayoff/my")
+ public ResponseEntity> myDayOffInfo(@AuthenticationPrincipal CustomUserDetails customUserDetails) {
+ User user = customUserDetails.getUser();
+ DayOffResponse.MyDayOffDTO myDayOffDTO = dayOffService.myDayOffInfo(user.getId());
+ return ResponseEntity.ok().body(ApiUtils.success(myDayOffDTO));
+ }
+
+ // ๋ด ์ฐ์ฐจ ๋ฆฌ์คํธ
+ @GetMapping("/dayoffList")
+ public ResponseEntity> myAppliedDayOffs(@AuthenticationPrincipal CustomUserDetails customUserDetails) {
+ User user = customUserDetails.getUser();
+ DayOffResponse.AppliedDayOffDTO[] appliedDayOffDTOs = dayOffService.myAppliedDayOffs(user.getId());
+ return ResponseEntity.ok().body(ApiUtils.success(appliedDayOffDTOs));
+ }
+
+ // ์ฐ์ฐจ ์ ์ฒญ ์ทจ์
+ @DeleteMapping("dayoff/{dayoffId}")
+ public ResponseEntity> cancelDayOff(@PathVariable Long dayoffId, @AuthenticationPrincipal CustomUserDetails customUserDetails) {
+ User user = customUserDetails.getUser();
+ dayOffService.cancelDayOff(dayoffId, user.getId());
+ return ResponseEntity.ok().body(ApiUtils.success("์ฐ์ฐจ ์ ์ฒญ์ด ์ทจ์๋์์ต๋๋ค."));
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/dayoff/DayOffRepository.java b/src/main/java/com/example/kdtbe5_miniproject/dayoff/DayOffRepository.java
new file mode 100644
index 0000000..02c3d05
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/dayoff/DayOffRepository.java
@@ -0,0 +1,13 @@
+package com.example.kdtbe5_miniproject.dayoff;
+
+import com.example.kdtbe5_miniproject.user.User;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface DayOffRepository extends JpaRepository {
+ List findByUser(User user);
+ List findByUserAndStatus(User user, DayOffStatus status);
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/dayoff/DayOffRequest.java b/src/main/java/com/example/kdtbe5_miniproject/dayoff/DayOffRequest.java
new file mode 100644
index 0000000..8e8ab08
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/dayoff/DayOffRequest.java
@@ -0,0 +1,53 @@
+package com.example.kdtbe5_miniproject.dayoff;
+
+import com.example.kdtbe5_miniproject.user.User;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotNull;
+import java.time.LocalDate;
+
+@Getter
+@Setter
+public class DayOffRequest {
+
+ @Getter
+ @Setter
+ public static class RegisterDTO {
+ @NotNull
+ private DayOffType type;
+ @NotNull
+ private LocalDate startDate;
+ @NotNull
+ private LocalDate endDate;
+ @NotNull
+ private String reason;
+
+ /*
+ public DayOff toEntity(User user, float numOfDayOff) {
+ return DayOff.builder()
+ .type(this.type)
+ .startDate(this.startDate)
+ .endDate(this.endDate)
+ .reason(this.reason)
+ .status(DayOffStatus.valueOf("๋๊ธฐ"))
+ .applyAt(LocalDate.now())
+ .user(user)
+ .numOfDayOff(numOfDayOff)
+ .build();
+ }
+ */
+
+ public DayOff toEntity(User user) {
+ return DayOff.builder()
+ .type(this.type)
+ .startDate(this.startDate)
+ .endDate(this.endDate)
+ .reason(this.reason)
+ .status(DayOffStatus.valueOf("๋๊ธฐ"))
+ .applyAt(LocalDate.now())
+ .user(user)
+ .build();
+ }
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/dayoff/DayOffResponse.java b/src/main/java/com/example/kdtbe5_miniproject/dayoff/DayOffResponse.java
new file mode 100644
index 0000000..2cbbef3
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/dayoff/DayOffResponse.java
@@ -0,0 +1,43 @@
+package com.example.kdtbe5_miniproject.dayoff;
+
+import lombok.Getter;
+
+import java.time.LocalDate;
+
+@Getter
+public class DayOffResponse {
+
+ @Getter
+ public static class MyDayOffDTO {
+ private float numOfInitialDayOff;
+
+ /*
+ public MyDayOffDTO(float valid, float used, float expired) {
+ this.valid = valid;
+ }
+ */
+
+ public MyDayOffDTO(float numOfInitialDayOff) {
+ this.numOfInitialDayOff = numOfInitialDayOff;
+ }
+ }
+
+ @Getter
+ public static class AppliedDayOffDTO {
+ private Long id;
+ private int type; // ์ซ์๋ก ๋ณ๊ฒฝ
+ private LocalDate startDate;
+ private LocalDate endDate;
+ private String reason;
+ private int status; // ์ซ์๋ก ๋ณ๊ฒฝ
+
+ public AppliedDayOffDTO(DayOff dayOff) {
+ this.id = dayOff.getId();
+ this.type = dayOff.getType().getValue(); // Enum์ ์ซ์ ๊ฐ์ ์ป์ด์ด
+ this.startDate = dayOff.getStartDate();
+ this.endDate = dayOff.getEndDate();
+ this.reason = dayOff.getReason();
+ this.status = dayOff.getStatus().getValue(); // Enum์ ์ซ์ ๊ฐ์ ์ป์ด์ด
+ }
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/dayoff/DayOffService.java b/src/main/java/com/example/kdtbe5_miniproject/dayoff/DayOffService.java
new file mode 100644
index 0000000..c89fec2
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/dayoff/DayOffService.java
@@ -0,0 +1,144 @@
+package com.example.kdtbe5_miniproject.dayoff;
+
+import com.example.kdtbe5_miniproject._core.errors.exception.UserNotFoundException;
+import com.example.kdtbe5_miniproject.user.User;
+import com.example.kdtbe5_miniproject.user.UserPosition;
+import com.example.kdtbe5_miniproject.user.UserRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDate;
+import java.util.List;
+
+@RequiredArgsConstructor
+@Service
+public class DayOffService {
+ private final DayOffRepository dayOffRepository;
+ private final UserRepository userRepository;
+
+ // ์ฐ์ฐจ ์ ์ฒญ ๋ง๊ฐ์ผ
+ private final LocalDate applicationDeadline = LocalDate.of(2023, 12, 31);
+
+ // ์ฐ์ฐจ ์ ์ฒญ
+ // TODO ์ด๋ฏธ ์ ์ฒญ์ค์ธ ๋ ์ง์ ์ค๋ณต์ ์ฒญ ํ๋ ๊ฒฝ์ฐ ์์ธ์ฒ๋ฆฌ
+ @Transactional
+ public void registerDayOff(Long userId, DayOffRequest.RegisterDTO registerDTO) {
+ User user = userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException("์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค."));
+
+ List dayOffs = dayOffRepository.findByUser(user);
+
+ // ๋์ผํ ๋ ์ง์ ์ ์ฒญํ๋์ง ์ฒดํฌ
+ for (DayOff dayOff : dayOffs) {
+ if (!((dayOff.getEndDate().isBefore(registerDTO.getStartDate()) ||
+ dayOff.getEndDate().isEqual(registerDTO.getStartDate())) ||
+ (dayOff.getStartDate().isAfter(registerDTO.getEndDate()) ||
+ dayOff.getStartDate().isEqual(registerDTO.getEndDate())))) {
+
+ // ๋์ผํ ๋ ์ง์ ๋ฐ์ฐจ ๋๋ฒ ์ ์ฒญํ๋ ๊ฒฝ์ฐ๋ฅผ ์ ํ
+ if (dayOff.getType() != DayOffType.์ฐ์ฐจ) {
+ throw new IllegalArgumentException("๋์ผํ ๋ ์ง์ ๋ฐ์ฐจ๋ฅผ ๋ ๋ฒ ์ ์ฒญํ ์ ์์ต๋๋ค.");
+ } else if (registerDTO.getType() != DayOffType.์ฐ์ฐจ) {
+ throw new IllegalArgumentException("์ ์ฒญํ๋ ค๋ ๋ ์ง๊ฐ ์ด๋ฏธ ๋ฑ๋ก๋ ์ฐ์ฐจ์ ๊ฒน์นฉ๋๋ค.");
+ }
+ }
+ }
+
+ // ์ฐ์ฐจ ์ ์ฒญ ๋ง๊ฐ์ผ ์ฒดํฌ
+ if (LocalDate.now().isAfter(applicationDeadline)) {
+ throw new IllegalArgumentException("์ฐ์ฐจ ์ ์ฒญ ๋ง๊ฐ์ผ์ด ์ง๋ฌ์ต๋๋ค.");
+ }
+
+ // ์ง๋ ๋ ์ง์ ๋ํ ์ฐ์ฐจ ์ ์ฒญ ์ฒดํฌ
+ if (registerDTO.getStartDate().isBefore(LocalDate.now())) {
+ throw new IllegalArgumentException("์ง๋ ๋ ์ง์ ๋ํ ์ฐ์ฐจ ์ ์ฒญ์ ๋ถ๊ฐ๋ฅํฉ๋๋ค.");
+ }
+
+ /*
+ // ์ฐ์ฐจ ์ ํ์ ๋ฐ๋ฅธ ์ ์ฒญ์ผ ๊ณ์ฐ
+ float appliedDayOff = 1.0f;
+ if (registerDTO.getType() == DayOffType.์ฐ์ฐจ) {
+ appliedDayOff = registerDTO.getStartDate().datesUntil(registerDTO.getEndDate()).filter(d -> d.getDayOfWeek().getValue() < 6).count();
+ } else if (registerDTO.getType() == DayOffType.์ค์ ๋ฐ์ฐจ || registerDTO.getType() == DayOffType.์คํ๋ฐ์ฐจ) {
+ // ๋ฐ์ฐจ์ผ ๊ฒฝ์ฐ ์ด ์ฐ์ฐจ์์ 0.5 ์ฐจ๊ฐ
+ appliedDayOff = 0.5f;
+ }
+
+ // ๋จ์ ํด๊ฐ ํ์ธ
+ float totalDayOff = user.determineInitialDayOff();
+ float usedDayOff = 0f;
+
+ for (DayOff dayOff : dayOffs) {
+ usedDayOff += dayOff.getStartDate().datesUntil(dayOff.getEndDate()).filter(d -> d.getDayOfWeek().getValue() < 6).count();
+ }
+
+ float remainingDayOff = totalDayOff - usedDayOff - appliedDayOff;
+
+ if (remainingDayOff < 0) {
+ throw new IllegalArgumentException("๋จ์ ์ฐ์ฐจ๊ฐ ๋ถ์กฑํฉ๋๋ค.");
+ }
+
+ */
+
+ /*
+ DayOff dayOff = registerDTO.toEntity(user, appliedDayOff);
+ */
+ DayOff dayOff = registerDTO.toEntity(user);
+ dayOffRepository.save(dayOff);
+ }
+
+ // ๋์ ๋จ์ ์ฐ์ฐจ (์ด๊ธฐ ์ฐ์ฐจ๋ง ์ค์ )
+ @Transactional(readOnly = true)
+ public DayOffResponse.MyDayOffDTO myDayOffInfo(Long userId) {
+ User user = userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException("์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค."));
+
+ float numOfInitialDayOff = user.determineInitialDayOff();
+
+ /*
+ float usedDayOff = 0;
+ List dayOffs = dayOffRepository.findByUser(user);
+
+ for(DayOff dayOff : dayOffs){
+ if (dayOff.getStatus() == DayOffStatus.์น์ธ || dayOff.getStatus() == DayOffStatus.๋๊ธฐ) {
+ usedDayOff += dayOff.getUsedDayOff();
+ }
+ }
+
+ float remainingDayOff = totalDayOff - usedDayOff;
+ */
+
+ /*
+ return new DayOffResponse.MyDayOffDTO(totalDayOff, usedDayOff, remainingDayOff);
+ */
+
+ return new DayOffResponse.MyDayOffDTO(numOfInitialDayOff);
+ }
+
+ // ๋ด ์ฐ์ฐจ ๋ฆฌ์คํธ
+ @Transactional(readOnly = true)
+ public DayOffResponse.AppliedDayOffDTO[] myAppliedDayOffs(Long userId) {
+ User user = userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException("์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค."));
+ List dayOffs = dayOffRepository.findByUser(user);
+ return dayOffs.stream().map(DayOffResponse.AppliedDayOffDTO::new).toArray(DayOffResponse.AppliedDayOffDTO[]::new);
+ }
+
+ // ์ฐ์ฐจ ์ ์ฒญ ์ทจ์
+ @Transactional
+ public void cancelDayOff(Long dayoffId, Long userId) {
+ User user = userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException("์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค."));
+ DayOff dayOff = dayOffRepository.findById(dayoffId).orElseThrow(() -> new UserNotFoundException("ํด๋น ์ฐ์ฐจ ์ ์ฒญ์ ์ฐพ์ ์ ์์ต๋๋ค."));
+
+ // ๋ณธ์ธ์ด ์ ์ฒญํ ์ฐ์ฐจ๋ง ์ทจ์ ๊ฐ๋ฅํ๋๋ก ์ฒดํฌ
+ if (!dayOff.getUser().getId().equals(userId)) {
+ throw new IllegalArgumentException("๋ณธ์ธ์ด ์ ์ฒญํ ์ฐ์ฐจ๋ง ์ทจ์ํ ์ ์์ต๋๋ค.");
+ }
+
+ // ์ฐ์ฐจ ์ ์ฒญ์ ์ํ๋ฅผ ์ทจ์๋ก ๋ณ๊ฒฝ
+ if (dayOff.getStatus() == DayOffStatus.์น์ธ || dayOff.getStatus() == DayOffStatus.๋ฐ๋ ค) {
+ throw new IllegalArgumentException("์ด๋ฏธ ์ฒ๋ฆฌ๋ ์ฐ์ฐจ ์ ์ฒญ์ ์ทจ์ํ ์ ์์ต๋๋ค.");
+ }
+
+ // ์ทจ์๋ ์ฐ์ฐจ๋ฅผ ์ญ์
+ dayOffRepository.delete(dayOff);
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/dayoff/DayOffStatus.java b/src/main/java/com/example/kdtbe5_miniproject/dayoff/DayOffStatus.java
new file mode 100644
index 0000000..811a0d4
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/dayoff/DayOffStatus.java
@@ -0,0 +1,19 @@
+package com.example.kdtbe5_miniproject.dayoff;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor
+public enum DayOffStatus {
+ ๋๊ธฐ(0),
+ ์น์ธ(1),
+ ๋ฐ๋ ค(2),
+ ์ทจ์(3);
+
+ private final int typeNumber;
+
+ public int getValue() {
+ return this.typeNumber;
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/dayoff/DayOffType.java b/src/main/java/com/example/kdtbe5_miniproject/dayoff/DayOffType.java
new file mode 100644
index 0000000..6f0556c
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/dayoff/DayOffType.java
@@ -0,0 +1,18 @@
+package com.example.kdtbe5_miniproject.dayoff;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor
+public enum DayOffType {
+ ์ฐ์ฐจ(0),
+ ์ค์ ๋ฐ์ฐจ(1),
+ ์คํ๋ฐ์ฐจ(2);
+
+ private final int typeNumber;
+
+ public int getValue() {
+ return this.typeNumber;
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/duty/Duty.java b/src/main/java/com/example/kdtbe5_miniproject/duty/Duty.java
new file mode 100644
index 0000000..fd1db32
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/duty/Duty.java
@@ -0,0 +1,31 @@
+package com.example.kdtbe5_miniproject.duty;
+
+import com.example.kdtbe5_miniproject.user.User;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.*;
+import java.time.LocalDate;
+
+@Entity
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@Getter
+@Table(name = "duty_tb")
+public class Duty {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ private User user;
+
+ private LocalDate date;
+ private String reason;
+ private DutyStatus status; //๋๊ธฐ(0), ์น์ธ(1), ๋ฐ๋ ค(2)
+ private LocalDate applyAt;
+ private LocalDate processAt;
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/kdtbe5_miniproject/duty/DutyController.java b/src/main/java/com/example/kdtbe5_miniproject/duty/DutyController.java
new file mode 100644
index 0000000..cf23266
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/duty/DutyController.java
@@ -0,0 +1,44 @@
+package com.example.kdtbe5_miniproject.duty;
+
+import com.example.kdtbe5_miniproject._core.security.CustomUserDetails;
+import com.example.kdtbe5_miniproject._core.util.ApiUtils;
+import com.example.kdtbe5_miniproject.user.User;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+
+@RequestMapping("/mypage")
+@RequiredArgsConstructor
+@RestController
+public class DutyController {
+
+ private final DutyService dutyService;
+
+ // ๋น์ง ์ ์ฒญ
+ @PostMapping("/duty/register")
+ public ResponseEntity> createDuty(@RequestBody @Valid DutyRequest.DutyDTO dutyDTO, @AuthenticationPrincipal CustomUserDetails customUserDetails) {
+ User user = customUserDetails.getUser();
+ DutyResponse.DutyDTO responseDTO = dutyService.createDuty(dutyDTO, user.getId());
+ return ResponseEntity.ok().body(ApiUtils.success("๋น์ง ์ ์ฒญ์ด ์๋ฃ๋์์ต๋๋ค."));
+ }
+
+ // ๋ด ๋น์ง ๋ฆฌ์คํธ
+ @GetMapping("/dutyList")
+ public ResponseEntity> getAppliedDuty(@AuthenticationPrincipal CustomUserDetails customUserDetails) {
+ User user = customUserDetails.getUser();
+ List appliedDuties = dutyService.getAppliedDuties(user.getId());
+ return ResponseEntity.ok().body(ApiUtils.success(appliedDuties));
+ }
+
+ // ๋น์ง ์ ์ฒญ ์ทจ์
+ @DeleteMapping("/duty/{dutyId}")
+ public ResponseEntity> deleteDuty(@PathVariable Long dutyId, @AuthenticationPrincipal CustomUserDetails customUserDetails) {
+ User user = customUserDetails.getUser();
+ dutyService.deleteDuty(dutyId, user.getId());
+ return ResponseEntity.ok().body(ApiUtils.success("๋น์ง ์ ์ฒญ์ด ์ทจ์๋์์ต๋๋ค."));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/kdtbe5_miniproject/duty/DutyRepository.java b/src/main/java/com/example/kdtbe5_miniproject/duty/DutyRepository.java
new file mode 100644
index 0000000..9149314
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/duty/DutyRepository.java
@@ -0,0 +1,17 @@
+package com.example.kdtbe5_miniproject.duty;
+
+import com.example.kdtbe5_miniproject.user.User;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.time.LocalDate;
+import java.util.List;
+import java.util.Optional;
+
+@Repository
+public interface DutyRepository extends JpaRepository {
+
+ Optional findByIdAndUser(Long id, User user);
+ List findAllByUser(User user);
+ List findAllByUserAndDate(User user, LocalDate date);
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/kdtbe5_miniproject/duty/DutyRequest.java b/src/main/java/com/example/kdtbe5_miniproject/duty/DutyRequest.java
new file mode 100644
index 0000000..e5489be
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/duty/DutyRequest.java
@@ -0,0 +1,31 @@
+package com.example.kdtbe5_miniproject.duty;
+
+import com.example.kdtbe5_miniproject.user.User;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.time.LocalDate;
+
+public class DutyRequest {
+ @Getter
+ @Setter
+ public static class DutyDTO {
+ @NotNull
+ private LocalDate date;
+ @NotEmpty
+ private String reason;
+ private LocalDate applyAt;
+
+ public Duty toEntity(User user) {
+ return Duty.builder()
+ .date(this.date)
+ .reason(this.reason)
+ .status(DutyStatus.valueOf("๋๊ธฐ"))
+ .applyAt(LocalDate.now())
+ .user(user)
+ .build();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/kdtbe5_miniproject/duty/DutyResponse.java b/src/main/java/com/example/kdtbe5_miniproject/duty/DutyResponse.java
new file mode 100644
index 0000000..a29bca0
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/duty/DutyResponse.java
@@ -0,0 +1,25 @@
+package com.example.kdtbe5_miniproject.duty;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDate;
+
+@Getter
+public class DutyResponse {
+
+ @Getter
+ public static class DutyDTO {
+ private Long id;
+ private LocalDate date;
+ private String reason;
+ private int status; // ์ซ์๋ก ๋ณ๊ฒฝ
+
+ public DutyDTO(Duty duty) {
+ this.id = duty.getId();
+ this.date = duty.getDate();
+ this.reason = duty.getReason();
+ this.status = duty.getStatus().getValue(); // Enum์ ์ซ์ ๊ฐ์ ์ป์ด์ด
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/kdtbe5_miniproject/duty/DutyService.java b/src/main/java/com/example/kdtbe5_miniproject/duty/DutyService.java
new file mode 100644
index 0000000..d4c7bb7
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/duty/DutyService.java
@@ -0,0 +1,68 @@
+package com.example.kdtbe5_miniproject.duty;
+
+import com.example.kdtbe5_miniproject._core.errors.exception.DutyNotFoundException;
+import com.example.kdtbe5_miniproject._core.errors.exception.UserNotFoundException;
+import com.example.kdtbe5_miniproject.dayoff.DayOff;
+import com.example.kdtbe5_miniproject.user.User;
+import com.example.kdtbe5_miniproject.user.UserRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDate;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RequiredArgsConstructor
+@Service
+public class DutyService {
+
+ private final DutyRepository dutyRepository;
+ private final UserRepository userRepository;
+
+ // ๋น์ง ์ ์ฒญ
+ @Transactional
+ public DutyResponse.DutyDTO createDuty(DutyRequest.DutyDTO dutyDTO, Long userId) {
+ User user = userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException("์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค."));
+
+ // ์ง๋ ๋ ์ง์ ๋น์ง์ ์ ์ฒญํ๋์ง ํ์ธ
+ LocalDate requestedDate = dutyDTO.getDate();
+ if (requestedDate.isBefore(LocalDate.now())) {
+ throw new IllegalArgumentException("๊ณผ๊ฑฐ์ ๋ ์ง์ ๋น์ง์ ์ ์ฒญํ ์ ์์ต๋๋ค.");
+ }
+
+ // ๋น์ง์ ๊ฐ์๋ ์ ์ ์ฒญํ๋์ง ํ์ธ
+ List existingDuties = dutyRepository.findAllByUserAndDate(user, requestedDate);
+ if (!existingDuties.isEmpty()) {
+ throw new IllegalArgumentException("๋น์ง์ ๊ฐ์ ๋ ์ ๋ ๋ฒ ์ ์ฒญํ ์ ์์ต๋๋ค.");
+ }
+
+ Duty duty = dutyDTO.toEntity(user);
+ Duty createdDuty = dutyRepository.save(duty);
+ return new DutyResponse.DutyDTO(createdDuty);
+ }
+
+
+ // ๋ด ๋น์ง ๋ฆฌ์คํธ
+ @Transactional(readOnly = true)
+ public List getAppliedDuties(Long userId) {
+ User user = userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException("์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค."));
+ return dutyRepository.findAllByUser(user).stream()
+ .map(DutyResponse.DutyDTO::new)
+ .collect(Collectors.toList());
+ }
+
+ // ๋น์ง ์ ์ฒญ ์ทจ์
+ @Transactional
+ public void deleteDuty(Long dutyId, Long userId) {
+ User user = userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException("์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค."));
+ Duty duty = dutyRepository.findByIdAndUser(dutyId, user).orElseThrow(() -> new DutyNotFoundException("ํด๋น ๋น์ง ์ ์ฒญ์ ์ฐพ์ ์ ์์ต๋๋ค."));
+
+ // ๋ณธ์ธ ๋น์ง๋ง ์ญ์ ๊ฐ๋ฅ
+ if (!duty.getUser().getId().equals(userId)) {
+ throw new IllegalArgumentException("๋น์ ์ ๋น์ง๋ง ์ญ์ ํ ์ ์์ต๋๋ค.");
+ }
+
+ dutyRepository.delete(duty);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/kdtbe5_miniproject/duty/DutyStatus.java b/src/main/java/com/example/kdtbe5_miniproject/duty/DutyStatus.java
new file mode 100644
index 0000000..f6ce719
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/duty/DutyStatus.java
@@ -0,0 +1,18 @@
+package com.example.kdtbe5_miniproject.duty;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor
+public enum DutyStatus {
+ ๋๊ธฐ(0),
+ ์น์ธ(1),
+ ๋ฐ๋ ค(2);
+
+ private final int status;
+
+ public int getValue() {
+ return this.status;
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/login_history/LoginHistory.java b/src/main/java/com/example/kdtbe5_miniproject/login_history/LoginHistory.java
new file mode 100644
index 0000000..732a250
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/login_history/LoginHistory.java
@@ -0,0 +1,27 @@
+package com.example.kdtbe5_miniproject.login_history;
+
+import com.example.kdtbe5_miniproject.user.User;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.*;
+import java.time.LocalDateTime;
+
+@Entity
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@Getter
+@Table(name = "login_tb")
+public class LoginHistory {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+ @ManyToOne(fetch = FetchType.LAZY)
+ private User user;
+ private String ip;
+ private String userAgent;
+ private LocalDateTime loginAt;
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/login_history/LoginHistoryRepository.java b/src/main/java/com/example/kdtbe5_miniproject/login_history/LoginHistoryRepository.java
new file mode 100644
index 0000000..2d22c8a
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/login_history/LoginHistoryRepository.java
@@ -0,0 +1,8 @@
+package com.example.kdtbe5_miniproject.login_history;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface LoginHistoryRepository extends JpaRepository {
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/login_history/LoginHistoryRequest.java b/src/main/java/com/example/kdtbe5_miniproject/login_history/LoginHistoryRequest.java
new file mode 100644
index 0000000..1105258
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/login_history/LoginHistoryRequest.java
@@ -0,0 +1,32 @@
+package com.example.kdtbe5_miniproject.login_history;
+
+import com.example.kdtbe5_miniproject.user.User;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+
+public class LoginHistoryRequest {
+
+ @AllArgsConstructor
+ @NoArgsConstructor
+ @Getter
+ @Setter
+ public static class LoginHistoryDTO {
+ private User user;
+ private String ip;
+ private String userAgent;
+ private LocalDateTime loginAt;
+
+ public LoginHistory toEntity() {
+ return LoginHistory.builder()
+ .user(user)
+ .ip(ip)
+ .userAgent(userAgent)
+ .loginAt(loginAt)
+ .build();
+ }
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/login_history/LoginHistoryService.java b/src/main/java/com/example/kdtbe5_miniproject/login_history/LoginHistoryService.java
new file mode 100644
index 0000000..1c020e6
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/login_history/LoginHistoryService.java
@@ -0,0 +1,18 @@
+package com.example.kdtbe5_miniproject.login_history;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import javax.transaction.Transactional;
+
+@RequiredArgsConstructor
+@Service
+public class LoginHistoryService {
+
+ private final LoginHistoryRepository loginHistoryRepository;
+
+ @Transactional
+ public void save(LoginHistoryRequest.LoginHistoryDTO loginHistoryDTO) {
+ loginHistoryRepository.save(loginHistoryDTO.toEntity());
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/schedule/ScheduleController.java b/src/main/java/com/example/kdtbe5_miniproject/schedule/ScheduleController.java
new file mode 100644
index 0000000..b384a3e
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/schedule/ScheduleController.java
@@ -0,0 +1,33 @@
+package com.example.kdtbe5_miniproject.schedule;
+import com.example.kdtbe5_miniproject._core.util.ApiUtils;
+import com.example.kdtbe5_miniproject.user.UserResponse;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDate;
+import java.util.HashMap;
+import java.util.List;
+@RequestMapping("/mypage")
+@RequiredArgsConstructor
+@RestController
+public class ScheduleController {
+
+ private final ScheduleService scheduleService;
+
+ // ์๋ณ ์ฐ์ฐจ + ๋น์ง ์ค์ผ์ค
+ @GetMapping("/schedule/{year}/{month}")
+ public ResponseEntity> scheduleByMonth(@PathVariable int year, @PathVariable int month) {
+ HashMap lists = new HashMap<>();
+ lists.put("dayOffList", scheduleService.findDayOffScheduleByMonth(year, month));
+ lists.put("dutyList", scheduleService.findDutyScheduleByMonth(year, month));
+
+ return ResponseEntity.ok().body(ApiUtils.success(lists));
+ }
+ // ์ ์ฒด ์ฌ์ฉ์ ๋ชฉ๋ก ์กฐํ
+ @GetMapping("/schedule/userList")
+ public ResponseEntity> getAppliedDuty() {
+ List allUsers = scheduleService.getAllUsers();
+ return ResponseEntity.ok().body(ApiUtils.success(allUsers));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/kdtbe5_miniproject/schedule/ScheduleRepository.java b/src/main/java/com/example/kdtbe5_miniproject/schedule/ScheduleRepository.java
new file mode 100644
index 0000000..32dad3e
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/schedule/ScheduleRepository.java
@@ -0,0 +1,41 @@
+package com.example.kdtbe5_miniproject.schedule;
+import com.example.kdtbe5_miniproject.dayoff.DayOff;
+import com.example.kdtbe5_miniproject.dayoff.DayOffStatus;
+import com.example.kdtbe5_miniproject.duty.Duty;
+import com.example.kdtbe5_miniproject.duty.DutyStatus;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Repository;
+import javax.persistence.EntityManager;
+import javax.persistence.Query;
+import java.time.LocalDate;
+import java.util.List;
+@Repository
+@RequiredArgsConstructor
+public class ScheduleRepository {
+ private final EntityManager entityManager;
+ // ์ฐ์ฐจ
+ // ์น์ธ status๋ก ์กฐํ
+ // ๋ ์ง ์กฐํ ์ถ๊ฐ ํ์
+ public List filterDayOffSchedule(DayOffStatus status, LocalDate startDate, LocalDate endDate) {
+ Query query = entityManager.createQuery(
+ "SELECT d FROM DayOff d INNER JOIN d.user u WHERE d.status = :status AND ((d.startDate >= :startDate AND d.startDate <= :endDate) OR (d.endDate >= :startDate AND d.endDate <= :endDate))", DayOff.class);
+ query.setParameter("status", status);
+ query.setParameter("startDate", startDate);
+ query.setParameter("endDate", endDate);
+
+ return query.getResultList();
+ }
+
+ // ๋น์ง
+ // ์น์ธ status๋ก ์กฐํ
+ // ๋ ์ง ์กฐํ ์ถ๊ฐ ํ์
+ public List filterDutySchedule(DutyStatus status, LocalDate startDate, LocalDate endDate) {
+ Query query = entityManager.createQuery(
+ "SELECT d FROM Duty d INNER JOIN d.user u WHERE d.status = :status AND d.date BETWEEN :startDate AND :endDate", Duty.class);
+ query.setParameter("status", status);
+ query.setParameter("startDate", startDate);
+ query.setParameter("endDate", endDate);
+
+ return query.getResultList();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/kdtbe5_miniproject/schedule/ScheduleResponse.java b/src/main/java/com/example/kdtbe5_miniproject/schedule/ScheduleResponse.java
new file mode 100644
index 0000000..9991f27
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/schedule/ScheduleResponse.java
@@ -0,0 +1,56 @@
+package com.example.kdtbe5_miniproject.schedule;
+
+import com.example.kdtbe5_miniproject.dayoff.DayOff;
+import com.example.kdtbe5_miniproject.duty.Duty;
+import com.example.kdtbe5_miniproject.user.UserPosition;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDate;
+
+public class ScheduleResponse {
+
+ @Getter
+ @Setter
+ public static class DayOffScheduleDTO {
+ private Long id;
+ private Long userId;
+ private String username;
+ private int position;
+ private String reason;
+ private int type;
+ private LocalDate startDate;
+ private LocalDate endDate;
+
+ public DayOffScheduleDTO(DayOff dayOff) {
+ this.id = dayOff.getId();
+ this.userId = dayOff.getUser().getId();
+ this.username = dayOff.getUser().getUsername();
+ this.position = dayOff.getUser().getPosition().getTypeNumber();
+ this.reason = dayOff.getReason();
+ this.type = dayOff.getType().getTypeNumber();
+ this.startDate = dayOff.getStartDate();
+ this.endDate = dayOff.getEndDate();
+ }
+ }
+
+ @Getter
+ @Setter
+ public static class DutyScheduleDTO {
+ private Long id;
+ private Long userId;
+ private String username;
+ private int position;
+ private String reason;
+ private LocalDate date;
+
+ public DutyScheduleDTO(Duty duty) {
+ this.id = duty.getId();
+ this.userId = duty.getUser().getId();
+ this.username = duty.getUser().getUsername();
+ this.position = duty.getUser().getPosition().getTypeNumber();
+ this.reason = duty.getReason();
+ this.date = duty.getDate();
+ }
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/schedule/ScheduleService.java b/src/main/java/com/example/kdtbe5_miniproject/schedule/ScheduleService.java
new file mode 100644
index 0000000..2d1db0d
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/schedule/ScheduleService.java
@@ -0,0 +1,51 @@
+package com.example.kdtbe5_miniproject.schedule;
+import com.example.kdtbe5_miniproject.admin.AdminResponse;
+import com.example.kdtbe5_miniproject.dayoff.DayOffStatus;
+import com.example.kdtbe5_miniproject.duty.Duty;
+import com.example.kdtbe5_miniproject.duty.DutyStatus;
+import com.example.kdtbe5_miniproject.user.UserRepository;
+import com.example.kdtbe5_miniproject.user.UserResponse;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import javax.transaction.Transactional;
+import java.time.LocalDate;
+import java.time.temporal.TemporalAdjusters;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RequiredArgsConstructor
+@Service
+public class ScheduleService {
+ private final UserRepository userRepository;
+ private final ScheduleRepository scheduleRepository;
+
+ @Transactional
+ public List findDayOffScheduleByMonth(int year, int month) {
+ LocalDate startDate = LocalDate.of(year, month, 1);
+ LocalDate endDate = startDate.with(TemporalAdjusters.lastDayOfMonth());
+ DayOffStatus status = DayOffStatus.valueOf("์น์ธ");
+
+ return scheduleRepository.filterDayOffSchedule(status, startDate, endDate).stream()
+ .map(ScheduleResponse.DayOffScheduleDTO::new)
+ .collect(Collectors.toList());
+ }
+
+ @Transactional
+ public List findDutyScheduleByMonth(int year, int month) {
+ LocalDate startDate = LocalDate.of(year, month, 1);
+ LocalDate endDate = startDate.with(TemporalAdjusters.lastDayOfMonth());
+ DutyStatus status = DutyStatus.valueOf("์น์ธ");
+
+ return scheduleRepository.filterDutySchedule(status, startDate, endDate).stream()
+ .map(ScheduleResponse.DutyScheduleDTO::new)
+ .collect(Collectors.toList());
+ }
+ // ์ ์ฒด ์ฌ์ฉ์ ๋ชฉ๋ก ์กฐํ
+ @Transactional
+ public List getAllUsers() {
+ return userRepository.findAll().stream()
+ .map(UserResponse.AllUsersDTO::new)
+ .collect(Collectors.toList());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/kdtbe5_miniproject/user/User.java b/src/main/java/com/example/kdtbe5_miniproject/user/User.java
new file mode 100644
index 0000000..b7eb240
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/user/User.java
@@ -0,0 +1,78 @@
+package com.example.kdtbe5_miniproject.user;
+
+import com.example.kdtbe5_miniproject._core.util.BaseTimeEntity;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.*;
+import java.time.LocalDate;
+import java.util.function.LongConsumer;
+
+@Entity
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@Getter
+@Table(name = "user_tb")
+public class User extends BaseTimeEntity {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+ private String username;
+ private String password;
+ private String email;
+ private String phoneNumber;
+ private UserPosition position; //์ฌ์(0), ์ฃผ์(1), ๋๋ฆฌ(2), ๊ณผ์ฅ(3), ์ฐจ์ฅ(4), ๋ถ์ฅ(5)
+ private UserRoles roles; //์ผ๋ฐ(0), ๊ด๋ฆฌ์(1)
+ private LocalDate joinDate;
+
+ public void updatePhoneNumber(String newPhoneNum) {
+ this.phoneNumber = newPhoneNum;
+ }
+
+ public UserResponse.LoginDTO toEntity() {
+ return UserResponse.LoginDTO.builder()
+ .id(this.id)
+ .username(this.username)
+ .email(this.email)
+ .phoneNumber(this.phoneNumber)
+ .position(this.position)
+ .roles(this.roles)
+ .joinDate(this.joinDate)
+ .build();
+ }
+
+ // ์ง๊ธ๋ณ ์ฐ์ฐจ ๊ณ์ฐ
+ public int determineInitialDayOff() {
+ // ์
์ฌ์ผ๋ก๋ถํฐ 1๋
์ด ์ง๋ฌ๋์ง ์ฒดํฌ
+ LocalDate now = LocalDate.now();
+ LocalDate oneYearAfterJoinDate = this.getJoinDate().plusYears(1);
+ int initialDayOff;
+
+ if (now.isBefore(oneYearAfterJoinDate)) { // ์
์ฌ ํ 1๋
๋ฏธ๋ง
+ int monthOfJoin = this.getJoinDate().getMonthValue();
+ initialDayOff = 11 - (monthOfJoin - 1); // ์
์ฌ ์ ๊ธฐ์ค์ผ๋ก ์ฐ์ฐจ ๊ณ์ฐ
+ } else { // ์
์ฌ ํ 1๋
์ด์
+ int position = this.getPosition().getTypeNumber();
+ if (position == 0) {
+ initialDayOff = 15; // ์ฌ์
+ } else if (position == 1) {
+ initialDayOff = 17; // ์ฃผ์
+ } else if (position == 2) {
+ initialDayOff = 19; // ๋๋ฆฌ
+ } else if (position == 3) {
+ initialDayOff = 21; // ๊ณผ์ฅ
+ } else if (position == 4) {
+ initialDayOff = 23; // ์ฐจ์ฅ
+ } else if (position == 5) {
+ initialDayOff = 25; // ๋ถ์ฅ
+ } else {
+ throw new IllegalArgumentException("์ง๊ธ: " + position);
+ }
+ }
+
+ return initialDayOff;
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/user/UserController.java b/src/main/java/com/example/kdtbe5_miniproject/user/UserController.java
new file mode 100644
index 0000000..37b855c
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/user/UserController.java
@@ -0,0 +1,102 @@
+package com.example.kdtbe5_miniproject.user;
+
+import com.example.kdtbe5_miniproject._core.errors.exception.Exception401;
+import com.example.kdtbe5_miniproject._core.security.CustomUserDetails;
+import com.example.kdtbe5_miniproject._core.security.JwtTokenProvider;
+import com.example.kdtbe5_miniproject._core.util.ApiUtils;
+import com.example.kdtbe5_miniproject.login_history.LoginHistoryRequest;
+import com.example.kdtbe5_miniproject.login_history.LoginHistoryService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
+import org.springframework.validation.Errors;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import java.security.Principal;
+import java.time.LocalDateTime;
+
+@RequiredArgsConstructor
+@RestController
+@Slf4j
+public class UserController {
+
+ private final LoginHistoryService loginHistoryService;
+ private final UserService userService;
+ private final AuthenticationManager authenticationManager;
+
+ // ํ์๊ฐ์
+ @PostMapping("/join")
+ public ResponseEntity> join(@RequestBody @Valid UserRequest.JoinDTO joinDTO, Errors errors) {
+ userService.joinUser(joinDTO);
+ log.info(joinDTO.getUsername() + " Joined");
+ return ResponseEntity.ok().body(ApiUtils.success("ํ์๊ฐ์
์ด ์๋ฃ๋์์ต๋๋ค"));
+ }
+
+ // ๋ก๊ทธ์ธ
+ @PostMapping("/login")
+ public ResponseEntity> login(@RequestBody @Valid UserRequest.LoginDTO loginDTO, Errors errors, HttpServletRequest request) {
+ try {
+ UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
+ = new UsernamePasswordAuthenticationToken(loginDTO.getEmail(), loginDTO.getPassword());
+ Authentication authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
+ CustomUserDetails myUserDetails = (CustomUserDetails) authentication.getPrincipal();
+
+ String jwt = JwtTokenProvider.create(myUserDetails.getUser());
+ addLoginHistory(request, myUserDetails);
+
+ UserResponse.LoginDTO user = userService.loginInform(loginDTO);
+ user.setAccessToken(jwt);
+
+ return ResponseEntity.ok().header("Authorization", jwt).body(ApiUtils.success(user));
+
+ } catch (Exception e) {
+ throw new Exception401("์ธ์ฆ๋์ง ์์์ต๋๋ค");
+ }
+ }
+
+ // ๋ก๊ทธ์ธ ๊ธฐ๋ก
+ private void addLoginHistory(HttpServletRequest request, CustomUserDetails myUserDetails) {
+ LoginHistoryRequest.LoginHistoryDTO loginHistoryDTO = new LoginHistoryRequest.LoginHistoryDTO();
+
+ loginHistoryDTO.setUser(myUserDetails.getUser());
+ loginHistoryDTO.setIp(request.getRemoteAddr());
+ loginHistoryDTO.setUserAgent(request.getHeader("User-Agent"));
+ loginHistoryDTO.setLoginAt(LocalDateTime.now());
+
+ loginHistoryService.save(loginHistoryDTO);
+ }
+
+ // ๋ก๊ทธ์์
+ @GetMapping(value = "/logout")
+ public String logout(HttpServletRequest request, HttpServletResponse response) {
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ if (authentication != null) {
+ new SecurityContextLogoutHandler().logout(request, response, authentication);
+ }
+ return "redirect:/login";
+ }
+
+ // ํธ๋ํฐ ๋ฒํธ ๋ณ๊ฒฝ
+ @PutMapping("/mypage/updatePhoneNumber")
+ public ResponseEntity> updatePhoneNumber(@AuthenticationPrincipal CustomUserDetails updateUser, @RequestBody @Valid UserRequest.UpdateDTO updateDTO) {
+ userService.updatePhoneNumber(updateUser.getUserId(), updateDTO);
+ return ResponseEntity.ok().body(ApiUtils.success("๋ณ๊ฒฝ๋์์ต๋๋ค."));
+ }
+
+ // ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ
+ @PutMapping("/mypage/updatePassword")
+ public ResponseEntity> userPasswordModify(@RequestBody UserRequest.ModifyPwdDTO request) {
+ userService.updatePwd(request);
+ return ResponseEntity.ok().body(ApiUtils.success("๋ณ๊ฒฝ๋์์ต๋๋ค."));
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/kdtbe5_miniproject/user/UserPosition.java b/src/main/java/com/example/kdtbe5_miniproject/user/UserPosition.java
new file mode 100644
index 0000000..bde028d
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/user/UserPosition.java
@@ -0,0 +1,17 @@
+package com.example.kdtbe5_miniproject.user;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor
+public enum UserPosition {
+ ์ฌ์(0),
+ ์ฃผ์(1),
+ ๋๋ฆฌ(2),
+ ๊ณผ์ฅ(3),
+ ์ฐจ์ฅ(4),
+ ๋ถ์ฅ(5);
+
+ private final int typeNumber;
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/user/UserRepository.java b/src/main/java/com/example/kdtbe5_miniproject/user/UserRepository.java
new file mode 100644
index 0000000..92e033b
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/user/UserRepository.java
@@ -0,0 +1,25 @@
+package com.example.kdtbe5_miniproject.user;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+
+@Repository
+public interface UserRepository extends JpaRepository {
+
+
+ Optional findByEmail(@Param("email") String email);
+
+ @Modifying
+ @Query(value = "UPDATE User SET password = :newPassword, updateDate = :now WHERE id = :id")
+ void updateById(@Param("newPassword") String newPassword, @Param("now")LocalDateTime now, @Param("id") Long id);
+ @Override
+ List findAll();
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/user/UserRequest.java b/src/main/java/com/example/kdtbe5_miniproject/user/UserRequest.java
new file mode 100644
index 0000000..20b7e18
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/user/UserRequest.java
@@ -0,0 +1,65 @@
+package com.example.kdtbe5_miniproject.user;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+import java.time.LocalDate;
+
+public class UserRequest {
+ @Getter
+ @Setter
+ public static class LoginDTO {
+ @NotEmpty
+ private String email;
+ @NotEmpty
+ @Size(min = 4, max = 20)
+ private String password;
+ }
+
+ @Getter
+ @Setter
+ public static class JoinDTO {
+ @NotEmpty
+ private String username;
+ @NotEmpty
+ @Size(min = 4, max = 20)
+ private String password;
+ @NotEmpty
+ @Pattern(regexp = "^[\\w._%+-]+@[\\w.-]+\\.[a-zA-Z]{2,6}$", message = "์ด๋ฉ์ผ ํ์์ผ๋ก ์์ฑํด์ฃผ์ธ์")
+ private String email;
+ @Pattern(regexp = "^01(?:0|1|[6-9])\\d{7,8}$", message = "01011112222์ ๊ฐ์ ํ์์ผ๋ก ์์ฑํด์ฃผ์ธ์")
+ private String phoneNumber;
+ private UserPosition position;
+ private Float numOfDayOff;
+ private LocalDate joinDate;
+
+ public User toEntity() {
+ return User.builder()
+ .username(username)
+ .password(password)
+ .email(email)
+ .phoneNumber(phoneNumber)
+ .position(position)
+ .roles(UserRoles.valueOf("์ผ๋ฐ"))
+ .joinDate(joinDate)
+ .build();
+ }
+ }
+ @Getter
+ public static class UpdateDTO {
+ @NotEmpty
+ @Pattern(regexp = "^01(?:0|1|[6-9])\\d{7,8}$", message = "01011112222์ ๊ฐ์ ํ์์ผ๋ก ์์ฑํด์ฃผ์ธ์")
+ private String phoneNumber;
+ }
+
+ @Getter
+ @Setter
+ public static class ModifyPwdDTO {
+ private Long userId;
+ private String oldPassword;
+ private String newPassword;
+ }
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/user/UserResponse.java b/src/main/java/com/example/kdtbe5_miniproject/user/UserResponse.java
new file mode 100644
index 0000000..a6f98c8
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/user/UserResponse.java
@@ -0,0 +1,57 @@
+package com.example.kdtbe5_miniproject.user;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDate;
+public class UserResponse {
+ @Getter
+ @Setter
+ public static class JoinDTO {
+ private Long id;
+ private String email;
+ private String username;
+ private String phoneNumber;
+ private int position;
+ private int roles;
+ private Float numOfDayOff;
+ private LocalDate joinDate;
+ public JoinDTO(User user) {
+ this.id = user.getId();
+ this.email = user.getEmail();
+ this.username = user.getUsername();
+ this.phoneNumber = user.getPhoneNumber();
+ this.position = user.getPosition().getTypeNumber();
+ this.roles = user.getRoles().getTypeNumber();
+ this.joinDate = user.getJoinDate();
+ }
+ }
+
+ @Getter
+ public static class AllUsersDTO {
+ private Long id;
+ private String username;
+ private int position;
+
+ public AllUsersDTO(User user) {
+ this.id = user.getId();
+ this.username = user.getUsername();
+ this.position = user.getPosition().getTypeNumber();
+ }
+ }
+
+ @Getter
+ @Setter
+ @Builder
+ public static class LoginDTO {
+ private Long id;
+ private String username;
+ private String email;
+ private String phoneNumber;
+ private UserPosition position;
+ private UserRoles roles;
+ private LocalDate joinDate;
+ private String accessToken;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/kdtbe5_miniproject/user/UserRoles.java b/src/main/java/com/example/kdtbe5_miniproject/user/UserRoles.java
new file mode 100644
index 0000000..c1f89a2
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/user/UserRoles.java
@@ -0,0 +1,13 @@
+package com.example.kdtbe5_miniproject.user;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor
+public enum UserRoles {
+ ์ผ๋ฐ(0),
+ ๊ด๋ฆฌ์(1);
+
+ private final int typeNumber;
+}
diff --git a/src/main/java/com/example/kdtbe5_miniproject/user/UserService.java b/src/main/java/com/example/kdtbe5_miniproject/user/UserService.java
new file mode 100644
index 0000000..d2985ba
--- /dev/null
+++ b/src/main/java/com/example/kdtbe5_miniproject/user/UserService.java
@@ -0,0 +1,70 @@
+package com.example.kdtbe5_miniproject.user;
+
+import com.example.kdtbe5_miniproject._core.errors.exception.DuplicatedEmailException;
+import com.example.kdtbe5_miniproject._core.errors.exception.UnCorrectPasswordException;
+import com.example.kdtbe5_miniproject._core.errors.exception.UserNotFoundException;
+import lombok.RequiredArgsConstructor;
+import org.aspectj.apache.bcel.classfile.Module;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.security.Principal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.Optional;
+
+@RequiredArgsConstructor
+@Service
+public class UserService {
+
+ private final UserRepository userRepository;
+ private final PasswordEncoder passwordEncoder;
+
+ // ํ์๊ฐ์
+ @Transactional
+ public UserResponse.JoinDTO joinUser(UserRequest.JoinDTO joinDTO) {
+
+ Optional userOP = userRepository.findByEmail(joinDTO.getEmail());
+ if(userOP.isPresent())
+ throw new DuplicatedEmailException("์ด๋ฏธ ์ฌ์ฉ์ค์ธ ์ด๋ฉ์ผ์
๋๋ค");
+
+ joinDTO.setPassword(passwordEncoder.encode(joinDTO.getPassword()));
+
+ //์ฌ์ฉ์ ์ ๋ณด ์ํธํ - ๊ธฐ๋ฅ ๊ตฌํ ์ ํท๊ฐ๋ฆด ๊ฒ ๊ฐ์์ ์ฃผ์ ์ฒ๋ฆฌ
+// joinDTO.setUsername(EncryptUtils.encrypt(joinDTO.getUsername()));
+// joinDTO.setPhoneNumber(EncryptUtils.encrypt(joinDTO.getPhoneNumber()));
+// joinDTO.setEmail(EncryptUtils.encrypt(joinDTO.getEmail()));
+
+ User userPS = userRepository.save(joinDTO.toEntity());
+ return new UserResponse.JoinDTO(userPS);
+ }
+
+ // ๋ก๊ทธ์ธ ์ฌ์ฉ์ ์ ๋ณด ์กฐํ
+ @Transactional
+ public UserResponse.LoginDTO loginInform(UserRequest.LoginDTO loginDTO) {
+ Optional userOP = userRepository.findByEmail(loginDTO.getEmail());
+ UserResponse.LoginDTO user = userOP.get().toEntity();
+ return user;
+ }
+
+ // ํธ๋ํฐ๋ฒํธ ๋ณ๊ฒฝ
+ @Transactional
+ public void updatePhoneNumber(Long userId, UserRequest.UpdateDTO updateDTO) {
+ User user = userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException("์ฌ์ฉ์ ์ ๋ณด๊ฐ ์์ต๋๋ค."));
+ user.updatePhoneNumber(updateDTO.getPhoneNumber());
+ userRepository.save(user);
+ }
+
+ // ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ
+ @Transactional
+ public void updatePwd(UserRequest.ModifyPwdDTO request) {
+ User user = userRepository.findById(request.getUserId())
+ .orElseThrow(() -> new UserNotFoundException("์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค."));
+
+ if (!passwordEncoder.matches(request.getOldPassword(), user.getPassword())) {
+ throw new UnCorrectPasswordException("๋น๋ฐ๋ฒํธ๊ฐ ์ผ์นํ์ง ์์ต๋๋ค.");
+ }
+ userRepository.updateById(passwordEncoder.encode(request.getNewPassword()), LocalDateTime.now(), request.getUserId());
+ }
+}
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
new file mode 100644
index 0000000..607397c
--- /dev/null
+++ b/src/main/resources/application-dev.yml
@@ -0,0 +1,35 @@
+server:
+ servlet:
+ encoding:
+ charset: utf-8
+ force: true
+ port: 8080
+spring:
+ datasource:
+ url: jdbc:h2:mem:test;MODE=MySQL
+ driver-class-name: org.h2.Driver
+ username: sa
+ password:
+ h2:
+ console:
+ enabled: true
+ jpa:
+ hibernate:
+ ddl-auto: create
+ show-sql: true
+ properties:
+ hibernate:
+ format_sql: true
+ default_batch_fetch_size: 100
+ open-in-view: false
+ defer-datasource-initialization: true
+ sql:
+ init:
+ data-locations: classpath*:db/data.sql
+ mode: always
+ platform: h2
+
+logging:
+ level:
+ '[MiniProject.Server]': DEBUG
+ '[org.hibernate.type]': TRACE
\ No newline at end of file
diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml
new file mode 100644
index 0000000..b003317
--- /dev/null
+++ b/src/main/resources/application-prod.yml
@@ -0,0 +1,23 @@
+server:
+ servlet:
+ encoding:
+ charset: utf-8
+ force: true
+ port: 5000
+spring:
+ datasource:
+ url: jdbc:mariadb://${rds.hostname}:${rds.port}/${rds.db.name}?allowPublicKeyRetrieval=true&useSSL=false
+ driver-class-name: org.mariadb.jdbc.Driver
+ username: ${rds.username}
+ password: ${rds.password}
+
+ jpa:
+ hibernate:
+ ddl-auto: none
+ properties:
+ default_batch_fetch_size: 100
+ open-in-view: false
+
+logging:
+ level:
+ '[MiniProject.Server]': INFO
\ No newline at end of file
diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml
new file mode 100644
index 0000000..b852d74
--- /dev/null
+++ b/src/main/resources/application-test.yml
@@ -0,0 +1,29 @@
+server:
+ servlet:
+ encoding:
+ charset: utf-8
+ force: true
+ port: 8080
+spring:
+ datasource:
+ url: jdbc:h2:mem:test;MODE=MySQL
+ driver-class-name: org.h2.Driver
+ username: sa
+ password:
+ h2:
+ console:
+ enabled: true
+ jpa:
+ hibernate:
+ ddl-auto: create
+ show-sql: true
+ properties:
+ hibernate:
+ format_sql: true
+ default_batch_fetch_size: 100
+ open-in-view: false
+
+logging:
+ level:
+ '[MiniProject.Server]': DEBUG
+ '[org.hibernate.type]': TRACE
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
deleted file mode 100644
index 8b13789..0000000
--- a/src/main/resources/application.properties
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..41ba440
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,4 @@
+spring:
+ profiles:
+ active:
+ - dev
\ No newline at end of file
diff --git a/src/main/resources/db/data.sql b/src/main/resources/db/data.sql
new file mode 100644
index 0000000..67889d8
--- /dev/null
+++ b/src/main/resources/db/data.sql
@@ -0,0 +1,76 @@
+/*
+ user_tb dummy
+*/
+insert into user_tb (id, username, password, email, phone_number, position, roles, join_date, update_date)
+values (1, '์ค์คํธ', '0000', 'tester01@gmail.com', '01012345678', '0', '0', '2023-07-27', '2023-07-27');
+insert into user_tb (id, username, password, email, phone_number, position, roles, join_date, update_date)
+values (2, '๊ณฝ๋น', '0000', 'tester02@gmail.com', '01012345678', '0', '0', '2023-07-27', '2023-07-27');
+insert into user_tb (id, username, password, email, phone_number, position, roles, join_date, update_date)
+values (3, '์ ํ๊ทผ', '0000', 'tester03@gmail.com', '01012345678', '0', '0', '2023-07-27', '2023-07-27');
+insert into user_tb (id, username, password, email, phone_number, position, roles, join_date, update_date)
+values (4, '์ด์ํ', '0000', 'tester04@gmail.com', '01012345678', '1', '0', '2023-07-27', '2023-07-27');
+insert into user_tb (id, username, password, email, phone_number, position, roles, join_date, update_date)
+values (5, '๋ฐ์น๊ตญ', '0000', 'tester05@gmail.com', '01012345678', '2', '0', '2023-07-27', '2023-07-27');
+insert into user_tb (id, username, password, email, phone_number, position, roles, join_date, update_date)
+values (6, '์์ํ', '0000', 'tester06@gmail.com', '01012345678', '2', '1', '2023-07-27', '2023-07-27');
+insert into user_tb (id, username, password, email, phone_number, position, roles, join_date, update_date)
+values (7, '์ ์๋น', '0000', 'tester07@gmail.com', '01012345678', '3', '0', '2023-07-27', '2023-07-27');
+insert into user_tb (id, username, password, email, phone_number, position, roles, join_date, update_date)
+values (8, '๊น์ฌํ', '0000', 'tester08@gmail.com', '01012345678', '4', '0', '2023-07-27', '2023-07-27');
+insert into user_tb (id, username, password, email, phone_number, position, roles, join_date, update_date)
+values (9, 'ํ๊ฒฝ๋ฏผ', '0000', 'tester09@gmail.com', '01012345678', '4', '1', '2023-07-27', '2023-07-27');
+insert into user_tb (id, username, password, email, phone_number, position, roles, join_date, update_date)
+values (10, '์์์ง', '0000', 'tester10@gmail.com', '01012345678', '5', '1', '2023-07-27', '2023-07-27');
+insert into user_tb (id, username, password, email, phone_number, position, roles, join_date, update_date)
+values (11, '์ด๋๋ฏผ', '{bcrypt}$2a$10$5nJepi7hydvCqZRDWjhlBe/isQmCbZFtmjUCAu8Bm7WAtfEfXMRUG', 'admin@admin.com', '01012345678', '5', '1', '2023-07-27', '2023-07-27');
+/*
+ login_tb dummy 10
+ */
+insert into login_tb (id, user_id, ip, user_agent, login_at) values (1, 10, '255.255.255.255', 'Mozilla/...','2023-07-27');
+insert into login_tb (id, user_id, ip, user_agent, login_at) values (2, 9, '255.255.255.0', 'Mozilla/...','2023-07-27');
+insert into login_tb (id, user_id, ip, user_agent, login_at) values (3, 8, '255.255.255.0', 'Mozilla/...','2023-07-27');
+insert into login_tb (id, user_id, ip, user_agent, login_at) values (4, 7, '255.255.255.0', 'Mozilla/...','2023-07-27');
+insert into login_tb (id, user_id, ip, user_agent, login_at) values (5, 6, '255.255.255.0', 'Mozilla/...','2023-07-27');
+insert into login_tb (id, user_id, ip, user_agent, login_at) values (6, 5, '255.255.255.0', 'Mozilla/...','2023-07-27');
+insert into login_tb (id, user_id, ip, user_agent, login_at) values (7, 4, '255.255.255.0', 'Mozilla/...','2023-07-27');
+insert into login_tb (id, user_id, ip, user_agent, login_at) values (8, 3, '255.255.255.1', 'Mozilla/...','2023-07-27');
+insert into login_tb (id, user_id, ip, user_agent, login_at) values (9, 2, '255.255.255.255', 'Mozilla/...','2023-07-27');
+insert into login_tb (id, user_id, ip, user_agent, login_at) values (10, 1, '255.255.255.255', 'Mozilla/...','2023-07-27');
+/*
+ dayoff_tb dummy 10
+ */
+insert into dayoff_tb (id, user_id, start_date, end_date, reason, type, status, num_of_day_off, apply_at, process_at)
+values (1, 10, '2023-08-27', '2023-08-27', '๋ฐฐ๊ณ ํ์', '2', '1', 12, '2023-08-07', null);
+insert into dayoff_tb (id, user_id, start_date, end_date, reason, type, status, num_of_day_off, apply_at, process_at)
+values (2, 6, '2023-08-27', '2023-08-27', '๋ฐฐ๊ณ ํ์', '1', '2', 12, '2023-08-08', null);
+insert into dayoff_tb (id, user_id, start_date, end_date, reason, type, status, num_of_day_off, apply_at, process_at)
+values (3, 9, '2023-08-28', '2023-08-28', '๋ฐฐ๊ณ ํ์', '2', '0', 12, '2023-08-09', null);
+insert into dayoff_tb (id, user_id, start_date, end_date, reason, type, status, num_of_day_off, apply_at, process_at)
+values (4, 10, '2023-08-29', '2023-08-29', '๋ฐฐ๊ณ ํ์', '1', '1', 12, '2023-08-10', null);
+insert into dayoff_tb (id, user_id, start_date, end_date, reason, type, status, num_of_day_off, apply_at, process_at)
+values (5, 9, '2023-09-02', '2023-09-02', '๋ฐฐ๊ณ ํ์', '2', '0', 15, '2023-08-11', null);
+insert into dayoff_tb (id, user_id, start_date, end_date, reason, type, status, num_of_day_off, apply_at, process_at)
+values (6, 8, '2023-09-04', '2023-09-05', '๋ฐฐ๊ณ ํ์', '0', '0', 15, '2023-08-12', null);
+insert into dayoff_tb (id, user_id, start_date, end_date, reason, type, status, num_of_day_off, apply_at, process_at)
+values (7, 7, '2023-09-06', '2023-09-06', '๋ฐฐ๊ณ ํ์', '2', '0', 15, '2023-08-13', null);
+insert into dayoff_tb (id, user_id, start_date, end_date, reason, type, status, num_of_day_off, apply_at, process_at)
+values (8, 8, '2023-09-27', '2023-09-27', '๋ฐฐ๊ณ ํ์', '0', '0', 15, '2023-08-14', null);
+insert into dayoff_tb (id, user_id, start_date, end_date, reason, type, status, num_of_day_off, apply_at, process_at)
+values (9, 9, '2023-09-27', '2023-09-27', '๋ฐฐ๊ณ ํ์', '2', '0', 15, '2023-08-15', null);
+insert into dayoff_tb (id, user_id, start_date, end_date, reason, type, status, num_of_day_off, apply_at, process_at)
+values (10, 10, '2023-09-29', '2023-09-29', '๋ฐฐ๊ณ ํ์', '2', '0', 15, '2023-08-16', null);
+insert into dayoff_tb (id, user_id, start_date, end_date, reason, type, status, num_of_day_off, apply_at, process_at)
+values (11, 10, '2023-09-29', '2023-10-29', '๋ฐฐ๊ณ ํ์', '0', '0', 15, '2023-08-17', null);
+/*
+ duty_tb dummy 10
+ */
+insert into duty_tb (id, user_id, date, reason, status) values (1, 1, '2023-08-01', '๋ง๋ด1', '1');
+insert into duty_tb (id, user_id, date, reason, status) values (2, 2, '2023-08-02', '๋ง๋ด2', '1');
+insert into duty_tb (id, user_id, date, reason, status) values (3, 3, '2023-08-03', '๋ง๋ด3', '1');
+insert into duty_tb (id, user_id, date, reason, status) values (4, 1, '2023-08-04', '๋ง๋ด1', '1');
+insert into duty_tb (id, user_id, date, reason, status) values (5, 2, '2023-08-05', '๋ง๋ด2', '1');
+insert into duty_tb (id, user_id, date, reason, status) values (6, 3, '2023-08-06', '๋ง๋ด3', '1');
+insert into duty_tb (id, user_id, date, reason, status) values (7, 1, '2023-08-07', '๋ง๋ด1', '0');
+insert into duty_tb (id, user_id, date, reason, status) values (8, 2, '2023-08-08', '๋ง๋ด2', '0');
+insert into duty_tb (id, user_id, date, reason, status) values (9, 3, '2023-08-09', '๋ง๋ด3', '0');
+insert into duty_tb (id, user_id, date, reason, status) values (10, 1, '2023-08-10', '๋ง๋ด1', '2');
\ No newline at end of file