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 +![header](https://capsule-render.vercel.app/api?type=waving&color=auto&height=200§ion=header&text=MiniProject;%20On_n_Off&fontSize=50) + + + + + +
+ +
+ + + + +# ์˜จ์•ค์˜คํ”„ +### ๐Ÿ† ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ +ํ”„๋ก ํŠธ(React), ๋ฐฑ์—”๋“œ(SpringBoot/Java) ๊ฐํŒ€์ด ํ˜‘์—…ํ•˜์—ฌ ui/ux๋ถ€ํ„ฐ ๋ฐฐํฌ์ž‘์—…๊นŒ์ง€ ์™„๋ฃŒํ•œ ์‚ฌ์ด๋“œํ”„๋กœ์ ํŠธ์ž…๋‹ˆ๋‹ค. +ํ”„๋กœ์ ํŠธ๋ช…์€ ์˜จ์•ค์˜คํ”„(On&Off)์ด๋ฉฐ, ํ•ด๋‹น ํ”„๋กœ์ ํŠธ๋Š” ํด๋ผ์ด์–ธํŠธ์˜ ์ผ์ •๊ด€๋ฆฌ๋ฅผ ์†์‰ฝ๊ฒŒ ์œ ์ง€๊ด€๋ฆฌ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +
+>**๋ฐฐํฌ๋งํฌ : https://on-n-off-mini.netlify.app/** +>
+>**๊ฐœ๋ฐœ๊ธฐ๊ฐ„ : 2023.07.24 ~ 2023.08.11** + + + +
+## ๐Ÿ“Œ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ +
+ Table of Contents +
    +
  1. + ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ + +
  2. +
  3. ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ
  4. +
  5. ํ”„๋กœ์ ํŠธ ์‹œํ˜„
  6. +
  7. ํŒ€์› ๋ฐ ์—ญํ• 
  8. +
  9. + ํ”„๋กœ์ ํŠธ ์Šคํƒ + +
  10. +
  11. ERD
  12. +
  13. ํ…Œ์ด๋ธ” ์„ค๊ณ„
  14. +
  15. + ์ฃผ์š” ๊ธฐ๋Šฅ + +
  16. +
  17. API ๋ช…์„ธ์„œ
  18. +
+
+ +

(back to top)

+ + + + +> **๋กœ๊ทธ์ธ / ํšŒ์›๊ด€๋ฆฌ** +> +| ๋กœ๊ทธ์ธ | ํšŒ์›๊ด€๋ฆฌ | +| :------------: | :------------: | +| ![๋กœ๊ทธ์ธ](https://github.com/FC-MINI-6/MiniProject_BE/assets/78328327/6b54f779-71b3-476e-87fb-518ca6ce88ea) | ![ํšŒ์›๊ฐ€์ž…](https://github.com/FC-MINI-6/MiniProject_BE/assets/78328327/f3664f54-95c5-44cf-8f86-a0bd1d348a8d) | +
+ +> **๋ฉ”์ธ(์ผ๋ฐ˜) / ๋ฉ”์ธ(๊ด€๋ฆฌ์ž)** +> +| ๋ฉ”์ธ(์ผ๋ฐ˜) | ๋ฉ”์ธ(๊ด€๋ฆฌ์ž) | +| :------------: | :------------: | +| ![๋ฉ”์ธ_์ผ๋ฐ˜](https://github.com/FC-MINI-6/MiniProject_BE/assets/78328327/80bc9c83-ba70-43db-a6f7-4fd8f8ac78ed) | ![๋ฉ”์ธ_๊ด€๋ฆฌ์ž](https://github.com/FC-MINI-6/MiniProject_BE/assets/78328327/767be3bc-bb80-483f-bcfe-ee46401813a0) | +
+ +> **๊ด€๋ฆฌ์ž** +> +| ๋‹น์งํœด๊ฐ€๊ด€๋ฆฌ | ์‚ฌ์›๊ด€๋ฆฌ | ์‚ฌ์›๊ด€๋ฆฌ_์„ธ๋ถ€ ์ •๋ณด | +| :------------: | :------------: | :------------: | +| ![๊ด€๋ฆฌ์ž_๋‹น์งํœด๊ฐ€๊ด€๋ฆฌ](https://github.com/FC-MINI-6/MiniProject_BE/assets/78328327/91635bd8-8922-4a62-8799-72638014a8ae) | ![๊ด€๋ฆฌ์ž_์‚ฌ์›๊ด€๋ฆฌ](https://github.com/FC-MINI-6/MiniProject_BE/assets/78328327/61c28bf7-9b06-4d7c-b94d-d5ac4018b740) | ![๊ด€๋ฆฌ์ž_์‚ฌ์›๊ด€๋ฆฌ_์„ธ๋ถ€ ์ •๋ณด](https://github.com/FC-MINI-6/MiniProject_BE/assets/78328327/091fc659-e093-490b-b9d6-7710b621d8b5) | +
+ +> **ํœด๊ฐ€/๋‹น์ง** +> +| ํœด๊ฐ€/๋‹น์ง๊ด€๋ฆฌ | ํœด๊ฐ€๋“ฑ๋ก | ๋‹น์ง๋“ฑ๋ก | +| :------------: | :------------: | :------------: | +| ![ํœด๊ฐ€๋‹น์ง๊ด€๋ฆฌ](https://github.com/FC-MINI-6/MiniProject_BE/assets/78328327/c0ba7031-a346-4b72-a23d-ff897c0727f5) | !![ํœด๊ฐ€๋“ฑ๋ก](https://github.com/FC-MINI-6/MiniProject_BE/assets/78328327/272b7b58-b9c1-4e13-a2a3-77d8bcc2893d) | ![๋‹น์ง๋“ฑ๋ก](https://github.com/FC-MINI-6/MiniProject_BE/assets/78328327/fd815383-5247-40ba-8b8e-04e7d4a81f73) | + +| ๋‹น์ง์‹ ์ฒญ๋‚ด์—ญ | ํœด๊ฐ€์‹ ์ฒญ๋‚ด์—ญ | +| :------------: | :------------: | +| ![๋‚˜์˜๋‹น์ง์‹ ์ฒญ](https://github.com/FC-MINI-6/MiniProject_BE/assets/78328327/9953028c-a67d-41cc-b0b8-f271eb6173e8) | ![๋‚˜์˜ํœด๊ฐ€์‹ ์ฒญ](https://github.com/FC-MINI-6/MiniProject_BE/assets/78328327/ae5394ef-d7af-493a-8bea-740477326997) | +
+ +> **๊ฐœ์ธ์ •๋ณด์ˆ˜์ •** +> +| ๋ณ€๊ฒฝ_์ „ํ™”๋ฒˆํ˜ธ | ๋ณ€๊ฒฝ_๋น„๋ฐ€๋ฒˆํ˜ธ | ์บ˜๋ฆฐ๋” | +| :------------: | :------------: | :------------: | +| ![๋‚ด์ •๋ณด๋ณ€๊ฒฝ_์ „ํ™”๋ฒˆํ˜ธ](https://github.com/FC-MINI-6/MiniProject_BE/assets/78328327/77f38877-a119-4120-955c-01cba08e29e2) | ![๋‚ด์ •๋ณด๋ณ€๊ฒฝ_๋น„๋ฐ€๋ฒˆํ˜ธ](https://github.com/FC-MINI-6/MiniProject_BE/assets/78328327/c14ece09-9bcf-404b-853f-e8a1ecbabeab) | ![์บ˜๋ฆฐ๋”](https://github.com/FC-MINI-6/MiniProject_BE/assets/78328327/16e67ea1-4ae0-4d8d-a2a7-650729f2c68b) | + +

(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 +![image](https://github.com/FC-MINI-6/MiniProject_BE/assets/78328327/c1b9c922-338e-4bb4-8316-752b248585b6) + +

(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)

+ + + + +## ๐ŸŽ€ ์ฃผ์š” ๊ธฐ๋Šฅ +

๐Ÿ”น ํšŒ์›๊ฐ€์ž…

+
    +
  1. jwt์ด์šฉ, ๋น„๋ฐ€๋ฒˆํ˜ธ ๋‹จ๋ฐฉํ–ฅ ์•”ํ˜ธํ™”
  2. +
  3. ๊ฐœ์ธ์ •๋ณด์žˆ๋Š”๊ฒฝ์šฐ(์ด๋ฆ„,ํœด๋Œ€ํฐ,์ด๋ฉ”์ผ) AES256์–‘๋ฐฉํ–ฅ ์•”ํ˜ธํ™”์ ์šฉ,๋ณตํ˜ธํ™”
  4. +
  5. ๊ด€๋ จํ•„๋“œ์— DB๊ฒ€์ƒ‰ํ• ๋–„๋„ ๊ฒ€์ƒ‰์กฐ๊ฑด์„ ์•”ํ˜ธํ™”
  6. +
+

๐Ÿ”น ๋กœ๊ทธ์ธ

+
    +
  1. jwt token ์ธ์ฆ๋ฐฉ์‹, spring security์ ์šฉ
  2. +
  3. login ์„ฑ๊ณต ํ›„ ๋งˆ์ง€๋ง‰ ๋กœ๊ทธ์ธ ์„ฑ๊ณต ๋‚ ์งœ ์—…๋ฐ์ดํŠธ ์ ์šฉ
  4. +
  5. login ์„ฑ๊ณต ํ›„ ํšŒ์›๋ฒˆํ˜ธ, user-agent, client ip, ์‹œ๊ฐ„ ๋กœ๊ทธ ๋“ฑ๋ก์ฒ˜๋ฆฌ
  6. +
  7. ์ผ๋ฐ˜๋กœ๊ทธ์ธ/๊ด€๋ฆฌ์ž๋กœ๊ทธ์ธ
  8. +
+

๐Ÿ”น ๊ฐœ์ธ์ •๋ณด์ˆ˜์ •

+
    +
  1. ๊ฐœ์ธ์ •๋ณด ์ˆ˜์ •,์‚ญ์ œ, ์ˆ˜์ •๋œ ์ผ์ž ์—…๋ฐ์ดํŠธ
  2. +
  3. ๊ฐœ์ธ์—ฐ์ฐจ/๋‹น์ง ๋“ฑ๋ก
  4. +
  5. ๊ด€๋ฆฌ์ž (์œ ์ €์˜ ๊ถŒํ•œ ์„ค์ •), ๋กœ๊ทธ์ธ ๋ฐœ๊ธ‰๋œ jwt token๊ฒ€์ฆ
  6. +
  7. ๋น„๋ฐ€๋ฒˆํ˜ธ๋ณ€๊ฒฝ(๋‹จ๋ฐฉํ–ฅ ์•”ํ˜ธํ™”), ์ „ํ™”๋ฒˆํ˜ธ๋ณ€๊ฒฝ
  8. +
+

๐Ÿ”น ์—ฐ์ฐจ/๋‹น์ง

+
    +
  1. ์‹ ์ฒญ๋‚ด์—ญ, ์‚ฌ์šฉ๋‚ด์—ญ, ๋“ฑ๋ก, ์กฐํšŒ, ์ทจ์†Œ(crud)
  2. +
  3. ์—ฐ์ฐจ/๋‹น์ง ๊ตฌ๋ถ„์ปฌ๋Ÿผ๊ตฌ์„ฑ, ํ…Œ์ด๋ธ”1๊ฐœ์„ค๊ณ„
  4. +
  5. ์ €์žฅ/์‚ญ์ œ api๊ตฌํ˜„ํ•„์š”
  6. +
  7. ๋‚ด ์—ฐ์ฐจ์ผ ์ˆ˜
  8. +
+

๐Ÿ”น ๊ด€๋ฆฌ์ž๊ธฐ๋Šฅ

+
    +
  1. ์—ฐ์ฐจ ์Šน์ธ
  2. +
  3. ์—ฐ์ฐจ ๋ฐ˜๋ ค
  4. +
+

๐Ÿ”น ์‚ฌ์šฉ์ž๊ฐ„๊ณต์œ 

+
    +
  1. ๋ฐ์ดํ„ฐ ๋‚ด๋ ค์ค„ ์กฐํšŒ api ๊ตฌํ˜„
  2. +
  3. ์›”๋ณ„ ์บ˜๋ฆฐ๋” ์ฃผ๊ฐ„/์ผ๊ฐ„ ๋“ฑ ๋‹ค์–‘ํ•˜๊ฒŒ ํ‘œํ˜„
  4. +
+ +

(back to top)

+ + + + +## ๐Ÿ“Œ API ๋ช…์„ธ์„œ +![image](https://github.com/FC-MINI-6/MiniProject_BE/assets/78328327/eabdf3c6-c6df-44e9-a6b1-8d868c3f9e44) + +

(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 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