diff --git a/.github/ISSUE_TEMPLATE/test-issue-template.yml b/.github/ISSUE_TEMPLATE/test-issue-template.yml deleted file mode 100644 index 465c4ef..0000000 --- a/.github/ISSUE_TEMPLATE/test-issue-template.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: "๐Ÿ’ฌ Test" -description: "์ฝ”๋“œ ํ…Œ์ŠคํŠธ" -labels: ["test"] -body: - - type: textarea - attributes: - label: ๐Ÿ“„ ์„ค๋ช… - description: ๊ฒ€์ฆํ•˜๊ณ ์ž ํ•˜๋Š” ์ฝ”๋“œ ๋Œ€ํ•œ ์„ค๋ช…์„ ์ž‘์„ฑํ•ด ์ฃผ์„ธ์š”. - placeholder: ๊ฒ€์ฆํ•˜๊ณ ์ž ํ•˜๋Š” ์ฝ”๋“œ์™€ ๊ฒ€์ฆ ๋ฐฉ์‹์— ๋Œ€ํ•ด ์ตœ๋Œ€ํ•œ ์ž์„ธํžˆ ์ž‘์„ฑํ•ด ์ฃผ์„ธ์š”! - validations: - required: true - - type: textarea - attributes: - label: โœ… ์ž‘์—…ํ•  ๋‚ด์šฉ - description: ํ•  ์ผ์„ ์ฒดํฌ๋ฐ•์Šค ํ˜•ํƒœ๋กœ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”. - placeholder: ์ตœ๋Œ€ํ•œ ์„ธ๋ถ„ํ™” ํ•ด์„œ ์ ์–ด์ฃผ์„ธ์š”! - validations: - required: true - - type: textarea - attributes: - label: ๐Ÿ™‹๐Ÿป ์ฐธ๊ณ  ์ž๋ฃŒ - description: ์ฐธ๊ณ  ์ž๋ฃŒ๊ฐ€ ์žˆ๋‹ค๋ฉด ์ž‘์„ฑํ•ด ์ฃผ์„ธ์š”. diff --git a/.github/workflows/auto-branch.yml b/.github/workflows/auto-branch.yml new file mode 100644 index 0000000..4634d2b --- /dev/null +++ b/.github/workflows/auto-branch.yml @@ -0,0 +1,17 @@ +name: Create Issue Branch +on: + issues: + types: [ assigned ] + issue_comment: + types: [ created ] + pull_request: + types: [ closed ] + +jobs: + create_issue_branch_job: + runs-on: ubuntu-latest + steps: + - name: Create Issue Branch + uses: robvanderleek/create-issue-branch@main + env: + GITHUB_TOKEN: ${{ secrets.TOKEN }} diff --git a/.github/workflows/close-issue.yml b/.github/workflows/close-issue.yml deleted file mode 100644 index cd25d28..0000000 --- a/.github/workflows/close-issue.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Close Issue When PR Merged -on: - pull_request: - types: [closed] - -jobs: - close_issue: - runs-on: ubuntu-latest - steps: - - name: Close linked issue - uses: peter-evans/close-issue@v2 - with: - github_token: ${{ secrets.TOKEN }} - comment: "โœ… PR์ด ๋จธ์ง€๋˜์–ด ํ•ด๋‹น ์ด์Šˆ๋ฅผ ๋‹ซ์Šต๋‹ˆ๋‹ค." diff --git a/.github/workflows/create-branch.yml b/.github/workflows/create-branch.yml deleted file mode 100644 index 443c204..0000000 --- a/.github/workflows/create-branch.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: Create Issue Branch -on: - issues: - types: [assigned] - issue_comment: - types: [created] - pull_request: - types: [closed] - -jobs: - create_issue_branch_job: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Debug Git repository - run: | - git remote -v - git branch - git status - - - name: Determine Branch Name Based on Issue Labels - id: detect_branch - run: | - LABEL=$(echo '${{ toJSON(github.event.issue.labels) }}' | jq -r '.[0].name') - if [[ -z "$LABEL" || "$LABEL" == "null" ]]; then - LABEL="feature" # ๊ธฐ๋ณธ๊ฐ’ ์„ค์ • - fi - BRANCH_NAME="${LABEL}/issue-${{ github.event.issue.number }}" - echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV - echo "๐Ÿ”น Detected branch name: $BRANCH_NAME" - - - name: Create Issue Branch - uses: robvanderleek/create-issue-branch@main - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - default-branch: dev - branch-name: "${{ env.BRANCH_NAME }}" - overwrite-existing: true - - - name: Check if PR already exists - id: check_pr - run: | - if gh pr list --state open --base dev --head "${{ env.BRANCH_NAME }}" --repo ${{ github.repository }} | grep "${{ env.BRANCH_NAME }}"; then - echo "pr_exists=true" >> $GITHUB_ENV - else - echo "pr_exists=false" >> $GITHUB_ENV - fi - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Open Pull Request - uses: peter-evans/create-pull-request@v5 - with: - token: ${{ secrets.GITHUB_TOKEN }} - base: dev - branch: "${{ env.BRANCH_NAME }}" - title: "๐Ÿš€ Pull Request #${{ github.event.issue.number }}" - body: | - ๐Ÿ”ฅ **์ด์Šˆ #${{ github.event.issue.number }}์— ๋Œ€ํ•œ PR ์ƒ์„ฑ!** ๐Ÿ”ฅ - - - ์ด์Šˆ ๋งํฌ: #${{ github.event.issue.number }} - delete-branch: false - allow-empty-commits: true diff --git a/.github/workflows/delete-branch.yml b/.github/workflows/delete-branch.yml deleted file mode 100644 index ad1416a..0000000 --- a/.github/workflows/delete-branch.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Delete Merged Branch -on: - pull_request: - types: [closed] - -jobs: - delete_merged_branch: - runs-on: ubuntu-latest - if: github.event.pull_request.merged == true - steps: - - name: Delete branch if merged - uses: github-actions/delete-merged-branch@v4 - with: - github_token: ${{ secrets.TOKEN }} diff --git a/.gitignore b/.gitignore index c2065bc..c25652d 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,8 @@ out/ ### VS Code ### .vscode/ + +.env + +### Mac ### +.DS_store \ No newline at end of file diff --git a/README.md b/README.md index 37b0f07..3bc95eb 100644 --- a/README.md +++ b/README.md @@ -10,4 +10,4 @@ - **Comment:** ํ•„์š”ํ•œ ์ฃผ์„ ์ถ”๊ฐ€ ๋ฐ ๋ณ€๊ฒฝ - **Dependency/Plugin:** Add a dependency/plugin - **Docs:** ๋ฌธ์„œ ์ˆ˜์ • -- **Rename:** ํŒŒ์ผ ํ˜น์€ ํด๋”๋ช…์„ ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜ ์˜ฎ๊ธฐ๋Š” ์ž‘์—…๋งŒ์ธ ๊ฒฝ์šฐ \ No newline at end of file +- **Rename:** ํŒŒ์ผ ํ˜น์€ ํด๋”๋ช…์„ ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜ ์˜ฎ๊ธฐ๋Š” ์ž‘์—…๋งŒ์ธ ๊ฒฝ์šฐ# Temporary commit for PR diff --git a/build.gradle b/build.gradle index b68e796..b5e78e7 100644 --- a/build.gradle +++ b/build.gradle @@ -27,12 +27,31 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0' + implementation 'io.jsonwebtoken:jjwt-api:0.12.3' + implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' + implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3' + developmentOnly 'org.springframework.boot:spring-boot-devtools' - compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' + + compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.security:spring-security-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + // okhttp + implementation 'com.squareup.okhttp3:okhttp:4.12.0' + + // mapper (ModelMapper) + implementation 'org.modelmapper:modelmapper:3.0.0' + + // dotenv (ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๊ด€๋ฆฌ) + implementation 'io.github.cdimascio:java-dotenv:5.2.2' } tasks.named('test') { diff --git a/feature-issue-10.patch b/feature-issue-10.patch new file mode 100644 index 0000000..4549bcd --- /dev/null +++ b/feature-issue-10.patch @@ -0,0 +1,2072 @@ +diff --git a/.github/ISSUE_TEMPLATE/test-issue-template.yml b/.github/ISSUE_TEMPLATE/test-issue-template.yml +deleted file mode 100644 +index 465c4ef..0000000 +--- a/.github/ISSUE_TEMPLATE/test-issue-template.yml ++++ /dev/null +@@ -1,22 +0,0 @@ +-name: "๐Ÿ’ฌ Test" +-description: "์ฝ”๋“œ ํ…Œ์ŠคํŠธ" +-labels: ["test"] +-body: +- - type: textarea +- attributes: +- label: ๐Ÿ“„ ์„ค๋ช… +- description: ๊ฒ€์ฆํ•˜๊ณ ์ž ํ•˜๋Š” ์ฝ”๋“œ ๋Œ€ํ•œ ์„ค๋ช…์„ ์ž‘์„ฑํ•ด ์ฃผ์„ธ์š”. +- placeholder: ๊ฒ€์ฆํ•˜๊ณ ์ž ํ•˜๋Š” ์ฝ”๋“œ์™€ ๊ฒ€์ฆ ๋ฐฉ์‹์— ๋Œ€ํ•ด ์ตœ๋Œ€ํ•œ ์ž์„ธํžˆ ์ž‘์„ฑํ•ด ์ฃผ์„ธ์š”! +- validations: +- required: true +- - type: textarea +- attributes: +- label: โœ… ์ž‘์—…ํ•  ๋‚ด์šฉ +- description: ํ•  ์ผ์„ ์ฒดํฌ๋ฐ•์Šค ํ˜•ํƒœ๋กœ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”. +- placeholder: ์ตœ๋Œ€ํ•œ ์„ธ๋ถ„ํ™” ํ•ด์„œ ์ ์–ด์ฃผ์„ธ์š”! +- validations: +- required: true +- - type: textarea +- attributes: +- label: ๐Ÿ™‹๐Ÿป ์ฐธ๊ณ  ์ž๋ฃŒ +- description: ์ฐธ๊ณ  ์ž๋ฃŒ๊ฐ€ ์žˆ๋‹ค๋ฉด ์ž‘์„ฑํ•ด ์ฃผ์„ธ์š”. +diff --git a/.github/workflows/auto-branch.yml b/.github/workflows/auto-branch.yml +new file mode 100644 +index 0000000..4634d2b +--- /dev/null ++++ b/.github/workflows/auto-branch.yml +@@ -0,0 +1,17 @@ ++name: Create Issue Branch ++on: ++ issues: ++ types: [ assigned ] ++ issue_comment: ++ types: [ created ] ++ pull_request: ++ types: [ closed ] ++ ++jobs: ++ create_issue_branch_job: ++ runs-on: ubuntu-latest ++ steps: ++ - name: Create Issue Branch ++ uses: robvanderleek/create-issue-branch@main ++ env: ++ GITHUB_TOKEN: ${{ secrets.TOKEN }} +diff --git a/.github/workflows/close-issue.yml b/.github/workflows/close-issue.yml +deleted file mode 100644 +index cd25d28..0000000 +--- a/.github/workflows/close-issue.yml ++++ /dev/null +@@ -1,14 +0,0 @@ +-name: Close Issue When PR Merged +-on: +- pull_request: +- types: [closed] +- +-jobs: +- close_issue: +- runs-on: ubuntu-latest +- steps: +- - name: Close linked issue +- uses: peter-evans/close-issue@v2 +- with: +- github_token: ${{ secrets.TOKEN }} +- comment: "โœ… PR์ด ๋จธ์ง€๋˜์–ด ํ•ด๋‹น ์ด์Šˆ๋ฅผ ๋‹ซ์Šต๋‹ˆ๋‹ค." +diff --git a/.github/workflows/create-branch.yml b/.github/workflows/create-branch.yml +deleted file mode 100644 +index 443c204..0000000 +--- a/.github/workflows/create-branch.yml ++++ /dev/null +@@ -1,68 +0,0 @@ +-name: Create Issue Branch +-on: +- issues: +- types: [assigned] +- issue_comment: +- types: [created] +- pull_request: +- types: [closed] +- +-jobs: +- create_issue_branch_job: +- runs-on: ubuntu-latest +- steps: +- - name: Checkout repository +- uses: actions/checkout@v4 +- with: +- fetch-depth: 0 +- +- - name: Debug Git repository +- run: | +- git remote -v +- git branch +- git status +- +- - name: Determine Branch Name Based on Issue Labels +- id: detect_branch +- run: | +- LABEL=$(echo '${{ toJSON(github.event.issue.labels) }}' | jq -r '.[0].name') +- if [[ -z "$LABEL" || "$LABEL" == "null" ]]; then +- LABEL="feature" # ๊ธฐ๋ณธ๊ฐ’ ์„ค์ • +- fi +- BRANCH_NAME="${LABEL}/issue-${{ github.event.issue.number }}" +- echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV +- echo "๐Ÿ”น Detected branch name: $BRANCH_NAME" +- +- - name: Create Issue Branch +- uses: robvanderleek/create-issue-branch@main +- env: +- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +- with: +- default-branch: dev +- branch-name: "${{ env.BRANCH_NAME }}" +- overwrite-existing: true +- +- - name: Check if PR already exists +- id: check_pr +- run: | +- if gh pr list --state open --base dev --head "${{ env.BRANCH_NAME }}" --repo ${{ github.repository }} | grep "${{ env.BRANCH_NAME }}"; then +- echo "pr_exists=true" >> $GITHUB_ENV +- else +- echo "pr_exists=false" >> $GITHUB_ENV +- fi +- env: +- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +- +- - name: Open Pull Request +- uses: peter-evans/create-pull-request@v5 +- with: +- token: ${{ secrets.GITHUB_TOKEN }} +- base: dev +- branch: "${{ env.BRANCH_NAME }}" +- title: "๐Ÿš€ Pull Request #${{ github.event.issue.number }}" +- body: | +- ๐Ÿ”ฅ **์ด์Šˆ #${{ github.event.issue.number }}์— ๋Œ€ํ•œ PR ์ƒ์„ฑ!** ๐Ÿ”ฅ +- +- - ์ด์Šˆ ๋งํฌ: #${{ github.event.issue.number }} +- delete-branch: false +- allow-empty-commits: true +diff --git a/.github/workflows/delete-branch.yml b/.github/workflows/delete-branch.yml +deleted file mode 100644 +index ad1416a..0000000 +--- a/.github/workflows/delete-branch.yml ++++ /dev/null +@@ -1,14 +0,0 @@ +-name: Delete Merged Branch +-on: +- pull_request: +- types: [closed] +- +-jobs: +- delete_merged_branch: +- runs-on: ubuntu-latest +- if: github.event.pull_request.merged == true +- steps: +- - name: Delete branch if merged +- uses: github-actions/delete-merged-branch@v4 +- with: +- github_token: ${{ secrets.TOKEN }} +diff --git a/.gitignore b/.gitignore +index a00fec8..e48b6be 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -34,6 +34,4 @@ out/ + /.nb-gradle/ + + ### VS Code ### +-.vscode/ +- +-.env ++.vscode/ +\ No newline at end of file +diff --git a/build.gradle b/build.gradle +index dd9d89a..56f4f0e 100644 +--- a/build.gradle ++++ b/build.gradle +@@ -24,30 +24,40 @@ repositories { + } + + dependencies { +- implementation 'org.springframework.boot:spring-boot-starter-web' +- implementation 'org.springframework.boot:spring-boot-starter-data-jpa' +- implementation 'org.springframework.boot:spring-boot-starter-validation' +- implementation 'org.springframework.boot:spring-boot-starter-security' +- implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' +- implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0' +- implementation 'io.jsonwebtoken:jjwt-api:0.12.3' +- implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' +- implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3' +- +- developmentOnly 'org.springframework.boot:spring-boot-devtools' +- runtimeOnly 'com.mysql:mysql-connector-j' +- +- compileOnly 'org.projectlombok:lombok' +- annotationProcessor 'org.projectlombok:lombok' +- +- testImplementation 'org.springframework.boot:spring-boot-starter-test' +- testImplementation 'org.springframework.security:spring-security-test' +- testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +- +- // okhttp +- implementation 'com.squareup.okhttp3:okhttp:4.12.0' ++ implementation 'org.springframework.boot:spring-boot-starter-data-jpa:3.0.4' ++ implementation 'org.springframework.boot:spring-boot-starter-web:3.1.0' ++ testImplementation 'org.projectlombok:lombok:1.18.26' ++ compileOnly 'org.projectlombok:lombok:1.18.26' ++ runtimeOnly 'com.mysql:mysql-connector-j:8.0.32' ++ annotationProcessor 'org.projectlombok:lombok:1.18.26' ++ testImplementation 'org.springframework.boot:spring-boot-starter-test:3.1.0' ++ ++ implementation 'org.hibernate.validator:hibernate-validator:8.0.1.Final' ++ implementation 'jakarta.validation:jakarta.validation-api:3.0.2' ++ ++ implementation 'com.squareup.okhttp3:okhttp:4.9.3' ++ ++ // Spring Doc ++ implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4' ++ ++ // Spring Security ++ implementation 'org.springframework.boot:spring-boot-starter-security:3.0.4' ++ ++ // JWT ++ implementation 'io.jsonwebtoken:jjwt-api:0.11.5' ++ implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' ++ implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' ++ ++ // chatGPT API ++ implementation 'io.github.flashvayne:chatgpt-spring-boot-starter:1.0.4' ++ ++ // JSON ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ++ implementation 'org.json:json:20230227' ++ ++ implementation 'org.springframework:spring-test:6.0.6' ++ testImplementation 'org.springframework.boot:spring-boot-starter-test:3.1.0' + } + + tasks.named('test') { + useJUnitPlatform() +-} +\ No newline at end of file ++} +diff --git a/src/main/java/com/bamboo/log/common/config/CorsMvcConfig.java b/src/main/java/com/bamboo/log/common/config/CorsMvcConfig.java +deleted file mode 100644 +index 2f3565b..0000000 +--- a/src/main/java/com/bamboo/log/common/config/CorsMvcConfig.java ++++ /dev/null +@@ -1,20 +0,0 @@ +-package com.bamboo.log.common.config; +- +-import org.springframework.context.annotation.Configuration; +-import org.springframework.web.servlet.config.annotation.CorsRegistry; +-import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +- +-@Configuration +-public class CorsMvcConfig implements WebMvcConfigurer { +- +- @Override +- public void addCorsMappings(CorsRegistry corsRegistry) { +- +- corsRegistry.addMapping("/**") +- .exposedHeaders("Set-Cookie") +- //ํ”„๋ก ํŠธ url ๋„ฃ๊ธฐ +- .allowedOrigins("http://localhost:3000"); +- } +-} +- +- +diff --git a/src/main/java/com/bamboo/log/common/config/SecurityConfig.java b/src/main/java/com/bamboo/log/common/config/SecurityConfig.java +deleted file mode 100644 +index 38ab583..0000000 +--- a/src/main/java/com/bamboo/log/common/config/SecurityConfig.java ++++ /dev/null +@@ -1,78 +0,0 @@ +-package com.bamboo.log.common.config; +- +-import com.bamboo.log.domain.user.jwt.service.JWTFilter; +-import com.bamboo.log.domain.user.jwt.service.JWTUtil; +-import com.bamboo.log.domain.user.logout.CustomLogoutFilter; +-import com.bamboo.log.domain.user.oauth.repository.RefreshRepository; +-import com.bamboo.log.domain.user.oauth.service.CustomFailureHandler; +-import com.bamboo.log.domain.user.oauth.service.CustomOAuth2UserService; +-import com.bamboo.log.domain.user.oauth.service.CustomSuccessHandler; +-import jakarta.servlet.http.HttpServletRequest; +-import lombok.RequiredArgsConstructor; +-import org.springframework.context.annotation.Bean; +-import org.springframework.context.annotation.Configuration; +-import org.springframework.security.config.annotation.web.builders.HttpSecurity; +-import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +-import org.springframework.security.config.http.SessionCreationPolicy; +-import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter; +-import org.springframework.security.web.SecurityFilterChain; +-import org.springframework.security.web.authentication.logout.LogoutFilter; +-import org.springframework.web.cors.CorsConfiguration; +-import org.springframework.web.cors.CorsConfigurationSource; +- +-import java.util.Collections; +- +-@Configuration +-@EnableWebSecurity +-@RequiredArgsConstructor +-public class SecurityConfig { +- +- private final RefreshRepository refreshRepository; +- private final CustomOAuth2UserService customOAuth2UserService; +- private final CustomSuccessHandler customSuccessHandler; +- private final CustomFailureHandler customFailureHandler; +- private final JWTUtil jwtUtil; +- +- +- @Bean +- public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { +- http.csrf((auth) -> auth.disable()); +- http.formLogin((auth) -> auth.disable()); +- http.httpBasic((auth) -> auth.disable()); +- http.oauth2Login((oauth2) -> oauth2 +- .userInfoEndpoint((userInfoEndpointConfig) -> userInfoEndpointConfig +- .userService(customOAuth2UserService)) +- .successHandler(customSuccessHandler) +- .failureHandler(customFailureHandler) +- ); +- http.addFilterAfter(new JWTFilter(jwtUtil), OAuth2LoginAuthenticationFilter.class); +- +- //์ด์ชฝ์—๋‹ค๊ฐ€ ๊ฐ๊ฐ์˜ ์—”๋“œํฌ์ธํŠธ๋ฅผ ๋„ฃ์–ด์•ผํ•จ +- http.authorizeHttpRequests((auth) -> auth +- .requestMatchers("/refresh").permitAll() +- .requestMatchers("/logout").hasAnyRole("USER") +- .requestMatchers("/swagger-ui/**","/v3/api-docs/**","/swagger-resources/**","/webjars/**").permitAll() +- .anyRequest().authenticated()); +- +- +- http.sessionManagement((session) -> session +- .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); +- http.addFilterBefore(new CustomLogoutFilter(jwtUtil, refreshRepository), LogoutFilter.class); +- http.cors(corsCustomizer -> corsCustomizer.configurationSource(new CorsConfigurationSource() { +- @Override +- public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { +- CorsConfiguration configuration = new CorsConfiguration(); +- //ํ”„๋ก ํŠธ url ๋„ฃ๊ธฐ +- configuration.setAllowedOrigins(Collections.singletonList("http://localhost:3000")); +- configuration.setAllowedMethods(Collections.singletonList("*")); +- configuration.setAllowCredentials(true); +- configuration.setAllowedHeaders(Collections.singletonList("*")); +- configuration.setMaxAge(3600L); +- configuration.setExposedHeaders(Collections.singletonList("Set-Cookie")); +- configuration.setExposedHeaders(Collections.singletonList("Authorization")); +- return configuration; +- } +- })); +- return http.build(); +- } +-} +diff --git a/src/main/java/com/bamboo/log/common/config/SwaggerConfig.java b/src/main/java/com/bamboo/log/common/config/SwaggerConfig.java +deleted file mode 100644 +index dd0a87a..0000000 +--- a/src/main/java/com/bamboo/log/common/config/SwaggerConfig.java ++++ /dev/null +@@ -1,12 +0,0 @@ +-package com.bamboo.log.common.config; +- +-import io.swagger.v3.oas.annotations.OpenAPIDefinition; +-import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; +-import io.swagger.v3.oas.annotations.info.Info; +-import io.swagger.v3.oas.annotations.security.SecurityScheme; +-import org.springframework.context.annotation.Configuration; +- +-@Configuration +-@OpenAPIDefinition(info = @Info(title = "ForRest", version = "v1")) +-public class SwaggerConfig { +-} +\ No newline at end of file +diff --git a/src/main/java/com/bamboo/log/diary/api/DiaryController.java b/src/main/java/com/bamboo/log/diary/api/DiaryController.java +new file mode 100644 +index 0000000..0b4e1c5 +--- /dev/null ++++ b/src/main/java/com/bamboo/log/diary/api/DiaryController.java +@@ -0,0 +1,33 @@ ++package com.bamboo.log.diary.api; ++ ++import com.bamboo.log.diary.dto.request.CreateDiaryRequest; ++import com.bamboo.log.diary.service.diary.DiaryService; ++import com.bamboo.log.utils.ResponseHandler; ++import com.bamboo.log.utils.dto.ResponseForm; ++import io.swagger.v3.oas.annotations.Operation; ++import lombok.RequiredArgsConstructor; ++import org.springframework.http.ResponseEntity; ++import org.springframework.web.bind.annotation.*; ++ ++import io.swagger.v3.oas.annotations.tags.Tag; ++ ++import java.util.List; ++ ++@RestController ++@RequiredArgsConstructor ++@RequestMapping("/diary") ++@Tag(name = "Diary API", description = "์ผ๊ธฐ ๊ด€๋ จ API") ++public class DiaryController { ++ ++ private final DiaryService diaryService; ++ ++ @Operation(summary = "๋žœ๋ค ์ฃผ์ œ ์กฐํšŒ") ++ @PostMapping("/create") ++ public ResponseEntity lookupRandomTopics(@RequestBody CreateDiaryRequest createDiaryRequest) { ++ try { ++ return diaryService.createDiary(createDiaryRequest); ++ } catch (Exception e) { ++ return ResponseHandler.create500Error(new ResponseForm(), e); ++ } ++ } ++} +diff --git a/src/main/java/com/bamboo/log/diary/domain/Diary.java b/src/main/java/com/bamboo/log/diary/domain/Diary.java +new file mode 100644 +index 0000000..c820a2c +--- /dev/null ++++ b/src/main/java/com/bamboo/log/diary/domain/Diary.java +@@ -0,0 +1,33 @@ ++package com.bamboo.log.diary.domain; ++ ++import jakarta.persistence.*; ++import lombok.AllArgsConstructor; ++import lombok.Builder; ++import lombok.Getter; ++import lombok.NoArgsConstructor; ++ ++import java.time.LocalDateTime; ++ ++@Entity ++@Getter ++@Builder ++@AllArgsConstructor ++@NoArgsConstructor ++@Table(name = "diaries") ++public class Diary { ++ ++ @Id ++ @GeneratedValue(strategy = GenerationType.AUTO) ++ @Column(name = "diary_id") ++ private Long id; ++ ++ @Column(nullable = false) ++ private String userId; ++ ++ @Column(columnDefinition = "TEXT", nullable = false) ++ private String context; ++ ++ @Column(nullable = false) ++ private LocalDateTime createdAt; ++ ++} +diff --git a/src/main/java/com/bamboo/log/diary/domain/TodaySummary.java b/src/main/java/com/bamboo/log/diary/domain/TodaySummary.java +new file mode 100644 +index 0000000..24af228 +--- /dev/null ++++ b/src/main/java/com/bamboo/log/diary/domain/TodaySummary.java +@@ -0,0 +1,29 @@ ++package com.bamboo.log.diary.domain; ++ ++import jakarta.persistence.*; ++import lombok.AllArgsConstructor; ++import lombok.Getter; ++import lombok.Builder; ++import lombok.NoArgsConstructor; ++ ++@Entity ++@Getter ++@Builder ++@NoArgsConstructor ++@AllArgsConstructor ++@Table(name = "today_summaries") ++public class TodaySummary { ++ ++ @Id ++ @GeneratedValue(strategy = GenerationType.AUTO) ++ private Long id; ++ ++ @Column(nullable = false) ++ private Long diaryId; ++ ++ @Lob ++ @Column(columnDefinition = "LONGBLOB", nullable = false) ++ private byte[] imageData; ++ ++} ++ +diff --git a/src/main/java/com/bamboo/log/diary/dto/request/CreateDiaryRequest.java b/src/main/java/com/bamboo/log/diary/dto/request/CreateDiaryRequest.java +new file mode 100644 +index 0000000..084708d +--- /dev/null ++++ b/src/main/java/com/bamboo/log/diary/dto/request/CreateDiaryRequest.java +@@ -0,0 +1,9 @@ ++package com.bamboo.log.diary.dto.request; ++ ++import jakarta.validation.constraints.NotEmpty; ++ ++import java.util.List; ++ ++public record CreateDiaryRequest(@NotEmpty(message = "User's Id shouldn't be empty") String userId, ++ @NotEmpty(message = "Diary Description shouldn't be empty") String diaryDetail) { ++} +diff --git a/src/main/java/com/bamboo/log/diary/dto/response/CreateDiaryResponse.java b/src/main/java/com/bamboo/log/diary/dto/response/CreateDiaryResponse.java +new file mode 100644 +index 0000000..9e994c0 +--- /dev/null ++++ b/src/main/java/com/bamboo/log/diary/dto/response/CreateDiaryResponse.java +@@ -0,0 +1,8 @@ ++package com.bamboo.log.diary.dto.response; ++ ++import jakarta.validation.constraints.NotEmpty; ++ ++import java.time.LocalDateTime; ++ ++public record CreateDiaryResponse(@NotEmpty(message = "created shouldn't be empty") LocalDateTime created) { ++} +diff --git a/src/main/java/com/bamboo/log/diary/repository/DiaryRepository.java b/src/main/java/com/bamboo/log/diary/repository/DiaryRepository.java +new file mode 100644 +index 0000000..416db42 +--- /dev/null ++++ b/src/main/java/com/bamboo/log/diary/repository/DiaryRepository.java +@@ -0,0 +1,7 @@ ++package com.bamboo.log.diary.repository; ++ ++import com.bamboo.log.diary.domain.Diary; ++import org.springframework.data.jpa.repository.JpaRepository; ++ ++public interface DiaryRepository extends JpaRepository { ++} +diff --git a/src/main/java/com/bamboo/log/diary/repository/TodaySummaryRepository.java b/src/main/java/com/bamboo/log/diary/repository/TodaySummaryRepository.java +new file mode 100644 +index 0000000..4093d83 +--- /dev/null ++++ b/src/main/java/com/bamboo/log/diary/repository/TodaySummaryRepository.java +@@ -0,0 +1,7 @@ ++package com.bamboo.log.diary.repository; ++ ++import com.bamboo.log.diary.domain.TodaySummary; ++import org.springframework.data.jpa.repository.JpaRepository; ++ ++public interface TodaySummaryRepository extends JpaRepository { ++} +diff --git a/src/main/java/com/bamboo/log/diary/service/diary/DiaryService.java b/src/main/java/com/bamboo/log/diary/service/diary/DiaryService.java +new file mode 100644 +index 0000000..50cb4ac +--- /dev/null ++++ b/src/main/java/com/bamboo/log/diary/service/diary/DiaryService.java +@@ -0,0 +1,10 @@ ++package com.bamboo.log.diary.service.diary; ++ ++import com.bamboo.log.diary.dto.request.CreateDiaryRequest; ++import org.springframework.http.ResponseEntity; ++ ++public interface DiaryService { ++ ++ ResponseEntity createDiary(CreateDiaryRequest createDiaryRequest); ++ ++} +diff --git a/src/main/java/com/bamboo/log/diary/service/diary/DiaryServiceImpl.java b/src/main/java/com/bamboo/log/diary/service/diary/DiaryServiceImpl.java +new file mode 100644 +index 0000000..5c201fd +--- /dev/null ++++ b/src/main/java/com/bamboo/log/diary/service/diary/DiaryServiceImpl.java +@@ -0,0 +1,70 @@ ++package com.bamboo.log.diary.service.diary; ++ ++import com.bamboo.log.diary.domain.Diary; ++import com.bamboo.log.diary.dto.request.CreateDiaryRequest; ++import com.bamboo.log.diary.dto.response.CreateDiaryResponse; ++import com.bamboo.log.diary.repository.DiaryRepository; ++import com.bamboo.log.diary.repository.TodaySummaryRepository; ++import com.bamboo.log.diary.service.mock.UserService; ++import com.bamboo.log.diary.service.summary.TodaySummaryService; ++import com.bamboo.log.utils.ResponseHandler; ++import com.bamboo.log.utils.dto.ResponseForm; ++import jakarta.persistence.EntityNotFoundException; ++import lombok.RequiredArgsConstructor; ++import org.springframework.http.ResponseEntity; ++import org.springframework.stereotype.Service; ++import org.springframework.transaction.annotation.Transactional; ++ ++import java.io.IOException; ++import java.time.LocalDateTime; ++ ++@Service ++@Transactional ++@RequiredArgsConstructor ++public class DiaryServiceImpl implements DiaryService { ++ ++ private final UserService userService; ++ private final DiaryRepository diaryRepository; ++ private final TodaySummaryService todaySummaryService; ++ ++ @Override ++ public ResponseEntity createDiary(CreateDiaryRequest createDiaryRequest) { ++ ++ // User ๊ฐ์ฒด ๊ฒ€์ฆ ๋กœ์ง ++ // ํ•ด๋‹น User ๊ฐ์ฒด ์ฐพ์„ ์ˆ˜ ์—†์„ ๊ฒฝ์šฐ, ์—๋Ÿฌ ๋ฐ˜ํ™˜ ++ if(userService.existsById(createDiaryRequest.userId())) { ++ return ResponseHandler.create404Error(new ResponseForm(), new EntityNotFoundException("์ž˜๋ชป๋œ ์œ ์ € ์ •๋ณด์ž…๋‹ˆ๋‹ค.")); ++ } ++ ++ // ํ•ด๋‹น ์‹œ์ ์˜ localDateTime ์ €์žฅ ++ LocalDateTime localDateTime = LocalDateTime.now(); ++ ++ // alice api ์—ฐ๊ฒฐ ํ•จ์ˆ˜ ํ˜ธ์ถœ ++ ++ // ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ ++ Diary diary = saveDiary(createDiaryRequest, localDateTime); ++ ++ byte[] todayImage = todaySummaryService.createTodaySummaryImage(createDiaryRequest.diaryDetail()); ++ ++ // todaySummaryImage ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ ++ try { ++ todaySummaryService.saveTodaySummaryImage(todayImage, diary.getId()); ++ } catch (IOException e) { ++ return ResponseHandler.create500Error(new ResponseForm(), e); ++ } ++ ++ // 201 code ๋ฐ˜ํ™˜ ++ return ResponseHandler.create201Response(new ResponseForm(), new CreateDiaryResponse(localDateTime)); ++ } ++ ++ private Diary saveDiary(CreateDiaryRequest createDiaryRequest, LocalDateTime localDateTime) { ++ Diary diary = Diary.builder() ++ .userId(createDiaryRequest.userId()) ++ .context(createDiaryRequest.diaryDetail()) ++ .createdAt(localDateTime) ++ .build(); ++ ++ return diaryRepository.save(diary); ++ } ++ ++} +diff --git a/src/main/java/com/bamboo/log/diary/service/mock/MockUserService.java b/src/main/java/com/bamboo/log/diary/service/mock/MockUserService.java +new file mode 100644 +index 0000000..86824f2 +--- /dev/null ++++ b/src/main/java/com/bamboo/log/diary/service/mock/MockUserService.java +@@ -0,0 +1,12 @@ ++package com.bamboo.log.diary.service.mock; ++ ++import org.springframework.stereotype.Service; ++ ++@Service ++public class MockUserService implements UserService { ++ ++ @Override ++ public boolean existsById(String userId) { ++ return true; ++ } ++} +diff --git a/src/main/java/com/bamboo/log/diary/service/mock/UserService.java b/src/main/java/com/bamboo/log/diary/service/mock/UserService.java +new file mode 100644 +index 0000000..0b37f9a +--- /dev/null ++++ b/src/main/java/com/bamboo/log/diary/service/mock/UserService.java +@@ -0,0 +1,5 @@ ++package com.bamboo.log.diary.service.mock; ++ ++public interface UserService { ++ boolean existsById(String userId); ++} +diff --git a/src/main/java/com/bamboo/log/diary/service/summary/TodaySummaryService.java b/src/main/java/com/bamboo/log/diary/service/summary/TodaySummaryService.java +new file mode 100644 +index 0000000..598d786 +--- /dev/null ++++ b/src/main/java/com/bamboo/log/diary/service/summary/TodaySummaryService.java +@@ -0,0 +1,11 @@ ++package com.bamboo.log.diary.service.summary; ++ ++import org.springframework.web.multipart.MultipartFile; ++ ++import java.io.IOException; ++ ++public interface TodaySummaryService { ++ ++ void saveTodaySummaryImage(byte[] image, Long diaryId) throws IOException; ++ byte[] createTodaySummaryImage(String prompt); ++} +diff --git a/src/main/java/com/bamboo/log/diary/service/summary/TodaySummaryServiceImpl.java b/src/main/java/com/bamboo/log/diary/service/summary/TodaySummaryServiceImpl.java +new file mode 100644 +index 0000000..06df2dd +--- /dev/null ++++ b/src/main/java/com/bamboo/log/diary/service/summary/TodaySummaryServiceImpl.java +@@ -0,0 +1,46 @@ ++package com.bamboo.log.diary.service.summary; ++ ++import com.bamboo.log.diary.domain.TodaySummary; ++import com.bamboo.log.diary.repository.TodaySummaryRepository; ++import com.bamboo.log.elice.service.ImageGenerationService; ++import com.bamboo.log.utils.ResponseHandler; ++import com.bamboo.log.utils.dto.ResponseForm; ++import lombok.RequiredArgsConstructor; ++import org.springframework.stereotype.Service; ++import org.springframework.transaction.annotation.Transactional; ++import org.springframework.web.multipart.MultipartFile; ++ ++import java.io.IOException; ++import java.util.Optional; ++ ++@Service ++@Transactional ++@RequiredArgsConstructor ++public class TodaySummaryServiceImpl implements TodaySummaryService { ++ ++ private final TodaySummaryRepository todaySummaryRepository; ++ private final ImageGenerationService imageGenerationService; ++ ++ @Override ++ public void saveTodaySummaryImage(byte[] image, Long diaryId) throws IOException { ++ TodaySummary todaySummary = TodaySummary.builder() ++ .diaryId(diaryId) ++ .imageData(image) ++ .build(); ++ ++ todaySummaryRepository.save(todaySummary); ++ } ++ ++ @Override ++ public byte[] createTodaySummaryImage(String prompt) { ++ try { ++ byte[] imageBytes = imageGenerationService.generateImage(prompt); ++ ++ return imageBytes; ++ } catch (IOException e) { ++ throw new RuntimeException("์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค‘์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."); ++ } ++ ++ } ++ ++} +diff --git a/src/main/java/com/bamboo/log/domain/user/controller/RefreshController.java b/src/main/java/com/bamboo/log/domain/user/controller/RefreshController.java +deleted file mode 100644 +index 3d38673..0000000 +--- a/src/main/java/com/bamboo/log/domain/user/controller/RefreshController.java ++++ /dev/null +@@ -1,33 +0,0 @@ +-package com.bamboo.log.domain.user.controller; +- +-import com.bamboo.log.domain.user.refresh.service.ProcessTokenReissue; +-import io.swagger.v3.oas.annotations.Operation; +-import io.swagger.v3.oas.annotations.responses.ApiResponse; +-import io.swagger.v3.oas.annotations.responses.ApiResponses; +-import jakarta.servlet.http.HttpServletRequest; +-import jakarta.servlet.http.HttpServletResponse; +-import lombok.RequiredArgsConstructor; +-import org.springframework.http.ResponseEntity; +-import org.springframework.web.bind.annotation.PostMapping; +-import org.springframework.web.bind.annotation.RestController; +- +-@RestController +-@RequiredArgsConstructor +-public class RefreshController { +- +- private final ProcessTokenReissue processTokenReissue; +- +- @Operation(summary = "ํ† ํฐ ๊ฐฑ์‹ ", description = "Refresh Token์„ ์‚ฌ์šฉํ•˜์—ฌ Access Token์„ ์žฌ๋ฐœ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค.") +- @ApiResponses({ +- @ApiResponse(responseCode = "200", description = "ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ์„ฑ๊ณต"), +- @ApiResponse(responseCode = "401", description = "์œ ํšจํ•˜์ง€ ์•Š์€ Refresh Token"), +- @ApiResponse(responseCode = "403", description = "Refresh Token์ด ๋งŒ๋ฃŒ๋จ") +- }) +- @PostMapping("/refresh") +- public ResponseEntity reissue( +- HttpServletRequest request, +- HttpServletResponse response) { +- return processTokenReissue.reissue(request, response); +- } +-} +- +diff --git a/src/main/java/com/bamboo/log/domain/user/jwt/service/JWTFilter.java b/src/main/java/com/bamboo/log/domain/user/jwt/service/JWTFilter.java +deleted file mode 100644 +index 36c936f..0000000 +--- a/src/main/java/com/bamboo/log/domain/user/jwt/service/JWTFilter.java ++++ /dev/null +@@ -1,76 +0,0 @@ +-package com.bamboo.log.domain.user.jwt.service; +- +-import com.bamboo.log.domain.user.oauth.dto.CustomOAuth2User; +-import com.bamboo.log.domain.user.oauth.dto.UserDTO; +-import io.jsonwebtoken.ExpiredJwtException; +-import jakarta.servlet.FilterChain; +-import jakarta.servlet.ServletException; +-import jakarta.servlet.http.Cookie; +-import jakarta.servlet.http.HttpServletRequest; +-import jakarta.servlet.http.HttpServletResponse; +-import lombok.RequiredArgsConstructor; +-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +-import org.springframework.security.core.Authentication; +-import org.springframework.security.core.context.SecurityContextHolder; +-import org.springframework.web.filter.OncePerRequestFilter; +- +-import java.io.IOException; +-import java.io.PrintWriter; +- +-@RequiredArgsConstructor +-public class JWTFilter extends OncePerRequestFilter { +- +- private final JWTUtil jwtUtil; +- +- +- @Override +- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { +- // ํ—ค๋”์—์„œ Authorization์— ๋‹ด๊ธด ํ† ํฐ์„ ๊บผ๋ƒ„ +- String accessToken = request.getHeader("Authorization"); +- +- if (accessToken != null && accessToken.startsWith("Bearer ")) { +- accessToken = accessToken.substring(7); // "Bearer "์˜ ๊ธธ์ด๋Š” 7 +- } +- +- // ํ† ํฐ์ด ์—†๋‹ค๋ฉด ๋‹ค์Œ ํ•„ํ„ฐ๋กœ ๋„˜๊น€ +- if (accessToken == null|| accessToken.isEmpty()) { +- filterChain.doFilter(request, response); +- return; +- } +- +- // ํ† ํฐ ๋งŒ๋ฃŒ ์—ฌ๋ถ€ ํ™•์ธ, ๋งŒ๋ฃŒ์‹œ ๋‹ค์Œ ํ•„ํ„ฐ๋กœ ๋„˜๊ธฐ์ง€ ์•Š์Œ +- try { +- jwtUtil.isExpired(accessToken); +- } catch (ExpiredJwtException e) { +- // ๋งŒ๋ฃŒ๋œ ํ† ํฐ์ผ ๊ฒฝ์šฐ ์‘๋‹ต ๋ณธ๋ฌธ์— ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ ๋ฐ ์ƒํƒœ ์ฝ”๋“œ ์„ค์ • +- PrintWriter writer = response.getWriter(); +- writer.print("access token expired"); +- response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); +- return; +- } +- +- // ํ† ํฐ์ด access์ธ์ง€ ํ™•์ธ (๋ฐœ๊ธ‰์‹œ ํŽ˜์ด๋กœ๋“œ์— ๋ช…์‹œ) +- String category = jwtUtil.getCategory(accessToken); +- if (!category.equals("access")) { +- // ์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ์ผ ๊ฒฝ์šฐ ์‘๋‹ต ๋ณธ๋ฌธ์— ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ ๋ฐ ์ƒํƒœ ์ฝ”๋“œ ์„ค์ • +- PrintWriter writer = response.getWriter(); +- writer.print("invalid access token"); +- response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); +- return; +- } +- +- +- String username = jwtUtil.getUsername(accessToken); +- String role = jwtUtil.getRole(accessToken); +- String name=jwtUtil.getName(accessToken); +- UserDTO userDTO = UserDTO.builder() +- .name(name) +- .username(username) +- .role(role) +- .build(); +- CustomOAuth2User customOAuth2User = new CustomOAuth2User(userDTO); +- Authentication authToken = new UsernamePasswordAuthenticationToken(customOAuth2User, null, customOAuth2User.getAuthorities()); +- SecurityContextHolder.getContext().setAuthentication(authToken); +- filterChain.doFilter(request, response); +- } +-} +diff --git a/src/main/java/com/bamboo/log/domain/user/jwt/service/JWTUtil.java b/src/main/java/com/bamboo/log/domain/user/jwt/service/JWTUtil.java +deleted file mode 100644 +index d7f3a78..0000000 +--- a/src/main/java/com/bamboo/log/domain/user/jwt/service/JWTUtil.java ++++ /dev/null +@@ -1,57 +0,0 @@ +-package com.bamboo.log.domain.user.jwt.service; +- +-import io.jsonwebtoken.JwtException; +-import io.jsonwebtoken.Jwts; +-import org.springframework.beans.factory.annotation.Value; +-import org.springframework.stereotype.Component; +- +-import javax.crypto.SecretKey; +-import javax.crypto.spec.SecretKeySpec; +-import java.nio.charset.StandardCharsets; +-import java.util.Date; +- +-@Component +-public class JWTUtil { +- +- private SecretKey secretKey; +- +- public JWTUtil(@Value("${spring.jwt.secret}")String secret) { +- secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm()); +- } +- +- public String getUsername(String token) { +- return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("username", String.class); +- } +- +- public String getRole(String token) { +- return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("role", String.class); +- } +- +- public Boolean isExpired(String token) { +- return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().getExpiration().before(new Date()); +- } +- +- public String getCategory(String token) { +- try { +- // ํ† ํฐ์—์„œ ์นดํ…Œ๊ณ ๋ฆฌ ์ •๋ณด๋ฅผ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค. +- return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("category", String.class); +- } catch (JwtException e) { +- throw new RuntimeException("ํ† ํฐ์—์„œ ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์ถ”์ถœํ•˜๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."); +- } +- } +- +- public String getName(String token){ +- return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("name", String.class); +- } +- public String createJwt(String category,String name,String username, String role, Long expiredMs) { +- return Jwts.builder() +- .claim("category",category) +- .claim("username", username) +- .claim("name",name) +- .claim("role", role) +- .issuedAt(new Date(System.currentTimeMillis())) +- .expiration(new Date(System.currentTimeMillis() + expiredMs)) +- .signWith(secretKey) +- .compact(); +- } +-} +diff --git a/src/main/java/com/bamboo/log/domain/user/jwt/service/UserContextUtil.java b/src/main/java/com/bamboo/log/domain/user/jwt/service/UserContextUtil.java +deleted file mode 100644 +index 6f196f7..0000000 +--- a/src/main/java/com/bamboo/log/domain/user/jwt/service/UserContextUtil.java ++++ /dev/null +@@ -1,33 +0,0 @@ +-package com.bamboo.log.domain.user.jwt.service; +- +-import com.bamboo.log.domain.user.oauth.dto.CustomOAuth2User; +-import org.springframework.security.core.Authentication; +-import org.springframework.security.core.context.SecurityContextHolder; +-import org.springframework.stereotype.Component; +- +-import java.util.Optional; +- +-@Component +-public class UserContextUtil { +- +- public Optional getCurrentUser() { +- Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); +- if (authentication == null || !(authentication.getPrincipal() instanceof CustomOAuth2User)) { +- return Optional.empty(); +- } +- return Optional.of((CustomOAuth2User) authentication.getPrincipal()); +- } +- +- public String getUsername() { +- return getCurrentUser() +- .map(CustomOAuth2User::getUsername) +- .orElseThrow(() -> new RuntimeException("User not authenticated")); +- } +- +- public String getName() { +- return getCurrentUser() +- .map(CustomOAuth2User::getName) +- .orElseThrow(() -> new RuntimeException("User not authenticated")); +- } +- +-} +diff --git a/src/main/java/com/bamboo/log/domain/user/logout/CustomLogoutFilter.java b/src/main/java/com/bamboo/log/domain/user/logout/CustomLogoutFilter.java +deleted file mode 100644 +index 3ff12c5..0000000 +--- a/src/main/java/com/bamboo/log/domain/user/logout/CustomLogoutFilter.java ++++ /dev/null +@@ -1,94 +0,0 @@ +-package com.bamboo.log.domain.user.logout; +- +-import com.bamboo.log.domain.user.jwt.service.JWTUtil; +-import com.bamboo.log.domain.user.oauth.repository.RefreshRepository; +-import io.jsonwebtoken.ExpiredJwtException; +-import jakarta.servlet.FilterChain; +-import jakarta.servlet.ServletException; +-import jakarta.servlet.ServletRequest; +-import jakarta.servlet.ServletResponse; +-import jakarta.servlet.http.Cookie; +-import jakarta.servlet.http.HttpServletRequest; +-import jakarta.servlet.http.HttpServletResponse; +-import lombok.RequiredArgsConstructor; +-import lombok.extern.slf4j.Slf4j; +-import org.springframework.transaction.annotation.Transactional; +-import org.springframework.web.filter.GenericFilterBean; +- +-import java.io.IOException; +- +-@RequiredArgsConstructor +-@Transactional +-@Slf4j +-public class CustomLogoutFilter extends GenericFilterBean { +- +- private final JWTUtil jwtUtil; +- private final RefreshRepository refreshRepository; +- +- @Override +- public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { +- doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain); +- } +- +- private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException { +- +- String requestUri = request.getRequestURI(); +- if (!requestUri.matches("^\\/logout$")) { +- filterChain.doFilter(request, response); +- return; +- } +- +- String requestMethod = request.getMethod(); +- if (!requestMethod.equals("POST")) { +- filterChain.doFilter(request, response); +- return; +- } +- +- String refresh = null; +- Cookie[] cookies = request.getCookies(); +- for (Cookie cookie : cookies) { +- if (cookie.getName().equals("refresh")) { +- refresh = cookie.getValue(); +- } +- } +- +- if (refresh == null) { +- log.error("๋กœ๊ทธ์•„์›ƒ ์‹œ ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์ด ์—†์Šต๋‹ˆ๋‹ค."); +- response.setStatus(HttpServletResponse.SC_BAD_REQUEST); +- return; +- } +- +- try { +- jwtUtil.isExpired(refresh); +- } catch (ExpiredJwtException e) { +- log.error("๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. refresh token: {}", refresh); +- response.setStatus(HttpServletResponse.SC_BAD_REQUEST); +- return; +- } +- +- String category = jwtUtil.getCategory(refresh); +- if (!category.equals("refresh")) { +- log.error("์œ ํšจํ•˜์ง€ ์•Š์€ ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์ž…๋‹ˆ๋‹ค. refresh token: {}", refresh); +- response.setStatus(HttpServletResponse.SC_BAD_REQUEST); +- return; +- } +- +- Boolean isExist = refreshRepository.existsByToken(refresh); +- if (!isExist) { +- log.error("๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. refresh token: {}", refresh); +- response.setStatus(HttpServletResponse.SC_BAD_REQUEST); +- return; +- } +- +- refreshRepository.deleteByToken(refresh); +- log.info("๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. refresh token: {}", refresh); +- +- Cookie cookie = new Cookie("refresh", null); +- cookie.setMaxAge(0); +- cookie.setPath("/"); +- +- response.addCookie(cookie); +- log.info("๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ์ฟ ํ‚ค๊ฐ€ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); +- response.setStatus(HttpServletResponse.SC_OK); +- } +-} +diff --git a/src/main/java/com/bamboo/log/domain/user/oauth/dto/CustomOAuth2User.java b/src/main/java/com/bamboo/log/domain/user/oauth/dto/CustomOAuth2User.java +deleted file mode 100644 +index 707915d..0000000 +--- a/src/main/java/com/bamboo/log/domain/user/oauth/dto/CustomOAuth2User.java ++++ /dev/null +@@ -1,48 +0,0 @@ +-package com.bamboo.log.domain.user.oauth.dto; +- +-import org.springframework.security.core.GrantedAuthority; +-import org.springframework.security.oauth2.core.user.OAuth2User; +- +-import java.util.ArrayList; +-import java.util.Collection; +-import java.util.Map; +- +-public class CustomOAuth2User implements OAuth2User { +- +- private final UserDTO userDTO; +- +- public CustomOAuth2User(UserDTO userDTO) { +- this.userDTO = userDTO; +- } +- +- @Override +- public Map getAttributes() { +- return null; +- } +- +- @Override +- public Collection getAuthorities() { +- +- Collection collection = new ArrayList<>(); +- +- collection.add(new GrantedAuthority() { +- +- @Override +- public String getAuthority() { +- +- return userDTO.getRole(); +- } +- }); +- +- return collection; +- } +- +- @Override +- public String getName() { +- return userDTO.getName(); +- } +- +- public String getUsername() { +- return userDTO.getUsername(); +- } +-} +diff --git a/src/main/java/com/bamboo/log/domain/user/oauth/dto/KakaoResponse.java b/src/main/java/com/bamboo/log/domain/user/oauth/dto/KakaoResponse.java +deleted file mode 100644 +index 2e16ed0..0000000 +--- a/src/main/java/com/bamboo/log/domain/user/oauth/dto/KakaoResponse.java ++++ /dev/null +@@ -1,40 +0,0 @@ +-package com.bamboo.log.domain.user.oauth.dto; +- +-import java.util.Map; +- +-public class KakaoResponse implements OAuth2Response { +- private final Map attributes; +- private final Map kakaoAccount; +- private final Map profile; +- +- public KakaoResponse(Map attributes) { +- this.attributes = attributes; +- this.kakaoAccount = (Map) attributes.get("kakao_account"); +- this.profile = (Map) kakaoAccount.get("profile"); +- } +- +- @Override +- public String getProvider() { +- return "kakao"; +- } +- +- @Override +- public String getProviderId() { +- return attributes.get("id").toString(); +- } +- +- @Override +- public String getEmail() { +- return kakaoAccount.get("email") != null ? kakaoAccount.get("email").toString() : "N/A"; +- } +- +- @Override +- public String getName() { +- return profile.get("nickname").toString(); +- } +- +- @Override +- public Map getAttributes() { +- return attributes; +- } +-} +diff --git a/src/main/java/com/bamboo/log/domain/user/oauth/dto/OAuth2Response.java b/src/main/java/com/bamboo/log/domain/user/oauth/dto/OAuth2Response.java +deleted file mode 100644 +index 28f415d..0000000 +--- a/src/main/java/com/bamboo/log/domain/user/oauth/dto/OAuth2Response.java ++++ /dev/null +@@ -1,11 +0,0 @@ +-package com.bamboo.log.domain.user.oauth.dto; +- +-import java.util.Map; +- +-public interface OAuth2Response { +- String getProvider(); +- String getProviderId(); +- String getEmail(); +- String getName(); +- Map getAttributes(); +-} +diff --git a/src/main/java/com/bamboo/log/domain/user/oauth/dto/UserDTO.java b/src/main/java/com/bamboo/log/domain/user/oauth/dto/UserDTO.java +deleted file mode 100644 +index f76f9a2..0000000 +--- a/src/main/java/com/bamboo/log/domain/user/oauth/dto/UserDTO.java ++++ /dev/null +@@ -1,14 +0,0 @@ +-package com.bamboo.log.domain.user.oauth.dto; +- +-import lombok.AllArgsConstructor; +-import lombok.Builder; +-import lombok.Getter; +- +-@AllArgsConstructor +-@Builder +-@Getter +-public class UserDTO { +- private String name; +- private String username; +- private String role; +-} +diff --git a/src/main/java/com/bamboo/log/domain/user/oauth/entity/RefreshToken.java b/src/main/java/com/bamboo/log/domain/user/oauth/entity/RefreshToken.java +deleted file mode 100644 +index e02ceb4..0000000 +--- a/src/main/java/com/bamboo/log/domain/user/oauth/entity/RefreshToken.java ++++ /dev/null +@@ -1,40 +0,0 @@ +-package com.bamboo.log.domain.user.oauth.entity; +- +-import jakarta.persistence.*; +-import lombok.AllArgsConstructor; +-import lombok.Builder; +-import lombok.Getter; +-import lombok.NoArgsConstructor; +- +-import java.util.Date; +-@Entity +-@Table(name = "refresh_token") +-@Getter +-@NoArgsConstructor +-@AllArgsConstructor +-@Builder +-public class RefreshToken { +- +- @Id +- @GeneratedValue(strategy = GenerationType.IDENTITY) +- private Long id; +- +- @Column(nullable = false) +- private String name; +- +- @Column(nullable = false) +- private String username; +- +- @Column(nullable = false) +- private String token; +- +- @Column(name = "expires_at", nullable = false) +- @Temporal(TemporalType.TIMESTAMP) +- private Date expiresAt; +- +- @Column(name = "created_at", nullable = false) +- @Temporal(TemporalType.TIMESTAMP) +- private Date createdAt; +- +-} +- +diff --git a/src/main/java/com/bamboo/log/domain/user/oauth/entity/UserEntity.java b/src/main/java/com/bamboo/log/domain/user/oauth/entity/UserEntity.java +deleted file mode 100644 +index fcf2836..0000000 +--- a/src/main/java/com/bamboo/log/domain/user/oauth/entity/UserEntity.java ++++ /dev/null +@@ -1,45 +0,0 @@ +-package com.bamboo.log.domain.user.oauth.entity; +- +-import jakarta.persistence.*; +-import lombok.Builder; +-import lombok.Getter; +-import lombok.NoArgsConstructor; +- +-@Getter +-@NoArgsConstructor +-@Entity +-@Table(name="users") +-public class UserEntity { +- +- @Id +- @GeneratedValue(strategy = GenerationType.IDENTITY) +- private Long id; +- +- @Column(unique = true, nullable = false) +- private String username; +- +- +- private String name; +- private String email; +- private String role; +- private String profile_img_url; +- +- @Builder +- public UserEntity(String username, String name, String email, String role,String profile_img_url) { +- this.username = username; +- this.name = name; +- this.email = email; +- this.role = role; +- this.profile_img_url = profile_img_url; +- } +- +- public UserEntity updateEmailAndName(String email, String name) { +- return UserEntity.builder() +- .username(this.username) +- .name(name) +- .email(email) +- .role(this.role) +- .profile_img_url(this.profile_img_url) +- .build(); +- } +-} +diff --git a/src/main/java/com/bamboo/log/domain/user/oauth/repository/RefreshRepository.java b/src/main/java/com/bamboo/log/domain/user/oauth/repository/RefreshRepository.java +deleted file mode 100644 +index 8bbe809..0000000 +--- a/src/main/java/com/bamboo/log/domain/user/oauth/repository/RefreshRepository.java ++++ /dev/null +@@ -1,10 +0,0 @@ +-package com.bamboo.log.domain.user.oauth.repository; +- +-import com.bamboo.log.domain.user.oauth.entity.RefreshToken; +-import org.springframework.data.jpa.repository.JpaRepository; +- +-public interface RefreshRepository extends JpaRepository { +- Boolean existsByToken(String token); +- void deleteByToken(String token); +- RefreshToken findByToken(String token); +-} +diff --git a/src/main/java/com/bamboo/log/domain/user/oauth/repository/UserRepository.java b/src/main/java/com/bamboo/log/domain/user/oauth/repository/UserRepository.java +deleted file mode 100644 +index 645a385..0000000 +--- a/src/main/java/com/bamboo/log/domain/user/oauth/repository/UserRepository.java ++++ /dev/null +@@ -1,8 +0,0 @@ +-package com.bamboo.log.domain.user.oauth.repository; +- +-import com.bamboo.log.domain.user.oauth.entity.UserEntity; +-import org.springframework.data.jpa.repository.JpaRepository; +- +-public interface UserRepository extends JpaRepository { +- UserEntity findByUsername(String username); +-} +\ No newline at end of file +diff --git a/src/main/java/com/bamboo/log/domain/user/oauth/service/CustomFailureHandler.java b/src/main/java/com/bamboo/log/domain/user/oauth/service/CustomFailureHandler.java +deleted file mode 100644 +index 1dac05d..0000000 +--- a/src/main/java/com/bamboo/log/domain/user/oauth/service/CustomFailureHandler.java ++++ /dev/null +@@ -1,20 +0,0 @@ +-package com.bamboo.log.domain.user.oauth.service; +- +-import jakarta.servlet.ServletException; +-import jakarta.servlet.http.HttpServletRequest; +-import jakarta.servlet.http.HttpServletResponse; +-import org.springframework.security.core.AuthenticationException; +-import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +-import org.springframework.stereotype.Component; +- +-import java.io.IOException; +- +-@Component +-public class CustomFailureHandler extends SimpleUrlAuthenticationFailureHandler { +- +- @Override +- public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { +- response.sendRedirect("http://localhost:3000"); +- } +-} +- +diff --git a/src/main/java/com/bamboo/log/domain/user/oauth/service/CustomOAuth2UserService.java b/src/main/java/com/bamboo/log/domain/user/oauth/service/CustomOAuth2UserService.java +deleted file mode 100644 +index 8de41c2..0000000 +--- a/src/main/java/com/bamboo/log/domain/user/oauth/service/CustomOAuth2UserService.java ++++ /dev/null +@@ -1,67 +0,0 @@ +-package com.bamboo.log.domain.user.oauth.service; +- +-import com.bamboo.log.domain.user.oauth.dto.CustomOAuth2User; +-import com.bamboo.log.domain.user.oauth.dto.KakaoResponse; +-import com.bamboo.log.domain.user.oauth.dto.OAuth2Response; +-import com.bamboo.log.domain.user.oauth.dto.UserDTO; +-import com.bamboo.log.domain.user.oauth.entity.UserEntity; +-import com.bamboo.log.domain.user.oauth.repository.UserRepository; +-import lombok.RequiredArgsConstructor; +-import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +-import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +-import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +-import org.springframework.security.oauth2.core.user.OAuth2User; +-import org.springframework.stereotype.Service; +- +- +-@Service +-@RequiredArgsConstructor +-public class CustomOAuth2UserService extends DefaultOAuth2UserService { +- +- private final UserRepository userRepository; +- +- @Override +- public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { +- OAuth2User oAuth2User = super.loadUser(userRequest); +- String registrationId = userRequest.getClientRegistration().getRegistrationId(); +- +- OAuth2Response oAuth2Response = null; +- if (registrationId.equals("kakao")) { +- oAuth2Response = new KakaoResponse(oAuth2User.getAttributes()); +- } else { +- return null; +- } +- +- System.out.println("OAuth2Response: " + oAuth2Response); +- String username = oAuth2Response.getProvider() + " " + oAuth2Response.getProviderId(); +- UserEntity existData = userRepository.findByUsername(username); +- System.out.println("username = " + username); +- System.out.println("Existing User: " + existData); +- +- if (existData == null) { +- UserEntity userEntity = UserEntity.builder() +- .username(username) +- .name(oAuth2Response.getName()) +- .email(oAuth2Response.getEmail()) +- .role("ROLE_USER") +- .build(); +- +- userRepository.save(userEntity); +- +- UserDTO userDTO = UserDTO.builder() +- .username(username) +- .name(oAuth2Response.getName()) +- .role("ROLE_USER") +- .build(); +- +- return new CustomOAuth2User(userDTO); +- } else { +- UserDTO userDTO = UserDTO.builder() +- .username(existData.getUsername()) +- .name(existData.getName()) +- .role(existData.getRole()) +- .build(); +- return new CustomOAuth2User(userDTO); +- } +- } +-} +diff --git a/src/main/java/com/bamboo/log/domain/user/oauth/service/CustomSuccessHandler.java b/src/main/java/com/bamboo/log/domain/user/oauth/service/CustomSuccessHandler.java +deleted file mode 100644 +index 069d271..0000000 +--- a/src/main/java/com/bamboo/log/domain/user/oauth/service/CustomSuccessHandler.java ++++ /dev/null +@@ -1,78 +0,0 @@ +-package com.bamboo.log.domain.user.oauth.service; +- +-import com.bamboo.log.domain.user.jwt.service.JWTUtil; +-import com.bamboo.log.domain.user.oauth.dto.CustomOAuth2User; +-import com.bamboo.log.domain.user.oauth.entity.RefreshToken; +-import com.bamboo.log.domain.user.oauth.repository.RefreshRepository; +-import jakarta.servlet.ServletException; +-import jakarta.servlet.http.Cookie; +-import jakarta.servlet.http.HttpServletRequest; +-import jakarta.servlet.http.HttpServletResponse; +-import lombok.RequiredArgsConstructor; +-import org.springframework.security.core.Authentication; +-import org.springframework.security.core.GrantedAuthority; +-import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +-import org.springframework.stereotype.Component; +- +-import java.io.IOException; +-import java.util.Collection; +-import java.util.Date; +-import java.util.Iterator; +- +-@Component +-@RequiredArgsConstructor +-public class CustomSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { +- +- private final JWTUtil jwtUtil; +- private final RefreshRepository refreshRepository; +- +- +- @Override +- public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { +- CustomOAuth2User customUserDetails = (CustomOAuth2User) authentication.getPrincipal(); +- String name=customUserDetails.getName(); +- String username = customUserDetails.getUsername(); +- Collection authorities = authentication.getAuthorities(); +- Iterator iterator = authorities.iterator(); +- GrantedAuthority auth = iterator.next(); +- String role = auth.getAuthority(); +- +- String refreshToken = jwtUtil.createJwt("refresh",name,username, role, 1800000L); +- String accessToken = jwtUtil.createJwt("access",name,username, role, 1209600000L); +- addRefreshEntity(name, username, refreshToken, 1209600000L); +- response.addCookie(createCookie("refresh", refreshToken)); +- response.addCookie(UnScretCreateCookie("access", accessToken)); +- response.sendRedirect("http://localhost:3000/"); +- } +- +- private Cookie createCookie(String key, String value) { +- +- Cookie cookie = new Cookie(key, value); +- cookie.setMaxAge(24 * 60 * 60 * 14); +- cookie.setPath("/"); +- cookie.setHttpOnly(true); +- +- return cookie; +- } +- private Cookie UnScretCreateCookie(String key, String value) { +- +- Cookie cookie = new Cookie(key, value); +- cookie.setMaxAge(60*60); +- cookie.setPath("/"); +- return cookie; +- } +- private void addRefreshEntity(String name, String username, String refresh, Long expiredMs) { +- Date CrDate = new Date(System.currentTimeMillis()); +- Date ExDate = new Date(System.currentTimeMillis() + expiredMs); +- refreshRepository.save( +- RefreshToken.builder() +- .name(name) +- .username(username) +- .token(refresh) +- .createdAt(CrDate) +- .expiresAt(ExDate) +- .build() +- ); +- } +-} +- +diff --git a/src/main/java/com/bamboo/log/domain/user/refresh/service/ProcessTokenReissue.java b/src/main/java/com/bamboo/log/domain/user/refresh/service/ProcessTokenReissue.java +deleted file mode 100644 +index 73dcdd3..0000000 +--- a/src/main/java/com/bamboo/log/domain/user/refresh/service/ProcessTokenReissue.java ++++ /dev/null +@@ -1,9 +0,0 @@ +-package com.bamboo.log.domain.user.refresh.service; +- +-import jakarta.servlet.http.HttpServletRequest; +-import jakarta.servlet.http.HttpServletResponse; +-import org.springframework.http.ResponseEntity; +- +-public interface ProcessTokenReissue { +- public ResponseEntity reissue(HttpServletRequest request, HttpServletResponse response); +-} +diff --git a/src/main/java/com/bamboo/log/domain/user/refresh/service/ProcessTokenReissueImpl.java b/src/main/java/com/bamboo/log/domain/user/refresh/service/ProcessTokenReissueImpl.java +deleted file mode 100644 +index e808c3f..0000000 +--- a/src/main/java/com/bamboo/log/domain/user/refresh/service/ProcessTokenReissueImpl.java ++++ /dev/null +@@ -1,103 +0,0 @@ +-package com.bamboo.log.domain.user.refresh.service; +- +-import com.bamboo.log.domain.user.jwt.service.JWTUtil; +-import com.bamboo.log.domain.user.oauth.entity.RefreshToken; +-import com.bamboo.log.domain.user.oauth.repository.RefreshRepository; +-import io.jsonwebtoken.ExpiredJwtException; +-import jakarta.servlet.http.Cookie; +-import jakarta.servlet.http.HttpServletRequest; +-import jakarta.servlet.http.HttpServletResponse; +-import lombok.RequiredArgsConstructor; +-import lombok.extern.slf4j.Slf4j; +-import org.springframework.http.HttpStatus; +-import org.springframework.http.ResponseEntity; +-import org.springframework.stereotype.Service; +-import org.springframework.transaction.annotation.Transactional; +- +-import java.util.Date; +- +-@Service +-@RequiredArgsConstructor +-@Transactional +-@Slf4j +-public class ProcessTokenReissueImpl implements ProcessTokenReissue { +- +- private final JWTUtil jwtUtil; +- private final RefreshRepository refreshRepository; +- +- public ResponseEntity reissue(HttpServletRequest request, HttpServletResponse response) { +- +- String refresh = null; +- Cookie[] cookies = request.getCookies(); +- for (Cookie cookie : cookies) { +- if (cookie.getName().equals("refresh")) { +- refresh = cookie.getValue(); +- } +- } +- +- if (refresh == null) { +- log.error("๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์ด ์—†์Šต๋‹ˆ๋‹ค."); +- return new ResponseEntity<>("refresh token null", HttpStatus.BAD_REQUEST); +- } +- +- try { +- jwtUtil.isExpired(refresh); +- } catch (ExpiredJwtException e) { +- log.error("๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. token: {}", refresh); +- return new ResponseEntity<>("refresh token expired", HttpStatus.BAD_REQUEST); +- } +- +- String category = jwtUtil.getCategory(refresh); +- if (!category.equals("refresh")) { +- log.error("์œ ํšจํ•˜์ง€ ์•Š์€ ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์ž…๋‹ˆ๋‹ค. token: {}", refresh); +- return new ResponseEntity<>("invalid refresh token", HttpStatus.BAD_REQUEST); +- } +- +- if (!refreshRepository.existsByToken(refresh)) { +- log.error("๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. token: {}", refresh); +- return new ResponseEntity<>("invalid refresh token", HttpStatus.BAD_REQUEST); +- } +- +- String name = jwtUtil.getName(refresh); +- String username = jwtUtil.getUsername(refresh); +- String role = jwtUtil.getRole(refresh); +- +- String newAccess = jwtUtil.createJwt("access", name, username , role, 1800000L); +- String newRefresh = jwtUtil.createJwt("refresh", name, username, role, 1209600000L); +- +- refreshRepository.deleteByToken(refresh); +- addRefreshEntity(name,username,role,1209600000L); +- +- log.info("์ƒˆ๋กœ์šด ์•ก์„ธ์Šค ํ† ํฐ๊ณผ ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์„ ๋ฐœ๊ธ‰ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ: {}", newRefresh); +- +- response.setHeader("Authorization", newAccess); +- response.addCookie(createCookie("refresh", newRefresh)); +- +- log.info("๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ์™„๋ฃŒ"); +- +- return new ResponseEntity<>(HttpStatus.OK); +- } +- +- private Cookie createCookie(String key, String value) { +- log.info("์ฟ ํ‚ค ์ƒ์„ฑ ์‹œ์ž‘"); +- Cookie cookie = new Cookie(key, value); +- cookie.setMaxAge(24 * 60 * 60 * 14); // 14์ผ ์œ ํšจ +- cookie.setHttpOnly(true); // ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ์ ‘๊ทผ ๋ถˆ๊ฐ€ +- log.info("์ฟ ํ‚ค ์ƒ์„ฑ ๋"); +- return cookie; +- } +- private void addRefreshEntity(String name, String username, String refresh, Long expiredMs) { +- Date CrDate = new Date(System.currentTimeMillis()); +- Date ExDate = new Date(System.currentTimeMillis() + expiredMs); +- refreshRepository.save( +- RefreshToken.builder() +- .name(name) +- .username(username) +- .token(refresh) +- .createdAt(CrDate) +- .expiresAt(ExDate) +- .build() +- ); +- } +-} +- +diff --git a/src/main/java/com/bamboo/log/elice/config/RestTemplateConfig.java b/src/main/java/com/bamboo/log/elice/config/RestTemplateConfig.java +new file mode 100644 +index 0000000..0ea3f07 +--- /dev/null ++++ b/src/main/java/com/bamboo/log/elice/config/RestTemplateConfig.java +@@ -0,0 +1,14 @@ ++package com.bamboo.log.elice.config; ++ ++import org.springframework.context.annotation.Bean; ++import org.springframework.context.annotation.Configuration; ++import org.springframework.web.client.RestTemplate; ++ ++@Configuration ++public class RestTemplateConfig { ++ ++ @Bean ++ public RestTemplate restTemplate() { ++ return new RestTemplate(); ++ } ++} +diff --git a/src/main/java/com/bamboo/log/elice/service/ImageGenerationService.java b/src/main/java/com/bamboo/log/elice/service/ImageGenerationService.java +new file mode 100644 +index 0000000..d7aa11b +--- /dev/null ++++ b/src/main/java/com/bamboo/log/elice/service/ImageGenerationService.java +@@ -0,0 +1,58 @@ ++package com.bamboo.log.elice.service; ++ ++import org.springframework.beans.factory.annotation.Value; ++import org.springframework.http.*; ++import org.springframework.stereotype.Service; ++import org.springframework.web.client.RestTemplate; ++ ++import okhttp3.*; ++import okhttp3.MediaType; ++ ++import java.io.IOException; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Map; ++ ++@Service ++public class ImageGenerationService { ++ ++ private final RestTemplate restTemplate; ++ private final String apiKey; ++ private final String apiUrl; ++ ++ public ImageGenerationService(RestTemplate restTemplate, ++ @Value("${elice.api.token}") String apiKey, ++ @Value("${elice.api.url}") String apiUrl) { ++ this.restTemplate = restTemplate; ++ this.apiKey = apiKey; ++ this.apiUrl = apiUrl; ++ } ++ ++ public byte[] generateImage(String prompt) throws IOException { ++ OkHttpClient client = new OkHttpClient(); ++ ++ String requestBody = String.format("{\"prompt\":\"%s\",\"style\":\"polaroid\"}", prompt); ++ ++ MediaType mediaType = MediaType.parse("application/json"); ++ RequestBody body = RequestBody.create(requestBody, mediaType); ++ ++ Request request = new Request.Builder() ++ .url(apiUrl) ++ .post(body) ++ .addHeader("accept", "application/json") ++ .addHeader("content-type", "application/json") ++ .addHeader("Authorization", "Bearer " + apiKey) ++ .build(); ++ ++ Response response = client.newCall(request).execute(); ++ ++ if (!response.isSuccessful()) { ++ throw new IOException("์š”์ฒญ ์‹คํŒจ: " + response.code()); ++ } ++ ++ byte[] imageBytes = response.body().bytes(); // โœ… ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ๋ฅผ byte[]๋กœ ๋ณ€ํ™˜ ++ response.close(); ++ return imageBytes; ++ } ++} ++ +diff --git a/src/main/java/com/bamboo/log/emotion/controller/FaceDetectController.java b/src/main/java/com/bamboo/log/emotion/controller/FaceDetectController.java +deleted file mode 100644 +index fee5189..0000000 +--- a/src/main/java/com/bamboo/log/emotion/controller/FaceDetectController.java ++++ /dev/null +@@ -1,32 +0,0 @@ +-package com.bamboo.log.emotion.controller; +- +-import com.bamboo.log.emotion.dto.FaceDetectionResponse; +-import com.bamboo.log.emotion.service.FaceDetectionService; +-import io.swagger.v3.oas.annotations.Operation; +-import io.swagger.v3.oas.annotations.tags.Tag; +-import lombok.RequiredArgsConstructor; +-import org.springframework.http.HttpStatus; +-import org.springframework.http.ResponseEntity; +-import org.springframework.web.bind.annotation.PostMapping; +-import org.springframework.web.bind.annotation.RequestMapping; +-import org.springframework.web.bind.annotation.RequestParam; +-import org.springframework.web.bind.annotation.RestController; +-import org.springframework.web.multipart.MultipartFile; +- +-@RestController +-@RequiredArgsConstructor +-@RequestMapping("/api/emotion") +-@Tag(name = "Face Detection", description = "์–ผ๊ตด ์ธ์‹ ๊ด€๋ จ API") +-public class FaceDetectController { +- private final FaceDetectionService faceDetectionService; +- +- @PostMapping("/detect") +- @Operation( +- summary = "์–ผ๊ตด ์ธ์‹ API", +- description = "์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๋ฉด ์–ผ๊ตด ์ธ์‹ ์—ฌ๋ถ€๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค." +- ) +- public ResponseEntity detectFace(@RequestParam("image") MultipartFile image) { +- FaceDetectionResponse response = faceDetectionService.detectFace(image); +- return ResponseEntity.status(HttpStatus.valueOf(response.statusCode())).body(response); +- } +-} +\ No newline at end of file +diff --git a/src/main/java/com/bamboo/log/emotion/dto/FaceDetectionResponse.java b/src/main/java/com/bamboo/log/emotion/dto/FaceDetectionResponse.java +deleted file mode 100644 +index f9a2b09..0000000 +--- a/src/main/java/com/bamboo/log/emotion/dto/FaceDetectionResponse.java ++++ /dev/null +@@ -1,13 +0,0 @@ +-package com.bamboo.log.emotion.dto; +- +-import org.springframework.http.HttpStatus; +- +-public record FaceDetectionResponse( +- int statusCode, +- String statusMessage, +- String message +-) { +- public FaceDetectionResponse(HttpStatus httpStatus, String message) { +- this(httpStatus.value(), httpStatus.name(), message); +- } +-} +\ No newline at end of file +diff --git a/src/main/java/com/bamboo/log/emotion/service/FaceDetectionService.java b/src/main/java/com/bamboo/log/emotion/service/FaceDetectionService.java +deleted file mode 100644 +index af5f1bc..0000000 +--- a/src/main/java/com/bamboo/log/emotion/service/FaceDetectionService.java ++++ /dev/null +@@ -1,8 +0,0 @@ +-package com.bamboo.log.emotion.service; +- +-import com.bamboo.log.emotion.dto.FaceDetectionResponse; +-import org.springframework.web.multipart.MultipartFile; +- +-public interface FaceDetectionService { +- FaceDetectionResponse detectFace(MultipartFile image); +-} +\ No newline at end of file +diff --git a/src/main/java/com/bamboo/log/emotion/service/impl/FaceDetectionServiceImpl.java b/src/main/java/com/bamboo/log/emotion/service/impl/FaceDetectionServiceImpl.java +deleted file mode 100644 +index 47e6d8a..0000000 +--- a/src/main/java/com/bamboo/log/emotion/service/impl/FaceDetectionServiceImpl.java ++++ /dev/null +@@ -1,90 +0,0 @@ +-package com.bamboo.log.emotion.service.impl; +- +-import com.bamboo.log.emotion.dto.FaceDetectionResponse; +-import com.bamboo.log.emotion.service.FaceDetectionService; +-import com.fasterxml.jackson.databind.JsonNode; +-import com.fasterxml.jackson.databind.ObjectMapper; +-import lombok.RequiredArgsConstructor; +-import lombok.extern.slf4j.Slf4j; +-import okhttp3.*; +-import org.springframework.beans.factory.annotation.Value; +-import org.springframework.http.HttpStatus; +-import org.springframework.stereotype.Service; +-import org.springframework.web.multipart.MultipartFile; +- +-import java.io.IOException; +- +-@Slf4j +-@Service +-@RequiredArgsConstructor +-public class FaceDetectionServiceImpl implements FaceDetectionService { +- +- @Value("${elice.api.url.face}") +- private String faceApiUrl; +- +- @Value("${elice.api.token}") +- private String faceToken; +- +- private final OkHttpClient client = new OkHttpClient(); +- +- @Override +- public FaceDetectionResponse detectFace(MultipartFile image) { +- if (image == null || image.isEmpty()) { +- log.error("ํŒŒ์ผ์ด ์ „๋‹ฌ๋˜์ง€ ์•Š์Œ."); +- return new FaceDetectionResponse(HttpStatus.BAD_REQUEST, "ํŒŒ์ผ์ด ์ „๋‹ฌ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."); +- } +- log.info("ํŒŒ์ผ ์ด๋ฆ„: {}", image.getOriginalFilename()); +- log.info("ํŒŒ์ผ ํฌ๊ธฐ: {} bytes", image.getSize()); +- +- try { +- String contentType = image.getContentType(); +- if (contentType == null) { +- contentType = "application/octet-stream"; // ๊ธฐ๋ณธ๊ฐ’ ์„ค์ • +- } +- +- RequestBody requestBody = new MultipartBody.Builder() +- .setType(MultipartBody.FORM) +- .addFormDataPart("image", image.getOriginalFilename(), +- RequestBody.create(image.getBytes(), MediaType.parse(contentType))) +- .build(); +- +- // API ์š”์ฒญ ์ƒ์„ฑ (ํ† ํฐ ํฌํ•จ) +- Request request = new Request.Builder() +- .url(faceApiUrl) +- .post(requestBody) +- .addHeader("accept", "application/json") +- .addHeader("Authorization", "Bearer " + faceToken) +- .build(); +- +- try (Response response = client.newCall(request).execute()) { +- log.info("API ์‘๋‹ต ์ฝ”๋“œ: {}", response.code()); +- log.info("API ์‘๋‹ต ๋ฉ”์‹œ์ง€: {}", response.message()); +- +- if (!response.isSuccessful()) { +- return new FaceDetectionResponse(HttpStatus.valueOf(response.code()), "API ์š”์ฒญ ์‹คํŒจ: " + response.message()); +- } +- +- String responseBody = response.body().string(); +- log.info("API ์‘๋‹ต ๋ณธ๋ฌธ: {}", responseBody); +- +- // JSON ํŒŒ์‹ฑ +- JsonNode jsonNode = new ObjectMapper().readTree(responseBody); +- JsonNode results = jsonNode.get("results"); +- +- // ์–ผ๊ตด ์ธ์‹ ์—ฌ๋ถ€ ํ™•์ธ +- if (results != null && results.isArray() && results.size() > 0) { +- for (JsonNode result : results) { +- if ("face".equals(result.get("name").asText())) { +- return new FaceDetectionResponse(HttpStatus.OK, responseBody); +- } +- } +- } +- return new FaceDetectionResponse(HttpStatus.NOT_FOUND, "์–ผ๊ตด์ด ์ธ์‹๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."); +- } +- } catch (IOException e) { +- log.error("API ์š”์ฒญ ์ค‘ ์˜ˆ์™ธ ๋ฐœ์ƒ", e); +- return new FaceDetectionResponse(HttpStatus.INTERNAL_SERVER_ERROR, "API ์š”์ฒญ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: " + e.getMessage()); +- } +- } +- +-} +\ No newline at end of file +diff --git a/src/main/java/com/bamboo/log/utils/ResponseHandler.java b/src/main/java/com/bamboo/log/utils/ResponseHandler.java +new file mode 100644 +index 0000000..c483a50 +--- /dev/null ++++ b/src/main/java/com/bamboo/log/utils/ResponseHandler.java +@@ -0,0 +1,51 @@ ++package com.bamboo.log.utils; ++ ++import com.bamboo.log.utils.dto.DetailResponse; ++import com.bamboo.log.utils.dto.ResponseForm; ++import org.springframework.http.HttpStatus; ++import org.springframework.http.ResponseEntity; ++ ++public class ResponseHandler { ++ ++ public static ResponseEntity create500Error(ResponseForm response, Exception e) { ++ response.of("result", "FAIL"); ++ response.of("error", DetailResponse.builder().code(500).message(e.getMessage()).build()); ++ return new ResponseEntity(response, HttpStatus.INTERNAL_SERVER_ERROR); ++ } ++ ++ public static ResponseEntity create400Error(ResponseForm response, Exception e) { ++ response.of("result", "FAIL"); ++ response.of("error", DetailResponse.builder().code(400).message(e.getMessage()).build()); ++ ++ return new ResponseEntity(response, HttpStatus.BAD_REQUEST); ++ } ++ ++ public static ResponseEntity create404Error(ResponseForm response, Exception e) { ++ response.of("result", "FAIL"); ++ response.of("error", DetailResponse.builder().code(404).message(e.getMessage()).build()); ++ ++ return new ResponseEntity(response, HttpStatus.NOT_FOUND); ++ } ++ ++ public static ResponseEntity create204Response(ResponseForm response, String message) { ++ response.of("result", "SUCCESS"); ++ response.of("code", DetailResponse.builder().code(204).message(message).build()); ++ ++ return new ResponseEntity(response, HttpStatus.NO_CONTENT); ++ } ++ ++ public static ResponseEntity create200Response(ResponseForm response, Object object) { ++ response.of("result", "SUCCESS"); ++ response.of("code", object); ++ ++ return new ResponseEntity(response, HttpStatus.OK); ++ } ++ ++ public static ResponseEntity create201Response(ResponseForm response, Object object) { ++ response.of("result", "SUCCESS"); ++ response.of("code", object); ++ ++ return new ResponseEntity(response, HttpStatus.CREATED); ++ } ++ ++} +diff --git a/src/main/java/com/bamboo/log/utils/SecurityConfig.java b/src/main/java/com/bamboo/log/utils/SecurityConfig.java +new file mode 100644 +index 0000000..5d51eb4 +--- /dev/null ++++ b/src/main/java/com/bamboo/log/utils/SecurityConfig.java +@@ -0,0 +1,23 @@ ++package com.bamboo.log.utils; ++ ++import org.springframework.context.annotation.Bean; ++import org.springframework.context.annotation.Configuration; ++import org.springframework.security.config.annotation.web.builders.HttpSecurity; ++import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; ++import org.springframework.security.web.SecurityFilterChain; ++ ++@Configuration ++@EnableWebSecurity ++public class SecurityConfig { ++ @Bean ++ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { ++ http ++ .csrf(csrf -> csrf.disable()) // CSRF ๋น„ํ™œ์„ฑํ™” ++ .authorizeHttpRequests(auth -> auth ++ .requestMatchers("/api/images/**").permitAll() // ์ด๋ฏธ์ง€ API ์ธ์ฆ ์—†์ด ํ—ˆ์šฉ ++ .anyRequest().authenticated() ++ ); ++ ++ return http.build(); ++ } ++} +diff --git a/src/main/java/com/bamboo/log/utils/dto/DetailResponse.java b/src/main/java/com/bamboo/log/utils/dto/DetailResponse.java +new file mode 100644 +index 0000000..61f22cf +--- /dev/null ++++ b/src/main/java/com/bamboo/log/utils/dto/DetailResponse.java +@@ -0,0 +1,13 @@ ++package com.bamboo.log.utils.dto; ++ ++import lombok.Builder; ++import lombok.Getter; ++ ++@Builder ++@Getter ++public class DetailResponse { ++ ++ private Integer code; ++ private String message; ++ ++} +\ No newline at end of file +diff --git a/src/main/java/com/bamboo/log/utils/dto/ResponseForm.java b/src/main/java/com/bamboo/log/utils/dto/ResponseForm.java +new file mode 100644 +index 0000000..04eccf6 +--- /dev/null ++++ b/src/main/java/com/bamboo/log/utils/dto/ResponseForm.java +@@ -0,0 +1,21 @@ ++package com.bamboo.log.utils.dto; ++ ++import lombok.Getter; ++ ++import java.util.HashMap; ++import java.util.Map; ++ ++@Getter ++public class ResponseForm { ++ ++ public ResponseForm() { ++ response = new HashMap<>(); ++ } ++ ++ public void of(String value1, Object value2) { ++ response.put(value1, value2); ++ } ++ ++ private Map response; ++ ++} +\ No newline at end of file +diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml +index 5379dd6..317cea6 100644 +--- a/src/main/resources/application.yml ++++ b/src/main/resources/application.yml +@@ -1,42 +1,23 @@ + spring: + datasource: ++ driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/${MYSQL_NAME} + username: ${MYSQL_USERNAME} + password: ${MYSQL_PASSWORD} +- driver-class-name: com.mysql.cj.jdbc.Driver ++ + jpa: + show-sql: true + open-in-view: false + hibernate: +- ddl-auto: update ++ ddl-auto: update + properties: + hibernate.dialect: org.hibernate.dialect.MySQL8Dialect + +- security: +- oauth2: +- client: +- registration: +- kakao: +- client-authentication-method: ${SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_CLIENT_AUTHENTICATION_METHOD} +- client-name: ${SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_CLIENT_NAME} +- client-id: ${SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_CLIENT_ID} +- client-secret: ${SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_CLIENT_SECRET} +- redirect-uri: ${SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_REDIRECT_URI} +- authorization-grant-type: ${SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_AUTHORIZATION_GRANT_TYPE} +- scope: ${SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_SCOPE} +- provider: +- kakao: +- authorization-uri: ${SECURITY_OAUTH2_PROVIDER_KAKAO_AUTHORIZATION_URI} +- token-uri: ${SECURITY_OAUTH2_PROVIDER_KAKAO_TOKEN_URI} +- user-info-uri: ${SECURITY_OAUTH2_PROVIDER_KAKAO_USER_INFO_URI} +- user-name-attribute: ${SECURITY_OAUTH2_PROVIDER_KAKAO_USER_NAME_ATTRIBUTE} +- jwt: +- secret: ${JWT_SECRET} + + elice: + api: + token: ${API_TOKEN} + url: +- face: ${FACE_URL} +- img: +- chat: +\ No newline at end of file ++ face: ${FACE_API_URL} ++ img: ${IMG_API_URL} ++ chat: ${CHAT_API_URL} diff --git a/.DS_Store b/src/.DS_Store similarity index 73% rename from .DS_Store rename to src/.DS_Store index dd46a99..7b0d367 100644 Binary files a/.DS_Store and b/src/.DS_Store differ diff --git a/src/main/.DS_Store b/src/main/.DS_Store new file mode 100644 index 0000000..508c44c Binary files /dev/null and b/src/main/.DS_Store differ diff --git a/src/main/java/.DS_Store b/src/main/java/.DS_Store new file mode 100644 index 0000000..35a54bf Binary files /dev/null and b/src/main/java/.DS_Store differ diff --git a/src/main/java/com/.DS_Store b/src/main/java/com/.DS_Store new file mode 100644 index 0000000..ce60248 Binary files /dev/null and b/src/main/java/com/.DS_Store differ diff --git a/src/main/java/com/bamboo/.DS_Store b/src/main/java/com/bamboo/.DS_Store new file mode 100644 index 0000000..b7c4346 Binary files /dev/null and b/src/main/java/com/bamboo/.DS_Store differ diff --git a/src/main/java/com/bamboo/log/.DS_Store b/src/main/java/com/bamboo/log/.DS_Store new file mode 100644 index 0000000..36b131d Binary files /dev/null and b/src/main/java/com/bamboo/log/.DS_Store differ diff --git a/src/main/java/com/bamboo/log/MyController.java b/src/main/java/com/bamboo/log/MyController.java new file mode 100644 index 0000000..a00d01d --- /dev/null +++ b/src/main/java/com/bamboo/log/MyController.java @@ -0,0 +1,17 @@ +package com.bamboo.log; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +@Slf4j +public class MyController { + @GetMapping("/my") + @ResponseBody + public String myPI() { + log.info("myPI"); + return "my route"; + } +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/common/config/CorsMvcConfig.java b/src/main/java/com/bamboo/log/common/config/CorsMvcConfig.java new file mode 100644 index 0000000..daa5f81 --- /dev/null +++ b/src/main/java/com/bamboo/log/common/config/CorsMvcConfig.java @@ -0,0 +1,17 @@ +package com.bamboo.log.common.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class CorsMvcConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry corsRegistry) { + corsRegistry.addMapping("/**") + .allowedOriginPatterns("*") + .allowCredentials(true) + .exposedHeaders("Set-Cookie"); + } +} diff --git a/src/main/java/com/bamboo/log/common/config/EnvConfig.java b/src/main/java/com/bamboo/log/common/config/EnvConfig.java new file mode 100644 index 0000000..e531e99 --- /dev/null +++ b/src/main/java/com/bamboo/log/common/config/EnvConfig.java @@ -0,0 +1,13 @@ +package com.bamboo.log.common.config; + +import io.github.cdimascio.dotenv.Dotenv; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class EnvConfig { + @Bean + public Dotenv dotenv() { + return Dotenv.configure().ignoreIfMissing().load(); + } +} diff --git a/src/main/java/com/bamboo/log/common/config/SecurityConfig.java b/src/main/java/com/bamboo/log/common/config/SecurityConfig.java new file mode 100644 index 0000000..f85103a --- /dev/null +++ b/src/main/java/com/bamboo/log/common/config/SecurityConfig.java @@ -0,0 +1,79 @@ +package com.bamboo.log.common.config; + +import com.bamboo.log.domain.user.jwt.service.JWTFilter; +import com.bamboo.log.domain.user.jwt.service.JWTUtil; +import com.bamboo.log.domain.user.logout.CustomLogoutFilter; +import com.bamboo.log.domain.user.oauth.repository.RefreshRepository; +import com.bamboo.log.domain.user.oauth.service.CustomFailureHandler; +import com.bamboo.log.domain.user.oauth.service.CustomOAuth2UserService; +import com.bamboo.log.domain.user.oauth.service.CustomSuccessHandler; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.logout.LogoutFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; + +import java.util.Collections; +import java.util.List; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + + private final RefreshRepository refreshRepository; + private final CustomOAuth2UserService customOAuth2UserService; + private final CustomSuccessHandler customSuccessHandler; + private final CustomFailureHandler customFailureHandler; + private final JWTUtil jwtUtil; + + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.csrf((auth) -> auth.disable()); + http.formLogin((auth) -> auth.disable()); + http.httpBasic((auth) -> auth.disable()); + http.oauth2Login((oauth2) -> oauth2 + .userInfoEndpoint((userInfoEndpointConfig) -> userInfoEndpointConfig + .userService(customOAuth2UserService)) + .successHandler(customSuccessHandler) + .failureHandler(customFailureHandler) + ); + http.addFilterAfter(new JWTFilter(jwtUtil), OAuth2LoginAuthenticationFilter.class); + + //์ด์ชฝ์—๋‹ค๊ฐ€ ๊ฐ๊ฐ์˜ ์—”๋“œํฌ์ธํŠธ๋ฅผ ๋„ฃ์–ด์•ผํ•จ + http.authorizeHttpRequests((auth) -> auth + .requestMatchers("/refresh").permitAll() + .requestMatchers("/swagger-ui/**","/v3/api-docs/**","/swagger-resources/**","/webjars/**").permitAll() + .anyRequest().permitAll()); + + + http.sessionManagement((session) -> session + .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); + http.addFilterBefore(new CustomLogoutFilter(jwtUtil, refreshRepository), LogoutFilter.class); + http.cors(corsCustomizer -> corsCustomizer.configurationSource(new CorsConfigurationSource() { + @Override + public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.addAllowedOriginPattern("*"); + configuration.setAllowedMethods(Collections.singletonList("*")); + configuration.setAllowCredentials(true); + configuration.setAllowedHeaders(Collections.singletonList("*")); + configuration.setMaxAge(3600L); + configuration.setExposedHeaders(Collections.singletonList("Set-Cookie")); + configuration.setExposedHeaders(Collections.singletonList("Authorization")); + return configuration; + } + })); + + return http.build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/common/config/SwaggerConfig.java b/src/main/java/com/bamboo/log/common/config/SwaggerConfig.java new file mode 100644 index 0000000..dd0a87a --- /dev/null +++ b/src/main/java/com/bamboo/log/common/config/SwaggerConfig.java @@ -0,0 +1,12 @@ +package com.bamboo.log.common.config; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.security.SecurityScheme; +import org.springframework.context.annotation.Configuration; + +@Configuration +@OpenAPIDefinition(info = @Info(title = "ForRest", version = "v1")) +public class SwaggerConfig { +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/diary/api/DiaryController.java b/src/main/java/com/bamboo/log/diary/api/DiaryController.java new file mode 100644 index 0000000..747dea9 --- /dev/null +++ b/src/main/java/com/bamboo/log/diary/api/DiaryController.java @@ -0,0 +1,42 @@ +package com.bamboo.log.diary.api; + +import com.bamboo.log.diary.dto.request.CreateDiaryRequest; +import com.bamboo.log.diary.service.diary.DiaryService; +import com.bamboo.log.utils.ResponseHandler; +import com.bamboo.log.utils.dto.ResponseForm; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import io.swagger.v3.oas.annotations.tags.Tag; + +import java.time.LocalDateTime; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/diary") +@Tag(name = "Diary API", description = "์ผ๊ธฐ ๊ด€๋ จ API") +public class DiaryController { + + private final DiaryService diaryService; + + @Operation(summary = "์ผ๊ธฐ ์ƒ์„ฑ") + @PostMapping("/create") + public ResponseEntity createDiary(@RequestBody CreateDiaryRequest createDiaryRequest) { + return diaryService.createDiary(createDiaryRequest); + } + + @Operation(summary = "์›”๋ณ„ ์ผ๊ธฐ ์กฐํšŒ") + @GetMapping("/month") + public ResponseEntity getDiariesByMonth(@RequestParam String date) { + return diaryService.getDiariesByMonth(date); + } + + @Operation(summary = "๋‚ ์งœ๋ณ„ ์ผ๊ธฐ ์กฐํšŒ") + @GetMapping("/date") + public ResponseEntity getDiaryByDate(@RequestParam LocalDateTime date) { + return diaryService.getDiaryByDate(date); + } + +} diff --git a/src/main/java/com/bamboo/log/diary/domain/Diary.java b/src/main/java/com/bamboo/log/diary/domain/Diary.java new file mode 100644 index 0000000..20f071d --- /dev/null +++ b/src/main/java/com/bamboo/log/diary/domain/Diary.java @@ -0,0 +1,35 @@ +package com.bamboo.log.diary.domain; + +import com.bamboo.log.domain.user.oauth.entity.UserEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Table(name = "diaries") +public class Diary { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "diary_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private UserEntity user; + + @Column(columnDefinition = "TEXT", nullable = false) + private String context; + + @Column(nullable = false) + private LocalDateTime createdAt; + +} diff --git a/src/main/java/com/bamboo/log/diary/domain/TodaySummary.java b/src/main/java/com/bamboo/log/diary/domain/TodaySummary.java new file mode 100644 index 0000000..24af228 --- /dev/null +++ b/src/main/java/com/bamboo/log/diary/domain/TodaySummary.java @@ -0,0 +1,29 @@ +package com.bamboo.log.diary.domain; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Builder; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "today_summaries") +public class TodaySummary { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Column(nullable = false) + private Long diaryId; + + @Lob + @Column(columnDefinition = "LONGBLOB", nullable = false) + private byte[] imageData; + +} + diff --git a/src/main/java/com/bamboo/log/diary/dto/ParseYearMonth.java b/src/main/java/com/bamboo/log/diary/dto/ParseYearMonth.java new file mode 100644 index 0000000..3811fea --- /dev/null +++ b/src/main/java/com/bamboo/log/diary/dto/ParseYearMonth.java @@ -0,0 +1,17 @@ +package com.bamboo.log.diary.dto; + +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; +import java.time.YearMonth; + +@Getter +@Builder +public class ParseYearMonth { + + private YearMonth parsedYearMonth; + private LocalDateTime startOfMonth; + private LocalDateTime endOfMonth; + +} diff --git a/src/main/java/com/bamboo/log/diary/dto/request/CreateDiaryRequest.java b/src/main/java/com/bamboo/log/diary/dto/request/CreateDiaryRequest.java new file mode 100644 index 0000000..8e5425b --- /dev/null +++ b/src/main/java/com/bamboo/log/diary/dto/request/CreateDiaryRequest.java @@ -0,0 +1,8 @@ +package com.bamboo.log.diary.dto.request; + +import jakarta.validation.constraints.NotEmpty; + +import java.util.List; + +public record CreateDiaryRequest(@NotEmpty(message = "Diary Description shouldn't be empty") String diaryDetail) { +} diff --git a/src/main/java/com/bamboo/log/diary/dto/response/CheckDiaryResponse.java b/src/main/java/com/bamboo/log/diary/dto/response/CheckDiaryResponse.java new file mode 100644 index 0000000..152e4db --- /dev/null +++ b/src/main/java/com/bamboo/log/diary/dto/response/CheckDiaryResponse.java @@ -0,0 +1,22 @@ +package com.bamboo.log.diary.dto.response; + +import com.bamboo.log.diary.domain.Diary; +import jakarta.validation.constraints.NotEmpty; +import lombok.Builder; +import java.time.LocalDateTime; +import java.util.Base64; + +@Builder +public record CheckDiaryResponse( + @NotEmpty(message = "Date shouldn't be empty") LocalDateTime date, + @NotEmpty(message = "Diary Description shouldn't be empty") String diaryDescription, + String summaryImage // ๐Ÿ‘ˆ Base64 ๋ฌธ์ž์—ด๋กœ ๋ณ€๊ฒฝ (byte[] ๋Œ€์‹  String) +) { + public static CheckDiaryResponse from(Diary diary, byte[] imageData) { + return new CheckDiaryResponse( + diary.getCreatedAt(), + diary.getContext(), + imageData != null ? Base64.getEncoder().encodeToString(imageData) : null + ); + } +} diff --git a/src/main/java/com/bamboo/log/diary/dto/response/CreateDiaryResponse.java b/src/main/java/com/bamboo/log/diary/dto/response/CreateDiaryResponse.java new file mode 100644 index 0000000..9e994c0 --- /dev/null +++ b/src/main/java/com/bamboo/log/diary/dto/response/CreateDiaryResponse.java @@ -0,0 +1,8 @@ +package com.bamboo.log.diary.dto.response; + +import jakarta.validation.constraints.NotEmpty; + +import java.time.LocalDateTime; + +public record CreateDiaryResponse(@NotEmpty(message = "created shouldn't be empty") LocalDateTime created) { +} diff --git a/src/main/java/com/bamboo/log/diary/dto/response/GetDiariesOfMonthResponse.java b/src/main/java/com/bamboo/log/diary/dto/response/GetDiariesOfMonthResponse.java new file mode 100644 index 0000000..6440971 --- /dev/null +++ b/src/main/java/com/bamboo/log/diary/dto/response/GetDiariesOfMonthResponse.java @@ -0,0 +1,19 @@ +package com.bamboo.log.diary.dto.response; + +import lombok.Builder; +import java.time.LocalDateTime; +import java.util.List; + +@Builder +public record GetDiariesOfMonthResponse( + int diariesCount, + String date, + List diaries +) { + @Builder + public record DiaryOfMonth( + LocalDateTime createdAt, + String context, + String summaryImage + ) {} +} diff --git a/src/main/java/com/bamboo/log/diary/repository/DiaryRepository.java b/src/main/java/com/bamboo/log/diary/repository/DiaryRepository.java new file mode 100644 index 0000000..27411bc --- /dev/null +++ b/src/main/java/com/bamboo/log/diary/repository/DiaryRepository.java @@ -0,0 +1,14 @@ +package com.bamboo.log.diary.repository; + +import com.bamboo.log.diary.domain.Diary; +import com.bamboo.log.domain.user.oauth.entity.UserEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.time.LocalDateTime; +import java.util.List; + +public interface DiaryRepository extends JpaRepository { + + List findByUserAndCreatedAtBetween(UserEntity user, LocalDateTime start, LocalDateTime end); + +} diff --git a/src/main/java/com/bamboo/log/diary/repository/TodaySummaryRepository.java b/src/main/java/com/bamboo/log/diary/repository/TodaySummaryRepository.java new file mode 100644 index 0000000..195266f --- /dev/null +++ b/src/main/java/com/bamboo/log/diary/repository/TodaySummaryRepository.java @@ -0,0 +1,15 @@ +package com.bamboo.log.diary.repository; + +import com.bamboo.log.diary.domain.TodaySummary; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface TodaySummaryRepository extends JpaRepository { + + Optional findByDiaryId(Long diaryId); + + List findByDiaryIdIn(List diaryIds); + +} diff --git a/src/main/java/com/bamboo/log/diary/service/diary/DiaryService.java b/src/main/java/com/bamboo/log/diary/service/diary/DiaryService.java new file mode 100644 index 0000000..76342cc --- /dev/null +++ b/src/main/java/com/bamboo/log/diary/service/diary/DiaryService.java @@ -0,0 +1,16 @@ +package com.bamboo.log.diary.service.diary; + +import com.bamboo.log.diary.dto.request.CreateDiaryRequest; +import org.springframework.http.ResponseEntity; + +import java.time.LocalDateTime; + +public interface DiaryService { + + ResponseEntity createDiary(CreateDiaryRequest createDiaryRequest); + + ResponseEntity getDiariesByMonth(String date); + + ResponseEntity getDiaryByDate(LocalDateTime date); + +} diff --git a/src/main/java/com/bamboo/log/diary/service/diary/DiaryServiceImpl.java b/src/main/java/com/bamboo/log/diary/service/diary/DiaryServiceImpl.java new file mode 100644 index 0000000..9d62714 --- /dev/null +++ b/src/main/java/com/bamboo/log/diary/service/diary/DiaryServiceImpl.java @@ -0,0 +1,149 @@ +package com.bamboo.log.diary.service.diary; + +import com.bamboo.log.diary.domain.Diary; +import com.bamboo.log.diary.domain.TodaySummary; +import com.bamboo.log.diary.dto.ParseYearMonth; +import com.bamboo.log.diary.dto.request.CreateDiaryRequest; +import com.bamboo.log.diary.dto.response.CheckDiaryResponse; +import com.bamboo.log.diary.dto.response.CreateDiaryResponse; +import com.bamboo.log.diary.dto.response.GetDiariesOfMonthResponse; +import com.bamboo.log.diary.dto.response.GetDiariesOfMonthResponse.DiaryOfMonth; +import com.bamboo.log.diary.repository.DiaryRepository; +import com.bamboo.log.diary.repository.TodaySummaryRepository; + +import com.bamboo.log.domain.user.oauth.repository.UserRepository; + +import com.bamboo.log.diary.service.summary.TodaySummaryService; +import com.bamboo.log.domain.user.jwt.service.UserContextUtil; +import com.bamboo.log.domain.user.oauth.entity.UserEntity; +import com.bamboo.log.utils.ResponseHandler; +import com.bamboo.log.utils.dto.ResponseForm; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.YearMonth; +import java.time.format.DateTimeFormatter; +import java.util.Base64; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +@Transactional +@RequiredArgsConstructor +public class DiaryServiceImpl implements DiaryService { + + private final UserContextUtil userContextUtil; + private final UserRepository userRepository; + private final DiaryRepository diaryRepository; + private final TodaySummaryService todaySummaryService; + private final TodaySummaryRepository todaySummaryRepository; + + @Override + public ResponseEntity createDiary(CreateDiaryRequest createDiaryRequest) { + LocalDateTime localDateTime = LocalDateTime.now(); + Diary diary = saveDiary(createDiaryRequest, localDateTime); + byte[] todayImage = todaySummaryService.createTodaySummaryImage(createDiaryRequest.diaryDetail()); + + try { + todaySummaryService.saveTodaySummaryImage(todayImage, diary.getId()); + } catch (RuntimeException e) { + return ResponseHandler.create404Error(new ResponseForm(), e); + } catch (IOException e) { + return ResponseHandler.create500Error(new ResponseForm(), e); + } + + return ResponseHandler.create201Response(new ResponseForm(), new CreateDiaryResponse(localDateTime)); + } + + @Override + public ResponseEntity getDiariesByMonth(String date) { + ParseYearMonth parseYearMonth = getParsedDate(date); + UserEntity user = userRepository.findByUsername(userContextUtil.getUsername()); + + try { + List diaries = diaryRepository.findByUserAndCreatedAtBetween(user, + parseYearMonth.getStartOfMonth(), parseYearMonth.getEndOfMonth()); + + List diaryIds = diaries.stream().map(Diary::getId).toList(); + Map summaryImageMap = todaySummaryRepository.findByDiaryIdIn(diaryIds) + .stream() + .collect(Collectors.toMap( + TodaySummary::getDiaryId, + summary -> Base64.getEncoder().encodeToString(summary.getImageData()))); // โœ… Base64 ๋ณ€ํ™˜ + + List diaryOfMonthList = diaries.stream() + .map(diary -> new DiaryOfMonth( + diary.getCreatedAt(), + diary.getContext(), + summaryImageMap.getOrDefault(diary.getId(), null) // Base64 ๋ฌธ์ž์—ด ์ „๋‹ฌ + )) + .toList(); + + return ResponseHandler.create200Response(new ResponseForm(), + GetDiariesOfMonthResponse.builder() + .diariesCount(diaryOfMonthList.size()) + .date(date) + .diaries(diaryOfMonthList) + .build()); + } catch (RuntimeException e) { + return ResponseHandler.create404Error(new ResponseForm(), e); + } catch (Exception e) { + return ResponseHandler.create500Error(new ResponseForm(), e); + } + } + + @Override + public ResponseEntity getDiaryByDate(LocalDateTime date) { + UserEntity user = userRepository.findByUsername(userContextUtil.getUsername()); + + try { + List diaries = diaryRepository.findByUserAndCreatedAtBetween(user, date, date); + + if (diaries.isEmpty()) { + throw new RuntimeException("ํ•ด๋‹น ๋‚ ์งœ์— ์ž‘์„ฑ๋œ ์ผ๊ธฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."); + } + + Diary diaryByDate = diaries.get(0); + Optional summaryImage = todaySummaryRepository.findByDiaryId(diaryByDate.getId()); + + return ResponseHandler.create200Response(new ResponseForm(), + CheckDiaryResponse.from(diaryByDate, summaryImage.map(TodaySummary::getImageData).orElse(null))); + } catch (RuntimeException e) { + return ResponseHandler.create404Error(new ResponseForm(), new IllegalArgumentException("ํ•ด๋‹น ๋‚ ์งœ์— ์ž‘์„ฑ๋œ ์ผ๊ธฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")); + } catch (Exception e) { + return ResponseHandler.create500Error(new ResponseForm(), e); + } + } + + + private Diary saveDiary(CreateDiaryRequest createDiaryRequest, LocalDateTime localDateTime) { + UserEntity user = userContextUtil.getUserEntity(); + + Diary diary = Diary.builder() + .user(user) + .context(createDiaryRequest.diaryDetail()) + .createdAt(localDateTime) + .build(); + + return diaryRepository.save(diary); + } + + private ParseYearMonth getParsedDate(String date) { + YearMonth parsedYearMonth = YearMonth.parse(date, DateTimeFormatter.ofPattern("yyyy-MM")); + LocalDateTime startOfMonth = parsedYearMonth.atDay(1).atStartOfDay(); + LocalDateTime endOfMonth = parsedYearMonth.atEndOfMonth().atTime(23, 59, 59); + + return ParseYearMonth.builder() + .parsedYearMonth(parsedYearMonth) + .startOfMonth(startOfMonth) + .endOfMonth(endOfMonth) + .build(); + } + +} diff --git a/src/main/java/com/bamboo/log/diary/service/summary/TodaySummaryService.java b/src/main/java/com/bamboo/log/diary/service/summary/TodaySummaryService.java new file mode 100644 index 0000000..598d786 --- /dev/null +++ b/src/main/java/com/bamboo/log/diary/service/summary/TodaySummaryService.java @@ -0,0 +1,11 @@ +package com.bamboo.log.diary.service.summary; + +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; + +public interface TodaySummaryService { + + void saveTodaySummaryImage(byte[] image, Long diaryId) throws IOException; + byte[] createTodaySummaryImage(String prompt); +} diff --git a/src/main/java/com/bamboo/log/diary/service/summary/TodaySummaryServiceImpl.java b/src/main/java/com/bamboo/log/diary/service/summary/TodaySummaryServiceImpl.java new file mode 100644 index 0000000..06df2dd --- /dev/null +++ b/src/main/java/com/bamboo/log/diary/service/summary/TodaySummaryServiceImpl.java @@ -0,0 +1,46 @@ +package com.bamboo.log.diary.service.summary; + +import com.bamboo.log.diary.domain.TodaySummary; +import com.bamboo.log.diary.repository.TodaySummaryRepository; +import com.bamboo.log.elice.service.ImageGenerationService; +import com.bamboo.log.utils.ResponseHandler; +import com.bamboo.log.utils.dto.ResponseForm; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.Optional; + +@Service +@Transactional +@RequiredArgsConstructor +public class TodaySummaryServiceImpl implements TodaySummaryService { + + private final TodaySummaryRepository todaySummaryRepository; + private final ImageGenerationService imageGenerationService; + + @Override + public void saveTodaySummaryImage(byte[] image, Long diaryId) throws IOException { + TodaySummary todaySummary = TodaySummary.builder() + .diaryId(diaryId) + .imageData(image) + .build(); + + todaySummaryRepository.save(todaySummary); + } + + @Override + public byte[] createTodaySummaryImage(String prompt) { + try { + byte[] imageBytes = imageGenerationService.generateImage(prompt); + + return imageBytes; + } catch (IOException e) { + throw new RuntimeException("์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค‘์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."); + } + + } + +} diff --git a/src/main/java/com/bamboo/log/domain/user/controller/RefreshController.java b/src/main/java/com/bamboo/log/domain/user/controller/RefreshController.java new file mode 100644 index 0000000..05cd4d9 --- /dev/null +++ b/src/main/java/com/bamboo/log/domain/user/controller/RefreshController.java @@ -0,0 +1,32 @@ +package com.bamboo.log.domain.user.controller; + +import com.bamboo.log.domain.user.refresh.service.ProcessTokenReissue; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class RefreshController { + + private final ProcessTokenReissue processTokenReissue; + + @Operation(summary = "ํ† ํฐ ๊ฐฑ์‹ ", description = "Refresh Token์„ ์‚ฌ์šฉํ•˜์—ฌ Access Token์„ ์žฌ๋ฐœ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ์„ฑ๊ณต"), + @ApiResponse(responseCode = "401", description = "์œ ํšจํ•˜์ง€ ์•Š์€ Refresh Token"), + @ApiResponse(responseCode = "403", description = "Refresh Token์ด ๋งŒ๋ฃŒ๋จ") + }) + @PostMapping("/refresh") + public ResponseEntity reissue( + HttpServletRequest request, + HttpServletResponse response) { + return processTokenReissue.reissue(request, response); + } +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/domain/user/jwt/service/JWTFilter.java b/src/main/java/com/bamboo/log/domain/user/jwt/service/JWTFilter.java new file mode 100644 index 0000000..1d4cdf5 --- /dev/null +++ b/src/main/java/com/bamboo/log/domain/user/jwt/service/JWTFilter.java @@ -0,0 +1,66 @@ +package com.bamboo.log.domain.user.jwt.service; + +import com.bamboo.log.domain.user.oauth.dto.CustomOAuth2User; +import com.bamboo.log.domain.user.oauth.dto.UserDTO; +import io.jsonwebtoken.ExpiredJwtException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.io.PrintWriter; + +@RequiredArgsConstructor +public class JWTFilter extends OncePerRequestFilter { + + private final JWTUtil jwtUtil; + + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + String requestUri = request.getRequestURI(); + + System.out.println("requestUri = " + requestUri); + + String authorization = null; + Cookie[] cookies = request.getCookies(); + for (Cookie cookie : cookies) { + System.out.println(cookie.getName()); + if (cookie.getName().equals("Authorization")) { + authorization = cookie.getValue(); + } + } + if (authorization == null) { + System.out.println("token null"); + filterChain.doFilter(request, response); + return; + } + String token = authorization; + if (jwtUtil.isExpired(token)) { + System.out.println("token expired"); + filterChain.doFilter(request, response); + return; + } + Long userId = jwtUtil.getId(token); + String username = jwtUtil.getUsername(token); + String role = jwtUtil.getRole(token); + String name=jwtUtil.getName(token); + UserDTO userDTO = UserDTO.builder() + .id(userId) + .name(name) + .username(username) + .role(role) + .build(); + CustomOAuth2User customOAuth2User = new CustomOAuth2User(userDTO); + Authentication authToken = new UsernamePasswordAuthenticationToken(customOAuth2User, null, customOAuth2User.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authToken); + filterChain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/domain/user/jwt/service/JWTUtil.java b/src/main/java/com/bamboo/log/domain/user/jwt/service/JWTUtil.java new file mode 100644 index 0000000..c2dd261 --- /dev/null +++ b/src/main/java/com/bamboo/log/domain/user/jwt/service/JWTUtil.java @@ -0,0 +1,62 @@ +package com.bamboo.log.domain.user.jwt.service; + +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +@Component +public class JWTUtil { + + private SecretKey secretKey; + + public JWTUtil(@Value("${spring.jwt.secret}")String secret) { + secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm()); + } + + public String getUsername(String token) { + return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("username", String.class); + } + + public String getRole(String token) { + return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("role", String.class); + } + + public Boolean isExpired(String token) { + return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().getExpiration().before(new Date()); + } + + public String getCategory(String token) { + try { + // ํ† ํฐ์—์„œ ์นดํ…Œ๊ณ ๋ฆฌ ์ •๋ณด๋ฅผ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค. + return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("category", String.class); + } catch (JwtException e) { + throw new RuntimeException("ํ† ํฐ์—์„œ ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์ถ”์ถœํ•˜๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."); + } + } + + public String getName(String token){ + return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("name", String.class); + } + public Long getId(String token){ + return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("id", Long.class); + } + + public String createJwt(Long id, String category,String name,String username, String role, Long expiredMs) { + return Jwts.builder() + .claim("id", id) + .claim("category",category) + .claim("username", username) + .claim("name",name) + .claim("role", role) + .issuedAt(new Date(System.currentTimeMillis())) + .expiration(new Date(System.currentTimeMillis() + expiredMs)) + .signWith(secretKey) + .compact(); + } +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/domain/user/jwt/service/UserContextUtil.java b/src/main/java/com/bamboo/log/domain/user/jwt/service/UserContextUtil.java new file mode 100644 index 0000000..19162c0 --- /dev/null +++ b/src/main/java/com/bamboo/log/domain/user/jwt/service/UserContextUtil.java @@ -0,0 +1,45 @@ +package com.bamboo.log.domain.user.jwt.service; + +import com.bamboo.log.domain.user.oauth.dto.CustomOAuth2User; +import com.bamboo.log.domain.user.oauth.entity.UserEntity; +import com.bamboo.log.domain.user.oauth.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +@Component +@RequiredArgsConstructor +public class UserContextUtil { + + private final UserRepository userRepository; + + public Optional getCurrentUser() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null || !(authentication.getPrincipal() instanceof CustomOAuth2User)) { + return Optional.empty(); + } + return Optional.of((CustomOAuth2User) authentication.getPrincipal()); + } + + public String getUsername() { + return getCurrentUser() + .map(CustomOAuth2User::getUsername) + .orElseThrow(() -> new RuntimeException("User not authenticated")); + } + + public String getName() { + return getCurrentUser() + .map(CustomOAuth2User::getName) + .orElseThrow(() -> new RuntimeException("User not authenticated")); + } + + public UserEntity getUserEntity() { + String username = getUsername(); + + return userRepository.findByUsername(username); + } + +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/domain/user/logout/CustomLogoutFilter.java b/src/main/java/com/bamboo/log/domain/user/logout/CustomLogoutFilter.java new file mode 100644 index 0000000..d663e5e --- /dev/null +++ b/src/main/java/com/bamboo/log/domain/user/logout/CustomLogoutFilter.java @@ -0,0 +1,94 @@ +package com.bamboo.log.domain.user.logout; + +import com.bamboo.log.domain.user.jwt.service.JWTUtil; +import com.bamboo.log.domain.user.oauth.repository.RefreshRepository; +import io.jsonwebtoken.ExpiredJwtException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.filter.GenericFilterBean; + +import java.io.IOException; + +@RequiredArgsConstructor +@Transactional +@Slf4j +public class CustomLogoutFilter extends GenericFilterBean { + + private final JWTUtil jwtUtil; + private final RefreshRepository refreshRepository; + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain); + } + + private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException { + + String requestUri = request.getRequestURI(); + if (!requestUri.matches("^\\/logout$")) { + filterChain.doFilter(request, response); + return; + } + + String requestMethod = request.getMethod(); + if (!requestMethod.equals("POST")) { + filterChain.doFilter(request, response); + return; + } + + String refresh = null; + Cookie[] cookies = request.getCookies(); + for (Cookie cookie : cookies) { + if (cookie.getName().equals("refresh")) { + refresh = cookie.getValue(); + } + } + + if (refresh == null) { + log.error("๋กœ๊ทธ์•„์›ƒ ์‹œ ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์ด ์—†์Šต๋‹ˆ๋‹ค."); + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return; + } + + try { + jwtUtil.isExpired(refresh); + } catch (ExpiredJwtException e) { + log.error("๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. refresh token: {}", refresh); + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return; + } + + String category = jwtUtil.getCategory(refresh); + if (!category.equals("refresh")) { + log.error("์œ ํšจํ•˜์ง€ ์•Š์€ ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์ž…๋‹ˆ๋‹ค. refresh token: {}", refresh); + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return; + } + + Boolean isExist = refreshRepository.existsByToken(refresh); + if (!isExist) { + log.error("๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. refresh token: {}", refresh); + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return; + } + + refreshRepository.deleteByToken(refresh); + log.info("๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. refresh token: {}", refresh); + + Cookie cookie = new Cookie("refresh", null); + cookie.setMaxAge(0); + cookie.setPath("/"); + + response.addCookie(cookie); + log.info("๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ์ฟ ํ‚ค๊ฐ€ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); + response.setStatus(HttpServletResponse.SC_OK); + } +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/domain/user/oauth/dto/CustomOAuth2User.java b/src/main/java/com/bamboo/log/domain/user/oauth/dto/CustomOAuth2User.java new file mode 100644 index 0000000..29e2adb --- /dev/null +++ b/src/main/java/com/bamboo/log/domain/user/oauth/dto/CustomOAuth2User.java @@ -0,0 +1,51 @@ +package com.bamboo.log.domain.user.oauth.dto; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.core.user.OAuth2User; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +public class CustomOAuth2User implements OAuth2User { + + private final UserDTO userDTO; + + public CustomOAuth2User(UserDTO userDTO) { + this.userDTO = userDTO; + } + + @Override + public Map getAttributes() { + return null; + } + + @Override + public Collection getAuthorities() { + + Collection collection = new ArrayList<>(); + + collection.add(new GrantedAuthority() { + + @Override + public String getAuthority() { + + return userDTO.getRole(); + } + }); + + return collection; + } + + @Override + public String getName() { + return userDTO.getName(); + } + + public String getUsername() { + return userDTO.getUsername(); + } + + public Long getId() { return userDTO.getId(); } + +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/domain/user/oauth/dto/KakaoResponse.java b/src/main/java/com/bamboo/log/domain/user/oauth/dto/KakaoResponse.java new file mode 100644 index 0000000..a8d8f79 --- /dev/null +++ b/src/main/java/com/bamboo/log/domain/user/oauth/dto/KakaoResponse.java @@ -0,0 +1,40 @@ +package com.bamboo.log.domain.user.oauth.dto; + +import java.util.Map; + +public class KakaoResponse implements OAuth2Response { + private final Map attributes; + private final Map kakaoAccount; + private final Map profile; + + public KakaoResponse(Map attributes) { + this.attributes = attributes; + this.kakaoAccount = (Map) attributes.get("kakao_account"); + this.profile = (Map) kakaoAccount.get("profile"); + } + + @Override + public String getProvider() { + return "kakao"; + } + + @Override + public String getProviderId() { + return attributes.get("id").toString(); + } + + @Override + public String getEmail() { + return kakaoAccount.get("email") != null ? kakaoAccount.get("email").toString() : "N/A"; + } + + @Override + public String getName() { + return profile.get("nickname").toString(); + } + + @Override + public Map getAttributes() { + return attributes; + } +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/domain/user/oauth/dto/OAuth2Response.java b/src/main/java/com/bamboo/log/domain/user/oauth/dto/OAuth2Response.java new file mode 100644 index 0000000..126b2ea --- /dev/null +++ b/src/main/java/com/bamboo/log/domain/user/oauth/dto/OAuth2Response.java @@ -0,0 +1,11 @@ +package com.bamboo.log.domain.user.oauth.dto; + +import java.util.Map; + +public interface OAuth2Response { + String getProvider(); + String getProviderId(); + String getEmail(); + String getName(); + Map getAttributes(); +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/domain/user/oauth/dto/UserDTO.java b/src/main/java/com/bamboo/log/domain/user/oauth/dto/UserDTO.java new file mode 100644 index 0000000..54f3499 --- /dev/null +++ b/src/main/java/com/bamboo/log/domain/user/oauth/dto/UserDTO.java @@ -0,0 +1,15 @@ +package com.bamboo.log.domain.user.oauth.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@AllArgsConstructor +@Builder +@Getter +public class UserDTO { + private Long id; + private String name; + private String username; + private String role; +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/domain/user/oauth/entity/RefreshToken.java b/src/main/java/com/bamboo/log/domain/user/oauth/entity/RefreshToken.java new file mode 100644 index 0000000..ae4b22c --- /dev/null +++ b/src/main/java/com/bamboo/log/domain/user/oauth/entity/RefreshToken.java @@ -0,0 +1,39 @@ +package com.bamboo.log.domain.user.oauth.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.Date; +@Entity +@Table(name = "refresh_token") +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class RefreshToken { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private String username; + + @Column(nullable = false) + private String token; + + @Column(name = "expires_at", nullable = false) + @Temporal(TemporalType.TIMESTAMP) + private Date expiresAt; + + @Column(name = "created_at", nullable = false) + @Temporal(TemporalType.TIMESTAMP) + private Date createdAt; + +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/domain/user/oauth/entity/UserEntity.java b/src/main/java/com/bamboo/log/domain/user/oauth/entity/UserEntity.java new file mode 100644 index 0000000..03b88bc --- /dev/null +++ b/src/main/java/com/bamboo/log/domain/user/oauth/entity/UserEntity.java @@ -0,0 +1,49 @@ +package com.bamboo.log.domain.user.oauth.entity; + +import com.bamboo.log.juksoon.domain.Juksoon; +import jakarta.persistence.*; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@Entity +@Table(name="users") +public class UserEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(unique = true, nullable = false) + private String username; + + + private String name; + private String email; + private String role; + private String profile_img_url; + + @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private Juksoon juksooni; + + @Builder + public UserEntity(String username, String name, String email, String role,String profile_img_url) { + this.username = username; + this.name = name; + this.email = email; + this.role = role; + this.profile_img_url = profile_img_url; + } + + public UserEntity updateEmailAndName(String email, String name) { + return UserEntity.builder() + .username(this.username) + .name(name) + .email(email) + .role(this.role) + .profile_img_url(this.profile_img_url) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/domain/user/oauth/repository/RefreshRepository.java b/src/main/java/com/bamboo/log/domain/user/oauth/repository/RefreshRepository.java new file mode 100644 index 0000000..0075488 --- /dev/null +++ b/src/main/java/com/bamboo/log/domain/user/oauth/repository/RefreshRepository.java @@ -0,0 +1,10 @@ +package com.bamboo.log.domain.user.oauth.repository; + +import com.bamboo.log.domain.user.oauth.entity.RefreshToken; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface RefreshRepository extends JpaRepository { + Boolean existsByToken(String token); + void deleteByToken(String token); + RefreshToken findByToken(String token); +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/domain/user/oauth/repository/UserRepository.java b/src/main/java/com/bamboo/log/domain/user/oauth/repository/UserRepository.java new file mode 100644 index 0000000..645a385 --- /dev/null +++ b/src/main/java/com/bamboo/log/domain/user/oauth/repository/UserRepository.java @@ -0,0 +1,8 @@ +package com.bamboo.log.domain.user.oauth.repository; + +import com.bamboo.log.domain.user.oauth.entity.UserEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository { + UserEntity findByUsername(String username); +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/domain/user/oauth/service/CustomFailureHandler.java b/src/main/java/com/bamboo/log/domain/user/oauth/service/CustomFailureHandler.java new file mode 100644 index 0000000..492f364 --- /dev/null +++ b/src/main/java/com/bamboo/log/domain/user/oauth/service/CustomFailureHandler.java @@ -0,0 +1,19 @@ +package com.bamboo.log.domain.user.oauth.service; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class CustomFailureHandler extends SimpleUrlAuthenticationFailureHandler { + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { + response.sendRedirect("http://localhost:3000/login"); + } +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/domain/user/oauth/service/CustomOAuth2UserService.java b/src/main/java/com/bamboo/log/domain/user/oauth/service/CustomOAuth2UserService.java new file mode 100644 index 0000000..93fcb76 --- /dev/null +++ b/src/main/java/com/bamboo/log/domain/user/oauth/service/CustomOAuth2UserService.java @@ -0,0 +1,70 @@ +package com.bamboo.log.domain.user.oauth.service; + +import com.bamboo.log.domain.user.oauth.dto.CustomOAuth2User; +import com.bamboo.log.domain.user.oauth.dto.KakaoResponse; +import com.bamboo.log.domain.user.oauth.dto.OAuth2Response; +import com.bamboo.log.domain.user.oauth.dto.UserDTO; +import com.bamboo.log.domain.user.oauth.entity.UserEntity; +import com.bamboo.log.domain.user.oauth.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + + +@Service +@RequiredArgsConstructor +public class CustomOAuth2UserService extends DefaultOAuth2UserService { + + private final UserRepository userRepository; + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + OAuth2User oAuth2User = super.loadUser(userRequest); + String registrationId = userRequest.getClientRegistration().getRegistrationId(); + + OAuth2Response oAuth2Response = null; + if (registrationId.equals("kakao")) { + oAuth2Response = new KakaoResponse(oAuth2User.getAttributes()); + } else { + return null; + } + + System.out.println("OAuth2Response: " + oAuth2Response); + String username = oAuth2Response.getProvider() + " " + oAuth2Response.getProviderId(); + UserEntity existData = userRepository.findByUsername(username); + System.out.println("username = " + username); + System.out.println("Existing User: " + existData); + + if (existData == null) { + UserEntity userEntity = UserEntity.builder() + .username(username) + .name(oAuth2Response.getName()) + .email(oAuth2Response.getEmail()) + .role("ROLE_USER") + .build(); + + userRepository.save(userEntity); + + UserDTO userDTO = UserDTO.builder() + .id(userEntity.getId()) + .username(username) + .name(oAuth2Response.getName()) + .role("ROLE_USER") + .build(); + + return new CustomOAuth2User(userDTO); + } else { + UserDTO userDTO = UserDTO.builder() + .id(existData.getId()) + .username(existData.getUsername()) + .name(existData.getName()) + .role(existData.getRole()) + .build(); + return new CustomOAuth2User(userDTO); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/domain/user/oauth/service/CustomSuccessHandler.java b/src/main/java/com/bamboo/log/domain/user/oauth/service/CustomSuccessHandler.java new file mode 100644 index 0000000..14b0310 --- /dev/null +++ b/src/main/java/com/bamboo/log/domain/user/oauth/service/CustomSuccessHandler.java @@ -0,0 +1,76 @@ +package com.bamboo.log.domain.user.oauth.service; + +import com.bamboo.log.domain.user.jwt.service.JWTUtil; +import com.bamboo.log.domain.user.oauth.dto.CustomOAuth2User; +import com.bamboo.log.domain.user.oauth.entity.RefreshToken; +import com.bamboo.log.domain.user.oauth.repository.RefreshRepository; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseCookie; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; + +@Component +@RequiredArgsConstructor +public class CustomSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { + + private final JWTUtil jwtUtil; + private final RefreshRepository refreshRepository; + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { + CustomOAuth2User customUserDetails = (CustomOAuth2User) authentication.getPrincipal(); + Long userId = customUserDetails.getId(); + String name = customUserDetails.getName(); + String username = customUserDetails.getUsername(); + Collection authorities = authentication.getAuthorities(); + Iterator iterator = authorities.iterator(); + GrantedAuthority auth = iterator.next(); + String role = auth.getAuthority(); + + String refreshToken = jwtUtil.createJwt(userId, "refresh", name, username, role, 1209600000L); + addRefreshEntity(name, username, refreshToken, 1209600000L); + + // Create and set the cookie + createCookie(response, "Authorization", refreshToken); + + response.sendRedirect("http://localhost:3000/welcome"); + } + + private void createCookie(HttpServletResponse response, String key, String value) { + // ResponseCookie ์‚ฌ์šฉ + ResponseCookie cookie = ResponseCookie.from(key, value) + .maxAge(1209600000 / 1000) + .path("/") + .httpOnly(true) + .secure(true) + .sameSite("None") // SameSite=None ์„ค์ • + .build(); + + // Response์— ์ฟ ํ‚ค ์ถ”๊ฐ€ + response.addHeader("Set-Cookie", cookie.toString()); + } + + private void addRefreshEntity(String name, String username, String refresh, Long expiredMs) { + Date CrDate = new Date(System.currentTimeMillis()); + Date ExDate = new Date(System.currentTimeMillis() + expiredMs); + refreshRepository.save( + RefreshToken.builder() + .name(name) + .username(username) + .token(refresh) + .createdAt(CrDate) + .expiresAt(ExDate) + .build() + ); + } +} diff --git a/src/main/java/com/bamboo/log/domain/user/refresh/service/ProcessTokenReissue.java b/src/main/java/com/bamboo/log/domain/user/refresh/service/ProcessTokenReissue.java new file mode 100644 index 0000000..8069876 --- /dev/null +++ b/src/main/java/com/bamboo/log/domain/user/refresh/service/ProcessTokenReissue.java @@ -0,0 +1,9 @@ +package com.bamboo.log.domain.user.refresh.service; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.ResponseEntity; + +public interface ProcessTokenReissue { + public ResponseEntity reissue(HttpServletRequest request, HttpServletResponse response); +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/domain/user/refresh/service/ProcessTokenReissueImpl.java b/src/main/java/com/bamboo/log/domain/user/refresh/service/ProcessTokenReissueImpl.java new file mode 100644 index 0000000..477002c --- /dev/null +++ b/src/main/java/com/bamboo/log/domain/user/refresh/service/ProcessTokenReissueImpl.java @@ -0,0 +1,102 @@ +package com.bamboo.log.domain.user.refresh.service; + +import com.bamboo.log.domain.user.jwt.service.JWTUtil; +import com.bamboo.log.domain.user.oauth.entity.RefreshToken; +import com.bamboo.log.domain.user.oauth.repository.RefreshRepository; +import io.jsonwebtoken.ExpiredJwtException; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Date; + +@Service +@RequiredArgsConstructor +@Transactional +@Slf4j +public class ProcessTokenReissueImpl implements ProcessTokenReissue { + + private final JWTUtil jwtUtil; + private final RefreshRepository refreshRepository; + + public ResponseEntity reissue(HttpServletRequest request, HttpServletResponse response) { + + String refresh = null; + Cookie[] cookies = request.getCookies(); + for (Cookie cookie : cookies) { + if (cookie.getName().equals("refresh")) { + refresh = cookie.getValue(); + } + } + + if (refresh == null) { + log.error("๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์ด ์—†์Šต๋‹ˆ๋‹ค."); + return new ResponseEntity<>("refresh token null", HttpStatus.BAD_REQUEST); + } + + try { + jwtUtil.isExpired(refresh); + } catch (ExpiredJwtException e) { + log.error("๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. token: {}", refresh); + return new ResponseEntity<>("refresh token expired", HttpStatus.BAD_REQUEST); + } + + String category = jwtUtil.getCategory(refresh); + if (!category.equals("refresh")) { + log.error("์œ ํšจํ•˜์ง€ ์•Š์€ ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์ž…๋‹ˆ๋‹ค. token: {}", refresh); + return new ResponseEntity<>("invalid refresh token", HttpStatus.BAD_REQUEST); + } + + if (!refreshRepository.existsByToken(refresh)) { + log.error("๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. token: {}", refresh); + return new ResponseEntity<>("invalid refresh token", HttpStatus.BAD_REQUEST); + } + Long userId = jwtUtil.getId(refresh); + String name = jwtUtil.getName(refresh); + String username = jwtUtil.getUsername(refresh); + String role = jwtUtil.getRole(refresh); + + String newAccess = jwtUtil.createJwt(userId,"access", name, username , role, 1800000L); + String newRefresh = jwtUtil.createJwt(userId,"refresh", name, username, role, 1209600000L); + + refreshRepository.deleteByToken(refresh); + addRefreshEntity(name,username,role,1209600000L); + + log.info("์ƒˆ๋กœ์šด ์•ก์„ธ์Šค ํ† ํฐ๊ณผ ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์„ ๋ฐœ๊ธ‰ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ: {}", newRefresh); + + response.setHeader("Authorization", newAccess); + response.addCookie(createCookie("refresh", newRefresh)); + + log.info("๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ์™„๋ฃŒ"); + + return new ResponseEntity<>(HttpStatus.OK); + } + + private Cookie createCookie(String key, String value) { + log.info("์ฟ ํ‚ค ์ƒ์„ฑ ์‹œ์ž‘"); + Cookie cookie = new Cookie(key, value); + cookie.setMaxAge(24 * 60 * 60 * 14); // 14์ผ ์œ ํšจ + cookie.setHttpOnly(true); // ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ์ ‘๊ทผ ๋ถˆ๊ฐ€ + log.info("์ฟ ํ‚ค ์ƒ์„ฑ ๋"); + return cookie; + } + private void addRefreshEntity(String name, String username, String refresh, Long expiredMs) { + Date CrDate = new Date(System.currentTimeMillis()); + Date ExDate = new Date(System.currentTimeMillis() + expiredMs); + refreshRepository.save( + RefreshToken.builder() + .name(name) + .username(username) + .token(refresh) + .createdAt(CrDate) + .expiresAt(ExDate) + .build() + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/elice/config/RestTemplateConfig.java b/src/main/java/com/bamboo/log/elice/config/RestTemplateConfig.java new file mode 100644 index 0000000..0ea3f07 --- /dev/null +++ b/src/main/java/com/bamboo/log/elice/config/RestTemplateConfig.java @@ -0,0 +1,14 @@ +package com.bamboo.log.elice.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} diff --git a/src/main/java/com/bamboo/log/elice/service/ImageGenerationService.java b/src/main/java/com/bamboo/log/elice/service/ImageGenerationService.java new file mode 100644 index 0000000..d318cce --- /dev/null +++ b/src/main/java/com/bamboo/log/elice/service/ImageGenerationService.java @@ -0,0 +1,58 @@ +package com.bamboo.log.elice.service; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import okhttp3.*; +import okhttp3.MediaType; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +public class ImageGenerationService { + + private final RestTemplate restTemplate; + private final String apiKey; + private final String apiUrl; + + public ImageGenerationService(RestTemplate restTemplate, + @Value("${elice.api.token}") String apiKey, + @Value("${elice.api.url.img}") String apiUrl) { + this.restTemplate = restTemplate; + this.apiKey = apiKey; + this.apiUrl = apiUrl; + } + + public byte[] generateImage(String prompt) throws IOException { + OkHttpClient client = new OkHttpClient(); + + String requestBody = String.format("{\"prompt\":\"%s\",\"style\":\"polaroid\"}", prompt); + + MediaType mediaType = MediaType.parse("application/json"); + RequestBody body = RequestBody.create(requestBody, mediaType); + + Request request = new Request.Builder() + .url(apiUrl) + .post(body) + .addHeader("accept", "application/json") + .addHeader("content-type", "application/json") + .addHeader("Authorization", "Bearer " + apiKey) + .build(); + + Response response = client.newCall(request).execute(); + + if (!response.isSuccessful()) { + throw new IOException("์š”์ฒญ ์‹คํŒจ: " + response.code()); + } + + byte[] imageBytes = response.body().bytes(); // โœ… ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ๋ฅผ byte[]๋กœ ๋ณ€ํ™˜ + response.close(); + return imageBytes; + } +} + diff --git a/src/main/java/com/bamboo/log/emotion/controller/EmotionController.java b/src/main/java/com/bamboo/log/emotion/controller/EmotionController.java new file mode 100644 index 0000000..4bfcba7 --- /dev/null +++ b/src/main/java/com/bamboo/log/emotion/controller/EmotionController.java @@ -0,0 +1,53 @@ +package com.bamboo.log.emotion.controller; + +import com.bamboo.log.emotion.dto.BoundingBox; +import com.bamboo.log.emotion.dto.EmotionAnalysisResponse; +import com.bamboo.log.emotion.dto.req.EmotionAnalysisRequest; +import com.bamboo.log.emotion.service.EmotionAnalysisService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@RestController +@RequestMapping("/api/emotion") +@RequiredArgsConstructor +@Tag(name = "Emotion Analysis", description = "์–ผ๊ตด ๊ฐ์ •(ํ‘œ์ •)๋ถ„์„ API") +public class EmotionController { + + private final EmotionAnalysisService emotionAnalysisService; + + @PostMapping(value = "/result", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @Operation( + summary = "๊ฐ์ • ๋ถ„์„", + description = "๋ฐ˜ํ™˜๋ฐ›์€ ์–ผ๊ตด ์˜์—ญ ์ขŒํ‘œ์™€ ์ด๋ฏธ์ง€๋ฅผ ๋ถ„์„ํ•˜์—ฌ ๊ฐ์ •์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค." + ) + public ResponseEntity analyzeEmotion( + + @RequestPart("image") + @Parameter(description = "๊ฐ์ • ๋ถ„์„ํ•  ์–ผ๊ตด ์ด๋ฏธ์ง€", required = true) + MultipartFile image, + + @RequestPart("faceBox") + @Parameter(description = "์–ผ๊ตด ์˜์—ญ ์ขŒํ‘œ (JSON ํ˜•์‹)", required = true) + List faceBoxList) { + + BoundingBox faceBox = faceBoxList.get(0); // ์ฒซ ๋ฒˆ์งธ ์–ผ๊ตด๋งŒ ์‚ฌ์šฉ + EmotionAnalysisRequest request = new EmotionAnalysisRequest(image, faceBox); + EmotionAnalysisResponse response = emotionAnalysisService.analyzeEmotion(request); + + HttpStatus status = HttpStatus.resolve(response.status().value()); + if (status == null) { + status = HttpStatus.INTERNAL_SERVER_ERROR; + } + + return ResponseEntity.status(status).body(response); + } +} diff --git a/src/main/java/com/bamboo/log/emotion/controller/FaceDetectController.java b/src/main/java/com/bamboo/log/emotion/controller/FaceDetectController.java new file mode 100644 index 0000000..62fd28f --- /dev/null +++ b/src/main/java/com/bamboo/log/emotion/controller/FaceDetectController.java @@ -0,0 +1,32 @@ +package com.bamboo.log.emotion.controller; + +import com.bamboo.log.emotion.dto.FaceDetectionResponse; +import com.bamboo.log.emotion.service.FaceDetectionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/face") +@Tag(name = "Face Detection", description = "์–ผ๊ตด ์ธ์‹ API") +public class FaceDetectController { + private final FaceDetectionService faceDetectionService; + + @PostMapping("/detect") + @Operation( + summary = "์–ผ๊ตด ์ธ์‹", + description = "์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๋ฉด ์–ผ๊ตด ์ธ์‹ ์—ฌ๋ถ€๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค." + ) + public ResponseEntity detectFace(@RequestParam("image") MultipartFile image) { + FaceDetectionResponse response = faceDetectionService.detectFace(image); + return ResponseEntity.status(HttpStatus.valueOf(response.statusCode())).body(response); + } +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/emotion/domain/EmotionType.java b/src/main/java/com/bamboo/log/emotion/domain/EmotionType.java new file mode 100644 index 0000000..8fbbe0e --- /dev/null +++ b/src/main/java/com/bamboo/log/emotion/domain/EmotionType.java @@ -0,0 +1,10 @@ +package com.bamboo.log.emotion.domain; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum EmotionType { + ANGRY, HAPPY, NEUTRAL, SAD, NONE ; +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/emotion/dto/BoundingBox.java b/src/main/java/com/bamboo/log/emotion/dto/BoundingBox.java new file mode 100644 index 0000000..7fcdb2a --- /dev/null +++ b/src/main/java/com/bamboo/log/emotion/dto/BoundingBox.java @@ -0,0 +1,3 @@ +package com.bamboo.log.emotion.dto; + +public record BoundingBox(double x1, double y1, double x2, double y2) {} diff --git a/src/main/java/com/bamboo/log/emotion/dto/EmotionAnalysisResponse.java b/src/main/java/com/bamboo/log/emotion/dto/EmotionAnalysisResponse.java new file mode 100644 index 0000000..cf925a5 --- /dev/null +++ b/src/main/java/com/bamboo/log/emotion/dto/EmotionAnalysisResponse.java @@ -0,0 +1,6 @@ +package com.bamboo.log.emotion.dto; + +import com.bamboo.log.emotion.domain.EmotionType; +import org.springframework.http.HttpStatus; + +public record EmotionAnalysisResponse (HttpStatus status, EmotionType emotion) {} diff --git a/src/main/java/com/bamboo/log/emotion/dto/FaceDetectionResponse.java b/src/main/java/com/bamboo/log/emotion/dto/FaceDetectionResponse.java new file mode 100644 index 0000000..16551a0 --- /dev/null +++ b/src/main/java/com/bamboo/log/emotion/dto/FaceDetectionResponse.java @@ -0,0 +1,15 @@ +package com.bamboo.log.emotion.dto; + +import org.springframework.http.HttpStatus; + +import java.util.List; + +public record FaceDetectionResponse( + int statusCode, + String statusMessage, + List faceBox +) { + public FaceDetectionResponse(HttpStatus httpStatus, List faceBox) { + this(httpStatus.value(), httpStatus.name(), faceBox); + } +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/emotion/dto/req/EmotionAnalysisRequest.java b/src/main/java/com/bamboo/log/emotion/dto/req/EmotionAnalysisRequest.java new file mode 100644 index 0000000..7961010 --- /dev/null +++ b/src/main/java/com/bamboo/log/emotion/dto/req/EmotionAnalysisRequest.java @@ -0,0 +1,6 @@ +package com.bamboo.log.emotion.dto.req; + +import com.bamboo.log.emotion.dto.BoundingBox; +import org.springframework.web.multipart.MultipartFile; + +public record EmotionAnalysisRequest(MultipartFile image, BoundingBox faceBox) {} diff --git a/src/main/java/com/bamboo/log/emotion/service/EmotionAnalysisService.java b/src/main/java/com/bamboo/log/emotion/service/EmotionAnalysisService.java new file mode 100644 index 0000000..7e4300d --- /dev/null +++ b/src/main/java/com/bamboo/log/emotion/service/EmotionAnalysisService.java @@ -0,0 +1,8 @@ +package com.bamboo.log.emotion.service; + +import com.bamboo.log.emotion.dto.EmotionAnalysisResponse; +import com.bamboo.log.emotion.dto.req.EmotionAnalysisRequest; + +public interface EmotionAnalysisService { + EmotionAnalysisResponse analyzeEmotion(EmotionAnalysisRequest request); +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/emotion/service/FaceDetectionService.java b/src/main/java/com/bamboo/log/emotion/service/FaceDetectionService.java new file mode 100644 index 0000000..af5f1bc --- /dev/null +++ b/src/main/java/com/bamboo/log/emotion/service/FaceDetectionService.java @@ -0,0 +1,8 @@ +package com.bamboo.log.emotion.service; + +import com.bamboo.log.emotion.dto.FaceDetectionResponse; +import org.springframework.web.multipart.MultipartFile; + +public interface FaceDetectionService { + FaceDetectionResponse detectFace(MultipartFile image); +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/emotion/service/impl/EmotionAnalysisServiceImpl.java b/src/main/java/com/bamboo/log/emotion/service/impl/EmotionAnalysisServiceImpl.java new file mode 100644 index 0000000..c707b8e --- /dev/null +++ b/src/main/java/com/bamboo/log/emotion/service/impl/EmotionAnalysisServiceImpl.java @@ -0,0 +1,84 @@ +package com.bamboo.log.emotion.service.impl; + +import com.bamboo.log.emotion.domain.EmotionType; +import com.bamboo.log.emotion.dto.BoundingBox; +import com.bamboo.log.emotion.dto.EmotionAnalysisResponse; +import com.bamboo.log.emotion.dto.req.EmotionAnalysisRequest; +import com.bamboo.log.emotion.service.EmotionAnalysisService; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; + +@Slf4j +@Service +@RequiredArgsConstructor +public class EmotionAnalysisServiceImpl implements EmotionAnalysisService { + @Value("${emotion.api.url}") + private String emotionApiUrl; + + private final OkHttpClient client = new OkHttpClient(); + + @Override + public EmotionAnalysisResponse analyzeEmotion(EmotionAnalysisRequest request) { + MultipartFile image = request.image(); + BoundingBox faceBox = request.faceBox(); + + log.info("Received EmotionAnalysisRequest: {}", request); + log.info("Received BoundingBox: {}", (faceBox != null) ? faceBox.toString() : "NULL"); + + if (faceBox == null) { + log.warn("์–ผ๊ตด ์˜์—ญ์ด ๊ฐ์ง€๋˜์ง€ ์•Š์Œ. ๊ฐ์ • ๋ถ„์„ ๋ถˆ๊ฐ€๋Šฅ"); + return new EmotionAnalysisResponse(HttpStatus.OK, EmotionType.NONE); + } + + EmotionAnalysisResponse response = callFastAPI(image, request); + + log.info("Received response: {}", response); + return response; + } + + private EmotionAnalysisResponse callFastAPI(MultipartFile image, EmotionAnalysisRequest request) { + try { + RequestBody requestBody = new MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("image", image.getOriginalFilename(), + RequestBody.create(image.getBytes(), MediaType.parse(image.getContentType()))) + .addFormDataPart("x1", String.valueOf(request.faceBox().x1())) + .addFormDataPart("y1", String.valueOf(request.faceBox().y1())) + .addFormDataPart("x2", String.valueOf(request.faceBox().x2())) + .addFormDataPart("y2", String.valueOf(request.faceBox().y2())) + .build(); + + Request fastApiRequest = new Request.Builder() + .url(emotionApiUrl) + .post(requestBody) + .addHeader("accept", "application/json") + .build(); + + try (Response response = client.newCall(fastApiRequest).execute()) { + if (!response.isSuccessful()) { + return new EmotionAnalysisResponse(HttpStatus.valueOf(response.code()), EmotionType.NONE); + } + + String responseBody = response.body().string(); + JsonNode jsonNode = new ObjectMapper().readTree(responseBody); + String emotionString = jsonNode.get(0).get("emotion").asText(); + EmotionType emotionType = EmotionType.valueOf(emotionString.toUpperCase()); + + log.info("Received response about api: {}", response); + return new EmotionAnalysisResponse(HttpStatus.OK, emotionType); + } + } catch (IOException e) { + log.error("FASTAPI ํ˜ธ์ถœ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ", e); + return new EmotionAnalysisResponse(HttpStatus.INTERNAL_SERVER_ERROR, EmotionType.NONE); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/emotion/service/impl/FaceDetectionServiceImpl.java b/src/main/java/com/bamboo/log/emotion/service/impl/FaceDetectionServiceImpl.java new file mode 100644 index 0000000..0175ac0 --- /dev/null +++ b/src/main/java/com/bamboo/log/emotion/service/impl/FaceDetectionServiceImpl.java @@ -0,0 +1,102 @@ +package com.bamboo.log.emotion.service.impl; + +import com.bamboo.log.emotion.dto.BoundingBox; +import com.bamboo.log.emotion.dto.FaceDetectionResponse; +import com.bamboo.log.emotion.service.FaceDetectionService; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class FaceDetectionServiceImpl implements FaceDetectionService { + + @Value("${elice.api.url.face}") + private String faceApiUrl; + + @Value("${elice.api.token}") + private String faceToken; + + private final OkHttpClient client = new OkHttpClient(); + + @Override + public FaceDetectionResponse detectFace(MultipartFile image) { + if (image == null || image.isEmpty()) { + log.error("ํŒŒ์ผ์ด ์ „๋‹ฌ๋˜์ง€ ์•Š์Œ."); + return new FaceDetectionResponse(HttpStatus.BAD_REQUEST, List.of()); + } + log.info("ํŒŒ์ผ ์ด๋ฆ„: {}", image.getOriginalFilename()); + log.info("ํŒŒ์ผ ํฌ๊ธฐ: {} bytes", image.getSize()); + + try { + String contentType = image.getContentType(); + if (contentType == null) { + contentType = "application/octet-stream"; // ๊ธฐ๋ณธ๊ฐ’ ์„ค์ • + } + + RequestBody requestBody = new MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("image", image.getOriginalFilename(), + RequestBody.create(image.getBytes(), MediaType.parse(contentType))) + .build(); + + // API ์š”์ฒญ ์ƒ์„ฑ (ํ† ํฐ ํฌํ•จ) + Request request = new Request.Builder() + .url(faceApiUrl) + .post(requestBody) + .addHeader("accept", "application/json") + .addHeader("Authorization", "Bearer " + faceToken) + .build(); + + try (Response response = client.newCall(request).execute()) { + log.info("API ์‘๋‹ต ์ฝ”๋“œ: {}", response.code()); + log.info("API ์‘๋‹ต ๋ฉ”์‹œ์ง€: {}", response.message()); + + if (!response.isSuccessful()) { + return new FaceDetectionResponse(HttpStatus.valueOf(response.code()), List.of()); + } + + String responseBody = response.body().string(); + log.info("API ์‘๋‹ต ๋ณธ๋ฌธ: {}", responseBody); + + // JSON ํŒŒ์‹ฑ + JsonNode jsonNode = new ObjectMapper().readTree(responseBody); + JsonNode results = jsonNode.get("results"); + + // ์–ผ๊ตด ์ธ์‹ ์—ฌ๋ถ€ ํ™•์ธ + List boxList = new ArrayList<>(); + if (results != null && results.isArray()) { + for (JsonNode result : results) { + if ("face".equals(result.get("name").asText())) { + JsonNode boxNode = result.get("box"); + if (boxNode != null) { + boxList.add(new BoundingBox( + boxNode.get("x1").asDouble(), + boxNode.get("y1").asDouble(), + boxNode.get("x2").asDouble(), + boxNode.get("y2").asDouble() + )); + } + } + } + } + return new FaceDetectionResponse(HttpStatus.OK, boxList); + } + } catch (IOException e) { + log.error("API ์š”์ฒญ ์ค‘ ์˜ˆ์™ธ ๋ฐœ์ƒ", e); + return new FaceDetectionResponse(HttpStatus.INTERNAL_SERVER_ERROR, List.of()); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/juksoon/controller/JuksoonController.java b/src/main/java/com/bamboo/log/juksoon/controller/JuksoonController.java new file mode 100644 index 0000000..b96dd38 --- /dev/null +++ b/src/main/java/com/bamboo/log/juksoon/controller/JuksoonController.java @@ -0,0 +1,46 @@ +package com.bamboo.log.juksoon.controller; + +import com.bamboo.log.domain.user.oauth.dto.CustomOAuth2User; +import com.bamboo.log.juksoon.dto.JuksooniResponse; +import com.bamboo.log.juksoon.service.JuksoonService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + + +@RestController +@RequestMapping("/api/juksooni") +@RequiredArgsConstructor +@Tag(name = "Juksooni", description = "์ฃฝ์ˆœ์ด ๋ถ„์–‘ ๊ด€๋ จ API") +public class JuksoonController { + private final JuksoonService juksoonService; + + @PostMapping("/adopt") + @Operation(summary = "์ฃฝ์ˆœ์ด ๋ถ„์–‘", description = "JWT ํ† ํฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.") + public ResponseEntity adoptJuksooni( + @AuthenticationPrincipal CustomOAuth2User customOAuth2User) { + + Long userId = customOAuth2User.getId(); + JuksooniResponse response = juksoonService.adoptJuksooni(userId); + return ResponseEntity.status(HttpStatus.CREATED).body(response); + } + + @GetMapping("/status") + @Operation(summary = "์ฃฝ์ˆœ์ด ๋ถ„์–‘ ์—ฌ๋ถ€ ์กฐํšŒ", description = "JWT ํ† ํฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.") + public ResponseEntity checkJuksooniStatus( + @AuthenticationPrincipal CustomOAuth2User customOAuth2User) { + + Long userId = customOAuth2User.getId(); + JuksooniResponse response = juksoonService.checkJuksooniStatus(userId); + + return ResponseEntity.ok(response); + + } +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/juksoon/domain/Juksoon.java b/src/main/java/com/bamboo/log/juksoon/domain/Juksoon.java new file mode 100644 index 0000000..bd8dfcc --- /dev/null +++ b/src/main/java/com/bamboo/log/juksoon/domain/Juksoon.java @@ -0,0 +1,22 @@ +package com.bamboo.log.juksoon.domain; + +import com.bamboo.log.domain.user.oauth.entity.UserEntity; +import jakarta.persistence.*; +import lombok.*; + +@Getter +@Entity +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "juksooni") +@Builder +public class Juksoon { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @OneToOne + @JoinColumn(name = "user_id", nullable = false, unique = true) + private UserEntity user; + +} diff --git a/src/main/java/com/bamboo/log/juksoon/dto/JuksooniResponse.java b/src/main/java/com/bamboo/log/juksoon/dto/JuksooniResponse.java new file mode 100644 index 0000000..cd4fe9b --- /dev/null +++ b/src/main/java/com/bamboo/log/juksoon/dto/JuksooniResponse.java @@ -0,0 +1,8 @@ +package com.bamboo.log.juksoon.dto; + +import org.springframework.http.HttpStatus; + +public record JuksooniResponse( + HttpStatus status, + boolean hasJuksooni +) { } diff --git a/src/main/java/com/bamboo/log/juksoon/repository/JuksoonRepository.java b/src/main/java/com/bamboo/log/juksoon/repository/JuksoonRepository.java new file mode 100644 index 0000000..8bf6a64 --- /dev/null +++ b/src/main/java/com/bamboo/log/juksoon/repository/JuksoonRepository.java @@ -0,0 +1,9 @@ +package com.bamboo.log.juksoon.repository; + +import com.bamboo.log.juksoon.domain.Juksoon; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface JuksoonRepository extends JpaRepository { + boolean existsByUserId(Long userId); + +} diff --git a/src/main/java/com/bamboo/log/juksoon/service/JuksoonService.java b/src/main/java/com/bamboo/log/juksoon/service/JuksoonService.java new file mode 100644 index 0000000..daf9999 --- /dev/null +++ b/src/main/java/com/bamboo/log/juksoon/service/JuksoonService.java @@ -0,0 +1,8 @@ +package com.bamboo.log.juksoon.service; + +import com.bamboo.log.juksoon.dto.JuksooniResponse; + +public interface JuksoonService { + JuksooniResponse adoptJuksooni(Long userId); + JuksooniResponse checkJuksooniStatus(Long userId); +} diff --git a/src/main/java/com/bamboo/log/juksoon/service/JuksoonServiceImpl.java b/src/main/java/com/bamboo/log/juksoon/service/JuksoonServiceImpl.java new file mode 100644 index 0000000..f866361 --- /dev/null +++ b/src/main/java/com/bamboo/log/juksoon/service/JuksoonServiceImpl.java @@ -0,0 +1,56 @@ +package com.bamboo.log.juksoon.service; + +import com.bamboo.log.domain.user.oauth.entity.UserEntity; +import com.bamboo.log.domain.user.oauth.repository.UserRepository; +import com.bamboo.log.juksoon.domain.Juksoon; +import com.bamboo.log.juksoon.dto.JuksooniResponse; +import com.bamboo.log.juksoon.repository.JuksoonRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class JuksoonServiceImpl implements JuksoonService{ + + private final JuksoonRepository juksoonRepository; + private final UserRepository userRepository; + + // ์ฃฝ์ˆœ์ด ๋ถ„์–‘ ๋กœ์ง + @Override + @Transactional + public JuksooniResponse adoptJuksooni(Long userId) { + if (userId == null) { + throw new IllegalArgumentException("์œ ์ € ID๊ฐ€ null์ž…๋‹ˆ๋‹ค."); + } + + // ์ด๋ฏธ ์ฃฝ์ˆœ์ด๋ฅผ ๋ถ„์–‘๋ฐ›์•˜๋Š”์ง€ ํ™•์ธ + if (juksoonRepository.existsByUserId(userId)) { + return new JuksooniResponse(HttpStatus.CONFLICT, false); + } + + // ์œ ์ € ์กฐํšŒ + UserEntity user = userRepository.findById(userId) + .orElseThrow(() -> new RuntimeException("ํ•ด๋‹น ์œ ์ €๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")); + + // ์ฃฝ์ˆœ์ด ์ƒ์„ฑ ๋ฐ ์ €์žฅ + Juksoon juksoon = Juksoon.builder() + .user(user) + .build(); + juksoonRepository.save(juksoon); + + return new JuksooniResponse(HttpStatus.CREATED, true); + } + + // ์ฃฝ์ˆœ์ด ๋ถ„์–‘ ์—ฌ๋ถ€ ์กฐํšŒ ๋กœ์ง + @Override + @Transactional(readOnly = true) + public JuksooniResponse checkJuksooniStatus(Long userId) { + boolean hasJuksooni = juksoonRepository.existsByUserId(userId); + + return new JuksooniResponse(HttpStatus.OK, hasJuksooni); + } +} diff --git a/src/main/java/com/bamboo/log/utils/ResponseHandler.java b/src/main/java/com/bamboo/log/utils/ResponseHandler.java new file mode 100644 index 0000000..c483a50 --- /dev/null +++ b/src/main/java/com/bamboo/log/utils/ResponseHandler.java @@ -0,0 +1,51 @@ +package com.bamboo.log.utils; + +import com.bamboo.log.utils.dto.DetailResponse; +import com.bamboo.log.utils.dto.ResponseForm; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +public class ResponseHandler { + + public static ResponseEntity create500Error(ResponseForm response, Exception e) { + response.of("result", "FAIL"); + response.of("error", DetailResponse.builder().code(500).message(e.getMessage()).build()); + return new ResponseEntity(response, HttpStatus.INTERNAL_SERVER_ERROR); + } + + public static ResponseEntity create400Error(ResponseForm response, Exception e) { + response.of("result", "FAIL"); + response.of("error", DetailResponse.builder().code(400).message(e.getMessage()).build()); + + return new ResponseEntity(response, HttpStatus.BAD_REQUEST); + } + + public static ResponseEntity create404Error(ResponseForm response, Exception e) { + response.of("result", "FAIL"); + response.of("error", DetailResponse.builder().code(404).message(e.getMessage()).build()); + + return new ResponseEntity(response, HttpStatus.NOT_FOUND); + } + + public static ResponseEntity create204Response(ResponseForm response, String message) { + response.of("result", "SUCCESS"); + response.of("code", DetailResponse.builder().code(204).message(message).build()); + + return new ResponseEntity(response, HttpStatus.NO_CONTENT); + } + + public static ResponseEntity create200Response(ResponseForm response, Object object) { + response.of("result", "SUCCESS"); + response.of("code", object); + + return new ResponseEntity(response, HttpStatus.OK); + } + + public static ResponseEntity create201Response(ResponseForm response, Object object) { + response.of("result", "SUCCESS"); + response.of("code", object); + + return new ResponseEntity(response, HttpStatus.CREATED); + } + +} diff --git a/src/main/java/com/bamboo/log/utils/dto/DetailResponse.java b/src/main/java/com/bamboo/log/utils/dto/DetailResponse.java new file mode 100644 index 0000000..61f22cf --- /dev/null +++ b/src/main/java/com/bamboo/log/utils/dto/DetailResponse.java @@ -0,0 +1,13 @@ +package com.bamboo.log.utils.dto; + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class DetailResponse { + + private Integer code; + private String message; + +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/utils/dto/ResponseForm.java b/src/main/java/com/bamboo/log/utils/dto/ResponseForm.java new file mode 100644 index 0000000..04eccf6 --- /dev/null +++ b/src/main/java/com/bamboo/log/utils/dto/ResponseForm.java @@ -0,0 +1,21 @@ +package com.bamboo.log.utils.dto; + +import lombok.Getter; + +import java.util.HashMap; +import java.util.Map; + +@Getter +public class ResponseForm { + + public ResponseForm() { + response = new HashMap<>(); + } + + public void of(String value1, Object value2) { + response.put(value1, value2); + } + + private Map response; + +} \ No newline at end of file diff --git a/src/main/resources/.DS_Store b/src/main/resources/.DS_Store new file mode 100644 index 0000000..8d6a5d0 Binary files /dev/null and b/src/main/resources/.DS_Store differ diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 356817f..125bc59 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,13 +1,46 @@ +sever: + port: ${SERVER_PORT} + spring: datasource: + driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/${MYSQL_NAME} username: ${MYSQL_USERNAME} password: ${MYSQL_PASSWORD} - driver-class-name: com.mysql.cj.jdbc.Driver + jpa: show-sql: true open-in-view: false hibernate: ddl-auto: update properties: - hibernate.dialect: org.hibernate.dialect.MySQL8Dialect \ No newline at end of file + hibernate.dialect: org.hibernate.dialect.MySQL8Dialect + security: + oauth2: + client: + registration: + kakao: + client-authentication-method: ${SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_CLIENT_AUTHENTICATION_METHOD} + client-name: ${SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_CLIENT_NAME} + client-id: ${SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_CLIENT_ID} + client-secret: ${SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_CLIENT_SECRET} + redirect-uri: ${SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_REDIRECT_URI} + authorization-grant-type: ${SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_AUTHORIZATION_GRANT_TYPE} + scope: ${SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_SCOPE} + provider: + kakao: + authorization-uri: ${SECURITY_OAUTH2_PROVIDER_KAKAO_AUTHORIZATION_URI} + token-uri: ${SECURITY_OAUTH2_PROVIDER_KAKAO_TOKEN_URI} + user-info-uri: ${SECURITY_OAUTH2_PROVIDER_KAKAO_USER_INFO_URI} + user-name-attribute: ${SECURITY_OAUTH2_PROVIDER_KAKAO_USER_NAME_ATTRIBUTE} + jwt: + secret: ${JWT_SECRET} +elice: + api: + token: ${API_TOKEN} + url: + face: ${FACE_URL} + img: ${IMG_URL} +emotion: + api: + url: ${EMOTION_URL} \ No newline at end of file