diff --git a/.github/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md similarity index 100% rename from .github/.github/ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE.md diff --git a/.github/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md similarity index 100% rename from .github/.github/PULL_REQUEST_TEMPLATE.md rename to .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000..25ca4f6 --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,121 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle + +name: Java CI with Gradle + +on: + push: + branches: [ "main", "dev" ] + pull_request: + branches: [ "main", "dev" ] + +jobs: + build_main: + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + + steps: + - name: checkout repository + uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'corretto' + + - name: make application-prod.yml + run: | + cd ./src/main/resources + touch ./application-prod.yml + echo "${{ secrets.APPLICATION_PROD }}" > ./application.yml + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build with Gradle + run: ./gradlew build + + - name: Build Docker image + run: docker build --platform linux/amd64 -t noparamin/watchboard . + + - name: Log in Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Publish to Docker Hub + run: docker push noparamin/watchboard:latest + + - name: Deploy with AWS SSM Send-Command + uses: peterkimzz/aws-ssm-send-command@v1.1.1 + id: ssm + with: + aws-region: ${{ secrets.AWS_REGION }} + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + instance-ids: ${{ secrets.INSTANCE_ID }} + working-directory: /usr/bin + command: | + docker pull noparamin/watchboard + docker stop $(docker ps -a -q) + docker run -d -p 8080:8080 noparamin/watchboard + docker rm $(docker ps --filter 'status=exited' -a -q) + docker image prune -a -f + + build_dev: + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/dev' + + steps: + - name: checkout repository + uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'corretto' + + - name: make application-dev.yml + run: | + cd ./src/main/resources + touch ./application-dev.yml + echo "${{ secrets.APPLICATION_DEV }}" > ./application.yml + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build with Gradle + run: ./gradlew build + + - name: Build Docker image + run: docker build --platform linux/amd64 -t noparamin/watchboard:test . + + - name: Log in Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Publish to Docker Hub + run: docker push noparamin/watchboard:test + + - name: Deploy with AWS SSM Send-Command + uses: peterkimzz/aws-ssm-send-command@v1.1.1 + id: ssm + with: + aws-region: ${{ secrets.AWS_REGION }} + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + instance-ids: ${{ secrets.INSTANCE_ID }} + working-directory: /usr/bin + command: | + docker pull noparamin/watchboard:test + docker stop $(docker ps -a -q) + docker run -d -p 8081:8081 noparamin/watchboard:test + docker rm $(docker ps --filter 'status=exited' -a -q) + docker image prune -a -f diff --git a/.gitignore b/.gitignore index 3a1a866..3bf0ae7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,42 +1,40 @@ -*# -*.iml -*.ipr -*.iws -*.jar -*.sw? -*~ -.#* -.*.md.html -.DS_Store -.attach_pid* +<<<<<<< HEAD +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated .classpath .factorypath -.gradle -.idea -.metadata .project -.recommenders .settings .springBeans -.vscode -/code -MANIFEST.MF -_site/ -activemq-data -bin -build -!/**/src/**/bin -!/**/src/**/build -build.log -dependency-reduced-pom.xml -dump.rdb -interpolated*.xml -lib/ -manifest.yml -out -overridedb.* -target -.flattened-pom.xml -secrets.yml -.gradletasknamecache -.sts4-cache \ No newline at end of file +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +application-dev.yml +application-prod.yml +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..357b3a0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,4 @@ +FROM amazoncorretto:17 +ARG JAR_FILE=./build/libs/*-SNAPSHOT.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["java","-jar","/app.jar"] \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..764993f --- /dev/null +++ b/build.gradle @@ -0,0 +1,56 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.1.1' + id 'io.spring.dependency-management' version '1.1.0' +} + +group = 'com.smart' +version = '0.0.1-SNAPSHOT' + +java { + sourceCompatibility = '17' +} + +configurations { + compileOnly { + extendsFrom annotationProcessor + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'mysql:mysql-connector-java:8.0.32' + implementation "org.springframework.boot:spring-boot-starter-data-mongodb" + + implementation "org.springframework.boot:spring-boot-starter-security" + implementation "org.springframework.boot:spring-boot-starter-oauth2-client" + implementation "org.springframework.security:spring-security-test" + + implementation 'org.springframework.boot:spring-boot-starter-websocket' + implementation 'io.nats:nats-spring-boot-starter:0.5.6' + implementation 'org.webjars:stomp-websocket:2.3.4' + + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + implementation 'com.auth0:java-jwt:4.4.0' + + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' + + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' + + implementation 'com.itextpdf:itextpdf:5.5.13.3' + + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..c1962a7 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..37aef8d --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..aeb74cb --- /dev/null +++ b/gradlew @@ -0,0 +1,245 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..6689b85 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..46e4c21 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'watchboard' diff --git a/src/main/java/com/smart/watchboard/WatchboardApplication.java b/src/main/java/com/smart/watchboard/WatchboardApplication.java new file mode 100644 index 0000000..8eae708 --- /dev/null +++ b/src/main/java/com/smart/watchboard/WatchboardApplication.java @@ -0,0 +1,12 @@ +package com.smart.watchboard; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +@SpringBootApplication +public class WatchboardApplication { + + public static void main(String[] args) { + SpringApplication.run(WatchboardApplication.class, args); + } + +} diff --git a/src/main/java/com/smart/watchboard/common/enums/Role.java b/src/main/java/com/smart/watchboard/common/enums/Role.java new file mode 100644 index 0000000..76b49f8 --- /dev/null +++ b/src/main/java/com/smart/watchboard/common/enums/Role.java @@ -0,0 +1,12 @@ +package com.smart.watchboard.common.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum Role { + ADMIN("ROLE_ADMIN"), USER("ROLE_USER"); + + private final String key; +} diff --git a/src/main/java/com/smart/watchboard/common/enums/SocialType.java b/src/main/java/com/smart/watchboard/common/enums/SocialType.java new file mode 100644 index 0000000..799a336 --- /dev/null +++ b/src/main/java/com/smart/watchboard/common/enums/SocialType.java @@ -0,0 +1,5 @@ +package com.smart.watchboard.common.enums; + +public enum SocialType { + KAKAO, NAVER, GOOGLE +} diff --git a/src/main/java/com/smart/watchboard/common/filter/JwtAuthenticationProcessingFilter.java b/src/main/java/com/smart/watchboard/common/filter/JwtAuthenticationProcessingFilter.java new file mode 100644 index 0000000..aaf177f --- /dev/null +++ b/src/main/java/com/smart/watchboard/common/filter/JwtAuthenticationProcessingFilter.java @@ -0,0 +1,149 @@ +package com.smart.watchboard.common.filter; + +import com.smart.watchboard.domain.User; +import com.smart.watchboard.repository.UserRepository; +import com.smart.watchboard.service.JwtService; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseCookie; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; +import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Optional; + +@RequiredArgsConstructor +@Slf4j +public class JwtAuthenticationProcessingFilter extends OncePerRequestFilter { + private final JwtService jwtService; + private final UserRepository userRepository; + + private static final String NO_CHECK_URL = "/login"; + private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + if (request.getRequestURI().equals(NO_CHECK_URL)) { + filterChain.doFilter(request, response); + return; // return으로 이후 현재 필터 진행 막기 (안해주면 아래로 내려가서 계속 필터 진행시킴) + } + // 사용자 요청 헤더에서 RefreshToken 추출 + // -> RefreshToken이 없거나 유효하지 않다면(DB에 저장된 RefreshToken과 다르다면) null을 반환 + // 사용자의 요청 헤더에 RefreshToken이 있는 경우는, AccessToken이 만료되어 요청한 경우밖에 없다. + // 따라서, 위의 경우를 제외하면 추출한 refreshToken은 모두 null + String refreshToken = jwtService.extractRefreshToken(request) + .filter(jwtService::isTokenValid) + .orElse(null); + + + // 리프레시 토큰이 요청 헤더에 존재했다면, 사용자가 AccessToken이 만료되어서 + // RefreshToken까지 보낸 것이므로 리프레시 토큰이 DB의 리프레시 토큰과 일치하는지 판단 후, + // 일치한다면 AccessToken을 재발급해준다. + if (refreshToken != null) { + checkRefreshTokenAndReIssueAccessToken(response, refreshToken); + return; // RefreshToken을 보낸 경우에는 AccessToken을 재발급 하고 인증 처리는 하지 않게 하기위해 바로 return으로 필터 진행 막기 + } + + // RefreshToken이 없거나 유효하지 않다면, AccessToken을 검사하고 인증을 처리하는 로직 수행 + // AccessToken이 없거나 유효하지 않다면, 인증 객체가 담기지 않은 상태로 다음 필터로 넘어가기 때문에 403 에러 발생 + // AccessToken이 유효하다면, 인증 객체가 담긴 상태로 다음 필터로 넘어가기 때문에 인증 성공 + if (refreshToken == null) { + checkAccessTokenAndAuthentication(request, response, filterChain); + } + } + + /** + * [리프레시 토큰으로 유저 정보 찾기 & 액세스 토큰/리프레시 토큰 재발급 메소드] + * 파라미터로 들어온 헤더에서 추출한 리프레시 토큰으로 DB에서 유저를 찾고, 해당 유저가 있다면 + * JwtService.createAccessToken()으로 AccessToken 생성, + * reIssueRefreshToken()로 리프레시 토큰 재발급 & DB에 리프레시 토큰 업데이트 메소드 호출 + * 그 후 JwtService.sendAccessTokenAndRefreshToken()으로 응답 헤더에 보내기 + */ + public void checkRefreshTokenAndReIssueAccessToken(HttpServletResponse response, String refreshToken) throws UnsupportedEncodingException { + if (jwtService.isTokenValid(refreshToken)) { + String reIssuedRefreshToken = reIssueRefreshToken(jwtService.extractUserId(refreshToken).get()); + jwtService.sendAccessAndRefreshToken(response, jwtService.createAccessToken(jwtService.extractUserId(refreshToken).get()), + reIssuedRefreshToken); + } + Optional userId = jwtService.extractUserId(refreshToken); + userRepository.findById(userId) + .ifPresent(user -> { + String reIssuedRefreshToken = reIssueRefreshToken(user.getId()); + try { + jwtService.sendAccessAndRefreshToken(response, jwtService.createAccessToken(user.getId()), + reIssuedRefreshToken); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + }); + } + + /** + * [리프레시 토큰 재발급 & DB에 리프레시 토큰 업데이트 메소드] + * jwtService.createRefreshToken()으로 리프레시 토큰 재발급 후 + * DB에 재발급한 리프레시 토큰 업데이트 후 Flush + */ + public String reIssueRefreshToken(Long userId) { + String reIssuedRefreshToken = jwtService.createRefreshToken(userId); + return reIssuedRefreshToken; + } + + /** + * [액세스 토큰 체크 & 인증 처리 메소드] + * request에서 extractAccessToken()으로 액세스 토큰 추출 후, isTokenValid()로 유효한 토큰인지 검증 + * 유효한 토큰이면, 액세스 토큰에서 extractEmail로 Email을 추출한 후 findByEmail()로 해당 이메일을 사용하는 유저 객체 반환 + * 그 유저 객체를 saveAuthentication()으로 인증 처리하여 + * 인증 허가 처리된 객체를 SecurityContextHolder에 담기 + * 그 후 다음 인증 필터로 진행 + */ + public void checkAccessTokenAndAuthentication(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + log.info("checkAccessTokenAndAuthentication() 호출"); + jwtService.extractAccessToken(request) + .filter(jwtService::isTokenValid) + .ifPresent(accessToken -> jwtService.extractUserId(accessToken) + .ifPresent(userId -> userRepository.findById(userId) + .ifPresent(this::saveAuthentication))); + filterChain.doFilter(request, response); + } + + /** + * [인증 허가 메소드] + * 파라미터의 유저 : 우리가 만든 회원 객체 / 빌더의 유저 : UserDetails의 User 객체 + *

+ * new UsernamePasswordAuthenticationToken()로 인증 객체인 Authentication 객체 생성 + * UsernamePasswordAuthenticationToken의 파라미터 + * 1. 위에서 만든 UserDetailsUser 객체 (유저 정보) + * 2. credential(보통 비밀번호로, 인증 시에는 보통 null로 제거) + * 3. Collection < ? extends GrantedAuthority>로, + * UserDetails의 User 객체 안에 Set authorities이 있어서 getter로 호출한 후에, + * new NullAuthoritiesMapper()로 GrantedAuthoritiesMapper 객체를 생성하고 mapAuthorities()에 담기 + *

+ * SecurityContextHolder.getContext()로 SecurityContext를 꺼낸 후, + * setAuthentication()을 이용하여 위에서 만든 Authentication 객체에 대한 인증 허가 처리 + */ + public void saveAuthentication(User myUser) { + String password = "asd"; + UserDetails userDetailsUser = org.springframework.security.core.userdetails.User.builder() + .username(myUser.getEmail()) + .password(password) + .roles(myUser.getRole().name()) + .build(); + + Authentication authentication = + new UsernamePasswordAuthenticationToken(userDetailsUser, null, + authoritiesMapper.mapAuthorities(userDetailsUser.getAuthorities())); + log.info("saveAuthentication"); + SecurityContextHolder.getContext().setAuthentication(authentication); + } +} diff --git a/src/main/java/com/smart/watchboard/common/oauth/CustomOAuth2User.java b/src/main/java/com/smart/watchboard/common/oauth/CustomOAuth2User.java new file mode 100644 index 0000000..25a9c0e --- /dev/null +++ b/src/main/java/com/smart/watchboard/common/oauth/CustomOAuth2User.java @@ -0,0 +1,37 @@ +package com.smart.watchboard.common.oauth; + +import com.smart.watchboard.common.enums.Role; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; + +import java.util.Collection; +import java.util.Map; + +@Getter +@Slf4j +public class CustomOAuth2User extends DefaultOAuth2User { + private Long userId; + private String email; + private Role role; + + /** + * Constructs a {@code DefaultOAuth2User} using the provided parameters. + * + * @param authorities the authorities granted to the user + * @param attributes the attributes about the user + * @param nameAttributeKey the key used to access the user's "name" from + * {@link #getAttributes()} + */ + public CustomOAuth2User(Collection authorities, + Map attributes, String nameAttributeKey, + Long userId, + String email, Role role) { + super(authorities, attributes, nameAttributeKey); + this.userId = userId; + this.email = email; + this.role = role; + } + +} diff --git a/src/main/java/com/smart/watchboard/common/oauth/KakaoOAuth2UserInfo.java b/src/main/java/com/smart/watchboard/common/oauth/KakaoOAuth2UserInfo.java new file mode 100644 index 0000000..6b198e1 --- /dev/null +++ b/src/main/java/com/smart/watchboard/common/oauth/KakaoOAuth2UserInfo.java @@ -0,0 +1,38 @@ +package com.smart.watchboard.common.oauth; + +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; + +@Slf4j +public class KakaoOAuth2UserInfo extends OAuth2UserInfo { + public KakaoOAuth2UserInfo(Map attributes) { + super(attributes); + } + + @Override + public String getId() { + return String.valueOf(attributes.get("id")); + } + + @Override + public String getNickname() { + Map account = (Map) attributes.get("kakao_account"); + Map profile = (Map) account.get("profile"); + + if (account == null || profile == null) { + return null; + } + + return (String) profile.get("nickname"); + } + + @Override + public String getEmail() { + Map account = (Map) attributes.get("kakao_account"); + if (account == null) { + return null; + } + return (String) account.get("email"); + } +} diff --git a/src/main/java/com/smart/watchboard/common/oauth/OAuth2UserInfo.java b/src/main/java/com/smart/watchboard/common/oauth/OAuth2UserInfo.java new file mode 100644 index 0000000..2547479 --- /dev/null +++ b/src/main/java/com/smart/watchboard/common/oauth/OAuth2UserInfo.java @@ -0,0 +1,20 @@ +package com.smart.watchboard.common.oauth; + +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; + +@Slf4j +public abstract class OAuth2UserInfo { + protected Map attributes; + + public OAuth2UserInfo(Map attributes) { + this.attributes = attributes; + } + + public abstract String getId(); //소셜 식별 값 : 구글 - "sub", 카카오 - "id", 네이버 - "id" + + public abstract String getNickname(); + + public abstract String getEmail(); +} diff --git a/src/main/java/com/smart/watchboard/common/support/AudioConcatenator.java b/src/main/java/com/smart/watchboard/common/support/AudioConcatenator.java new file mode 100644 index 0000000..4b77812 --- /dev/null +++ b/src/main/java/com/smart/watchboard/common/support/AudioConcatenator.java @@ -0,0 +1,115 @@ +package com.smart.watchboard.common.support; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +@Service +@Transactional +@RequiredArgsConstructor +public class AudioConcatenator { + + private final AwsS3Uploader awsS3Uploader; + + public void concatenateAudioFiles(MultipartFile multipartFile1, Long documentId, Long fileId) throws IOException { +// 오디오 파일 처음 만드는거면 파일을 먼저 생성해야함, 파일 존재하면 파일 아이디도 있다. + // Read the data from the first MultipartFile + byte[] data1 = multipartFile1.getBytes(); + + // Read the data from the second MultipartFile + //byte[] data2 = multipartFile2.getBytes(); + String outputPath = "/Users/kms/Downloads/output.wav"; + String fileName = "output.wav"; // 파일 이름 + String contentType = "audio/wav"; // 컨텐츠 타입 + + // s3업로드 이어붙인 후 어떻게? multipartfile로 바꿔야 하나 + if (fileId != null) { + byte[] data2 = awsS3Uploader.getFileContent(fileId); + // Merge the data into a single byte array + byte[] mergedData = mergeWavData(data2, data1); + MultipartFile multipartFile = convertByteArrayToMultipartFile(mergedData, fileName, contentType); + //awsS3Uploader.uploadFile(multipartFile, documentId, fileId); + writeWavFile(outputPath, mergedData); + } else { + //awsS3Uploader.uploadFile(multipartFile1, documentId, fileId); + writeWavFile(outputPath, data1); + } + +// // Merge the data into a single byte array +// byte[] mergedData = mergeWavData(data1, data2); +// +// // Create a new WAV file and write the combined data to it +// String outputPath = "/Users/kms/Downloads/output.wav"; +// writeWavFile(outputPath, mergedData); + } + + private byte[] mergeWavData(byte[] data1, byte[] data2) { + // Calculate the total length of the merged WAV file + int totalLength = data1.length + data2.length - 44; // Subtract 44 bytes for the header + + // Create a byte array for the merged WAV data + byte[] mergedData = new byte[totalLength]; + + // Copy the first WAV data (excluding the header) + System.arraycopy(data1, 44, mergedData, 0, data1.length - 44); + + // Copy the second WAV data (excluding the header) + System.arraycopy(data2, 44, mergedData, data1.length - 44, data2.length - 44); + + return mergedData; + } + + private void writeWavFile(String outputFilePath, byte[] mergedData) throws IOException { + // Create a new WAV file + try (FileOutputStream fos = new FileOutputStream(outputFilePath)) { + // Write the WAV file header + byte[] header = createWavHeader(mergedData.length); + fos.write(header); + + // Write the merged WAV data (excluding the header) + fos.write(mergedData); + } + } + + private byte[] createWavHeader(int dataLength) { + byte[] header = new byte[44]; + + // WAV header constants + String riffHeader = "RIFF"; + String waveHeader = "WAVEfmt "; + int headerSize = 16; + short audioFormat = 1; // PCM + short numChannels = 1; // Mono + int sampleRate = 44100; // 44.1 kHz + int bitsPerSample = 16; // Bits per sample + + // Calculate the file size excluding the first 8 bytes (RIFF and file size) + int fileSize = dataLength + 36; + + // Fill in the header + System.arraycopy(riffHeader.getBytes(), 0, header, 0, 4); + ByteBuffer.wrap(header, 4, 4).order(ByteOrder.LITTLE_ENDIAN).putInt(fileSize); + System.arraycopy(waveHeader.getBytes(), 0, header, 8, 8); + ByteBuffer.wrap(header, 16, 4).order(ByteOrder.LITTLE_ENDIAN).putInt(headerSize); + ByteBuffer.wrap(header, 20, 2).order(ByteOrder.LITTLE_ENDIAN).putShort(audioFormat); + ByteBuffer.wrap(header, 22, 2).order(ByteOrder.LITTLE_ENDIAN).putShort(numChannels); + ByteBuffer.wrap(header, 24, 4).order(ByteOrder.LITTLE_ENDIAN).putInt(sampleRate); + ByteBuffer.wrap(header, 28, 4).order(ByteOrder.LITTLE_ENDIAN).putInt(sampleRate * numChannels * bitsPerSample / 8); + ByteBuffer.wrap(header, 32, 2).order(ByteOrder.LITTLE_ENDIAN).putShort((short)(numChannels * bitsPerSample / 8)); + ByteBuffer.wrap(header, 34, 2).order(ByteOrder.LITTLE_ENDIAN).putShort((short) bitsPerSample); + System.arraycopy("data".getBytes(), 0, header, 36, 4); + ByteBuffer.wrap(header, 40, 4).order(ByteOrder.LITTLE_ENDIAN).putInt(dataLength); + + return header; + } + + public MultipartFile convertByteArrayToMultipartFile(byte[] byteArray, String fileName, String contentType) { + return new MockMultipartFile(fileName, fileName, contentType, byteArray); + } +} diff --git a/src/main/java/com/smart/watchboard/common/support/AwsS3Uploader.java b/src/main/java/com/smart/watchboard/common/support/AwsS3Uploader.java new file mode 100644 index 0000000..21d2e65 --- /dev/null +++ b/src/main/java/com/smart/watchboard/common/support/AwsS3Uploader.java @@ -0,0 +1,114 @@ +package com.smart.watchboard.common.support; + +import com.amazonaws.AmazonServiceException; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.model.*; +import com.smart.watchboard.domain.File; +import com.smart.watchboard.domain.Note; +import com.smart.watchboard.dto.FileDto; +import com.smart.watchboard.dto.S3Dto; +import com.smart.watchboard.service.FileService; +import com.smart.watchboard.service.NoteService; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Optional; + + +@Service +@Transactional +@RequiredArgsConstructor +@Slf4j +public class AwsS3Uploader { + private static final String S3_BUCKET_DIRECTORY_NAME = "file"; + private final AmazonS3Client amazonS3Client; + private final FileService fileService; + private final NoteService noteService; + + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + public String uploadFile(S3Dto s3Dto) { + ObjectMetadata objectMetadata = new ObjectMetadata(); + objectMetadata.setContentType(s3Dto.getFile().getContentType()); + objectMetadata.setContentLength(s3Dto.getFile().getSize()); + + String directoryName = s3Dto.getFile().getContentType(); + String fileName = directoryName + "/" + s3Dto.getUserId() + "_" + s3Dto.getDocumentId() + "." + s3Dto.getDataType(); + + try (InputStream inputStream = s3Dto.getFile().getInputStream()) { + amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, inputStream, objectMetadata) + .withCannedAcl(CannedAccessControlList.PublicRead)); + + String path = amazonS3Client.getResourceUrl(bucket, fileName); + FileDto fileDto = new FileDto(s3Dto.getFile(), path, s3Dto.getFile().getContentType(), s3Dto.getDocumentId()); + File file = fileService.findFileByDocument(s3Dto.getDocumentId()); + if (file == null) { + fileService.createFile(fileDto); + } else if (file != null) { + fileService.updateFile(fileDto); + } + } catch (IOException e) { + log.error("S3 파일 업로드에 실패했습니다. {}", e.getMessage()); + throw new IllegalStateException("S3 파일 업로드에 실패했습니다."); + } + return amazonS3Client.getUrl(bucket, fileName).toString(); + } + + public String uploadTextPdfFile(S3Dto s3Dto) { + ObjectMetadata objectMetadata = new ObjectMetadata(); + objectMetadata.setContentType(s3Dto.getFile().getContentType()); + objectMetadata.setContentLength(s3Dto.getFile().getSize()); + + String directoryName = s3Dto.getFile().getContentType(); + String fileName = directoryName + "/" + s3Dto.getUserId() + "_" + s3Dto.getDocumentId() + "." + s3Dto.getDataType(); + + try (InputStream inputStream = s3Dto.getFile().getInputStream()) { + amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, inputStream, objectMetadata) + .withCannedAcl(CannedAccessControlList.PublicRead)); + + String path = amazonS3Client.getResourceUrl(bucket, fileName); + FileDto fileDto = new FileDto(s3Dto.getFile(), path, s3Dto.getFile().getContentType(), s3Dto.getDocumentId()); + Note note = noteService.findByDocument(s3Dto.getDocumentId()); + if (note == null) { + noteService.createNote(fileDto); + } else if (note != null) { + noteService.updateNote(fileDto); + } + } catch (IOException e) { + log.error("S3 파일 업로드에 실패했습니다. {}", e.getMessage()); + throw new IllegalStateException("S3 파일 업로드에 실패했습니다."); + } + return amazonS3Client.getUrl(bucket, fileName).toString(); + } + + public byte[] getFileContent(Long fileId) throws IOException { + String objectKey = fileService.findFile(fileId).get().getObjectKey(); + S3Object s3Object = amazonS3Client.getObject(bucket, objectKey); + S3ObjectInputStream objectInputStream = s3Object.getObjectContent(); + + return objectInputStream.readAllBytes(); + } + + public void deleteFile(String filePath) { + try{ + try { + amazonS3Client.deleteObject(bucket, filePath); + } catch (AmazonServiceException e) { + log.info(e.getErrorMessage()); + } + + } catch (Exception exception) { + log.info(exception.getMessage()); + } + log.info("[S3Uploader] : S3에 있는 파일 삭제"); + } + + +} diff --git a/src/main/java/com/smart/watchboard/common/support/PdfConverter.java b/src/main/java/com/smart/watchboard/common/support/PdfConverter.java new file mode 100644 index 0000000..941013b --- /dev/null +++ b/src/main/java/com/smart/watchboard/common/support/PdfConverter.java @@ -0,0 +1,60 @@ +package com.smart.watchboard.common.support; + +import com.itextpdf.text.*; +import com.itextpdf.text.pdf.BaseFont; +import com.itextpdf.text.pdf.PdfReader; +import com.itextpdf.text.pdf.PdfWriter; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +public class PdfConverter { + public static File convertStringToPdf(String content, String fileName) throws DocumentException, IOException { + Document document = new Document(); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + + try { + BaseFont objBaseFont = BaseFont.createFont("src/main/resources/templates/NanumGothic.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED); + Font objFont = new Font(objBaseFont, 12); + //Font koreanFont = FontFactory.getFont("NanumGothic", BaseFont.IDENTITY_H, BaseFont.EMBEDDED); + PdfWriter.getInstance(document, byteArrayOutputStream); + document.open(); + document.add(new Paragraph(content, objFont)); + } finally { + document.close(); + } + + byte[] pdfBytes = byteArrayOutputStream.toByteArray(); + return saveByteArrayToFile(pdfBytes, fileName); + } + + public static File saveByteArrayToFile(byte[] bytes, String fileName) throws IOException { + File outputFile = new File(fileName); + try (FileOutputStream fos = new FileOutputStream(outputFile)) { + fos.write(bytes); + } + return outputFile; + } + + public static Integer countPdfPage(MultipartFile pdfFile) throws IOException { + byte[] bytes = pdfFile.getBytes(); + PdfReader reader = new PdfReader(bytes); + + // PDF 파일 전체 페이지 수 구하기 + int pageCount = reader.getNumberOfPages(); + + return pageCount; + } + + public static Boolean checkPageLimit(int page) { + if (page > 30) { + return false; + } + return true; + } +} diff --git a/src/main/java/com/smart/watchboard/config/S3Config.java b/src/main/java/com/smart/watchboard/config/S3Config.java new file mode 100644 index 0000000..9d0f3a7 --- /dev/null +++ b/src/main/java/com/smart/watchboard/config/S3Config.java @@ -0,0 +1,29 @@ +package com.smart.watchboard.config; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class S3Config { + @Value("${cloud.aws.credentials.access-key}") + private String iamAccessKey; + @Value("${cloud.aws.credentials.secret-key}") + private String iamSecretKey; + @Value("${cloud.aws.region.static}") + private String region; + + @Bean + public AmazonS3Client amazonS3Client(){ + BasicAWSCredentials awsCredentials = new BasicAWSCredentials(iamAccessKey, iamSecretKey); + + return (AmazonS3Client) AmazonS3ClientBuilder.standard() + .withRegion(region).enablePathStyleAccess() + .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) + .build(); + } +} diff --git a/src/main/java/com/smart/watchboard/config/SecurityConfig.java b/src/main/java/com/smart/watchboard/config/SecurityConfig.java new file mode 100644 index 0000000..b601867 --- /dev/null +++ b/src/main/java/com/smart/watchboard/config/SecurityConfig.java @@ -0,0 +1,75 @@ +package com.smart.watchboard.config; + +import com.smart.watchboard.common.filter.JwtAuthenticationProcessingFilter; +import com.smart.watchboard.handler.OAuth2AuthenticationFailureHandler; +import com.smart.watchboard.handler.OAuth2AuthenticationSuccessHandler; +import com.smart.watchboard.repository.UserRepository; +import com.smart.watchboard.service.CustomOAuth2UserService; +import com.smart.watchboard.service.JwtService; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +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.web.SecurityFilterChain; +import org.springframework.security.web.authentication.logout.LogoutFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.Arrays; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + @Value("${front-url}") + private String frontUrl; + + private final OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler; + private final OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler; + private final CustomOAuth2UserService customOAuth2UserService; + private final JwtService jwtService; + private final UserRepository userRepository; + + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.formLogin().disable(); + http.httpBasic().disable(); + http.csrf().disable(); + http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); + http.cors().configurationSource(corsConfigurationSource()).and().authorizeHttpRequests(authorize -> authorize.requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html", "/users/token").permitAll() + .anyRequest().authenticated()); + http.oauth2Login() + .successHandler(oAuth2AuthenticationSuccessHandler) + .failureHandler(oAuth2AuthenticationFailureHandler) + .userInfoEndpoint() + .userService(customOAuth2UserService); + + http.addFilterAfter(jwtAuthenticationProcessingFilter(), LogoutFilter.class); + + return http.build(); + } + + @Bean + CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")); + configuration.setAllowedHeaders(Arrays.asList("*")); + configuration.setExposedHeaders(Arrays.asList("Authorization")); + configuration.setAllowedOrigins(Arrays.asList(frontUrl)); // 추후 수정 + configuration.setAllowCredentials(true); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } + + @Bean + public JwtAuthenticationProcessingFilter jwtAuthenticationProcessingFilter() { + JwtAuthenticationProcessingFilter jwtAuthenticationProcessingFilter = new JwtAuthenticationProcessingFilter(jwtService, userRepository); + return jwtAuthenticationProcessingFilter; + } +} diff --git a/src/main/java/com/smart/watchboard/config/SwaggerConfig.java b/src/main/java/com/smart/watchboard/config/SwaggerConfig.java new file mode 100644 index 0000000..a435e2c --- /dev/null +++ b/src/main/java/com/smart/watchboard/config/SwaggerConfig.java @@ -0,0 +1,37 @@ +package com.smart.watchboard.config; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI openAPI() { + String jwtSchemeName = "jwtAuth"; + SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwtSchemeName); + Components components = new Components() + .addSecuritySchemes(jwtSchemeName, new SecurityScheme() + .name(jwtSchemeName) + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT")); + + return new OpenAPI() + .info(apiInfo()) + .addSecurityItem(securityRequirement) + .components(components); + } + + private Info apiInfo() { + return new Info() + .title("WatchBoard API") + .description("WatchBoard API Swagger UI") + .version("1.0.0"); + } +} diff --git a/src/main/java/com/smart/watchboard/config/WebConfig.java b/src/main/java/com/smart/watchboard/config/WebConfig.java new file mode 100644 index 0000000..97e2c43 --- /dev/null +++ b/src/main/java/com/smart/watchboard/config/WebConfig.java @@ -0,0 +1,37 @@ +package com.smart.watchboard.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class WebConfig { + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + +// @Autowired +// CloseableHttpClient httpClient; +// +// @Value("${api.host.baseurl}") +// private String apiHost; +// +// @Bean +// public RestTemplate restTemplate() { +// +// RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory()); +// restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(apiHost)); +// return restTemplate; +// } +// +// @Bean +// @ConditionalOnMissingBean +// public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() { +// +// HttpComponentsClientHttpRequestFactory clientHttpRequestFactory +// = new HttpComponentsClientHttpRequestFactory(); +// clientHttpRequestFactory.setHttpClient(httpClient); +// return clientHttpRequestFactory; +// } +} diff --git a/src/main/java/com/smart/watchboard/controller/AudioFileController.java b/src/main/java/com/smart/watchboard/controller/AudioFileController.java new file mode 100644 index 0000000..24efb22 --- /dev/null +++ b/src/main/java/com/smart/watchboard/controller/AudioFileController.java @@ -0,0 +1,152 @@ +package com.smart.watchboard.controller; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.itextpdf.text.DocumentException; +import com.smart.watchboard.common.support.AwsS3Uploader; +import com.smart.watchboard.domain.SttData; +import com.smart.watchboard.dto.*; +import com.smart.watchboard.service.*; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.sound.sampled.UnsupportedAudioFileException; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Optional; + +import static com.smart.watchboard.common.support.PdfConverter.*; + + +@RestController +@RequestMapping("/documents") +@Tag(name = "강의 녹음파일 API", description = "강의 녹음파일 관련 API") +@RequiredArgsConstructor +@Slf4j +public class AudioFileController { + private final AwsS3Uploader awsS3Uploader; + private final NoteService noteService; + private final LectureNoteService lectureNoteService; + //private final RequestService requestService; + private final STTService sttService; + private final SummaryService summaryService; + private final FileService fileService; + private final MindmapService mindmapService; + private final WhiteboardService whiteboardService; + private final JwtService jwtService; + private final SseService sseService; + + @PostMapping("/{documentID}/audio") + public ResponseEntity uploadAudioFile(@PathVariable(value = "documentID") long documentId, @RequestParam("audio") MultipartFile audioFile, @RequestHeader("Authorization") String accessToken) throws UnsupportedAudioFileException, IOException, DocumentException { + Optional id = jwtService.extractUserId(accessToken); + Long userId = id.orElse(null); + + // s3에 오디오 파일 저장 + S3Dto s3Dto = new S3Dto(audioFile, documentId, userId, "mp3"); + String path = awsS3Uploader.uploadFile(s3Dto); + sseService.notifySTT(documentId, path, userId); + + whiteboardService.setDataType(documentId, "audio"); + + // 응답 키워드, stt + return new ResponseEntity<>(HttpStatus.OK); + } + + @PutMapping("/{documentID}/audio") + public ResponseEntity updateAudioFile(@PathVariable(value = "documentID") long documentId, @RequestParam("audio") MultipartFile audioFile, @RequestHeader("Authorization") String accessToken) throws UnsupportedAudioFileException, IOException, DocumentException { + Optional id = jwtService.extractUserId(accessToken); + Long userId = id.orElse(null); + + S3Dto s3Dto = new S3Dto(audioFile, documentId, userId, "mp3"); + String path = awsS3Uploader.uploadFile(s3Dto); + sseService.notifySTT(documentId, path, userId); + + whiteboardService.setDataType(documentId, "audio"); + + return new ResponseEntity<>(HttpStatus.OK); + } + + @DeleteMapping("/{documentID}/audio") + public ResponseEntity deleteAudioFile(@PathVariable(value = "documentID") long documentId, @RequestHeader("Authorization") String accessToken) throws UnsupportedAudioFileException, IOException { + fileService.deleteAudioFile(documentId); + // 음성 파일 삭제 했을 때, Stt, 요약본, 마인드맵 모두 삭제 처리 + noteService.deleteNote(documentId); + summaryService.deleteSummary(documentId); + // 마인드맵 삭제 구현 + mindmapService.deleteMindmap(documentId); + + return new ResponseEntity<>(HttpStatus.OK); + } + + @GetMapping("/{documentID}/audio") + public ResponseEntity getAudioFile(@PathVariable(value = "documentID") long documentId, @RequestHeader("Authorization") String accessToken) throws JsonProcessingException { + String path = fileService.getPath(documentId); + List data = lectureNoteService.getData(documentId); + SttDto body = fileService.createResponseBody(path, data); + ResponseEntity responseEntity = new ResponseEntity<>(body, HttpStatus.OK); + return responseEntity; + } + + @PostMapping("/testffff") + public ResponseEntity test(@RequestHeader("Authorization") String accessToken) throws UnsupportedAudioFileException, IOException, DocumentException { +// String body = """ +// {"keywords":["eatssss","food","today"]} +// """; +// String path = "https://watchboard-record-bucket.s3.ap-northeast-2.amazonaws.com/audio/mp4/26.1.m4a"; +// ResponseEntity responseEntity = sttService.getSTT(path); +// //System.out.println(responseEntity.getBody().toString()); +// List sttData = sttService.getSTTData(responseEntity); +// String text = sttService.getText(responseEntity); +// System.out.println(text); + String sttResult = "안녕하세요 Hello!!!!"; + String sttFileName = "sttResult.pdf"; + File textPdfFile = convertStringToPdf(sttResult, sttFileName); + String contentType = "application/pdf"; + String originalFilename = textPdfFile.getName(); + String name = textPdfFile.getName(); + + + FileInputStream fileInputStream = new FileInputStream(textPdfFile); + MultipartFile multipartFile = new MockMultipartFile(name, originalFilename, contentType, fileInputStream); + S3Dto s3Dto = new S3Dto(multipartFile, 26L, 7L, "pdf"); + String path = awsS3Uploader.uploadTextPdfFile(s3Dto); + System.out.println(path); + +// String path = "https://s3.ap-northeast-2.amazonaws.com/watchboard-record-bucket/application/pdf/감정분류.pdf"; +// ResponseEntity responseEntity = requestService.requestPdfSummary(path); +// System.out.println(responseEntity.getBody().getSummary()); + +// String sttResult = "안녕하세요"; +// String sttFileName = "hello" + ".txt"; +// File textFile = createTextFile(sttResult, sttFileName); +// String contentType = "text/plain"; +// String originalFilename = textFile.getName(); +// String name = textFile.getName(); +// +// FileInputStream fileInputStream = new FileInputStream(textFile); +// MultipartFile multipartFile = createMockMultipartFile(name, originalFilename, textFile); +// //MultipartFile multipartFile = new MockMultipartFile(name, originalFilename, contentType, fileInputStream); +// //System.out.println(multipartFile.getInputStream().read()); +// S3Dto s3DtoForSTT = new S3Dto(multipartFile, 26L, 7L, "txt"); +// String textPdfPath = awsS3Uploader.uploadTextPdfFile(s3DtoForSTT); +// System.out.println(textPdfPath); +// try (InputStream inputStream = multipartFile.getInputStream()) { +// // InputStream에서 데이터를 읽어와서 콘솔에 출력 +// int byteRead; +// while ((byteRead = inputStream.read()) != -1) { +// System.out.print((char) byteRead); +// } +// } + return new ResponseEntity<>(HttpStatus.OK); + } + +} diff --git a/src/main/java/com/smart/watchboard/controller/AuthController.java b/src/main/java/com/smart/watchboard/controller/AuthController.java new file mode 100644 index 0000000..60e0123 --- /dev/null +++ b/src/main/java/com/smart/watchboard/controller/AuthController.java @@ -0,0 +1,77 @@ +package com.smart.watchboard.controller; + +import com.smart.watchboard.service.JwtService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseCookie; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.view.RedirectView; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +@RestController +@Tag(name = "로그인 API", description = "Oauth2.0 로그인 관련 API(mock)") +@RequiredArgsConstructor +@Slf4j +public class AuthController { + + private final JwtService jwtService; + +// @GetMapping("/users/auth/kakao/callback") +// @Operation(summary = "카카오 로그인", description = "인가코드를 받아 이후 작업을 진행하고 로그인을 완료한다.") +// public RedirectView oauthLoginCallback(@RequestParam("code") String code) { +// RedirectView redirectView = new RedirectView(); +// redirectView.setUrl("https://watchboard.me"); +// redirectView.setStatusCode(HttpStatus.FOUND); +// +// HttpHeaders headers = new HttpHeaders(); +// headers.setLocation(URI.create("/")); +// +// List cookies = new ArrayList<>(); +// cookies.add(ResponseCookie.from("accessToken", "asdasdqweqwe") +// .httpOnly(true) +// .secure(true) +// .path("/") +// .build()); +// cookies.add(ResponseCookie.from("refreshToken", "asdklj123") +// .httpOnly(true) +// .secure(true) +// .path("/") +// .build()); +// +// // 쿠키들을 하나씩 HttpHeaders에 추가 +// for (ResponseCookie cookie : cookies) { +// headers.add(HttpHeaders.SET_COOKIE, cookie.toString()); +// } +// return redirectView; +// +// //return new ResponseEntity<>(headers, HttpStatus.MOVED_PERMANENTLY); +// +// } + + @GetMapping("/users/token") + @Operation(summary = "로그인 후 토큰 전달", description = "로그인 후 request로 받은 리프레시 토큰 검증 후 액세스 토큰과 리프레시 토큰 전달") + public ResponseEntity getTokens(@CookieValue("refreshToken") String refreshToken) throws UnsupportedEncodingException { + HttpHeaders headers = jwtService.createHeaderWithTokens(refreshToken); + + return new ResponseEntity<>(headers, HttpStatus.OK); + } + + @GetMapping("/users/logout") + @Operation(summary = "로그아웃", description = "로그아웃을 위해 refreshToken 쿠키 만료") + public ResponseEntity logout(@RequestHeader("Authorization") String accessToken) { + HttpHeaders headers = jwtService.createHeaderWithDeletedCookie(accessToken); + //headers.setLocation(URI.create("/")); + + return new ResponseEntity<>(headers, HttpStatus.OK); + } + +} diff --git a/src/main/java/com/smart/watchboard/controller/GraphController.java b/src/main/java/com/smart/watchboard/controller/GraphController.java new file mode 100644 index 0000000..a5eb7dc --- /dev/null +++ b/src/main/java/com/smart/watchboard/controller/GraphController.java @@ -0,0 +1,143 @@ +package com.smart.watchboard.controller; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.smart.watchboard.domain.Answer; +import com.smart.watchboard.domain.Document; +import com.smart.watchboard.domain.Keyword; +import com.smart.watchboard.dto.AnswerDto; +import com.smart.watchboard.dto.KeywordsBodyDto; +import com.smart.watchboard.dto.KeywordsDto; +import com.smart.watchboard.dto.MindmapDto; +import com.smart.watchboard.service.*; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.parameters.P; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import javax.sound.sampled.UnsupportedAudioFileException; +import java.io.IOException; +import java.util.List; +import java.util.Optional; + +@RestController +@Tag(name = "그래프 API", description = "그래프 관련 API") +@RequiredArgsConstructor +@Slf4j +public class GraphController { + + private final LectureNoteService lectureNoteService; + private final MindmapService mindmapService; + //private final RequestService requestService; + private final FileService fileService; + private final WhiteboardService whiteboardService; + private final SseService sseService; + private final KeywordService keywordService; + private final JwtService jwtService; + private final QuestionService questionService; + private final SummaryService summaryService; + @PostMapping("/graph/{documentID}") + @Operation(summary = "마인드맵 생성", description = "ai 서버에 마인드맵 요청한다.") + public void createMindmap(@PathVariable(value = "documentID") long documentId, @RequestBody KeywordsBodyDto keywordsBodyDto, @RequestHeader("Authorization") String accessToken) throws JsonProcessingException { + sseService.notify(documentId, keywordsBodyDto.getKeywords()); +// if (whiteboardService.isPdfType(documentId)) { +// String path = fileService.getPdfUrl(documentId); +// ResponseEntity body = requestService.requestPdfMindmap(path, documentId, keywords); +// return new ResponseEntity<>(body, HttpStatus.OK); +// } else if (whiteboardService.isAudioType(documentId)) { +// String text = lectureNoteService.getText(documentId); +// ResponseEntity body = requestService.requestSTTMindmap(text, documentId, keywords); +// return new ResponseEntity<>(body, HttpStatus.OK); +// } +// return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + + @GetMapping("/documents/{documentID}/mindmap") + @Operation(summary = "마인드맵 조회", description = "마인드맵 조회") + public ResponseEntity getMindmap(@PathVariable(value = "documentID") long documentId, @RequestHeader("Authorization") String accessToken) { + Optional id = jwtService.extractUserId(accessToken); + Long userId = id.orElse(null); + + MindmapDto mindmapDto = mindmapService.getMindmap(documentId); + if (mindmapDto != null) { + return new ResponseEntity<>(mindmapDto, HttpStatus.OK); + } + + Document document = whiteboardService.findDoc(documentId); + if (document.getDataType().equals("pdf")) { + String pdfUrl = fileService.getPath(documentId); + if (mindmapDto == null) { + sseService.notifySummary(documentId, pdfUrl); + sseService.notifyKeywords(documentId, pdfUrl); + return new ResponseEntity<>(HttpStatus.ACCEPTED); + } + } else { + String url = fileService.getPath(documentId); + if (mindmapDto == null) { + sseService.notifySTT(documentId, url, userId); + return new ResponseEntity<>(HttpStatus.ACCEPTED); + } + } + return new ResponseEntity<>(mindmapDto, HttpStatus.OK); + } + + + @PutMapping("/documents/{documentID}/mindmap/keyword") + @Operation(summary = "키워드 업데이트", description = "키워드 추가 및 삭제") + public ResponseEntity updateKeywords(@PathVariable(value = "documentID") long documentId, @RequestHeader("Authorization") String accessToken, @RequestBody KeywordsDto keywordsDto) { + Keyword keywords = keywordService.updateKeywords(keywordsDto, documentId); + sseService.notify(documentId, keywords.getKeywords()); + + return new ResponseEntity<>(HttpStatus.OK); + } + + @GetMapping("/documents/{documentID}/mindmap/keyword/{keywordLabel}") + @Operation(summary = "키워드 질문", description = "키워드 AI에 질문") + public ResponseEntity getAnswer(@PathVariable(value = "documentID") long documentId, @PathVariable String keywordLabel, @RequestHeader("Authorization") String accessToken) throws JsonProcessingException { + AnswerDto answerDto = questionService.getAnswer(documentId, keywordLabel); + if (!summaryService.checkSummary(documentId)) { + return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE); + } + if (answerDto == null) { + sseService.notifyAnswer(documentId, keywordLabel); + return new ResponseEntity<>(HttpStatus.ACCEPTED); + } + if (answerDto.getText().equals("processing")) { + return new ResponseEntity<>(HttpStatus.ACCEPTED); + } + + return new ResponseEntity<>(answerDto, HttpStatus.OK); + } + + @GetMapping(value = "documents/{documentID}/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public SseEmitter subscribe(@PathVariable(value = "documentID") long documentId) { + return sseService.subscribe(documentId); + } + + @PostMapping("/send-data/{documentID}") + public void sendData(@PathVariable(value = "documentID") long documentId, @RequestBody KeywordsBodyDto keywordsBodyDto) { + sseService.notify(documentId, keywordsBodyDto.getKeywords()); + } + + @PostMapping("/abc/{documentID}") + public ResponseEntity test(@PathVariable(value = "documentID") long documentId) throws UnsupportedAudioFileException, IOException { + System.out.println(documentId); + if (whiteboardService.isPdfType(documentId)) { +// String path = fileService.getPdfUrl(documentId); +// System.out.println(path); + //ResponseEntity body = requestService.requestPdfMindmap(path, documentId, keywords); + //return new ResponseEntity<>(body, HttpStatus.OK); + } else if (whiteboardService.isAudioType(documentId)) { + String text = lectureNoteService.getText(documentId); + System.out.println(text); + //ResponseEntity body = requestService.requestSTTMindmap(text, documentId, keywords); + //return new ResponseEntity<>(body, HttpStatus.OK); + } + return new ResponseEntity<>("", HttpStatus.OK); + } +} diff --git a/src/main/java/com/smart/watchboard/controller/LearningFileController.java b/src/main/java/com/smart/watchboard/controller/LearningFileController.java new file mode 100644 index 0000000..65b5de8 --- /dev/null +++ b/src/main/java/com/smart/watchboard/controller/LearningFileController.java @@ -0,0 +1,150 @@ +package com.smart.watchboard.controller; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.itextpdf.text.pdf.PdfDocument; +import com.itextpdf.text.pdf.PdfReader; +import com.smart.watchboard.common.support.AwsS3Uploader; +import com.smart.watchboard.dto.*; +import com.smart.watchboard.service.*; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.sound.sampled.UnsupportedAudioFileException; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import static com.smart.watchboard.common.support.PdfConverter.checkPageLimit; +import static com.smart.watchboard.common.support.PdfConverter.countPdfPage; + +@RestController +@RequestMapping("/documents") +@Tag(name = "pdf 학습파일 API", description = "학습파일 관련 API") +@RequiredArgsConstructor +@Slf4j +public class LearningFileController { + private final AwsS3Uploader awsS3Uploader; + private final FileService fileService; + private final WhiteboardService whiteboardService; + private final JwtService jwtService; + private final SseService sseService; + private final RequestService requestService; + + @PostMapping("/{documentID}/pdf") + public ResponseEntity uploadLearningFile(@PathVariable(value = "documentID") long documentId, @RequestParam("pdf") MultipartFile pdfFile, @RequestHeader("Authorization") String accessToken) throws UnsupportedAudioFileException, IOException { + Optional id = jwtService.extractUserId(accessToken); + Long userId = id.orElse(null); + + if (!checkPageLimit(countPdfPage(pdfFile))) { + return new ResponseEntity<>(HttpStatus.UNPROCESSABLE_ENTITY); + } + + S3Dto s3Dto = new S3Dto(pdfFile, documentId, userId, "pdf"); + String path = awsS3Uploader.uploadFile(s3Dto); + //sseService.notifyKeywords(documentId, path); + //sseService.notifySummary(documentId, path); +// ResponseEntity responseEntity = requestService.requestPdfKeywords(path); +// keywordService.createKeywords(responseEntity, documentId); +// +// ResponseEntity summary = requestService.requestPdfSummary(path); +// summaryService.createSummary(documentId, summary.getBody().getSummary()); + whiteboardService.setDataType(documentId, "pdf"); + + //return new ResponseEntity<>(responseEntity.getBody(), HttpStatus.OK); + return new ResponseEntity<>(HttpStatus.OK); + } + + @PutMapping("/{documentID}/pdf") + public ResponseEntity updateLearningFile(@PathVariable(value = "documentID") long documentId, @RequestParam("pdf") MultipartFile pdfFile, @RequestHeader("Authorization") String accessToken) throws UnsupportedAudioFileException, IOException { + Optional id = jwtService.extractUserId(accessToken); + Long userId = id.orElse(null); + + if (!checkPageLimit(countPdfPage(pdfFile))) { + return new ResponseEntity<>(HttpStatus.UNPROCESSABLE_ENTITY); + } + + S3Dto s3Dto = new S3Dto(pdfFile, documentId, userId, "pdf"); + String path = awsS3Uploader.uploadFile(s3Dto); + sseService.notifyKeywords(documentId, path); + sseService.notifySummary(documentId, path); +// ResponseEntity responseEntity = requestService.requestPdfKeywords(path); +// keywordService.renewKeywords(responseEntity, documentId); +// +// ResponseEntity summary = requestService.requestPdfSummary(path); +// summaryService.updateSummary(documentId, summary.getBody().getSummary()); + whiteboardService.setDataType(documentId, "pdf"); + + return new ResponseEntity<>(HttpStatus.OK); + } + + @DeleteMapping("/{documentID}/pdf") + public ResponseEntity deleteLearningFile(@PathVariable(value = "documentID") long documentId, @RequestParam(value = "fileID", required = false) Long fileId, @RequestHeader("Authorization") String accessToken) throws UnsupportedAudioFileException, IOException { + fileService.deleteFile(fileId); + + return new ResponseEntity<>("", HttpStatus.OK); + } + + @GetMapping("/{documentID}/pdf") + public ResponseEntity getLearningFile(@PathVariable(value = "documentID") long documentId, @RequestHeader("Authorization") String accessToken) { + PdfUrlDto body = fileService.getPdfUrl(documentId); + ResponseEntity responseEntity = new ResponseEntity<>(body, HttpStatus.OK); + + return responseEntity; + } + + @PostMapping("/test") + public ResponseEntity test(@RequestHeader("Authorization") String accessToken) throws IOException { +// Optional id = jwtService.extractUserId(accessToken); +// Long userId = id.orElse(null); +// +// S3Dto s3Dto = new S3Dto(pdfFile, 11L, userId, "pdf"); +// String path = awsS3Uploader.uploadFile(s3Dto); + + //String path = "https://watchboard-record-bucket.s3.ap-northeast-2.amazonaws.com/application/pdf/36.감정분류.pdf"; + //ResponseEntity responseEntity = requestService.requestPdfKeywords(path); + + //ResponseEntity responseEntity1 = requestService.requestPdfMindmap(path, 36L, responseEntity.getBody().getKeywords()); + +//// PDF 파일 읽기 +// PdfReader reader = new PdfReader(pdfFile); +// +// // PDF 파일 전체 페이지 수 구하기 +// int pageCount = reader.getNumberOfPages(); + + //System.out.println(path); + //String path = "abcd"; + //ResponseEntity responseEntity = requestService.requestPdfKeywords(path); +// String xx = """ +// {"root":1,"keywords":["나는","eat","food","today"],"graph":{"1":[0,2],"2":[3]}} +// """; + + String xx = """ + { + "add": ["추가할", "키워드", "목록"], + "delete": ["eat"], + } + """; +// List add = new ArrayList<>(); +// add.add("추가할"); +// add.add("키워드"); +// List delete = new ArrayList<>(); +// delete.add("eat"); +// KeywordsDto keywordsDto = new KeywordsDto(add, delete); + //ResponseEntity entity = new ResponseEntity<>(xx, HttpStatus.OK); +// mindmapService.updateKeywords(keywordsDto, 11L); + //mindmapService.createMindmap(entity, 11L, "pdf"); + //String body = fileService.getPdfUrl(4L); + //System.out.println(body); + String filePath = "https://watchboard-record-bucket.s3.ap-northeast-2.amazonaws.com/application/pdf/36.감정분류.pdf"; + ResponseEntity responseEntity = requestService.requestPdfSummary(filePath); + + return new ResponseEntity<>(responseEntity, HttpStatus.OK); + + } +} diff --git a/src/main/java/com/smart/watchboard/controller/UserController.java b/src/main/java/com/smart/watchboard/controller/UserController.java new file mode 100644 index 0000000..c5cdeb8 --- /dev/null +++ b/src/main/java/com/smart/watchboard/controller/UserController.java @@ -0,0 +1,31 @@ +package com.smart.watchboard.controller; + +import com.smart.watchboard.dto.UserInformationDto; +import com.smart.watchboard.service.JwtService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + + +@RestController +@RequestMapping("/users") +@Tag(name = "사용자 API", description = "사용자 관련 API(mock)") +@RequiredArgsConstructor +@Slf4j +public class UserController { + private final JwtService jwtService; + @GetMapping("/info") + @Operation(summary = "사용자 정보 조회", description = "요청받은 사용자의 정보를 조회한다.") + public ResponseEntity getUserInformation(@RequestHeader("Authorization") String accessToken) { + UserInformationDto userInformationDto = jwtService.getUserInformation(accessToken); + + return new ResponseEntity<>(userInformationDto, HttpStatus.OK); + } +} diff --git a/src/main/java/com/smart/watchboard/controller/WhiteboardController.java b/src/main/java/com/smart/watchboard/controller/WhiteboardController.java new file mode 100644 index 0000000..c7e272e --- /dev/null +++ b/src/main/java/com/smart/watchboard/controller/WhiteboardController.java @@ -0,0 +1,70 @@ +package com.smart.watchboard.controller; + +import com.smart.watchboard.domain.WhiteboardData; +import com.smart.watchboard.dto.*; +import com.smart.watchboard.service.JwtService; +import com.smart.watchboard.service.WhiteboardService; +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.*; + +import java.util.List; + +@RestController +@RequestMapping(value = "/documents") +@Tag(name = "문서 API", description = "문서 관련 API(mock)") +@RequiredArgsConstructor +public class WhiteboardController { + + private final WhiteboardService whiteboardService; + private final JwtService jwtService; + + @GetMapping() + @Operation(summary = "문서 목록 조회", description = "사용자가 속해 있는 모든 문서 목록을 조회한다.") + public ResponseEntity getAllDocuments(@RequestHeader("Authorization") String accessToken) { + List documents = whiteboardService.findDocumentsByUserId(accessToken); + + return new ResponseEntity<>(documents, HttpStatus.OK); + } + + @GetMapping("/{documentID}") + @Operation(summary = "문서 데이터 조회", description = "특정 문서의 데이터를 조회한다.") + public ResponseEntity getDocument(@PathVariable(value = "documentID") long documentId, @RequestHeader("Authorization") String accessToken) { + String extractedAccessToken = jwtService.extractAccessToken(accessToken); + Long userId = jwtService.extractUserId(extractedAccessToken).orElse(null); + if (!whiteboardService.checkAuthorization(documentId, userId)) { + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } + DocumentResponseDto response = whiteboardService.findDocument(documentId, accessToken); + + return new ResponseEntity<>(response, HttpStatus.OK); + } + + @CrossOrigin + @PostMapping() + @Operation(summary = "문서 생성", description = "화이트보드를 생성한다.") + public ResponseEntity createDocument(@RequestBody RequestCreatedDocumentDto requestCreatedDocumentDto, @RequestHeader("Authorization") String accessToken) { + DocumentCreatedResponseDto documentCreatedResponseDto = whiteboardService.createDocument(requestCreatedDocumentDto, accessToken); + + return new ResponseEntity<>(documentCreatedResponseDto, HttpStatus.CREATED); + } + + @DeleteMapping("/{documentID}") + @Operation(summary = "문서 삭제", description = "특정 화이트보드를 삭제한다.") + public ResponseEntity deleteDocument(@PathVariable(value = "documentID") long documentId, @RequestHeader("Authorization") String accessToken) { + whiteboardService.deleteDocument(documentId, accessToken); + + return new ResponseEntity<>(HttpStatus.OK); + } + +// @PostMapping("/{documentID}/data") +// @Operation(summary = "화이트보드 문서 데이터 생성 및 수정", description = "화이트보드 문서 내의 데이터 생성 및 수정한다.") +// public ResponseEntity createDocumentData(@PathVariable(value = "documentID") long documentId, @RequestHeader("Authorization") String accessToken, @RequestBody Map documentData) { +// whiteboardService.createWhiteboardData(documentData, documentId, accessToken); +// +// return new ResponseEntity<>(HttpStatus.OK); +// } +} diff --git a/src/main/java/com/smart/watchboard/domain/Answer.java b/src/main/java/com/smart/watchboard/domain/Answer.java new file mode 100644 index 0000000..8729116 --- /dev/null +++ b/src/main/java/com/smart/watchboard/domain/Answer.java @@ -0,0 +1,29 @@ +package com.smart.watchboard.domain; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.time.Instant; +import java.util.List; +import java.util.Map; + +@AllArgsConstructor +@Getter +@Setter +@Builder +@Document("answer") +public class Answer { + @Id + private ObjectId objectId; + + private Long documentId; + + private String keyword; + + private String text; +} diff --git a/src/main/java/com/smart/watchboard/domain/Document.java b/src/main/java/com/smart/watchboard/domain/Document.java new file mode 100644 index 0000000..fea6439 --- /dev/null +++ b/src/main/java/com/smart/watchboard/domain/Document.java @@ -0,0 +1,61 @@ +package com.smart.watchboard.domain; + +import com.smart.watchboard.dto.DocumentDto; +import jakarta.persistence.*; +import lombok.*; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +@Getter +@Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +@Table(name = "DOCUMENT") +public class Document { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "document_id") + private long documentId; + + private String documentName; + + private Long createdAt; + + private Long modifiedAt; + + private boolean isDeleted; + + private String dataType; + + @ManyToOne + @JoinColumn(name = "user_id") + private User user; + +// @OneToMany(mappedBy = "document") +// private List userDocuments = new ArrayList<>(); + + @OneToOne(mappedBy = "document", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private File file; + + public Document(String documentName, Long createdAt, Long modifiedAt, boolean isDeleted, String dataType, User user) { + this.documentName = documentName; + this.createdAt = createdAt; + this.modifiedAt = modifiedAt; + this.isDeleted = isDeleted; + this.dataType = dataType; + this.user = user; + } + + @Override + public String toString() { + return "Document{" + + "documentId=" + documentId + + ", documentName='" + documentName + '\'' + + ", createdAt=" + createdAt + + ", modifiedAt=" + modifiedAt + + '}'; + } +} diff --git a/src/main/java/com/smart/watchboard/domain/File.java b/src/main/java/com/smart/watchboard/domain/File.java new file mode 100644 index 0000000..cd69f1c --- /dev/null +++ b/src/main/java/com/smart/watchboard/domain/File.java @@ -0,0 +1,39 @@ +package com.smart.watchboard.domain; + +import jakarta.persistence.*; +import lombok.*; + +import java.time.Instant; + +@Getter +@Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +@Builder +@Table(name = "FILES") +@AllArgsConstructor +public class File { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "file_id") + private Long id; + + private String fileName; + private String objectKey; + + @Column(columnDefinition = "varchar(1000)", nullable = false) + private String path; + private String fileType; + + private Long size; + private Instant createdAt; + + private Instant modifiedAt; + private boolean isDelete; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "document_id") + private Document document; + +} diff --git a/src/main/java/com/smart/watchboard/domain/Keyword.java b/src/main/java/com/smart/watchboard/domain/Keyword.java new file mode 100644 index 0000000..282b5bf --- /dev/null +++ b/src/main/java/com/smart/watchboard/domain/Keyword.java @@ -0,0 +1,25 @@ +package com.smart.watchboard.domain; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.util.List; + +@AllArgsConstructor +@Getter +@Setter +@Builder +@Document("keyword") +public class Keyword { + @Id + private ObjectId objectId; + + private Long documentId; + + private List keywords; +} diff --git a/src/main/java/com/smart/watchboard/domain/LectureNote.java b/src/main/java/com/smart/watchboard/domain/LectureNote.java new file mode 100644 index 0000000..53e9095 --- /dev/null +++ b/src/main/java/com/smart/watchboard/domain/LectureNote.java @@ -0,0 +1,27 @@ +package com.smart.watchboard.domain; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.util.List; + +@AllArgsConstructor +@Getter +@Setter +@Builder +@Document("note") +public class LectureNote { + @Id + private ObjectId objectId; + + private Long documentId; + + private List data; + + private String text; +} diff --git a/src/main/java/com/smart/watchboard/domain/Mindmap.java b/src/main/java/com/smart/watchboard/domain/Mindmap.java new file mode 100644 index 0000000..1571846 --- /dev/null +++ b/src/main/java/com/smart/watchboard/domain/Mindmap.java @@ -0,0 +1,37 @@ +package com.smart.watchboard.domain; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.time.Instant; +import java.util.List; +import java.util.Map; + +@AllArgsConstructor +@Getter +@Setter +@Builder +@Document("mindmap") +public class Mindmap { + @Id + private ObjectId objectId; + + private Long documentId; + + private String documentName; + + private Instant createdAt; + + private Instant modifiedAt; + + private int root; + + //private List keywords; + + private Map> graph; +} diff --git a/src/main/java/com/smart/watchboard/domain/Note.java b/src/main/java/com/smart/watchboard/domain/Note.java new file mode 100644 index 0000000..e51495c --- /dev/null +++ b/src/main/java/com/smart/watchboard/domain/Note.java @@ -0,0 +1,35 @@ +package com.smart.watchboard.domain; + +import jakarta.persistence.*; +import lombok.*; + +import java.time.Instant; + +@Getter +@Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +@Builder +@Table(name = "NOTES") +@AllArgsConstructor +public class Note { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "note_id") + private Long id; + + private String fileName; + private String objectKey; + + @Column(columnDefinition = "varchar(1000)", nullable = false) + private String path; + + private Long size; + private Instant createdAt; + private Instant modifiedAt; + private boolean isDelete; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "document_id") + private Document document; +} diff --git a/src/main/java/com/smart/watchboard/domain/SttData.java b/src/main/java/com/smart/watchboard/domain/SttData.java new file mode 100644 index 0000000..629a808 --- /dev/null +++ b/src/main/java/com/smart/watchboard/domain/SttData.java @@ -0,0 +1,19 @@ +package com.smart.watchboard.domain; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +public class SttData { + int start; + int end; + String text; + + public SttData(int start, int end, String text) { + this.start = start; + this.end = end; + this.text = text; + } +} diff --git a/src/main/java/com/smart/watchboard/domain/Summary.java b/src/main/java/com/smart/watchboard/domain/Summary.java new file mode 100644 index 0000000..aeb4586 --- /dev/null +++ b/src/main/java/com/smart/watchboard/domain/Summary.java @@ -0,0 +1,33 @@ +package com.smart.watchboard.domain; + +import jakarta.persistence.*; +import lombok.*; + +import java.time.Instant; + +@Getter +@Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +@Builder +@Table(name = "SUMMARY") +@AllArgsConstructor +public class Summary { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "summary_id") + private Long id; + + @Lob + @Column(columnDefinition = "TEXT") + private String content; + + //private Long size; + private Instant createdAt; + private Instant modifiedAt; + private boolean isDelete; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "document_id") + private Document document; +} diff --git a/src/main/java/com/smart/watchboard/domain/User.java b/src/main/java/com/smart/watchboard/domain/User.java new file mode 100644 index 0000000..45b99c2 --- /dev/null +++ b/src/main/java/com/smart/watchboard/domain/User.java @@ -0,0 +1,41 @@ +package com.smart.watchboard.domain; + +import com.smart.watchboard.common.enums.Role; +import com.smart.watchboard.common.enums.SocialType; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +@Builder +@Table(name = "USERS") +@AllArgsConstructor +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_id") + private Long id; + + private String email; + private String nickname; + @Enumerated(EnumType.STRING) + private Role role; + + @Enumerated(EnumType.STRING) + private SocialType socialType; + + private String socialId; + +// @OneToMany(mappedBy = "user") +// private List userDocuments = new ArrayList<>(); + + public void authorizeUser() { + this.role = Role.USER; + } + +} diff --git a/src/main/java/com/smart/watchboard/domain/UserDocument.java b/src/main/java/com/smart/watchboard/domain/UserDocument.java new file mode 100644 index 0000000..3decc2f --- /dev/null +++ b/src/main/java/com/smart/watchboard/domain/UserDocument.java @@ -0,0 +1,32 @@ +package com.smart.watchboard.domain; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@EntityListeners(AuditingEntityListener.class) +@Table(name = "USER_DOCUMENT") +@Entity +public class UserDocument { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "document_id") + private Document document; + + public UserDocument(User user, Document document) { + this.user = user; + this.document = document; + } +} diff --git a/src/main/java/com/smart/watchboard/domain/Whiteboard.java b/src/main/java/com/smart/watchboard/domain/Whiteboard.java new file mode 100644 index 0000000..add38e0 --- /dev/null +++ b/src/main/java/com/smart/watchboard/domain/Whiteboard.java @@ -0,0 +1,32 @@ +package com.smart.watchboard.domain; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.util.Map; + +@AllArgsConstructor +@Getter +@Setter +@Builder +@Document("whiteboard") +public class Whiteboard { + + @Id + private ObjectId objectId; + + private Long documentId; + + private String documentName; + + private long createdAt; + + private long modifiedAt; + + private Map documentData; +} diff --git a/src/main/java/com/smart/watchboard/domain/WhiteboardData.java b/src/main/java/com/smart/watchboard/domain/WhiteboardData.java new file mode 100644 index 0000000..8faba5f --- /dev/null +++ b/src/main/java/com/smart/watchboard/domain/WhiteboardData.java @@ -0,0 +1,23 @@ +package com.smart.watchboard.domain; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.data.mongodb.core.mapping.Document; + +@Getter +@Setter +@Document +public class WhiteboardData { + private String objId; + private String type; + private int x; + private int y; + private double depth; + private String parentId; + private int w; + private int h; + private int fontSize; + private String overflow; + private String text; + private String color; +} diff --git a/src/main/java/com/smart/watchboard/dto/AnswerDto.java b/src/main/java/com/smart/watchboard/dto/AnswerDto.java new file mode 100644 index 0000000..12e9647 --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/AnswerDto.java @@ -0,0 +1,17 @@ +package com.smart.watchboard.dto; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AnswerDto { + private String text; + + @JsonCreator + public AnswerDto(@JsonProperty("text") String text) { + this.text = text; + } +} diff --git a/src/main/java/com/smart/watchboard/dto/DocumentCreatedResponseDto.java b/src/main/java/com/smart/watchboard/dto/DocumentCreatedResponseDto.java new file mode 100644 index 0000000..fa9c53a --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/DocumentCreatedResponseDto.java @@ -0,0 +1,21 @@ +package com.smart.watchboard.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +public class DocumentCreatedResponseDto { + private long documentId; + private String documentName; + private long createdAt; + private long modifiedAt; + + public DocumentCreatedResponseDto(long documentId, String documentName, long createdAt, long modifiedAt) { + this.documentId = documentId; + this.documentName = documentName; + this.createdAt = createdAt; + this.modifiedAt = modifiedAt; + } +} diff --git a/src/main/java/com/smart/watchboard/dto/DocumentDto.java b/src/main/java/com/smart/watchboard/dto/DocumentDto.java new file mode 100644 index 0000000..f99c1a5 --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/DocumentDto.java @@ -0,0 +1,18 @@ +package com.smart.watchboard.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class DocumentDto { + + private Long documentId; + private String documentName; + private long createdAt; + private long modifiedAt; +} diff --git a/src/main/java/com/smart/watchboard/dto/DocumentLineDto.java b/src/main/java/com/smart/watchboard/dto/DocumentLineDto.java new file mode 100644 index 0000000..41cbf09 --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/DocumentLineDto.java @@ -0,0 +1,96 @@ +package com.smart.watchboard.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class DocumentLineDto extends DocumentObjectDto { + private int w; + private int h; + private int fontSize; + private String color; + + public static class DocumentLineDtoBuilder { + private String objId; + private String type; + private int x; + + private int y; + private double depth; + private String parentId; + private int w; + private int h; + private int fontSize; + private String color; + + public DocumentLineDto.DocumentLineDtoBuilder objId(String objId) { + this.objId = objId; + return this; + } + + public DocumentLineDto.DocumentLineDtoBuilder type(String type) { + this.type = type; + return this; + } + + public DocumentLineDto.DocumentLineDtoBuilder x(int x) { + this.x = x; + return this; + } + + public DocumentLineDto.DocumentLineDtoBuilder y(int y) { + this.y = y; + return this; + } + + public DocumentLineDto.DocumentLineDtoBuilder depth(double depth) { + this.depth = depth; + return this; + } + + public DocumentLineDto.DocumentLineDtoBuilder parentId(String parentId) { + this.parentId = parentId; + return this; + } + + public DocumentLineDto.DocumentLineDtoBuilder w(int w) { + this.w = w; + return this; + } + + public DocumentLineDto.DocumentLineDtoBuilder h(int h) { + this.h = h; + return this; + } + + public DocumentLineDto.DocumentLineDtoBuilder fontSize(int fontSize) { + this.fontSize = fontSize; + return this; + } + + public DocumentLineDto.DocumentLineDtoBuilder color(String color) { + this.color = color; + return this; + } + + public DocumentLineDto build() { + DocumentLineDto lineDto = new DocumentLineDto(); + lineDto.setObjId(objId); + lineDto.setType(type); + lineDto.setX(x); + lineDto.setY(y); + lineDto.setDepth(depth); + lineDto.setParentId(parentId); + lineDto.setW(w); + lineDto.setH(h); + lineDto.setFontSize(fontSize); + lineDto.setColor(color); + + return lineDto; + } + + + } +} diff --git a/src/main/java/com/smart/watchboard/dto/DocumentObjectDto.java b/src/main/java/com/smart/watchboard/dto/DocumentObjectDto.java new file mode 100644 index 0000000..d88d132 --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/DocumentObjectDto.java @@ -0,0 +1,18 @@ +package com.smart.watchboard.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public abstract class DocumentObjectDto { + private String objId; + private String type; + private int x; + private int y; + private double depth; + private String parentId; +} diff --git a/src/main/java/com/smart/watchboard/dto/DocumentObjectDtoFactory.java b/src/main/java/com/smart/watchboard/dto/DocumentObjectDtoFactory.java new file mode 100644 index 0000000..a4c47b8 --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/DocumentObjectDtoFactory.java @@ -0,0 +1,51 @@ +package com.smart.watchboard.dto; + +import com.smart.watchboard.domain.WhiteboardData; + +public class DocumentObjectDtoFactory { + public static DocumentObjectDto createDtoFromWhiteboardData(WhiteboardData whiteboardData) { + if ("TEXT".equals(whiteboardData.getType())) { + return new DocumentTextDto.DocumentTextDtoBuilder() + .objId(whiteboardData.getObjId()) + .type(whiteboardData.getType()) + .x(whiteboardData.getX()) + .y(whiteboardData.getY()) + .depth(whiteboardData.getDepth()) + .parentId(whiteboardData.getParentId()) + .w(whiteboardData.getW()) + .fontSize(whiteboardData.getFontSize()) + .overflow(whiteboardData.getOverflow()) + .text(whiteboardData.getText()) + .color(whiteboardData.getColor()) + .build(); + + } else if (whiteboardData.getType().equals("RECT")) { + return new DocumentRectDto.DocumentRectDtoBuilder() + .objId(whiteboardData.getObjId()) + .type(whiteboardData.getType()) + .x(whiteboardData.getX()) + .y(whiteboardData.getY()) + .depth(whiteboardData.getDepth()) + .parentId(whiteboardData.getParentId()) + .w(whiteboardData.getW()) + .h(whiteboardData.getH()) + .color(whiteboardData.getColor()) + .build(); + + } else if (whiteboardData.getType().equals("LINE")) { + return new DocumentLineDto.DocumentLineDtoBuilder() + .objId(whiteboardData.getObjId()) + .type(whiteboardData.getType()) + .x(whiteboardData.getX()) + .y(whiteboardData.getY()) + .depth(whiteboardData.getDepth()) + .parentId(whiteboardData.getParentId()) + .w(whiteboardData.getW()) + .h(whiteboardData.getH()) + .fontSize(whiteboardData.getFontSize()) + .color(whiteboardData.getColor()) + .build(); + } + return null; + } +} diff --git a/src/main/java/com/smart/watchboard/dto/DocumentRectDto.java b/src/main/java/com/smart/watchboard/dto/DocumentRectDto.java new file mode 100644 index 0000000..57cb3e3 --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/DocumentRectDto.java @@ -0,0 +1,84 @@ +package com.smart.watchboard.dto; + +import lombok.*; + +@Getter +@Setter +public class DocumentRectDto extends DocumentObjectDto { + private int w; + private int h; + private String color; + + public static class DocumentRectDtoBuilder { + private String objId; + private String type; + private int x; + private int y; + private double depth; + private String parentId; + private int w; + private int h; + private String color; + + public DocumentRectDto.DocumentRectDtoBuilder objId(String objId) { + this.objId = objId; + return this; + } + + public DocumentRectDto.DocumentRectDtoBuilder type(String type) { + this.type = type; + return this; + } + + public DocumentRectDto.DocumentRectDtoBuilder x(int x) { + this.x = x; + return this; + } + + public DocumentRectDto.DocumentRectDtoBuilder y(int y) { + this.y = y; + return this; + } + + public DocumentRectDto.DocumentRectDtoBuilder depth(double depth) { + this.depth = depth; + return this; + } + + public DocumentRectDto.DocumentRectDtoBuilder parentId(String parentId) { + this.parentId = parentId; + return this; + } + + public DocumentRectDto.DocumentRectDtoBuilder w(int w) { + this.w = w; + return this; + } + + public DocumentRectDto.DocumentRectDtoBuilder h(int h) { + this.h = h; + return this; + } + + public DocumentRectDto.DocumentRectDtoBuilder color(String color) { + this.color = color; + return this; + } + + public DocumentRectDto build() { + DocumentRectDto rectDto = new DocumentRectDto(); + rectDto.setObjId(objId); + rectDto.setType(type); + rectDto.setX(x); + rectDto.setY(y); + rectDto.setDepth(depth); + rectDto.setParentId(parentId); + rectDto.setW(w); + rectDto.setH(h); + rectDto.setColor(color); + + return rectDto; + } + } + +} diff --git a/src/main/java/com/smart/watchboard/dto/DocumentResponseDto.java b/src/main/java/com/smart/watchboard/dto/DocumentResponseDto.java new file mode 100644 index 0000000..c3d0558 --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/DocumentResponseDto.java @@ -0,0 +1,21 @@ +package com.smart.watchboard.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +import java.util.Map; + +@Getter +@Setter +@RequiredArgsConstructor +public class DocumentResponseDto { + private long documentId; + private String documentName; + private long createdAt; + private long modifiedAt; + private String dataType; + //private Map documentData; + + // DocumentData는 map key: value, id : object +} diff --git a/src/main/java/com/smart/watchboard/dto/DocumentTextDto.java b/src/main/java/com/smart/watchboard/dto/DocumentTextDto.java new file mode 100644 index 0000000..dd6f42d --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/DocumentTextDto.java @@ -0,0 +1,103 @@ +package com.smart.watchboard.dto; + +import lombok.*; + +@Getter +@Setter +public class DocumentTextDto extends DocumentObjectDto{ + private int w; + private int fontSize; + private String overflow; + private String text; + private String color; + + public static class DocumentTextDtoBuilder { + private String objId; + private String type; + private int x; + + private int y; + private double depth; + private String parentId; + private int w; + private int fontSize; + private String overflow; + private String text; + private String color; + + public DocumentTextDto.DocumentTextDtoBuilder objId(String objId) { + this.objId = objId; + return this; + } + + public DocumentTextDto.DocumentTextDtoBuilder type(String type) { + this.type = type; + return this; + } + + public DocumentTextDto.DocumentTextDtoBuilder x(int x) { + this.x = x; + return this; + } + + public DocumentTextDto.DocumentTextDtoBuilder y(int y) { + this.y = y; + return this; + } + + public DocumentTextDto.DocumentTextDtoBuilder depth(double depth) { + this.depth = depth; + return this; + } + + public DocumentTextDto.DocumentTextDtoBuilder parentId(String parentId) { + this.parentId = parentId; + return this; + } + + public DocumentTextDto.DocumentTextDtoBuilder w(int w) { + this.w = w; + return this; + } + + public DocumentTextDto.DocumentTextDtoBuilder fontSize(int fontSize) { + this.fontSize = fontSize; + return this; + } + + public DocumentTextDto.DocumentTextDtoBuilder overflow(String overflow) { + this.overflow = overflow; + return this; + } + + public DocumentTextDto.DocumentTextDtoBuilder text(String text) { + this.text = text; + return this; + } + + public DocumentTextDto.DocumentTextDtoBuilder color(String color) { + this.color = color; + return this; + } + + public DocumentTextDto build() { + DocumentTextDto textDto = new DocumentTextDto(); + textDto.setObjId(objId); + textDto.setType(type); + textDto.setX(x); + textDto.setY(y); + textDto.setDepth(depth); + textDto.setParentId(parentId); + textDto.setW(w); + textDto.setFontSize(fontSize); + textDto.setOverflow(overflow); + textDto.setText(text); + textDto.setColor(color); + + return textDto; + } + + + } + +} diff --git a/src/main/java/com/smart/watchboard/dto/FileDto.java b/src/main/java/com/smart/watchboard/dto/FileDto.java new file mode 100644 index 0000000..2deab1c --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/FileDto.java @@ -0,0 +1,23 @@ +package com.smart.watchboard.dto; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.web.multipart.MultipartFile; + +@Getter +@Setter +public class FileDto { + private MultipartFile file; + private String path; + private String fileType; + private Long documentId; + //private Long fileId; + + public FileDto(MultipartFile multipartFile, String path, String fileType, Long documentId) { + this.file = multipartFile; + this.path = path; + this.fileType = fileType; + this.documentId = documentId; + //this.fileId = fileId; + } +} diff --git a/src/main/java/com/smart/watchboard/dto/KeywordsBodyDto.java b/src/main/java/com/smart/watchboard/dto/KeywordsBodyDto.java new file mode 100644 index 0000000..c1e73a4 --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/KeywordsBodyDto.java @@ -0,0 +1,19 @@ +package com.smart.watchboard.dto; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class KeywordsBodyDto { + private List keywords; + + @JsonCreator + public KeywordsBodyDto(@JsonProperty("keywords") List keywords) { + this.keywords = keywords; + } +} diff --git a/src/main/java/com/smart/watchboard/dto/KeywordsDto.java b/src/main/java/com/smart/watchboard/dto/KeywordsDto.java new file mode 100644 index 0000000..06c2e01 --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/KeywordsDto.java @@ -0,0 +1,18 @@ +package com.smart.watchboard.dto; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class KeywordsDto { + private List add; + private List delete; + + public KeywordsDto(List add, List delete) { + this.add = add; + this.delete = delete; + } +} diff --git a/src/main/java/com/smart/watchboard/dto/MindmapDto.java b/src/main/java/com/smart/watchboard/dto/MindmapDto.java new file mode 100644 index 0000000..cc92787 --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/MindmapDto.java @@ -0,0 +1,23 @@ +package com.smart.watchboard.dto; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; +import java.util.Map; + +@Getter +@Setter +public class MindmapDto { + private int root; + + private List keywords; + + private Map> graph; + + public MindmapDto(int root, List keywords, Map> graph) { + this.root = root; + this.keywords = keywords; + this.graph = graph; + } +} diff --git a/src/main/java/com/smart/watchboard/dto/MindmapRequestDto.java b/src/main/java/com/smart/watchboard/dto/MindmapRequestDto.java new file mode 100644 index 0000000..1238370 --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/MindmapRequestDto.java @@ -0,0 +1,22 @@ +package com.smart.watchboard.dto; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class MindmapRequestDto { + private String key; + private String dataType; + private List keywords; + private Long documentId; + + public MindmapRequestDto(String key, String dataType, List keywords, Long documentId) { + this.key = key; + this.dataType = dataType; + this.keywords = keywords; + this.documentId = documentId; + } +} diff --git a/src/main/java/com/smart/watchboard/dto/MindmapResponseDto.java b/src/main/java/com/smart/watchboard/dto/MindmapResponseDto.java new file mode 100644 index 0000000..fe01592 --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/MindmapResponseDto.java @@ -0,0 +1,24 @@ +package com.smart.watchboard.dto; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; +import java.util.Map; + +@Getter +@Setter +public class MindmapResponseDto { + private int root; + private List keywords; + private Map> graph; + + @JsonCreator + public MindmapResponseDto(@JsonProperty("root") int root, @JsonProperty("keywords") List keywords, @JsonProperty("graph") Map> graph) { + this.root = root; + this.keywords = keywords; + this.graph = graph; + } +} diff --git a/src/main/java/com/smart/watchboard/dto/OAuthAttributes.java b/src/main/java/com/smart/watchboard/dto/OAuthAttributes.java new file mode 100644 index 0000000..2c35565 --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/OAuthAttributes.java @@ -0,0 +1,64 @@ +package com.smart.watchboard.dto; + +import com.smart.watchboard.common.oauth.KakaoOAuth2UserInfo; +import com.smart.watchboard.common.oauth.OAuth2UserInfo; +import com.smart.watchboard.common.enums.Role; +import com.smart.watchboard.common.enums.SocialType; +import com.smart.watchboard.domain.User; +import lombok.Builder; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; + +@Getter +@Slf4j +public class OAuthAttributes { + private String nameAttributeKey; // OAuth2 로그인 진행 시 키가 되는 필드 값, PK와 같은 의미 + private OAuth2UserInfo oauth2UserInfo; // 소셜 타입별 로그인 유저 정보(닉네임, 이메일, 프로필 사진 등등) + + @Builder + public OAuthAttributes(String nameAttributeKey, OAuth2UserInfo oauth2UserInfo) { + log.info(nameAttributeKey); + this.nameAttributeKey = nameAttributeKey; + this.oauth2UserInfo = oauth2UserInfo; + } + + /** + * SocialType에 맞는 메소드 호출하여 OAuthAttributes 객체 반환 + * 파라미터 : userNameAttributeName -> OAuth2 로그인 시 키(PK)가 되는 값 / attributes : OAuth 서비스의 유저 정보들 + * 회원의 식별값(id), attributes, nameAttributeKey를 저장 후 build + */ + public static OAuthAttributes of(SocialType socialType, + String userNameAttributeName, Map attributes) { + + if (socialType == SocialType.KAKAO) { + log.info("kakao"); + } + return ofKakao(userNameAttributeName, attributes); + } + + private static OAuthAttributes ofKakao(String userNameAttributeName, Map attributes) { + + return OAuthAttributes.builder() + .nameAttributeKey(userNameAttributeName) + .oauth2UserInfo(new KakaoOAuth2UserInfo(attributes)) + .build(); + } + + /** + * of메소드로 OAuthAttributes 객체가 생성되어, 유저 정보들이 담긴 OAuth2UserInfo가 소셜 타입별로 주입된 상태 + * OAuth2UserInfo에서 가져와서 build + */ + public User toEntity(SocialType socialType, OAuth2UserInfo oauth2UserInfo) { + + return User.builder() + .socialType(socialType) + .socialId(oauth2UserInfo.getId()) + .email(oauth2UserInfo.getEmail()) + .nickname(oauth2UserInfo.getNickname()) + .role(Role.USER) + .build(); + } + +} diff --git a/src/main/java/com/smart/watchboard/dto/PdfUrlDto.java b/src/main/java/com/smart/watchboard/dto/PdfUrlDto.java new file mode 100644 index 0000000..f45a6aa --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/PdfUrlDto.java @@ -0,0 +1,14 @@ +package com.smart.watchboard.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class PdfUrlDto { + public String url; + + public PdfUrlDto(String url) { + this.url = url; + } +} diff --git a/src/main/java/com/smart/watchboard/dto/RequestBodyToServerDto.java b/src/main/java/com/smart/watchboard/dto/RequestBodyToServerDto.java new file mode 100644 index 0000000..6cca8f6 --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/RequestBodyToServerDto.java @@ -0,0 +1,16 @@ +package com.smart.watchboard.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class RequestBodyToServerDto { + private String key; + private String dataType; + + public RequestBodyToServerDto(String key, String dataType) { + this.key = key; + this.dataType = dataType; + } +} diff --git a/src/main/java/com/smart/watchboard/dto/RequestCreatedDocumentDto.java b/src/main/java/com/smart/watchboard/dto/RequestCreatedDocumentDto.java new file mode 100644 index 0000000..4d35154 --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/RequestCreatedDocumentDto.java @@ -0,0 +1,10 @@ +package com.smart.watchboard.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class RequestCreatedDocumentDto { + private String documentName; +} diff --git a/src/main/java/com/smart/watchboard/dto/S3Dto.java b/src/main/java/com/smart/watchboard/dto/S3Dto.java new file mode 100644 index 0000000..3213615 --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/S3Dto.java @@ -0,0 +1,23 @@ +package com.smart.watchboard.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import org.springframework.web.multipart.MultipartFile; + +@Getter +@Setter +@RequiredArgsConstructor +public class S3Dto { + private MultipartFile file; + private Long documentId; + private Long userId; + private String dataType; + + public S3Dto(MultipartFile file, Long documentId, Long userId, String dataType) { + this.file = file; + this.documentId = documentId; + this.userId = userId; + this.dataType = dataType; + } +} diff --git a/src/main/java/com/smart/watchboard/dto/SttDataDto.java b/src/main/java/com/smart/watchboard/dto/SttDataDto.java new file mode 100644 index 0000000..40790e5 --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/SttDataDto.java @@ -0,0 +1,17 @@ +package com.smart.watchboard.dto; + +import com.smart.watchboard.domain.SttData; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class SttDataDto { + private List data; + + public SttDataDto(List data) { + this.data = data; + } +} diff --git a/src/main/java/com/smart/watchboard/dto/SttDto.java b/src/main/java/com/smart/watchboard/dto/SttDto.java new file mode 100644 index 0000000..4e46599 --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/SttDto.java @@ -0,0 +1,20 @@ +package com.smart.watchboard.dto; + +import com.smart.watchboard.domain.SttData; +import lombok.Getter; +import lombok.Setter; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@Getter +@Setter +public class SttDto { + private String url; + private List data; + + public SttDto(String url, List data) { + this.url = url; + this.data = data; + } +} diff --git a/src/main/java/com/smart/watchboard/dto/SttRequestDto.java b/src/main/java/com/smart/watchboard/dto/SttRequestDto.java new file mode 100644 index 0000000..6b71540 --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/SttRequestDto.java @@ -0,0 +1,14 @@ +package com.smart.watchboard.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class SttRequestDto { + private String key; + + public SttRequestDto(String key) { + this.key = key; + } +} diff --git a/src/main/java/com/smart/watchboard/dto/SummaryDto.java b/src/main/java/com/smart/watchboard/dto/SummaryDto.java new file mode 100644 index 0000000..9a5ab33 --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/SummaryDto.java @@ -0,0 +1,18 @@ +package com.smart.watchboard.dto; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class SummaryDto { + + private String summary; + + @JsonCreator + public SummaryDto(@JsonProperty("summary") String summary) { + this.summary = summary; + } +} diff --git a/src/main/java/com/smart/watchboard/dto/UploadDto.java b/src/main/java/com/smart/watchboard/dto/UploadDto.java new file mode 100644 index 0000000..870a251 --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/UploadDto.java @@ -0,0 +1,19 @@ +package com.smart.watchboard.dto; + +import com.smart.watchboard.domain.SttData; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class UploadDto { + private List keywords; + private List data; + + public UploadDto(List keywords, List data) { + this.keywords = keywords; + this.data = data; + } +} diff --git a/src/main/java/com/smart/watchboard/dto/UserInformationDto.java b/src/main/java/com/smart/watchboard/dto/UserInformationDto.java new file mode 100644 index 0000000..242907f --- /dev/null +++ b/src/main/java/com/smart/watchboard/dto/UserInformationDto.java @@ -0,0 +1,17 @@ +package com.smart.watchboard.dto; + +import lombok.Getter; + +@Getter +public class UserInformationDto { + private long userId; + private String nickname; + private String email; + + + public UserInformationDto(long userId, String nickname, String email) { + this.userId = userId; + this.nickname = nickname; + this.email = email; + } +} diff --git a/src/main/java/com/smart/watchboard/handler/OAuth2AuthenticationFailureHandler.java b/src/main/java/com/smart/watchboard/handler/OAuth2AuthenticationFailureHandler.java new file mode 100644 index 0000000..e4c5e2b --- /dev/null +++ b/src/main/java/com/smart/watchboard/handler/OAuth2AuthenticationFailureHandler.java @@ -0,0 +1,25 @@ +package com.smart.watchboard.handler; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Slf4j +@Component +public class OAuth2AuthenticationFailureHandler implements AuthenticationFailureHandler { + + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + response.getWriter().write("소셜 로그인 실패! 서버 로그를 확인해주세요."); + exception.printStackTrace(); + log.info("소셜 로그인에 실패했습니다. 에러 메시지 : {}", exception.getMessage()); + } +} diff --git a/src/main/java/com/smart/watchboard/handler/OAuth2AuthenticationSuccessHandler.java b/src/main/java/com/smart/watchboard/handler/OAuth2AuthenticationSuccessHandler.java new file mode 100644 index 0000000..58a37d7 --- /dev/null +++ b/src/main/java/com/smart/watchboard/handler/OAuth2AuthenticationSuccessHandler.java @@ -0,0 +1,63 @@ +package com.smart.watchboard.handler; + +import com.smart.watchboard.common.oauth.CustomOAuth2User; +import com.smart.watchboard.common.enums.Role; +import com.smart.watchboard.domain.User; +import com.smart.watchboard.repository.UserRepository; +import com.smart.watchboard.service.JwtService; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseCookie; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +@Slf4j +@RequiredArgsConstructor +public class OAuth2AuthenticationSuccessHandler implements AuthenticationSuccessHandler { + @Value("${front-url}") + private String frontUrl; + + private final JwtService jwtService; + private final UserRepository userRepository; + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { + log.info("OAuth2 Login 성공!"); + try { + CustomOAuth2User oAuth2User = (CustomOAuth2User) authentication.getPrincipal(); + if (oAuth2User.getRole() == Role.USER) { + String refreshToken = jwtService.createRefreshToken(oAuth2User.getUserId()); + ResponseCookie cookie = jwtService.setCookieRefreshToken(refreshToken); + response.setHeader("Set-Cookie", cookie.toString()); + response.sendRedirect(frontUrl); // 추후 수정 +// response.sendRedirect("http//localhost:8081"); + User findUser = userRepository.findByEmail(oAuth2User.getEmail()) + .orElseThrow(() -> new IllegalArgumentException("이메일에 해당하는 유저가 없습니다.")); + findUser.authorizeUser(); + } else { + loginSuccess(response, oAuth2User); + } + } catch (Exception e) { + throw e; + } + + } + + private void loginSuccess(HttpServletResponse response, CustomOAuth2User oAuth2User) throws IOException { + log.info("loginSuccess"); + String accessToken = jwtService.createAccessToken(oAuth2User.getUserId()); + String refreshToken = jwtService.createRefreshToken(oAuth2User.getUserId()); + response.addHeader(jwtService.getAccessHeader(), "Bearer " + accessToken); + response.addHeader(jwtService.getRefreshHeader(), "Bearer " + refreshToken); + + jwtService.sendAccessAndRefreshToken(response, accessToken, refreshToken); + } +} diff --git a/src/main/java/com/smart/watchboard/repository/AnswerRepository.java b/src/main/java/com/smart/watchboard/repository/AnswerRepository.java new file mode 100644 index 0000000..eb21501 --- /dev/null +++ b/src/main/java/com/smart/watchboard/repository/AnswerRepository.java @@ -0,0 +1,11 @@ +package com.smart.watchboard.repository; + +import com.smart.watchboard.domain.Answer; +import com.smart.watchboard.domain.Keyword; +import org.springframework.data.mongodb.repository.MongoRepository; + +import java.util.Optional; + +public interface AnswerRepository extends MongoRepository { + Optional findByDocumentIdAndKeyword(Long documentId, String keyword); +} diff --git a/src/main/java/com/smart/watchboard/repository/DocumentRepository.java b/src/main/java/com/smart/watchboard/repository/DocumentRepository.java new file mode 100644 index 0000000..22b88ed --- /dev/null +++ b/src/main/java/com/smart/watchboard/repository/DocumentRepository.java @@ -0,0 +1,16 @@ +package com.smart.watchboard.repository; + +import com.smart.watchboard.domain.Document; +import com.smart.watchboard.domain.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface DocumentRepository extends JpaRepository { + + List findByUserAndIsDeletedFalse(User user); + + Document findByDocumentId(Long documentId); +} diff --git a/src/main/java/com/smart/watchboard/repository/EmitterRepository.java b/src/main/java/com/smart/watchboard/repository/EmitterRepository.java new file mode 100644 index 0000000..c98bffd --- /dev/null +++ b/src/main/java/com/smart/watchboard/repository/EmitterRepository.java @@ -0,0 +1,26 @@ +package com.smart.watchboard.repository; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Repository +@RequiredArgsConstructor +public class EmitterRepository { + private final Map emitters = new ConcurrentHashMap<>(); + + public void save(Long id, SseEmitter emitter) { + emitters.put(id, emitter); + } + + public void deleteById(Long id) { + emitters.remove(id); + } + + public SseEmitter get(Long id) { + return emitters.get(id); + } +} diff --git a/src/main/java/com/smart/watchboard/repository/FileRepository.java b/src/main/java/com/smart/watchboard/repository/FileRepository.java new file mode 100644 index 0000000..1fcb29c --- /dev/null +++ b/src/main/java/com/smart/watchboard/repository/FileRepository.java @@ -0,0 +1,9 @@ +package com.smart.watchboard.repository; + +import com.smart.watchboard.domain.Document; +import com.smart.watchboard.domain.File; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface FileRepository extends JpaRepository { + File findByDocument(Document document); +} diff --git a/src/main/java/com/smart/watchboard/repository/KeywordRepository.java b/src/main/java/com/smart/watchboard/repository/KeywordRepository.java new file mode 100644 index 0000000..2bcc802 --- /dev/null +++ b/src/main/java/com/smart/watchboard/repository/KeywordRepository.java @@ -0,0 +1,12 @@ +package com.smart.watchboard.repository; + +import com.smart.watchboard.domain.Document; +import com.smart.watchboard.domain.Keyword; +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.repository.MongoRepository; + +import java.util.Optional; + +public interface KeywordRepository extends MongoRepository { + Optional findByDocumentId(Long documentId); +} diff --git a/src/main/java/com/smart/watchboard/repository/LectureNoteRepository.java b/src/main/java/com/smart/watchboard/repository/LectureNoteRepository.java new file mode 100644 index 0000000..04a7ced --- /dev/null +++ b/src/main/java/com/smart/watchboard/repository/LectureNoteRepository.java @@ -0,0 +1,8 @@ +package com.smart.watchboard.repository; + +import com.smart.watchboard.domain.LectureNote; +import org.springframework.data.mongodb.repository.MongoRepository; + +public interface LectureNoteRepository extends MongoRepository { + LectureNote findByDocumentId(Long documentId); +} diff --git a/src/main/java/com/smart/watchboard/repository/MindmapRepository.java b/src/main/java/com/smart/watchboard/repository/MindmapRepository.java new file mode 100644 index 0000000..778dc6e --- /dev/null +++ b/src/main/java/com/smart/watchboard/repository/MindmapRepository.java @@ -0,0 +1,12 @@ +package com.smart.watchboard.repository; + +import com.smart.watchboard.domain.Mindmap; +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.repository.MongoRepository; + +import java.util.Optional; + +public interface MindmapRepository extends MongoRepository { + Optional findByDocumentId(Long documentId); + void deleteByDocumentId(Long documentId); +} diff --git a/src/main/java/com/smart/watchboard/repository/NoteRepository.java b/src/main/java/com/smart/watchboard/repository/NoteRepository.java new file mode 100644 index 0000000..96acd94 --- /dev/null +++ b/src/main/java/com/smart/watchboard/repository/NoteRepository.java @@ -0,0 +1,13 @@ +package com.smart.watchboard.repository; + + +import com.smart.watchboard.domain.Document; +import com.smart.watchboard.domain.Note; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface NoteRepository extends JpaRepository { + + Note findByDocument(Document document); +} diff --git a/src/main/java/com/smart/watchboard/repository/SummaryRepository.java b/src/main/java/com/smart/watchboard/repository/SummaryRepository.java new file mode 100644 index 0000000..cc29677 --- /dev/null +++ b/src/main/java/com/smart/watchboard/repository/SummaryRepository.java @@ -0,0 +1,9 @@ +package com.smart.watchboard.repository; + +import com.smart.watchboard.domain.Document; +import com.smart.watchboard.domain.Summary; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface SummaryRepository extends JpaRepository { + Summary findByDocument(Document document); +} diff --git a/src/main/java/com/smart/watchboard/repository/UserDocumentRepository.java b/src/main/java/com/smart/watchboard/repository/UserDocumentRepository.java new file mode 100644 index 0000000..dafcc6c --- /dev/null +++ b/src/main/java/com/smart/watchboard/repository/UserDocumentRepository.java @@ -0,0 +1,11 @@ +package com.smart.watchboard.repository; + +import com.smart.watchboard.domain.Document; +import com.smart.watchboard.domain.User; +import com.smart.watchboard.domain.UserDocument; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserDocumentRepository extends JpaRepository { + boolean existsByUserAndDocument(User user, Document document); + +} diff --git a/src/main/java/com/smart/watchboard/repository/UserRepository.java b/src/main/java/com/smart/watchboard/repository/UserRepository.java new file mode 100644 index 0000000..b524068 --- /dev/null +++ b/src/main/java/com/smart/watchboard/repository/UserRepository.java @@ -0,0 +1,15 @@ +package com.smart.watchboard.repository; + +import com.smart.watchboard.common.enums.SocialType; +import com.smart.watchboard.domain.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; +public interface UserRepository extends JpaRepository { + Optional findByEmail(String email); + Optional findByNickname(String nickname); + Optional findBySocialTypeAndSocialId(SocialType socialType, String socialId); + + Optional findById(Optional userId); + User getById(Optional userId); +} diff --git a/src/main/java/com/smart/watchboard/repository/WhiteboardRepository.java b/src/main/java/com/smart/watchboard/repository/WhiteboardRepository.java new file mode 100644 index 0000000..7e663fd --- /dev/null +++ b/src/main/java/com/smart/watchboard/repository/WhiteboardRepository.java @@ -0,0 +1,11 @@ +package com.smart.watchboard.repository; + +import com.smart.watchboard.domain.Whiteboard; +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.repository.MongoRepository; + +import java.util.Optional; + +public interface WhiteboardRepository extends MongoRepository { + Optional findByDocumentId(long documentId); +} diff --git a/src/main/java/com/smart/watchboard/service/CustomOAuth2UserService.java b/src/main/java/com/smart/watchboard/service/CustomOAuth2UserService.java new file mode 100644 index 0000000..24bd582 --- /dev/null +++ b/src/main/java/com/smart/watchboard/service/CustomOAuth2UserService.java @@ -0,0 +1,95 @@ +package com.smart.watchboard.service; + +import com.smart.watchboard.common.oauth.CustomOAuth2User; +import com.smart.watchboard.common.enums.SocialType; +import com.smart.watchboard.domain.User; +import com.smart.watchboard.dto.OAuthAttributes; +import com.smart.watchboard.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.Map; + +@Slf4j +@Service +@RequiredArgsConstructor +public class CustomOAuth2UserService implements OAuth2UserService { + + private final UserRepository userRepository; + private static final String KAKAO = "kakao"; + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + log.info("CustomOAuth2UserService.loadUser() 실행 - OAuth2 로그인 요청 진입"); + + /** + * DefaultOAuth2UserService 객체를 생성하여, loadUser(userRequest)를 통해 DefaultOAuth2User 객체를 생성 후 반환 + * DefaultOAuth2UserService의 loadUser()는 소셜 로그인 API의 사용자 정보 제공 URI로 요청을 보내서 + * 사용자 정보를 얻은 후, 이를 통해 DefaultOAuth2User 객체를 생성 후 반환한다. + * 결과적으로, OAuth2User는 OAuth 서비스에서 가져온 유저 정보를 담고 있는 유저 + */ + OAuth2UserService delegate = new DefaultOAuth2UserService(); + OAuth2User oAuth2User = delegate.loadUser(userRequest); + + /** + * userRequest에서 registrationId 추출 후 registrationId으로 SocialType 저장 + * http://localhost:8080/oauth2/authorization/kakao에서 kakao가 registrationId + * userNameAttributeName은 이후에 nameAttributeKey로 설정된다. + */ + String registrationId = userRequest.getClientRegistration().getRegistrationId(); + SocialType socialType = getSocialType(registrationId); + String userNameAttributeName = userRequest.getClientRegistration() + .getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName(); // OAuth2 로그인 시 키(PK)가 되는 값 + Map attributes = oAuth2User.getAttributes(); // 소셜 로그인에서 API가 제공하는 userInfo의 Json 값(유저 정보들) + + // socialType에 따라 유저 정보를 통해 OAuthAttributes 객체 생성 + OAuthAttributes extractAttributes = OAuthAttributes.of(socialType, userNameAttributeName, attributes); + + User createdUser = getUser(extractAttributes, socialType); // getUser() 메소드로 User 객체 생성 후 반환 + + // DefaultOAuth2User를 구현한 CustomOAuth2User 객체를 생성해서 반환 + return new CustomOAuth2User( + Collections.singleton(new SimpleGrantedAuthority(createdUser.getRole().getKey())), + attributes, + extractAttributes.getNameAttributeKey(), + createdUser.getId(), + createdUser.getEmail(), + createdUser.getRole() + ); + } + + private SocialType getSocialType(String registrationId) { + return SocialType.KAKAO; + } + + /** + * SocialType과 attributes에 들어있는 소셜 로그인의 식별값 id를 통해 회원을 찾아 반환하는 메소드 + * 만약 찾은 회원이 있다면, 그대로 반환하고 없다면 saveUser()를 호출하여 회원을 저장한다. + */ + private User getUser(OAuthAttributes attributes, SocialType socialType) { + User findUser = userRepository.findBySocialTypeAndSocialId(socialType, + attributes.getOauth2UserInfo().getId()).orElse(null); + + if (findUser == null) { + return saveUser(attributes, socialType); + } + return findUser; + } + + /** + * OAuthAttributes의 toEntity() 메소드를 통해 빌더로 User 객체 생성 후 반환 + * 생성된 User 객체를 DB에 저장 : socialType, socialId, email, role 값만 있는 상태 + */ + private User saveUser(OAuthAttributes attributes, SocialType socialType) { + User createdUser = attributes.toEntity(socialType, attributes.getOauth2UserInfo()); + return userRepository.save(createdUser); + } +} diff --git a/src/main/java/com/smart/watchboard/service/FileService.java b/src/main/java/com/smart/watchboard/service/FileService.java new file mode 100644 index 0000000..b998263 --- /dev/null +++ b/src/main/java/com/smart/watchboard/service/FileService.java @@ -0,0 +1,129 @@ +package com.smart.watchboard.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.smart.watchboard.domain.Document; +import com.smart.watchboard.domain.File; +import com.smart.watchboard.domain.SttData; +import com.smart.watchboard.dto.FileDto; +import com.smart.watchboard.dto.PdfUrlDto; +import com.smart.watchboard.dto.SttDto; +import com.smart.watchboard.repository.FileRepository; +import jakarta.transaction.Transactional; +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.web.multipart.MultipartFile; + +import java.time.Instant; +import java.util.List; +import java.util.Optional; + +@Service +@Transactional +@RequiredArgsConstructor +@Slf4j +public class FileService { + + private final FileRepository fileRepository; + private final WhiteboardService whiteboardService; + + public void createFile(FileDto fileDto) { + Document document = whiteboardService.findDoc(fileDto.getDocumentId()); + String key = fileDto.getFileType() + "/" + fileDto.getDocumentId() + "." + fileDto.getFile().getOriginalFilename(); + File file = File.builder() + .fileName(fileDto.getFile().getOriginalFilename()) + .objectKey(key) + .path(fileDto.getPath()) + .fileType(fileDto.getFileType()) + .size(fileDto.getFile().getSize()) + .createdAt(Instant.now()) + .modifiedAt(Instant.now()) + .isDelete(false) + .document(document) + .build(); + + fileRepository.save(file); + } + + public void updateFile(FileDto fileDto) { + Document document = whiteboardService.findDoc(fileDto.getDocumentId()); + File updatedFile = fileRepository.findByDocument(document); + String key = fileDto.getFileType() + "/" + fileDto.getDocumentId() + "." + fileDto.getFile().getOriginalFilename(); + //Optional file = findFile(fileDto.getFileId()); + //File updatedFile = file.get(); + updatedFile.setFileName(fileDto.getFile().getOriginalFilename()); + updatedFile.setObjectKey(key); + updatedFile.setPath(fileDto.getPath()); + updatedFile.setFileType(fileDto.getFileType()); + updatedFile.setSize(fileDto.getFile().getSize()); + updatedFile.setCreatedAt(Instant.now()); + updatedFile.setModifiedAt(Instant.now()); + updatedFile.setDocument(document); + fileRepository.save(updatedFile); + } + + public void deleteFile(long fileId) { + Optional file = findFile(fileId); + File deletedFile = file.get(); + deletedFile.setDelete(true); + fileRepository.save(deletedFile); + } + + public Optional findFile(long fileId) { + Optional file = fileRepository.findById(fileId); + + return file; + } + + public File findFileByDocument(Long documentId) { + Document document = whiteboardService.findDoc(documentId); + File file = fileRepository.findByDocument(document); + + return file; + } + + public PdfUrlDto getPdfUrl(Long documentId) { + Document document = whiteboardService.findDoc(documentId); + File file = fileRepository.findByDocument(document); + PdfUrlDto pdfUrlDto = new PdfUrlDto(file.getPath()); +// String body = """ +// { +// "url": %s +// } +// """.formatted(file.getPath()); + return pdfUrlDto; + } + +// public String createResponseBody2(ResponseEntity keywordResponseEntity, String text) throws JsonProcessingException { +// ObjectMapper objectMapper = new ObjectMapper(); +// JsonNode jsonNode = objectMapper.readTree(keywordResponseEntity.getBody()); +// ((ObjectNode) jsonNode).put("text", text); +// String updatedJsonString = jsonNode.toString(); +// +// return updatedJsonString; +// +// } + + public SttDto createResponseBody(String path, List data) throws JsonProcessingException { + SttDto sttDto = new SttDto(path, data); + return sttDto; + } + + public void deleteAudioFile(Long documentId) { + Document document = whiteboardService.findDoc(documentId); + File file = fileRepository.findByDocument(document); + file.setDelete(true); + fileRepository.save(file); + } + + public String getPath(Long documentId) { + Document document = whiteboardService.findDoc(documentId); + File file = fileRepository.findByDocument(document); + return file.getPath(); + } +} diff --git a/src/main/java/com/smart/watchboard/service/JwtService.java b/src/main/java/com/smart/watchboard/service/JwtService.java new file mode 100644 index 0000000..f614c23 --- /dev/null +++ b/src/main/java/com/smart/watchboard/service/JwtService.java @@ -0,0 +1,270 @@ +package com.smart.watchboard.service; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTVerificationException; +import com.smart.watchboard.domain.User; +import com.smart.watchboard.dto.UserInformationDto; +import com.smart.watchboard.repository.UserRepository; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseCookie; +import org.springframework.stereotype.Service; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.Date; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Getter +@Slf4j +public class JwtService { + @Value("${jwt.secretKey}") + private String secretKey; + + @Value("${jwt.access.expiration}") + private Long accessTokenExpirationPeriod; + + @Value("${jwt.refresh.expiration}") + private Long refreshTokenExpirationPeriod; + + @Value("${jwt.access.header}") + private String accessHeader; + + @Value("${jwt.refresh.header}") + private String refreshHeader; + + private static final String ACCESS_TOKEN_SUBJECT = "AccessToken"; + private static final String REFRESH_TOKEN_SUBJECT = "RefreshToken"; + private static final String ISSUER_CLAIM = "issuer"; + private static final String ISSUER_CLAIM_VALUE = "wb"; + private static final String BEARER = "Bearer "; + + private final UserRepository userRepository; + + /** + * AccessToken 생성 메소드 + */ + public String createAccessToken(Long userId) { + Date now = new Date(); + + return JWT.create() // JWT 토큰을 생성하는 빌더 반환 + .withSubject(ACCESS_TOKEN_SUBJECT) + .withExpiresAt(new Date(now.getTime() + accessTokenExpirationPeriod)) // 토큰 만료 시간 설정 + .withClaim(ISSUER_CLAIM, ISSUER_CLAIM_VALUE) + .withClaim("userId", userId) + .sign(Algorithm.HMAC512(secretKey)); + } + + /** + * RefreshToken 생성 + */ + public String createRefreshToken(Long userId) { + Date now = new Date(); + return JWT.create() + .withSubject(REFRESH_TOKEN_SUBJECT) + .withExpiresAt(new Date(now.getTime() + refreshTokenExpirationPeriod)) + .withClaim(ISSUER_CLAIM, ISSUER_CLAIM_VALUE) + .withClaim("userId", userId) + .sign(Algorithm.HMAC512(secretKey)); + } + + /** + * AccessToken 헤더에 실어서 보내기 + */ + public void sendAccessToken(HttpServletResponse response, String accessToken) { + response.setStatus(HttpServletResponse.SC_OK); + + response.setHeader(accessHeader, accessToken); + log.info("재발급된 Access Token : {}", accessToken); + } + + /** + * AccessToken + RefreshToken 헤더에 실어서 보내기 + */ + public void sendAccessAndRefreshToken(HttpServletResponse response, String accessToken, String refreshToken) throws UnsupportedEncodingException { + response.setStatus(HttpServletResponse.SC_OK); + + setAccessTokenHeader(response, accessToken); + setRefreshTokenHeader(response, refreshToken); + log.info("AccessToken: ", accessToken); + log.info("Access Token, Refresh Token 헤더 설정 완료"); + } + + /** + * 헤더에서 RefreshToken 추출 + * 토큰 형식 : Bearer XXX에서 Bearer를 제외하고 순수 토큰만 가져오기 위해서 + * 헤더를 가져온 후 "Bearer"를 삭제(""로 replace) + */ + public Optional extractRefreshToken(HttpServletRequest request) { + Cookie[] cookies = request.getCookies(); + String bearerRefreshToken = ""; + if (cookies != null) { + for (int i = 0; i < cookies.length && cookies[i] != null; i++) { + if (cookies[i].getName().equals("refreshToken")) { + bearerRefreshToken = cookies[i].getValue(); + break; + } + } + } + + return Optional.ofNullable(bearerRefreshToken) + .filter(refreshToken -> refreshToken.startsWith(BEARER)) + .map(refreshToken -> refreshToken.replace(BEARER, "")); + } + + /** + * 헤더에서 AccessToken 추출 + * 토큰 형식 : Bearer XXX에서 Bearer를 제외하고 순수 토큰만 가져오기 위해서 + * 헤더를 가져온 후 "Bearer"를 삭제(""로 replace) + */ + public Optional extractAccessToken(HttpServletRequest request) { + return Optional.ofNullable(request.getHeader(accessHeader)) + .filter(refreshToken -> refreshToken.startsWith(BEARER)) + .map(refreshToken -> refreshToken.replace(BEARER, "")); + } + + public String extractAccessToken(String accessToken) { + try { + isAccessTokenFormatValid(accessToken); + return accessToken.substring(7); + } catch (IllegalArgumentException e) { + log.info("Error: " + e); + } + + return accessToken.substring(7); + } + + public void isAccessTokenFormatValid(String accessToken) { + if (accessToken == null) { + throw new IllegalArgumentException("Input cannot be null"); + } + + if (!accessToken.startsWith("Bearer ")) { + throw new IllegalArgumentException("Invalid input format"); + } + } + + + /** + * userId 추출 + */ + public Optional extractUserId(String token) { + try { + return Optional.ofNullable(JWT.require(Algorithm.HMAC512(secretKey)) + .build() + .verify(token) + .getClaim("userId") + .asLong()); + } catch (Exception e) { + log.error("토큰이 유효하지 않습니다."); + return Optional.empty(); + } + } + + /** + * AccessToken 헤더 설정 + */ + public void setAccessTokenHeader(HttpServletResponse response, String accessToken) { + String bearerAccessToken = "Bearer " + accessToken; + response.setHeader(accessHeader, bearerAccessToken); + } + + /** + * RefreshToken 헤더 설정 + */ + public void setRefreshTokenHeader(HttpServletResponse response, String refreshToken) throws UnsupportedEncodingException { + ResponseCookie cookie = setCookieRefreshToken(refreshToken); + response.setHeader("Set-Cookie", cookie.toString()); + //response.setHeader(refreshHeader, refreshToken); + } + + + public boolean isTokenValid(String token) { + try { + JWT.require(Algorithm.HMAC512(secretKey)).build().verify(token); + return true; + } catch (JWTVerificationException exception) { + log.error("유효하지 않은 토큰입니다. {}", exception.getMessage()); + return false; + } + } + + public String extractDecodedToken(String token) { + return token.substring(7); + } + + public ResponseCookie setCookieRefreshToken(String refreshToken) throws UnsupportedEncodingException { + refreshToken = "Bearer " + refreshToken; + String encodedBearerAndToken = URLEncoder.encode(refreshToken, "UTF-8"); + ResponseCookie cookie = ResponseCookie.from("refreshToken", encodedBearerAndToken) + .maxAge(1209600000) + .path("/users/token") + .secure(true) + .sameSite("None") + .httpOnly(true) + .build(); + + return cookie; + } + + public ResponseCookie deleteCookieRefreshToken() { + ResponseCookie cookie = ResponseCookie.from("refreshToken", "") + .maxAge(0) + .path("/users/token") + .secure(true) + .sameSite("None") + .httpOnly(true) + .build(); + + return cookie; + } + + public HttpHeaders createHeaderWithTokens(String refreshToken) throws UnsupportedEncodingException { + HttpHeaders headers = new HttpHeaders(); + String decodedRefreshToken = extractDecodedToken(URLDecoder.decode(refreshToken, "UTF-8")); + log.info(decodedRefreshToken); + if (isTokenValid(decodedRefreshToken)) { + headers.add(accessHeader, "Bearer " + createAccessToken(extractUserId(decodedRefreshToken).get())); + String newRefreshToken = createRefreshToken(extractUserId(decodedRefreshToken).get()); + ResponseCookie cookie = setCookieRefreshToken(newRefreshToken); + headers.add("Set-Cookie", cookie.toString()); + } else { + log.info("유효하지 않은 토큰"); + log.info(decodedRefreshToken); + } + + return headers; + } + + public HttpHeaders createHeaderWithDeletedCookie(String accessToken) { + HttpHeaders headers = new HttpHeaders(); + String decodedAccessToken = extractDecodedToken(accessToken); + if (isTokenValid(decodedAccessToken)) { + ResponseCookie cookie = deleteCookieRefreshToken(); + headers.add("Set-Cookie", cookie.toString()); + } else { + log.info("유효하지 않은 토큰"); + } + + return headers; + } + + public UserInformationDto getUserInformation(String accessToken) { + String extractedToken = extractDecodedToken(accessToken); + Long userId = extractUserId(extractedToken).orElse(null); + Optional user = userRepository.findById(userId); + UserInformationDto userInformationDto = new UserInformationDto(userId, user.get().getNickname(), user.get().getEmail()); + + return userInformationDto; + } +} diff --git a/src/main/java/com/smart/watchboard/service/KeywordService.java b/src/main/java/com/smart/watchboard/service/KeywordService.java new file mode 100644 index 0000000..7fcf61b --- /dev/null +++ b/src/main/java/com/smart/watchboard/service/KeywordService.java @@ -0,0 +1,98 @@ +package com.smart.watchboard.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.smart.watchboard.domain.Document; +import com.smart.watchboard.domain.Keyword; +import com.smart.watchboard.domain.Mindmap; +import com.smart.watchboard.dto.KeywordsBodyDto; +import com.smart.watchboard.dto.KeywordsDto; +import com.smart.watchboard.repository.KeywordRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Slf4j +public class KeywordService { + private final WhiteboardService whiteboardService; + private final KeywordRepository keywordRepository; + + public void createKeywords(ResponseEntity responseEntity, Long documentId) throws JsonProcessingException { + Document document = whiteboardService.findDoc(documentId); + +// ObjectMapper objectMapper = new ObjectMapper(); +// JsonNode jsonNode = objectMapper.readTree(responseEntity.getBody().getKeywords().toString()); +// JsonNode keywordsNode = jsonNode.get("keywords"); +// List keywords = objectMapper.convertValue(keywordsNode, new TypeReference>() {}); + + Keyword keyword = Keyword.builder() + .documentId(documentId) + .keywords(responseEntity.getBody().getKeywords()) + .build(); + + keywordRepository.save(keyword); + + //return keywords; + } + + public void renewKeywords(ResponseEntity responseEntity, Long documentId) throws JsonProcessingException { + Document document = whiteboardService.findDoc(documentId); + // 마인드맵 생성 + Optional keyword = keywordRepository.findByDocumentId(documentId); + Keyword formerKeyword = Keyword.builder() + .objectId(keyword.get().getObjectId()) + .documentId(keyword.get().getDocumentId()) + .keywords(keyword.get().getKeywords()) + .build(); + deleteAllKeywords(formerKeyword); + +// ObjectMapper objectMapper = new ObjectMapper(); +// JsonNode jsonNode = objectMapper.readTree(responseEntity.getBody().getKeywords().toString()); +// JsonNode keywordsNode = jsonNode.get("keywords"); +// List keywords = objectMapper.convertValue(keywordsNode, new TypeReference>() {}); + + Keyword newKeyword = Keyword.builder() + .documentId(documentId) + .keywords(responseEntity.getBody().getKeywords()) + .build(); + + keywordRepository.save(newKeyword); + } + + public Keyword updateKeywords(KeywordsDto keywordsDto, Long documentId) { + Optional keyword = keywordRepository.findByDocumentId(documentId); + List keywords = keyword.get().getKeywords(); + List addKeywords = keywordsDto.getAdd(); + List deleteKeywords = keywordsDto.getDelete(); + + List newKeywords = new ArrayList<>(keywords); + newKeywords.removeAll(deleteKeywords); + newKeywords.addAll(addKeywords); + + Keyword updatedKeyword = keyword.orElse(null); + updatedKeyword.setKeywords(newKeywords); + + keywordRepository.save(updatedKeyword); + return updatedKeyword; + } + + public Keyword findKeywords(Long documentId) { + Optional keyword = keywordRepository.findByDocumentId(documentId); + + return keyword.orElse(null); + } + + public void deleteAllKeywords(Keyword keyword) { + keywordRepository.delete(keyword); + } +} diff --git a/src/main/java/com/smart/watchboard/service/LectureNoteService.java b/src/main/java/com/smart/watchboard/service/LectureNoteService.java new file mode 100644 index 0000000..bf87dec --- /dev/null +++ b/src/main/java/com/smart/watchboard/service/LectureNoteService.java @@ -0,0 +1,52 @@ +package com.smart.watchboard.service; + +import com.smart.watchboard.domain.Document; +import com.smart.watchboard.domain.LectureNote; +import com.smart.watchboard.domain.Note; +import com.smart.watchboard.domain.SttData; +import com.smart.watchboard.repository.LectureNoteRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.util.List; + +@Service +@RequiredArgsConstructor +@Slf4j +public class LectureNoteService { + private final LectureNoteRepository lectureNoteRepository; + + public void createLectureNote(Long documentId, List data, String sttResult) { + //Document document = whiteboardService.findDoc(documentId); + LectureNote lectureNote = LectureNote.builder() + .documentId(documentId) + .data(data) + .text(sttResult) + .build(); + + lectureNoteRepository.save(lectureNote); + } + + public void updateLectureNote(Long documentId, List data, String sttResult) { + //Document document = whiteboardService.findDoc(documentId); + LectureNote lectureNote = LectureNote.builder() + .documentId(documentId) + .data(data) + .text(sttResult) + .build(); + + lectureNoteRepository.save(lectureNote); + } + + public List getData(Long documentId) { + LectureNote lectureNote = lectureNoteRepository.findByDocumentId(documentId); + return lectureNote.getData(); + } + + public String getText(Long documentId) { + LectureNote lectureNote = lectureNoteRepository.findByDocumentId(documentId); + return lectureNote.getText(); + } +} diff --git a/src/main/java/com/smart/watchboard/service/MindmapService.java b/src/main/java/com/smart/watchboard/service/MindmapService.java new file mode 100644 index 0000000..21ca5bf --- /dev/null +++ b/src/main/java/com/smart/watchboard/service/MindmapService.java @@ -0,0 +1,78 @@ +package com.smart.watchboard.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.smart.watchboard.domain.Document; +import com.smart.watchboard.domain.Keyword; +import com.smart.watchboard.domain.Mindmap; +import com.smart.watchboard.dto.MindmapDto; +import com.smart.watchboard.dto.MindmapResponseDto; +import com.smart.watchboard.repository.MindmapRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Slf4j +public class MindmapService { + + private final MindmapRepository mindmapRepository; + private final WhiteboardService whiteboardService; + private final KeywordService keywordService; + + public void createMindmap(ResponseEntity responseEntity, Long documentId) throws JsonProcessingException { + Document document = whiteboardService.findDoc(documentId); + +// ObjectMapper objectMapper = new ObjectMapper(); +// JsonNode jsonNode = objectMapper.readTree(responseEntity.getBody()); +// Integer root = jsonNode.get("root").asInt(); +// JsonNode keywordsNode = jsonNode.get("keywords"); +// List keywords = objectMapper.convertValue(keywordsNode, new TypeReference>() {}); +// JsonNode graphNode = jsonNode.get("graph"); +// Map> graphMap = objectMapper.convertValue(graphNode, new TypeReference>>() {}); + + Optional formerMindmap = mindmapRepository.findByDocumentId(documentId); + if (formerMindmap.isPresent()) { + Mindmap mindmap = Mindmap.builder() + .objectId(formerMindmap.get().getObjectId()) + .documentId(documentId) + .documentName(document.getDocumentName()) + .createdAt(formerMindmap.get().getCreatedAt()) + .modifiedAt(Instant.now()) + .root(responseEntity.getBody().getRoot()) + .graph(responseEntity.getBody().getGraph()) + .build(); + mindmapRepository.save(mindmap); + } else if (!formerMindmap.isPresent()) { + Mindmap mindmap = Mindmap.builder() + .documentId(documentId) + .documentName(document.getDocumentName()) + .createdAt(Instant.now()) + .modifiedAt(Instant.now()) + .root(responseEntity.getBody().getRoot()) + .graph(responseEntity.getBody().getGraph()) + .build(); + mindmapRepository.save(mindmap); + } + } + + public MindmapDto getMindmap(Long documentId) { + Optional mindmap = mindmapRepository.findByDocumentId(documentId); + Keyword keyword = keywordService.findKeywords(documentId); + + if (mindmap.isEmpty()) { + return null; + } + MindmapDto mindmapDto = new MindmapDto(mindmap.get().getRoot(), keyword.getKeywords(), mindmap.get().getGraph()); + + return mindmapDto; + } + + public void deleteMindmap(Long documentId) { + mindmapRepository.deleteByDocumentId(documentId); + } +} diff --git a/src/main/java/com/smart/watchboard/service/NoteService.java b/src/main/java/com/smart/watchboard/service/NoteService.java new file mode 100644 index 0000000..ba08669 --- /dev/null +++ b/src/main/java/com/smart/watchboard/service/NoteService.java @@ -0,0 +1,62 @@ +package com.smart.watchboard.service; + +import com.smart.watchboard.domain.Document; +import com.smart.watchboard.domain.File; +import com.smart.watchboard.domain.Note; +import com.smart.watchboard.dto.FileDto; +import com.smart.watchboard.repository.NoteRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Slf4j +public class NoteService { + private final NoteRepository noteRepository; + private final WhiteboardService whiteboardService; + + public void createNote(FileDto fileDto) { + Document document = whiteboardService.findDoc(fileDto.getDocumentId()); + String key = fileDto.getFileType() + "/" + fileDto.getDocumentId() + "." + fileDto.getFile().getOriginalFilename(); + Note note = Note.builder() + .fileName(fileDto.getFile().getOriginalFilename()) + .objectKey(key) + .path(fileDto.getPath()) + .createdAt(Instant.now()) + .modifiedAt(Instant.now()) + .isDelete(false) + .document(document) + .build(); + + noteRepository.save(note); + } + + public void updateNote(FileDto fileDto) { + Document document = whiteboardService.findDoc(fileDto.getDocumentId()); + Note note = findNote(document); + note.setFileName(fileDto.getFile().getOriginalFilename()); + note.setPath(fileDto.getPath()); + note.setModifiedAt(Instant.now()); + + noteRepository.save(note); + } + + public void deleteNote(Long documentId) { + Document document = whiteboardService.findDoc(documentId); + Note note = findNote(document); + noteRepository.delete(note); + } + + public Note findNote(Document document) { + return noteRepository.findByDocument(document); + } + + public Note findByDocument(Long documentId) { + Document document = whiteboardService.findDoc(documentId); + return noteRepository.findByDocument(document); + } +} diff --git a/src/main/java/com/smart/watchboard/service/QuestionService.java b/src/main/java/com/smart/watchboard/service/QuestionService.java new file mode 100644 index 0000000..bba16f7 --- /dev/null +++ b/src/main/java/com/smart/watchboard/service/QuestionService.java @@ -0,0 +1,51 @@ +package com.smart.watchboard.service; + +import com.smart.watchboard.domain.Answer; +import com.smart.watchboard.dto.AnswerDto; +import com.smart.watchboard.repository.AnswerRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Slf4j +public class QuestionService { + private final AnswerRepository answerRepository; + + public void createAnswer(Long documentId, String keyword, ResponseEntity responseEntity) { + String text = responseEntity.getBody().getText(); + if (text.equals("init")) { + Answer answer = Answer.builder() + .documentId(documentId) + .keyword(keyword) + .text("processing") + .build(); + answerRepository.save(answer); + } else { + AnswerDto answerDto = getAnswer(documentId, keyword); + if (answerDto.getText().equals("processing")) { + Answer answer = getAnswerForCreate(documentId, keyword); + answer.setText(responseEntity.getBody().getText()); + answerRepository.save(answer); + } + } + } + + public Answer getAnswerForCreate(Long documentId, String keyword) { + Optional answer = answerRepository.findByDocumentIdAndKeyword(documentId, keyword); + Answer answerData = answer.orElse(null); + return answerData; + } + public AnswerDto getAnswer(Long documentId, String keyword) { + Optional answer = answerRepository.findByDocumentIdAndKeyword(documentId, keyword); + if (answer.isEmpty()) { + return null; + } + AnswerDto answerDto = new AnswerDto(answer.get().getText()); + return answerDto; + } +} diff --git a/src/main/java/com/smart/watchboard/service/RequestService.java b/src/main/java/com/smart/watchboard/service/RequestService.java new file mode 100644 index 0000000..7d5a8fe --- /dev/null +++ b/src/main/java/com/smart/watchboard/service/RequestService.java @@ -0,0 +1,227 @@ +package com.smart.watchboard.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.smart.watchboard.domain.Summary; +import com.smart.watchboard.dto.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +import java.io.UnsupportedEncodingException; +import java.util.List; + + +@Service +@RequiredArgsConstructor +@Slf4j +public class RequestService { + @Value("${ai-url}") + private String aiUrl; + + private final MindmapService mindmapService; + private final SummaryService summaryService; + + public ResponseEntity requestPdfKeywords(String filePath) { + int startIndex = filePath.indexOf("application/pdf/") + "application/pdf/".length(); + String fileName = filePath.substring(startIndex); + + RestTemplate restTemplate = new RestTemplate(); + String url = aiUrl + "/keywords"; + // 요청 헤더 추가 + HttpHeaders headers = new HttpHeaders(); + headers.set("Content-Type", MediaType.APPLICATION_JSON_VALUE); + +// // 요청 본문 추가 +// String requestBody = """ +// { +// "dbInfo": { +// "key": "%s", +// "dataType": "pdf" +// } +// } +// """.formatted(fileName); + RequestBodyToServerDto requestBodyToServerDto = new RequestBodyToServerDto(fileName, "audio"); + HttpEntity requestEntity = new HttpEntity<>(requestBodyToServerDto, headers); + // 요청 보내기 + try { + ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, KeywordsBodyDto.class); + //ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class); + return responseEntity; + } catch (HttpClientErrorException e) { + // 에러 응답 처리 + System.err.println("Error response: " + e.getResponseBodyAsString()); + return new ResponseEntity<>(e.getStatusCode()); + } + + // 요청 보내기 +// ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class); +// System.out.println(responseEntity.getBody().toString()); +// ResponseEntity responseEntity1 = new ResponseEntity<>(HttpStatus.OK); +// return responseEntity1; + // JSONObject requestBody = new JSONObject(); +// requestBody.put("key", "value"); + } + + // 수정필요 + public ResponseEntity requestSTTKeywords(String filePath) { + int startIndex = filePath.indexOf("application/pdf/") + "application/pdf/".length(); + String fileName = filePath.substring(startIndex); + + RestTemplate restTemplate = new RestTemplate(); + String url = aiUrl + "/keywords"; + // 요청 헤더 추가 + HttpHeaders headers = new HttpHeaders(); + headers.set("Content-Type", "application/json"); + + RequestBodyToServerDto requestBodyToServerDto = new RequestBodyToServerDto(fileName, "pdf"); + HttpEntity requestEntity = new HttpEntity<>(requestBodyToServerDto, headers); + + // 요청 보내기 + ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, KeywordsBodyDto.class); + //ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class); + return responseEntity; + } + + public ResponseEntity requestPdfSummary(String filePath) throws JsonProcessingException, UnsupportedEncodingException { + int startIndex = filePath.indexOf("application/pdf/") + "application/pdf/".length(); + String fileName = filePath.substring(startIndex); + System.out.println(fileName); + + RestTemplate restTemplate = new RestTemplate(); + String url = aiUrl + "/summary"; + //String url = "http://echo.jsontest.com/key/value/one/two"; + // 요청 헤더 추가 + HttpHeaders headers = new HttpHeaders(); + headers.set("Content-Type", MediaType.APPLICATION_JSON_VALUE); + +// // 요청 본문 추가 +// String requestBody = """ +// { +// "key": "%s", +// "dataType": "pdf" +// } +// """.formatted(fileName); + + RequestBodyToServerDto requestBodyToServerDto = new RequestBodyToServerDto(fileName, "pdf"); + HttpEntity requestEntity = new HttpEntity<>(requestBodyToServerDto, headers); + + // 요청 보내기 + ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, SummaryDto.class); + //ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class); +// responseEntity.getBody(). +// ObjectMapper objectMapper = new ObjectMapper(); +// JsonNode jsonNode = objectMapper.readTree(responseEntity.getBody()); +// String text = jsonNode.get("summary").asText(); + + return responseEntity; + } + + public ResponseEntity requestSTTSummary(String filePath) throws JsonProcessingException { + int startIndex = filePath.indexOf("application/pdf/") + "application/pdf/".length(); + String fileName = filePath.substring(startIndex); + + RestTemplate restTemplate = new RestTemplate(); + String url = aiUrl + "/summary"; + //String url = "http://echo.jsontest.com/key/value/one/two"; + // 요청 헤더 추가 + HttpHeaders headers = new HttpHeaders(); + headers.set("Content-Type", "application/json"); + + RequestBodyToServerDto requestBodyToServerDto = new RequestBodyToServerDto(fileName, "audio"); + HttpEntity requestEntity = new HttpEntity<>(requestBodyToServerDto, headers); + + // 요청 보내기 + ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, SummaryDto.class); + //ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class); + +// ObjectMapper objectMapper = new ObjectMapper(); +// JsonNode jsonNode = objectMapper.readTree(responseEntity.getBody()); +// String text = jsonNode.get("summary").asText(); + + //return responseEntity; + return responseEntity; + } + + + public ResponseEntity requestPdfMindmap(String filePath, Long documentId, List keywords) throws JsonProcessingException { + int startIndex = filePath.indexOf("application/pdf/") + "application/pdf/".length(); + String fileName = filePath.substring(startIndex); + + RestTemplate restTemplate = new RestTemplate(); + String url = aiUrl + "/mindmap"; + //String url = "http://echo.jsontest.com/key/value/one/two"; + // 요청 헤더 추가 + HttpHeaders headers = new HttpHeaders(); + headers.set("Content-Type", "application/json"); + +// // 요청 본문 추가 +// String requestBody = """ +// { +// "dbInfo": { +// "key": "%s", +// "dataType": "pdf" +// }, +// "keywords": "%s", +// "documentId": %d +// } +// """.formatted(filePath, keywords.toString(), documentId); +// HttpEntity requestEntity = new HttpEntity<>(requestBody, headers); + MindmapRequestDto mindmapRequestDto = new MindmapRequestDto(fileName, "pdf", keywords, documentId); + HttpEntity requestEntity = new HttpEntity<>(mindmapRequestDto, headers); + + // 요청 보내기 + ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, MindmapResponseDto.class); + + // 마인드맵 mongoDB에 저장 + mindmapService.createMindmap(responseEntity, documentId); + + return responseEntity; + } + + public ResponseEntity requestSTTMindmap(String filePath, Long documentId, List keywords) throws JsonProcessingException { + int startIndex = filePath.indexOf("application/pdf/") + "application/pdf/".length(); + String fileName = filePath.substring(startIndex); + + RestTemplate restTemplate = new RestTemplate(); + String url = aiUrl + "/mindmap"; + //String url = "http://echo.jsontest.com/key/value/one/two"; + // 요청 헤더 추가 + HttpHeaders headers = new HttpHeaders(); + headers.set("Content-Type", "application/json"); + + MindmapRequestDto mindmapRequestDto = new MindmapRequestDto(fileName, "pdf", keywords, documentId); + HttpEntity requestEntity = new HttpEntity<>(mindmapRequestDto, headers); + + // 요청 보내기 + ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, MindmapResponseDto.class); + + mindmapService.createMindmap(responseEntity, documentId); + + return responseEntity; + } + + public ResponseEntity requestAnswer(Long documentId, String keywordLabel) throws JsonProcessingException { + Summary summary = summaryService.findSummary(documentId); + RestTemplate restTemplate = new RestTemplate(); + String url = aiUrl + "/question"; + + HttpHeaders headers = new HttpHeaders(); + headers.set("Content-Type", "application/json"); + + String requestBody = """ + { + "keyword": "%s", + "summary": "%s" + } + """.formatted(keywordLabel, summary.getContent()); + HttpEntity requestEntity = new HttpEntity<>(requestBody, headers); + + ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, AnswerDto.class); + + return responseEntity; + } +} diff --git a/src/main/java/com/smart/watchboard/service/STTService.java b/src/main/java/com/smart/watchboard/service/STTService.java new file mode 100644 index 0000000..02c4ac0 --- /dev/null +++ b/src/main/java/com/smart/watchboard/service/STTService.java @@ -0,0 +1,70 @@ +package com.smart.watchboard.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.smart.watchboard.domain.SttData; +import com.smart.watchboard.dto.SttRequestDto; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +@RequiredArgsConstructor +@Slf4j +public class STTService { + @Value("${ai-url}") + private String aiUrl; + + public ResponseEntity getSTT(String path) throws JsonProcessingException { + String url = aiUrl + "/stt"; + int startIndex = path.indexOf("audio/mpeg/") + "audio/mpeg/".length(); + String fileName = path.substring(startIndex); + + RestTemplate restTemplate = new RestTemplate(); + HttpHeaders headers = new HttpHeaders(); + headers.set("Content-Type", "application/json"); + SttRequestDto sttRequestDto = new SttRequestDto(fileName); + HttpEntity requestEntity = new HttpEntity<>(sttRequestDto, headers); + ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class); + + return responseEntity; + } + + public List getSTTData(ResponseEntity sttResponseEntity) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(sttResponseEntity.getBody()); + JsonNode segmentsNode = rootNode.get("segments"); + Map> result = new HashMap<>(); + List sttDatas = new ArrayList<>(); + for(JsonNode segment : segmentsNode) { + int start = segment.get("start").asInt(); + int end = segment.get("end").asInt(); + String text = segment.get("text").asText(); + + SttData sttData = new SttData(start, end, text); + sttDatas.add(sttData); + } + + return sttDatas; + } + + public String getText(ResponseEntity sttResponseEntity) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(sttResponseEntity.getBody()); + String text = rootNode.get("text").asText(); + + return text; + } +} diff --git a/src/main/java/com/smart/watchboard/service/SseService.java b/src/main/java/com/smart/watchboard/service/SseService.java new file mode 100644 index 0000000..94cd721 --- /dev/null +++ b/src/main/java/com/smart/watchboard/service/SseService.java @@ -0,0 +1,263 @@ +package com.smart.watchboard.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.itextpdf.text.DocumentException; +import com.smart.watchboard.common.support.AwsS3Uploader; +import com.smart.watchboard.domain.Document; +import com.smart.watchboard.domain.Note; +import com.smart.watchboard.domain.SttData; +import com.smart.watchboard.dto.*; +import com.smart.watchboard.repository.EmitterRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.time.Instant; +import java.util.List; + +import static com.smart.watchboard.common.support.PdfConverter.convertStringToPdf; + +@Service +@RequiredArgsConstructor +public class SseService { + private static final Long DEFAULT_TIMEOUT = 60L * 1000 * 60; + private final EmitterRepository emitterRepository; + private final FileService fileService; + private final WhiteboardService whiteboardService; + private final RequestService requestService; + private final LectureNoteService lectureNoteService; + private final NoteService noteService; + private final KeywordService keywordService; + private final SummaryService summaryService; + private final STTService sttService; + private final AwsS3Uploader awsS3Uploader; + private final QuestionService questionService; + + public SseEmitter subscribe(Long documentId) { + SseEmitter emitter = createEmitter(documentId); + + sendToClientFirst(documentId, "EventStream Created. [documentId=" + documentId + "]"); + return emitter; + } + + public void notify(Long documentId, List keywords) { + sendToClient(documentId, keywords); + } + + private void sendToClientFirst(Long documentId, Object data) { + SseEmitter emitter = emitterRepository.get(documentId); + if (emitter != null) { + try { + emitter.send(SseEmitter.event().id(String.valueOf(documentId)).name("sse").data(data)); + } catch (IOException exception) { + emitterRepository.deleteById(documentId); + emitter.completeWithError(exception); + } + } + } + + private void sendToClient(Long documentId, List keywords) { + SseEmitter emitter = emitterRepository.get(documentId); + if (emitter != null) { + try { + if (whiteboardService.isPdfType(documentId)) { + String path = fileService.getPath(documentId); + ResponseEntity body = requestService.requestPdfMindmap(path, documentId, keywords); + KeywordsBodyDto keywordsDto = new KeywordsBodyDto(body.getBody().getKeywords()); + ResponseEntity keywordsDtoResponseEntity = new ResponseEntity<>(keywordsDto, HttpStatus.OK); + keywordService.renewKeywords(keywordsDtoResponseEntity, documentId); + emitter.send(SseEmitter.event().id(String.valueOf(documentId)).name("mindmap").data("mindmap")); + } else if (whiteboardService.isAudioType(documentId)) { + Note note = noteService.findByDocument(documentId); + ResponseEntity body = requestService.requestSTTMindmap(note.getPath(), documentId, keywords); + KeywordsBodyDto keywordsDto = new KeywordsBodyDto(body.getBody().getKeywords()); + ResponseEntity keywordsDtoResponseEntity = new ResponseEntity<>(keywordsDto, HttpStatus.OK); + keywordService.renewKeywords(keywordsDtoResponseEntity, documentId); + emitter.send(SseEmitter.event().id(String.valueOf(documentId)).name("mindmap").data("mindmap")); + } + } catch (IOException exception) { + emitterRepository.deleteById(documentId); + emitter.completeWithError(exception); + } + } + } + + public void notifyKeywords(Long documentId, String path) { + sendKeywords(documentId, path); + } + + public void notifySummary(Long documentId, String path) { + sendSummary(documentId, path); + } + + public void notifyAnswer(Long documentId, String keywordLabel) throws JsonProcessingException { + sendAnswer(documentId, keywordLabel); + } + + public void notifySTT(Long documentId, String path, Long userId) { + Note note = noteService.findByDocument(documentId); + if (note == null) { + sendSTT(documentId, path, userId); + } else { + sendSTTUpdate(documentId, path, userId); + } + } + + private void sendKeywords(Long documentId, String path) { + SseEmitter emitter = emitterRepository.get(documentId); + if (emitter != null) { + try { + Document document = whiteboardService.findDoc(documentId); + ResponseEntity responseEntity; + if (document.getDataType().equals("pdf")) { + responseEntity = requestService.requestPdfKeywords(path); + if (keywordService.findKeywords(documentId) == null) { + keywordService.createKeywords(responseEntity, documentId); + } else { + keywordService.renewKeywords(responseEntity, documentId); + } + emitter.send(SseEmitter.event().id(String.valueOf(documentId)).name("keywords").data("keywords")); + sendToClient(documentId, responseEntity.getBody().getKeywords()); + } else { + responseEntity = requestService.requestSTTKeywords(path); + if (keywordService.findKeywords(documentId) == null) { + keywordService.createKeywords(responseEntity, documentId); + } else { + keywordService.renewKeywords(responseEntity, documentId); + } + emitter.send(SseEmitter.event().id(String.valueOf(documentId)).name("keywords").data("keywords")); + sendToClient(documentId, responseEntity.getBody().getKeywords()); + } + } catch (IOException exception) { + emitterRepository.deleteById(documentId); + emitter.completeWithError(exception); + } + + } + } + + private void sendSummary(Long documentId, String path) { + SseEmitter emitter = emitterRepository.get(documentId); + if (emitter != null) { + try { + ResponseEntity summary = requestService.requestSTTSummary(path); + if (summaryService.findSummary(documentId) == null) { + summaryService.createSummary(documentId, summary.getBody().getSummary()); + } else { + summaryService.updateSummary(documentId, summary.getBody().getSummary()); + } + } catch (IOException exception) { + emitterRepository.deleteById(documentId); + emitter.completeWithError(exception); + } + } + } + + private void sendAnswer(Long documentId, String keyword) throws JsonProcessingException { + AnswerDto answerDto = questionService.getAnswer(documentId, keyword); + if (answerDto == null) { + answerDto = new AnswerDto("init"); + ResponseEntity temp = new ResponseEntity<>(answerDto, HttpStatus.OK); + questionService.createAnswer(documentId, keyword, temp); + } + ResponseEntity responseEntity = requestService.requestAnswer(documentId, keyword); + questionService.createAnswer(documentId, keyword, responseEntity); + + SseEmitter emitter = emitterRepository.get(documentId); + if (emitter != null) { + try { + emitter.send(SseEmitter.event().id(String.valueOf(documentId)).name("answer").data(keyword)); + } catch (IOException exception) { + emitterRepository.deleteById(documentId); + emitter.completeWithError(exception); + } + } + } + + private void sendSTT(Long documentId, String path, Long userId) { + SseEmitter emitter = emitterRepository.get(documentId); + if (emitter != null) { + try { + ResponseEntity sttResponseEntity = sttService.getSTT(path); + String sttResult = sttService.getText(sttResponseEntity); + + List data = sttService.getSTTData(sttResponseEntity); + lectureNoteService.createLectureNote(documentId, data, sttResult); + SttDataDto sttDataDto = new SttDataDto(data); + + String sttFileName = String.valueOf(userId) + "_" + String.valueOf(documentId) + ".txt"; + File textFile = convertStringToPdf(sttResult, sttFileName); + + String contentType = "application/pdf"; + String originalFilename = textFile.getName(); + String name = textFile.getName(); + + FileInputStream fileInputStream = new FileInputStream(textFile); + MultipartFile multipartFile = new MockMultipartFile(name, originalFilename, contentType, fileInputStream); + S3Dto s3DtoForSTT = new S3Dto(multipartFile, documentId, userId, "pdf"); + String textPdfPath = awsS3Uploader.uploadTextPdfFile(s3DtoForSTT); + + notifyKeywords(documentId, textPdfPath); + notifySummary(documentId, textPdfPath); + + emitter.send(SseEmitter.event().id(String.valueOf(documentId)).name("audio")); + } catch (IOException | DocumentException exception) { + emitterRepository.deleteById(documentId); + emitter.completeWithError(exception); + } + } + } + + private void sendSTTUpdate(Long documentId, String path, Long userId) { + SseEmitter emitter = emitterRepository.get(documentId); + if (emitter != null) { + try { + ResponseEntity sttResponseEntity = sttService.getSTT(path); + String sttResult = sttService.getText(sttResponseEntity); + + List data = sttService.getSTTData(sttResponseEntity); + lectureNoteService.updateLectureNote(documentId, data, sttResult); + SttDataDto sttDataDto = new SttDataDto(data); + + String sttFileName = String.valueOf(userId) + "_" + String.valueOf(documentId) + ".txt"; + File textPdfFile = convertStringToPdf(sttResult, sttFileName); + + String contentType = "application/pdf"; + String originalFilename = textPdfFile.getName(); + String name = textPdfFile.getName(); + + FileInputStream fileInputStream = new FileInputStream(textPdfFile); + MultipartFile multipartFile = new MockMultipartFile(name, originalFilename, contentType, fileInputStream); + S3Dto s3DtoForSTT = new S3Dto(multipartFile, documentId, userId, "pdf"); + String textPdfPath = awsS3Uploader.uploadTextPdfFile(s3DtoForSTT); + + notifyKeywords(documentId, textPdfPath); + notifySummary(documentId, textPdfPath); + + emitter.send(SseEmitter.event().id(String.valueOf(documentId)).name("audio")); + } catch (IOException | DocumentException exception) { + emitterRepository.deleteById(documentId); + emitter.completeWithError(exception); + } + } + } + + private SseEmitter createEmitter(Long documentId) { + SseEmitter emitter = new SseEmitter(DEFAULT_TIMEOUT); + emitterRepository.save(documentId, emitter); + + // Emitter가 완료될 때(모든 데이터가 성공적으로 전송된 상태) Emitter를 삭제한다. + emitter.onCompletion(() -> emitterRepository.deleteById(documentId)); + // Emitter가 타임아웃 되었을 때(지정된 시간동안 어떠한 이벤트도 전송되지 않았을 때) Emitter를 삭제한다. + emitter.onTimeout(() -> emitterRepository.deleteById(documentId)); + + return emitter; + } +} diff --git a/src/main/java/com/smart/watchboard/service/SummaryService.java b/src/main/java/com/smart/watchboard/service/SummaryService.java new file mode 100644 index 0000000..b3c1192 --- /dev/null +++ b/src/main/java/com/smart/watchboard/service/SummaryService.java @@ -0,0 +1,63 @@ +package com.smart.watchboard.service; + +import com.smart.watchboard.domain.Document; +import com.smart.watchboard.domain.Note; +import com.smart.watchboard.domain.Summary; +import com.smart.watchboard.repository.SummaryRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.time.Instant; + +@Service +@RequiredArgsConstructor +@Slf4j +public class SummaryService { + + private final SummaryRepository summaryRepository; + private final WhiteboardService whiteboardService; + + public void createSummary(Long documentId, String summaryContent) { + Document document = whiteboardService.findDoc(documentId); + Summary summary = Summary.builder() + .content(summaryContent) + .createdAt(Instant.now()) + .modifiedAt(Instant.now()) + .isDelete(false) + .document(document) + .build(); + + summaryRepository.save(summary); + } + + public void updateSummary(Long documentId, String summaryContent) { + Document document = whiteboardService.findDoc(documentId); + Summary summary = summaryRepository.findByDocument(document); + summary.setContent(summaryContent); + summary.setModifiedAt(Instant.now()); + + summaryRepository.save(summary); + } + + public void deleteSummary(Long documentId) { + Document document = whiteboardService.findDoc(documentId); + Summary summary = summaryRepository.findByDocument(document); + summaryRepository.delete(summary); + } + + public Summary findSummary(Long documentId) { + Document document = whiteboardService.findDoc(documentId); + Summary summary = summaryRepository.findByDocument(document); + + return summary; + } + public boolean checkSummary(Long documentId) { + Summary summary = findSummary(documentId); + if (summary == null) { + return false; + } + return true; + } + +} diff --git a/src/main/java/com/smart/watchboard/service/WhiteboardService.java b/src/main/java/com/smart/watchboard/service/WhiteboardService.java new file mode 100644 index 0000000..f25dd88 --- /dev/null +++ b/src/main/java/com/smart/watchboard/service/WhiteboardService.java @@ -0,0 +1,189 @@ +package com.smart.watchboard.service; + +import com.smart.watchboard.domain.*; +import com.smart.watchboard.dto.*; +import com.smart.watchboard.repository.DocumentRepository; +import com.smart.watchboard.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.parameters.P; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.util.*; + +@Service +@RequiredArgsConstructor +@Slf4j +public class WhiteboardService { + private final JwtService jwtService; + private final DocumentRepository documentRepository; + private final UserRepository userRepository; + + public List findDocumentsByUserId(String accessToken) { + String extractedAccessToken = jwtService.extractAccessToken(accessToken); + Optional userId = jwtService.extractUserId(extractedAccessToken); + + User user = userRepository.findById(userId).orElse(null); + List documents = documentRepository.findByUserAndIsDeletedFalse(user); + List documentDtos = new ArrayList<>(); + for (Document document : documents) { + documentDtos.add(new DocumentDto(document.getDocumentId(), document.getDocumentName(), document.getCreatedAt(), document.getModifiedAt())); + } + + return documentDtos; + } + + public DocumentCreatedResponseDto createDocument(RequestCreatedDocumentDto requestCreatedDocumentDto, String accessToken) { + String extractedAccessToken = jwtService.extractAccessToken(accessToken); + Optional userId = jwtService.extractUserId(extractedAccessToken); + User user = userRepository.getById(userId); + + Document document = new Document(requestCreatedDocumentDto.getDocumentName(), Instant.now().toEpochMilli(), Instant.now().toEpochMilli(), false, "none", user); + documentRepository.save(document); +// createUserDocument(user, document); + +// Map documentData = new HashMap<>(); +// Whiteboard whiteboard = setWhiteboard(document, documentData); +// whiteboardRepository.save(whiteboard); + DocumentCreatedResponseDto responseDto = new DocumentCreatedResponseDto(document.getDocumentId(), document.getDocumentName(), document.getCreatedAt(), document.getModifiedAt()); + + return responseDto; + } + +// public void createUserDocument(User user, Document document) { +// UserDocument userDocument = new UserDocument(user, document); +// userDocumentRepository.save(userDocument); +// } + + public void deleteDocument(long documentId, String accessToken) { + String extractedAccessToken = jwtService.extractAccessToken(accessToken); + jwtService.extractUserId(extractedAccessToken); + Optional document = documentRepository.findById(documentId); + if (document.isPresent()) { + Document updatedDocument = document.get(); + updatedDocument.setDeleted(true); + documentRepository.save(updatedDocument); + } + + } + + public boolean checkAuthorization(Long documentId, Long userId) { + Document document = findDoc(documentId); + if (userId != document.getUser().getId()) { + return false; + } + return true; + } + +// public void createWhiteboardData(Map documentData, long documentId, String accessToken) { +// String extractedAccessToken = jwtService.extractAccessToken(accessToken); +// Optional userId = jwtService.extractUserId(extractedAccessToken); +// User user = userRepository.getById(userId); +// Document document = documentRepository.findByDocumentId(documentId); +// +// if (!userDocumentRepository.existsByUserAndDocument(user, document)) { +// throw new HttpClientErrorException(HttpStatus.BAD_REQUEST); +// } +// +// Optional exsistingWhiteboardData = whiteboardRepository.findByDocumentId(document.getDocumentId()); +// exsistingWhiteboardData.ifPresentOrElse(data -> { +// data.setDocumentData(documentData); +// whiteboardRepository.save(data); +// }, () -> { +// Whiteboard whiteboard = setWhiteboard(document, documentData); +// whiteboardRepository.save(whiteboard); +// }); +// } + +// public Whiteboard setWhiteboard(Document document, Map documentData) { +// Whiteboard whiteboard = Whiteboard.builder() +// .documentId(document.getDocumentId()) +// .documentName(document.getDocumentName()) +// .createdAt(document.getCreatedAt().toEpochMilli()) +// .modifiedAt(document.getModifiedAt().toEpochMilli()) +// .documentData(documentData) +// .build(); +// +// return whiteboard; +// } + +// public Map setDocumentDataMap(Optional whiteboard) { +// Map documentDataMap = new HashMap<>(); +// +// for (Map.Entry entry : whiteboard.get().getDocumentData().entrySet()) { +// String key = entry.getKey(); +// WhiteboardData value = entry.getValue(); +// +// DocumentObjectDto documentObjectDto = DocumentObjectDtoFactory.createDtoFromWhiteboardData(value); +// if (documentObjectDto != null) { +// documentDataMap.put(key, documentObjectDto); +// } +// } +// +// return documentDataMap; +// } + + public DocumentResponseDto findDocument(long documentId, String accessToken) { + String extractedAccessToken = jwtService.extractAccessToken(accessToken); + Optional userId = jwtService.extractUserId(extractedAccessToken); + //Optional whiteboard = whiteboardRepository.findByDocumentId(documentId); + User user = userRepository.getById(userId); + //Document document = documentRepository.findByDocumentId(whiteboard.get().getDocumentId()); + Document document = documentRepository.findByDocumentId(documentId); +// if (!userDocumentRepository.existsByUserAndDocument(user, document)) { +// throw new HttpClientErrorException(HttpStatus.BAD_REQUEST); +// } + + //String dataType = mindmapService.getDataType(documentId); + + DocumentResponseDto documentResponseDto = new DocumentResponseDto(); + documentResponseDto.setDocumentId(documentId); +// documentResponseDto.setDocumentName(whiteboard.get().getDocumentName()); +// documentResponseDto.setCreatedAt(whiteboard.get().getCreatedAt()); +// documentResponseDto.setModifiedAt(whiteboard.get().getModifiedAt()); +// documentResponseDto.setDataType(dataType); + + documentResponseDto.setDocumentName(document.getDocumentName()); + documentResponseDto.setCreatedAt(document.getCreatedAt()); + documentResponseDto.setModifiedAt(document.getModifiedAt()); + documentResponseDto.setDataType(document.getDataType()); + +// Map documentDataMap = setDocumentDataMap(whiteboard); +// documentResponseDto.setDocumentData(documentDataMap); + + return documentResponseDto; + } + + public Document findDoc(long documentId) { + Document document = documentRepository.findByDocumentId(documentId); + return document; + } + + public void setDataType(Long documentId, String dataType) { + Document document = findDoc(documentId); + document.setDataType(dataType); + documentRepository.save(document); + } + + public boolean isPdfType(Long documentId) { + Document document = findDoc(documentId); + if (document.getDataType().equals("pdf")) { + return true; + } + return false; + } + + public boolean isAudioType(Long documentId) { + Document document = findDoc(documentId); + if (document.getDataType().equals("audio")) { + return true; + } + return false; + } + +// private DocumentCreatedResponseDto convertToDocumentCreatedResponseDto(Document document) { +// DocumentCreatedResponseDto dto = new DocumentCreatedResponseDto(document.getDocumentId(), document.getDocumentName(), document.getCreatedAt().toEpochMilli(), document.getModifiedAt().toEpochMilli()); +// return dto; +// } +} diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html new file mode 100644 index 0000000..122d78b --- /dev/null +++ b/src/main/resources/static/index.html @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + Hello WebSocket + + + + + + + +

Seems your browser doesn't support Javascript! Websocket relies on Javascript being + enabled. Please enable + Javascript and reload this page!

+
+
+
+
+
+ + + +
+
+
+
+
+
+ + +
+ +
+
+
+
+
+ + + + + + + + +
Greetings
+
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/NanumGothic.ttf b/src/main/resources/templates/NanumGothic.ttf new file mode 100644 index 0000000..75d010a Binary files /dev/null and b/src/main/resources/templates/NanumGothic.ttf differ diff --git a/src/test/java/com/smart/watchboard/WatchboardApplicationTests.java b/src/test/java/com/smart/watchboard/WatchboardApplicationTests.java new file mode 100644 index 0000000..33c2640 --- /dev/null +++ b/src/test/java/com/smart/watchboard/WatchboardApplicationTests.java @@ -0,0 +1,13 @@ +package com.smart.watchboard; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +//@SpringBootTest +class WatchboardApplicationTests { + + @Test + void contextLoads() { + } + +}