[
  {
    "path": ".gitignore",
    "content": "HELP.md\ntarget/\n!.mvn/wrapper/maven-wrapper.jar\n!**/src/main/**/target/\n!**/src/test/**/target/\n\n### STS ###\n.apt_generated\n.classpath\n.factorypath\n.project\n.settings\n.springBeans\n.sts4-cache\n\n### IntelliJ IDEA ###\n.idea\n*.iws\n*.iml\n*.ipr\n\n### NetBeans ###\n/nbproject/private/\n/nbbuild/\n/dist/\n/nbdist/\n/.nb-gradle/\nbuild/\n!**/src/main/**/build/\n!**/src/test/**/build/\n\n### VS Code ###\n.vscode/\n"
  },
  {
    "path": ".mvn/wrapper/MavenWrapperDownloader.java",
    "content": "/*\n * Copyright 2007-present the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport java.net.*;\nimport java.io.*;\nimport java.nio.channels.*;\nimport java.util.Properties;\n\npublic class MavenWrapperDownloader {\n\n    private static final String WRAPPER_VERSION = \"0.5.6\";\n    /**\n     * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.\n     */\n    private static final String DEFAULT_DOWNLOAD_URL = \"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/\"\n        + WRAPPER_VERSION + \"/maven-wrapper-\" + WRAPPER_VERSION + \".jar\";\n\n    /**\n     * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to\n     * use instead of the default one.\n     */\n    private static final String MAVEN_WRAPPER_PROPERTIES_PATH =\n            \".mvn/wrapper/maven-wrapper.properties\";\n\n    /**\n     * Path where the maven-wrapper.jar will be saved to.\n     */\n    private static final String MAVEN_WRAPPER_JAR_PATH =\n            \".mvn/wrapper/maven-wrapper.jar\";\n\n    /**\n     * Name of the property which should be used to override the default download url for the wrapper.\n     */\n    private static final String PROPERTY_NAME_WRAPPER_URL = \"wrapperUrl\";\n\n    public static void main(String args[]) {\n        System.out.println(\"- Downloader started\");\n        File baseDirectory = new File(args[0]);\n        System.out.println(\"- Using base directory: \" + baseDirectory.getAbsolutePath());\n\n        // If the maven-wrapper.properties exists, read it and check if it contains a custom\n        // wrapperUrl parameter.\n        File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);\n        String url = DEFAULT_DOWNLOAD_URL;\n        if(mavenWrapperPropertyFile.exists()) {\n            FileInputStream mavenWrapperPropertyFileInputStream = null;\n            try {\n                mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);\n                Properties mavenWrapperProperties = new Properties();\n                mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);\n                url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);\n            } catch (IOException e) {\n                System.out.println(\"- ERROR loading '\" + MAVEN_WRAPPER_PROPERTIES_PATH + \"'\");\n            } finally {\n                try {\n                    if(mavenWrapperPropertyFileInputStream != null) {\n                        mavenWrapperPropertyFileInputStream.close();\n                    }\n                } catch (IOException e) {\n                    // Ignore ...\n                }\n            }\n        }\n        System.out.println(\"- Downloading from: \" + url);\n\n        File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);\n        if(!outputFile.getParentFile().exists()) {\n            if(!outputFile.getParentFile().mkdirs()) {\n                System.out.println(\n                        \"- ERROR creating output directory '\" + outputFile.getParentFile().getAbsolutePath() + \"'\");\n            }\n        }\n        System.out.println(\"- Downloading to: \" + outputFile.getAbsolutePath());\n        try {\n            downloadFileFromURL(url, outputFile);\n            System.out.println(\"Done\");\n            System.exit(0);\n        } catch (Throwable e) {\n            System.out.println(\"- Error downloading\");\n            e.printStackTrace();\n            System.exit(1);\n        }\n    }\n\n    private static void downloadFileFromURL(String urlString, File destination) throws Exception {\n        if (System.getenv(\"MVNW_USERNAME\") != null && System.getenv(\"MVNW_PASSWORD\") != null) {\n            String username = System.getenv(\"MVNW_USERNAME\");\n            char[] password = System.getenv(\"MVNW_PASSWORD\").toCharArray();\n            Authenticator.setDefault(new Authenticator() {\n                @Override\n                protected PasswordAuthentication getPasswordAuthentication() {\n                    return new PasswordAuthentication(username, password);\n                }\n            });\n        }\n        URL website = new URL(urlString);\n        ReadableByteChannel rbc;\n        rbc = Channels.newChannel(website.openStream());\n        FileOutputStream fos = new FileOutputStream(destination);\n        fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);\n        fos.close();\n        rbc.close();\n    }\n\n}\n"
  },
  {
    "path": ".mvn/wrapper/maven-wrapper.properties",
    "content": "distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip\nwrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar\n"
  },
  {
    "path": "README.md",
    "content": "# 1.변경사항\n- 스프링 시큐리티 설정 수정 (2025-02-13)\n  - 애플리케이션 기동 후 첫 post에서 csrf 토큰이 생성되지 않는 문제 발견\n  - CsrfCookieFilter에서 csrfToken.getToken() 을 호출하여 csrf 토큰이 없는 경우 강제로 생성\n  - SecurityConfig에 CsrfCookieFilter 추가\n- 스프링 시큐리티 설정 수정 (2025-02-03)\n  - 로그인 후 redirect 오류로 인한 SecurityConfig -> defaultSucceeUrl 항상 \"/\"로 가도록 true 설정 \n  - SecurityConfig에서 csrfTokenRepository로 CookieCsrfTokenRepository 를 사용하도록 수정. \n    -> 디폴트로 HttpSessionCsrfTokenRepository를 사용하는데 세션이 생성되기 전에 csrf가 필요한 페이지 접근 시 csrf 값을 가져올 수 없는 문제가 있음\n    -> CookieCsrfTokenRepository를 사용하여 http only cookie에 csrf 토큰을 저장하도록 수정 \n- 스프링 시큐리티 설정 수정 (2025-01-26)\n  - 상품 상세 페이지에서 order() 실행 시 비로그인 상태의 경우 http status 401로 응답이 되지 않는 버그 발견\n  - CustomAuthenticationEntryPoint 클래스 신규 생성 및 SecurityConfig 설정 추가\n  - CustomAuthenticationFailureHandler -> FormLoginAuthenticationFailureHandler 클래스명 리팩토링 진행\n- 스프링부트3(2024-08-17) branch\n  - 스프링부트 3.3.2 버전으로 업데이트\n  - 스프링시큐리티 설정 수정 (SecurityConfig, CustomAuthenticationEntryPoint -> CustomAuthenticationFailureHandler 수정)\n  - thymeleaf-layout-dialect 3.2.1 -> 3.3.0 버전업 진행\n- 스프링부트3(2023-07-17) branch\n  - 스프링부트 3.1.1 버전으로 업데이트\n  - 스프링부트3 버전부터는 자바 17버전 이상을 사용해야합니다.\n  - pom.xml 버전 정보가 많이 바뀌었습니다. 최신 pom.xml 버전을 참고해주세요\n  - javax에서 jakarta로 변경됨에 따라서 많은 import 들이 jakarta로 수정되었습니다.\n    - javax.validation => jakarta.validation\n    - javax.persistence => jakarta.persistence\n    - CustomAuthenticationEntryPoint.java (javax.servlet => jakarta.servlet)\n    - Security 버전이 수정됨에 따라 기존 메소드가 deprecated 됐습니다. 최신 설정은 추후 올려두도록하겠습니다.\n  - layout1.html 파일 내용 수정\n    - thymeleaf layout 버전 증가에 따른 코드 수정 \n    - th:replace=\"fragments/header::header\">   =>   th:replace=\"~{fragments/header::header}\">\n    - th:replace=\"fragments/footer::footer\">   =>   th:replace=\"~{fragments/footer::footer}\">\n- 2022-06-26 branch \n   - 스프링부트 2.7.1 버전으로 업데이트\n   - querydsl 5.0.0 버전으로 pom.xml 업데이트 및 ItemRepositoryCustomImpl의 fetchResults() deprecated 대응\n     (list 조회 및 count 조회 쿼리 분리)\n   - WebSecurityConfigurerAdapter deprecated로 인한 SecurityConfig 파일 수정\n   - thymeleaf-layout-dialect 3.1.0 버전으로 업데이트\n   - modelmapper 3.1.0 버전으로 업데이트\n  \n# 2.안내사항\n - 스프링부트3로 진행 시 기존 소스코드와 import 등 코드가 많이 달라져서 스프링부트 2.7.1 버전 기준으로 진행을 권장드립니다.\n - 백견불여일타 스프링부트 with JPA 질의응답 게시판 공지사항을 보시면 자주 겪으시는 오류 사항들도 계속 업데이트 중입니다. 참고부탁드립니다. (https://cafe.naver.com/codefirst)\n"
  },
  {
    "path": "mvnw",
    "content": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the NOTICE file\n# distributed with this work for additional information\n# regarding copyright ownership.  The ASF licenses this file\n# to you under the Apache License, Version 2.0 (the\n# \"License\"); you may not use this file except in compliance\n# with the License.  You may obtain a copy of the License at\n#\n#    https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing,\n# software distributed under the License is distributed on an\n# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n# KIND, either express or implied.  See the License for the\n# specific language governing permissions and limitations\n# under the License.\n# ----------------------------------------------------------------------------\n\n# ----------------------------------------------------------------------------\n# Maven Start Up Batch script\n#\n# Required ENV vars:\n# ------------------\n#   JAVA_HOME - location of a JDK home dir\n#\n# Optional ENV vars\n# -----------------\n#   M2_HOME - location of maven2's installed home dir\n#   MAVEN_OPTS - parameters passed to the Java VM when running Maven\n#     e.g. to debug Maven itself, use\n#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\n#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files\n# ----------------------------------------------------------------------------\n\nif [ -z \"$MAVEN_SKIP_RC\" ] ; then\n\n  if [ -f /etc/mavenrc ] ; then\n    . /etc/mavenrc\n  fi\n\n  if [ -f \"$HOME/.mavenrc\" ] ; then\n    . \"$HOME/.mavenrc\"\n  fi\n\nfi\n\n# OS specific support.  $var _must_ be set to either true or false.\ncygwin=false;\ndarwin=false;\nmingw=false\ncase \"`uname`\" in\n  CYGWIN*) cygwin=true ;;\n  MINGW*) mingw=true;;\n  Darwin*) darwin=true\n    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home\n    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html\n    if [ -z \"$JAVA_HOME\" ]; then\n      if [ -x \"/usr/libexec/java_home\" ]; then\n        export JAVA_HOME=\"`/usr/libexec/java_home`\"\n      else\n        export JAVA_HOME=\"/Library/Java/Home\"\n      fi\n    fi\n    ;;\nesac\n\nif [ -z \"$JAVA_HOME\" ] ; then\n  if [ -r /etc/gentoo-release ] ; then\n    JAVA_HOME=`java-config --jre-home`\n  fi\nfi\n\nif [ -z \"$M2_HOME\" ] ; then\n  ## resolve links - $0 may be a link to maven's home\n  PRG=\"$0\"\n\n  # need this for relative symlinks\n  while [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n      PRG=\"$link\"\n    else\n      PRG=\"`dirname \"$PRG\"`/$link\"\n    fi\n  done\n\n  saveddir=`pwd`\n\n  M2_HOME=`dirname \"$PRG\"`/..\n\n  # make it fully qualified\n  M2_HOME=`cd \"$M2_HOME\" && pwd`\n\n  cd \"$saveddir\"\n  # echo Using m2 at $M2_HOME\nfi\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched\nif $cygwin ; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=`cygpath --unix \"$M2_HOME\"`\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\n  [ -n \"$CLASSPATH\" ] &&\n    CLASSPATH=`cygpath --path --unix \"$CLASSPATH\"`\nfi\n\n# For Mingw, ensure paths are in UNIX format before anything is touched\nif $mingw ; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=\"`(cd \"$M2_HOME\"; pwd)`\"\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=\"`(cd \"$JAVA_HOME\"; pwd)`\"\nfi\n\nif [ -z \"$JAVA_HOME\" ]; then\n  javaExecutable=\"`which javac`\"\n  if [ -n \"$javaExecutable\" ] && ! [ \"`expr \\\"$javaExecutable\\\" : '\\([^ ]*\\)'`\" = \"no\" ]; then\n    # readlink(1) is not available as standard on Solaris 10.\n    readLink=`which readlink`\n    if [ ! `expr \"$readLink\" : '\\([^ ]*\\)'` = \"no\" ]; then\n      if $darwin ; then\n        javaHome=\"`dirname \\\"$javaExecutable\\\"`\"\n        javaExecutable=\"`cd \\\"$javaHome\\\" && pwd -P`/javac\"\n      else\n        javaExecutable=\"`readlink -f \\\"$javaExecutable\\\"`\"\n      fi\n      javaHome=\"`dirname \\\"$javaExecutable\\\"`\"\n      javaHome=`expr \"$javaHome\" : '\\(.*\\)/bin'`\n      JAVA_HOME=\"$javaHome\"\n      export JAVA_HOME\n    fi\n  fi\nfi\n\nif [ -z \"$JAVACMD\" ] ; then\n  if [ -n \"$JAVA_HOME\"  ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n      # IBM's JDK on AIX uses strange locations for the executables\n      JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n      JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n  else\n    JAVACMD=\"`which java`\"\n  fi\nfi\n\nif [ ! -x \"$JAVACMD\" ] ; then\n  echo \"Error: JAVA_HOME is not defined correctly.\" >&2\n  echo \"  We cannot execute $JAVACMD\" >&2\n  exit 1\nfi\n\nif [ -z \"$JAVA_HOME\" ] ; then\n  echo \"Warning: JAVA_HOME environment variable is not set.\"\nfi\n\nCLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher\n\n# traverses directory structure from process work directory to filesystem root\n# first directory with .mvn subdirectory is considered project base directory\nfind_maven_basedir() {\n\n  if [ -z \"$1\" ]\n  then\n    echo \"Path not specified to find_maven_basedir\"\n    return 1\n  fi\n\n  basedir=\"$1\"\n  wdir=\"$1\"\n  while [ \"$wdir\" != '/' ] ; do\n    if [ -d \"$wdir\"/.mvn ] ; then\n      basedir=$wdir\n      break\n    fi\n    # workaround for JBEAP-8937 (on Solaris 10/Sparc)\n    if [ -d \"${wdir}\" ]; then\n      wdir=`cd \"$wdir/..\"; pwd`\n    fi\n    # end of workaround\n  done\n  echo \"${basedir}\"\n}\n\n# concatenates all lines of a file\nconcat_lines() {\n  if [ -f \"$1\" ]; then\n    echo \"$(tr -s '\\n' ' ' < \"$1\")\"\n  fi\n}\n\nBASE_DIR=`find_maven_basedir \"$(pwd)\"`\nif [ -z \"$BASE_DIR\" ]; then\n  exit 1;\nfi\n\n##########################################################################################\n# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central\n# This allows using the maven wrapper in projects that prohibit checking in binary data.\n##########################################################################################\nif [ -r \"$BASE_DIR/.mvn/wrapper/maven-wrapper.jar\" ]; then\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Found .mvn/wrapper/maven-wrapper.jar\"\n    fi\nelse\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ...\"\n    fi\n    if [ -n \"$MVNW_REPOURL\" ]; then\n      jarUrl=\"$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar\"\n    else\n      jarUrl=\"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar\"\n    fi\n    while IFS=\"=\" read key value; do\n      case \"$key\" in (wrapperUrl) jarUrl=\"$value\"; break ;;\n      esac\n    done < \"$BASE_DIR/.mvn/wrapper/maven-wrapper.properties\"\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Downloading from: $jarUrl\"\n    fi\n    wrapperJarPath=\"$BASE_DIR/.mvn/wrapper/maven-wrapper.jar\"\n    if $cygwin; then\n      wrapperJarPath=`cygpath --path --windows \"$wrapperJarPath\"`\n    fi\n\n    if command -v wget > /dev/null; then\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Found wget ... using wget\"\n        fi\n        if [ -z \"$MVNW_USERNAME\" ] || [ -z \"$MVNW_PASSWORD\" ]; then\n            wget \"$jarUrl\" -O \"$wrapperJarPath\"\n        else\n            wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD \"$jarUrl\" -O \"$wrapperJarPath\"\n        fi\n    elif command -v curl > /dev/null; then\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Found curl ... using curl\"\n        fi\n        if [ -z \"$MVNW_USERNAME\" ] || [ -z \"$MVNW_PASSWORD\" ]; then\n            curl -o \"$wrapperJarPath\" \"$jarUrl\" -f\n        else\n            curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o \"$wrapperJarPath\" \"$jarUrl\" -f\n        fi\n\n    else\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Falling back to using Java to download\"\n        fi\n        javaClass=\"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java\"\n        # For Cygwin, switch paths to Windows format before running javac\n        if $cygwin; then\n          javaClass=`cygpath --path --windows \"$javaClass\"`\n        fi\n        if [ -e \"$javaClass\" ]; then\n            if [ ! -e \"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class\" ]; then\n                if [ \"$MVNW_VERBOSE\" = true ]; then\n                  echo \" - Compiling MavenWrapperDownloader.java ...\"\n                fi\n                # Compiling the Java class\n                (\"$JAVA_HOME/bin/javac\" \"$javaClass\")\n            fi\n            if [ -e \"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class\" ]; then\n                # Running the downloader\n                if [ \"$MVNW_VERBOSE\" = true ]; then\n                  echo \" - Running MavenWrapperDownloader.java ...\"\n                fi\n                (\"$JAVA_HOME/bin/java\" -cp .mvn/wrapper MavenWrapperDownloader \"$MAVEN_PROJECTBASEDIR\")\n            fi\n        fi\n    fi\nfi\n##########################################################################################\n# End of extension\n##########################################################################################\n\nexport MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-\"$BASE_DIR\"}\nif [ \"$MVNW_VERBOSE\" = true ]; then\n  echo $MAVEN_PROJECTBASEDIR\nfi\nMAVEN_OPTS=\"$(concat_lines \"$MAVEN_PROJECTBASEDIR/.mvn/jvm.config\") $MAVEN_OPTS\"\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=`cygpath --path --windows \"$M2_HOME\"`\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=`cygpath --path --windows \"$JAVA_HOME\"`\n  [ -n \"$CLASSPATH\" ] &&\n    CLASSPATH=`cygpath --path --windows \"$CLASSPATH\"`\n  [ -n \"$MAVEN_PROJECTBASEDIR\" ] &&\n    MAVEN_PROJECTBASEDIR=`cygpath --path --windows \"$MAVEN_PROJECTBASEDIR\"`\nfi\n\n# Provide a \"standardized\" way to retrieve the CLI args that will\n# work with both Windows and non-Windows executions.\nMAVEN_CMD_LINE_ARGS=\"$MAVEN_CONFIG $@\"\nexport MAVEN_CMD_LINE_ARGS\n\nWRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\n\nexec \"$JAVACMD\" \\\n  $MAVEN_OPTS \\\n  -classpath \"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar\" \\\n  \"-Dmaven.home=${M2_HOME}\" \"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}\" \\\n  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG \"$@\"\n"
  },
  {
    "path": "mvnw.cmd",
    "content": "@REM ----------------------------------------------------------------------------\n@REM Licensed to the Apache Software Foundation (ASF) under one\n@REM or more contributor license agreements.  See the NOTICE file\n@REM distributed with this work for additional information\n@REM regarding copyright ownership.  The ASF licenses this file\n@REM to you under the Apache License, Version 2.0 (the\n@REM \"License\"); you may not use this file except in compliance\n@REM with the License.  You may obtain a copy of the License at\n@REM\n@REM    https://www.apache.org/licenses/LICENSE-2.0\n@REM\n@REM Unless required by applicable law or agreed to in writing,\n@REM software distributed under the License is distributed on an\n@REM \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n@REM KIND, either express or implied.  See the License for the\n@REM specific language governing permissions and limitations\n@REM under the License.\n@REM ----------------------------------------------------------------------------\n\n@REM ----------------------------------------------------------------------------\n@REM Maven Start Up Batch script\n@REM\n@REM Required ENV vars:\n@REM JAVA_HOME - location of a JDK home dir\n@REM\n@REM Optional ENV vars\n@REM M2_HOME - location of maven2's installed home dir\n@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands\n@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending\n@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven\n@REM     e.g. to debug Maven itself, use\n@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\n@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files\n@REM ----------------------------------------------------------------------------\n\n@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'\n@echo off\n@REM set title of command window\ntitle %0\n@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'\n@if \"%MAVEN_BATCH_ECHO%\" == \"on\"  echo %MAVEN_BATCH_ECHO%\n\n@REM set %HOME% to equivalent of $HOME\nif \"%HOME%\" == \"\" (set \"HOME=%HOMEDRIVE%%HOMEPATH%\")\n\n@REM Execute a user defined script before this one\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPre\n@REM check for pre script, once with legacy .bat ending and once with .cmd ending\nif exist \"%HOME%\\mavenrc_pre.bat\" call \"%HOME%\\mavenrc_pre.bat\"\nif exist \"%HOME%\\mavenrc_pre.cmd\" call \"%HOME%\\mavenrc_pre.cmd\"\n:skipRcPre\n\n@setlocal\n\nset ERROR_CODE=0\n\n@REM To isolate internal variables from possible post scripts, we use another setlocal\n@setlocal\n\n@REM ==== START VALIDATION ====\nif not \"%JAVA_HOME%\" == \"\" goto OkJHome\n\necho.\necho Error: JAVA_HOME not found in your environment. >&2\necho Please set the JAVA_HOME variable in your environment to match the >&2\necho location of your Java installation. >&2\necho.\ngoto error\n\n:OkJHome\nif exist \"%JAVA_HOME%\\bin\\java.exe\" goto init\n\necho.\necho Error: JAVA_HOME is set to an invalid directory. >&2\necho JAVA_HOME = \"%JAVA_HOME%\" >&2\necho Please set the JAVA_HOME variable in your environment to match the >&2\necho location of your Java installation. >&2\necho.\ngoto error\n\n@REM ==== END VALIDATION ====\n\n:init\n\n@REM Find the project base dir, i.e. the directory that contains the folder \".mvn\".\n@REM Fallback to current working directory if not found.\n\nset MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%\nIF NOT \"%MAVEN_PROJECTBASEDIR%\"==\"\" goto endDetectBaseDir\n\nset EXEC_DIR=%CD%\nset WDIR=%EXEC_DIR%\n:findBaseDir\nIF EXIST \"%WDIR%\"\\.mvn goto baseDirFound\ncd ..\nIF \"%WDIR%\"==\"%CD%\" goto baseDirNotFound\nset WDIR=%CD%\ngoto findBaseDir\n\n:baseDirFound\nset MAVEN_PROJECTBASEDIR=%WDIR%\ncd \"%EXEC_DIR%\"\ngoto endDetectBaseDir\n\n:baseDirNotFound\nset MAVEN_PROJECTBASEDIR=%EXEC_DIR%\ncd \"%EXEC_DIR%\"\n\n:endDetectBaseDir\n\nIF NOT EXIST \"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\" goto endReadAdditionalConfig\n\n@setlocal EnableExtensions EnableDelayedExpansion\nfor /F \"usebackq delims=\" %%a in (\"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a\n@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%\n\n:endReadAdditionalConfig\n\nSET MAVEN_JAVA_EXE=\"%JAVA_HOME%\\bin\\java.exe\"\nset WRAPPER_JAR=\"%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.jar\"\nset WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\n\nset DOWNLOAD_URL=\"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar\"\n\nFOR /F \"tokens=1,2 delims==\" %%A IN (\"%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.properties\") DO (\n    IF \"%%A\"==\"wrapperUrl\" SET DOWNLOAD_URL=%%B\n)\n\n@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central\n@REM This allows using the maven wrapper in projects that prohibit checking in binary data.\nif exist %WRAPPER_JAR% (\n    if \"%MVNW_VERBOSE%\" == \"true\" (\n        echo Found %WRAPPER_JAR%\n    )\n) else (\n    if not \"%MVNW_REPOURL%\" == \"\" (\n        SET DOWNLOAD_URL=\"%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar\"\n    )\n    if \"%MVNW_VERBOSE%\" == \"true\" (\n        echo Couldn't find %WRAPPER_JAR%, downloading it ...\n        echo Downloading from: %DOWNLOAD_URL%\n    )\n\n    powershell -Command \"&{\"^\n\t\t\"$webclient = new-object System.Net.WebClient;\"^\n\t\t\"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {\"^\n\t\t\"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');\"^\n\t\t\"}\"^\n\t\t\"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')\"^\n\t\t\"}\"\n    if \"%MVNW_VERBOSE%\" == \"true\" (\n        echo Finished downloading %WRAPPER_JAR%\n    )\n)\n@REM End of extension\n\n@REM Provide a \"standardized\" way to retrieve the CLI args that will\n@REM work with both Windows and non-Windows executions.\nset MAVEN_CMD_LINE_ARGS=%*\n\n%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% \"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%\" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*\nif ERRORLEVEL 1 goto error\ngoto end\n\n:error\nset ERROR_CODE=1\n\n:end\n@endlocal & set ERROR_CODE=%ERROR_CODE%\n\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPost\n@REM check for post script, once with legacy .bat ending and once with .cmd ending\nif exist \"%HOME%\\mavenrc_post.bat\" call \"%HOME%\\mavenrc_post.bat\"\nif exist \"%HOME%\\mavenrc_post.cmd\" call \"%HOME%\\mavenrc_post.cmd\"\n:skipRcPost\n\n@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'\nif \"%MAVEN_BATCH_PAUSE%\" == \"on\" pause\n\nif \"%MAVEN_TERMINATE_CMD%\" == \"on\" exit %ERROR_CODE%\n\nexit /B %ERROR_CODE%\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\txsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\t<parent>\n\t\t<groupId>org.springframework.boot</groupId>\n\t\t<artifactId>spring-boot-starter-parent</artifactId>\n\t\t<version>3.3.2</version>\n\t\t<relativePath/> <!-- lookup parent from repository -->\n\t</parent>\n\t<groupId>com.shop</groupId>\n\t<artifactId>shop</artifactId>\n\t<version>0.0.1-SNAPSHOT</version>\n\t<name>shop</name>\n\t<description>Shop Project for Spring Boot</description>\n\t<properties>\n\t\t<java.version>17</java.version>\n\t</properties>\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-data-jpa</artifactId>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-thymeleaf</artifactId>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-web</artifactId>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>com.h2database</groupId>\n\t\t\t<artifactId>h2</artifactId>\n\t\t\t<scope>runtime</scope>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>mysql</groupId>\n\t\t\t<artifactId>mysql-connector-java</artifactId>\n\t\t\t<version>8.0.33</version>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>org.projectlombok</groupId>\n\t\t\t<artifactId>lombok</artifactId>\n\t\t\t<optional>true</optional>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>com.querydsl</groupId>\n\t\t\t<artifactId>querydsl-jpa</artifactId>\n\t\t\t<version>5.0.0</version>\n\t\t\t<classifier>jakarta</classifier>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>com.querydsl</groupId>\n\t\t\t<artifactId>querydsl-apt</artifactId>\n\t\t\t<version>5.0.0</version>\n\t\t\t<classifier>jakarta</classifier>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>com.querydsl</groupId>\n\t\t\t<artifactId>querydsl-core</artifactId>\n\t\t\t<version>5.0.0</version>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>nz.net.ultraq.thymeleaf</groupId>\n\t\t\t<artifactId>thymeleaf-layout-dialect</artifactId>\n\t\t\t<version>3.3.0</version>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-security</artifactId>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-validation</artifactId>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.security</groupId>\n\t\t\t<artifactId>spring-security-test</artifactId>\n\t\t\t<scope>test</scope>\n\t\t\t<version>${spring-security.version}</version>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>org.thymeleaf.extras</groupId>\n\t\t\t<artifactId>thymeleaf-extras-springsecurity6</artifactId>\n\t\t\t<version>3.1.1.RELEASE</version>\n\t\t</dependency>\n\n        <dependency>\n            <groupId>org.modelmapper</groupId>\n            <artifactId>modelmapper</artifactId>\n\t\t\t<version>3.1.0</version>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-test</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t</dependencies>\n\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t<artifactId>spring-boot-maven-plugin</artifactId>\n\t\t\t\t<configuration>\n\t\t\t\t\t<excludes>\n\t\t\t\t\t\t<exclude>\n\t\t\t\t\t\t\t<groupId>org.projectlombok</groupId>\n\t\t\t\t\t\t\t<artifactId>lombok</artifactId>\n\t\t\t\t\t\t</exclude>\n\t\t\t\t\t</excludes>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n\n</project>\n"
  },
  {
    "path": "src/main/java/com/shop/ShopApplication.java",
    "content": "package com.shop;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\n@SpringBootApplication\npublic class ShopApplication {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(ShopApplication.class, args);\n\t}\n\n}\n"
  },
  {
    "path": "src/main/java/com/shop/config/AuditConfig.java",
    "content": "package com.shop.config;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.data.domain.AuditorAware;\nimport org.springframework.data.jpa.repository.config.EnableJpaAuditing;\n\n@Configuration\n@EnableJpaAuditing\npublic class AuditConfig {\n\n    @Bean\n    public AuditorAware<String> auditorProvider() {\n        return new AuditorAwareImpl();\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/config/AuditorAwareImpl.java",
    "content": "package com.shop.config;\n\nimport org.springframework.data.domain.AuditorAware;\nimport org.springframework.security.core.Authentication;\nimport org.springframework.security.core.context.SecurityContextHolder;\nimport java.util.Optional;\n\npublic class AuditorAwareImpl implements AuditorAware<String> {\n\n    @Override\n    public Optional<String> getCurrentAuditor() {\n        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();\n        String userId = \"\";\n        if(authentication != null){\n            userId = authentication.getName();\n        }\n        return Optional.of(userId);\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/config/CsrfCookieFilter.java",
    "content": "package com.shop.config;\n\nimport jakarta.servlet.FilterChain;\nimport jakarta.servlet.ServletException;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.security.web.csrf.CsrfToken;\nimport org.springframework.web.filter.OncePerRequestFilter;\n\nimport java.io.IOException;\n\npublic class CsrfCookieFilter extends OncePerRequestFilter {\n\n    @Override\n    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {\n        // HttpServletRequest에서 CsrfToken 속성 조회\n        CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());\n        if (csrfToken != null) {\n            // csrf 토큰 강제 생성\n            csrfToken.getToken();\n        }\n        filterChain.doFilter(request, response);\n    }\n}"
  },
  {
    "path": "src/main/java/com/shop/config/CustomAuthenticationEntryPoint.java",
    "content": "package com.shop.config;\n\nimport jakarta.servlet.ServletException;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.security.core.AuthenticationException;\nimport org.springframework.security.web.AuthenticationEntryPoint;\n\nimport java.io.IOException;\n\npublic class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {\n\n    @Override\n    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {\n        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, \"Unauthorized\");\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/shop/config/FormLoginAuthenticationFailureHandler.java",
    "content": "package com.shop.config;\n\nimport jakarta.servlet.ServletException;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.security.core.AuthenticationException;\nimport org.springframework.security.web.authentication.AuthenticationFailureHandler;\n\nimport java.io.IOException;\n\npublic class FormLoginAuthenticationFailureHandler implements AuthenticationFailureHandler {\n\n    @Override\n    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {\n        response.sendRedirect(\"/members/login/error\");\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/shop/config/SecurityConfig.java",
    "content": "package com.shop.config;\n\nimport com.shop.service.MemberService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;\nimport org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;\nimport org.springframework.security.crypto.password.PasswordEncoder;\nimport org.springframework.security.web.SecurityFilterChain;\nimport org.springframework.security.web.authentication.www.BasicAuthenticationFilter;\nimport org.springframework.security.web.csrf.CookieCsrfTokenRepository;\nimport org.springframework.security.web.util.matcher.AntPathRequestMatcher;\n\n@Configuration\n@EnableWebSecurity\npublic class SecurityConfig {\n\n    @Autowired\n    MemberService memberService;\n\n    @Bean\n    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n        return http\n                .csrf(csrf -> csrf\n                        .csrfTokenRepository(new CookieCsrfTokenRepository())   // csrf 토큰 저장소를 http only cookie로 설정\n                )\n                .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class)\n                .authorizeHttpRequests(authorizeHttpRequestsCustomizer -> authorizeHttpRequestsCustomizer\n                        .requestMatchers(\"/css/**\", \"/js/**\", \"/img/**\").permitAll()\n                        .requestMatchers(\"/\", \"/members/**\", \"/item/**\", \"/images/**\").permitAll()\n                        .requestMatchers(\"/admin/**\").hasRole(\"ADMIN\")\n                        .anyRequest()\n                        .authenticated()\n                ).formLogin(formLoginCustomizer -> formLoginCustomizer\n                        .loginPage(\"/members/login\")\n                        .defaultSuccessUrl(\"/\", true)\n                        .usernameParameter(\"email\")\n                        .failureHandler(new FormLoginAuthenticationFailureHandler())\n                ).logout( logoutCustomizer -> logoutCustomizer\n                        .logoutRequestMatcher(new AntPathRequestMatcher(\"/members/logout\"))\n                        .logoutSuccessUrl(\"/\")\n                ).exceptionHandling(e -> e\n                        .authenticationEntryPoint(new CustomAuthenticationEntryPoint())\n                )\n                .build()\n        ;\n    }\n\n    @Bean\n    public PasswordEncoder passwordEncoder() {\n        return new BCryptPasswordEncoder();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/shop/config/WebMvcConfig.java",
    "content": "package com.shop.config;\n\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\n\n@Configuration\npublic class WebMvcConfig implements WebMvcConfigurer {\n\n    @Value(\"${uploadPath}\")\n    String uploadPath;\n\n    @Override\n    public void addResourceHandlers(ResourceHandlerRegistry registry) {\n        registry.addResourceHandler(\"/images/**\")\n                .addResourceLocations(uploadPath);\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/constant/ItemSellStatus.java",
    "content": "package com.shop.constant;\n\npublic enum ItemSellStatus {\n    SELL, SOLD_OUT\n}"
  },
  {
    "path": "src/main/java/com/shop/constant/OrderStatus.java",
    "content": "package com.shop.constant;\n\npublic enum OrderStatus {\n    ORDER, CANCEL\n}"
  },
  {
    "path": "src/main/java/com/shop/constant/Role.java",
    "content": "package com.shop.constant;\n\npublic enum Role {\n    USER, ADMIN\n}"
  },
  {
    "path": "src/main/java/com/shop/controller/CartController.java",
    "content": "package com.shop.controller;\nimport com.shop.dto.CartItemDto;\nimport com.shop.service.CartService;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.validation.FieldError;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport jakarta.validation.Valid;\nimport java.security.Principal;\nimport java.util.List;\n\nimport com.shop.dto.CartDetailDto;\nimport org.springframework.ui.Model;\nimport org.springframework.web.bind.annotation.GetMapping;\n\nimport org.springframework.web.bind.annotation.PatchMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\n\nimport org.springframework.web.bind.annotation.DeleteMapping;\n\nimport com.shop.dto.CartOrderDto;\n\n@Controller\n@RequiredArgsConstructor\npublic class CartController {\n\n    private final CartService cartService;\n\n    @PostMapping(value = \"/cart\")\n    public @ResponseBody ResponseEntity order(@RequestBody @Valid CartItemDto cartItemDto, BindingResult bindingResult, Principal principal){\n\n        if(bindingResult.hasErrors()){\n            StringBuilder sb = new StringBuilder();\n            List<FieldError> fieldErrors = bindingResult.getFieldErrors();\n\n            for (FieldError fieldError : fieldErrors) {\n                sb.append(fieldError.getDefaultMessage());\n            }\n\n            return new ResponseEntity<String>(sb.toString(), HttpStatus.BAD_REQUEST);\n        }\n\n        String email = principal.getName();\n        Long cartItemId;\n\n        try {\n            cartItemId = cartService.addCart(cartItemDto, email);\n        } catch(Exception e){\n            return new ResponseEntity<String>(e.getMessage(), HttpStatus.BAD_REQUEST);\n        }\n\n        return new ResponseEntity<Long>(cartItemId, HttpStatus.OK);\n    }\n\n    @GetMapping(value = \"/cart\")\n    public String orderHist(Principal principal, Model model){\n        List<CartDetailDto> cartDetailList = cartService.getCartList(principal.getName());\n        model.addAttribute(\"cartItems\", cartDetailList);\n        return \"cart/cartList\";\n    }\n\n    @PatchMapping(value = \"/cartItem/{cartItemId}\")\n    public @ResponseBody ResponseEntity updateCartItem(@PathVariable(\"cartItemId\") Long cartItemId, int count, Principal principal){\n\n        if(count <= 0){\n            return new ResponseEntity<String>(\"최소 1개 이상 담아주세요\", HttpStatus.BAD_REQUEST);\n        } else if(!cartService.validateCartItem(cartItemId, principal.getName())){\n            return new ResponseEntity<String>(\"수정 권한이 없습니다.\", HttpStatus.FORBIDDEN);\n        }\n\n        cartService.updateCartItemCount(cartItemId, count);\n        return new ResponseEntity<Long>(cartItemId, HttpStatus.OK);\n    }\n\n    @DeleteMapping(value = \"/cartItem/{cartItemId}\")\n    public @ResponseBody ResponseEntity deleteCartItem(@PathVariable(\"cartItemId\") Long cartItemId, Principal principal){\n\n        if(!cartService.validateCartItem(cartItemId, principal.getName())){\n            return new ResponseEntity<String>(\"수정 권한이 없습니다.\", HttpStatus.FORBIDDEN);\n        }\n\n        cartService.deleteCartItem(cartItemId);\n\n        return new ResponseEntity<Long>(cartItemId, HttpStatus.OK);\n    }\n\n    @PostMapping(value = \"/cart/orders\")\n    public @ResponseBody ResponseEntity orderCartItem(@RequestBody CartOrderDto cartOrderDto, Principal principal){\n\n        List<CartOrderDto> cartOrderDtoList = cartOrderDto.getCartOrderDtoList();\n\n        if(cartOrderDtoList == null || cartOrderDtoList.size() == 0){\n            return new ResponseEntity<String>(\"주문할 상품을 선택해주세요\", HttpStatus.FORBIDDEN);\n        }\n\n        for (CartOrderDto cartOrder : cartOrderDtoList) {\n            if(!cartService.validateCartItem(cartOrder.getCartItemId(), principal.getName())){\n                return new ResponseEntity<String>(\"주문 권한이 없습니다.\", HttpStatus.FORBIDDEN);\n            }\n        }\n\n        Long orderId = cartService.orderCartItem(cartOrderDtoList, principal.getName());\n        return new ResponseEntity<Long>(orderId, HttpStatus.OK);\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/controller/ItemController.java",
    "content": "package com.shop.controller;\n\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.annotation.GetMapping;\n\nimport org.springframework.ui.Model;\nimport com.shop.dto.ItemFormDto;\n\nimport com.shop.service.ItemService;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport jakarta.validation.Valid;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.multipart.MultipartFile;\nimport java.util.List;\n\nimport org.springframework.web.bind.annotation.PathVariable;\nimport jakarta.persistence.EntityNotFoundException;\n\nimport com.shop.dto.ItemSearchDto;\nimport com.shop.entity.Item;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.PageRequest;\nimport org.springframework.data.domain.Pageable;\nimport java.util.Optional;\n\n@Controller\n@RequiredArgsConstructor\npublic class ItemController {\n\n    private final ItemService itemService;\n\n    @GetMapping(value = \"/admin/item/new\")\n    public String itemForm(Model model){\n        model.addAttribute(\"itemFormDto\", new ItemFormDto());\n        return \"item/itemForm\";\n    }\n\n    @PostMapping(value = \"/admin/item/new\")\n    public String itemNew(@Valid ItemFormDto itemFormDto, BindingResult bindingResult,\n                          Model model, @RequestParam(\"itemImgFile\") List<MultipartFile> itemImgFileList){\n\n        if(bindingResult.hasErrors()){\n            return \"item/itemForm\";\n        }\n\n        if(itemImgFileList.get(0).isEmpty() && itemFormDto.getId() == null){\n            model.addAttribute(\"errorMessage\", \"첫번째 상품 이미지는 필수 입력 값 입니다.\");\n            return \"item/itemForm\";\n        }\n\n        try {\n            itemService.saveItem(itemFormDto, itemImgFileList);\n        } catch (Exception e){\n            model.addAttribute(\"errorMessage\", \"상품 등록 중 에러가 발생하였습니다.\");\n            return \"item/itemForm\";\n        }\n\n        return \"redirect:/\";\n    }\n\n    @GetMapping(value = \"/admin/item/{itemId}\")\n    public String itemDtl(@PathVariable(\"itemId\") Long itemId, Model model){\n\n        try {\n            ItemFormDto itemFormDto = itemService.getItemDtl(itemId);\n            model.addAttribute(\"itemFormDto\", itemFormDto);\n        } catch(EntityNotFoundException e){\n            model.addAttribute(\"errorMessage\", \"존재하지 않는 상품 입니다.\");\n            model.addAttribute(\"itemFormDto\", new ItemFormDto());\n            return \"item/itemForm\";\n        }\n\n        return \"item/itemForm\";\n    }\n\n    @PostMapping(value = \"/admin/item/{itemId}\")\n    public String itemUpdate(@Valid ItemFormDto itemFormDto, BindingResult bindingResult,\n                             @RequestParam(\"itemImgFile\") List<MultipartFile> itemImgFileList, Model model){\n        if(bindingResult.hasErrors()){\n            return \"item/itemForm\";\n        }\n\n        if(itemImgFileList.get(0).isEmpty() && itemFormDto.getId() == null){\n            model.addAttribute(\"errorMessage\", \"첫번째 상품 이미지는 필수 입력 값 입니다.\");\n            return \"item/itemForm\";\n        }\n\n        try {\n            itemService.updateItem(itemFormDto, itemImgFileList);\n        } catch (Exception e){\n            model.addAttribute(\"errorMessage\", \"상품 수정 중 에러가 발생하였습니다.\");\n            return \"item/itemForm\";\n        }\n\n        return \"redirect:/\";\n    }\n\n    @GetMapping(value = {\"/admin/items\", \"/admin/items/{page}\"})\n    public String itemManage(ItemSearchDto itemSearchDto, @PathVariable(\"page\") Optional<Integer> page, Model model){\n\n        Pageable pageable = PageRequest.of(page.isPresent() ? page.get() : 0, 3);\n        Page<Item> items = itemService.getAdminItemPage(itemSearchDto, pageable);\n\n        model.addAttribute(\"items\", items);\n        model.addAttribute(\"itemSearchDto\", itemSearchDto);\n        model.addAttribute(\"maxPage\", 5);\n\n        return \"item/itemMng\";\n    }\n\n    @GetMapping(value = \"/item/{itemId}\")\n    public String itemDtl(Model model, @PathVariable(\"itemId\") Long itemId){\n        ItemFormDto itemFormDto = itemService.getItemDtl(itemId);\n        model.addAttribute(\"item\", itemFormDto);\n        return \"item/itemDtl\";\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/controller/MainController.java",
    "content": "package com.shop.controller;\n\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.annotation.GetMapping;\n\nimport com.shop.dto.ItemSearchDto;\nimport com.shop.dto.MainItemDto;\nimport com.shop.service.ItemService;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.PageRequest;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.ui.Model;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport java.util.Optional;\n\n@Controller\n@RequiredArgsConstructor\npublic class MainController {\n\n    private final ItemService itemService;\n\n    @GetMapping(value = \"/\")\n    public String main(ItemSearchDto itemSearchDto, Optional<Integer> page, Model model){\n\n        Pageable pageable = PageRequest.of(page.isPresent() ? page.get() : 0, 6);\n        Page<MainItemDto> items = itemService.getMainItemPage(itemSearchDto, pageable);\n\n        model.addAttribute(\"items\", items);\n        model.addAttribute(\"itemSearchDto\", itemSearchDto);\n        model.addAttribute(\"maxPage\", 5);\n\n        return \"main\";\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/controller/MemberController.java",
    "content": "package com.shop.controller;\n\nimport com.shop.dto.MemberFormDto;\nimport com.shop.service.MemberService;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.ui.Model;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\n\nimport com.shop.entity.Member;\nimport org.springframework.security.crypto.password.PasswordEncoder;\nimport org.springframework.web.bind.annotation.PostMapping;\n\nimport org.springframework.validation.BindingResult;\nimport jakarta.validation.Valid;\n\n@RequestMapping(\"/members\")\n@Controller\n@RequiredArgsConstructor\npublic class MemberController {\n\n    private final MemberService memberService;\n    private final PasswordEncoder passwordEncoder;\n\n    @GetMapping(value = \"/new\")\n    public String memberForm(Model model){\n        model.addAttribute(\"memberFormDto\", new MemberFormDto());\n        return \"member/memberForm\";\n    }\n\n    @PostMapping(value = \"/new\")\n    public String newMember(@Valid MemberFormDto memberFormDto, BindingResult bindingResult, Model model){\n\n        if(bindingResult.hasErrors()){\n            return \"member/memberForm\";\n        }\n\n        try {\n            Member member = Member.createMember(memberFormDto, passwordEncoder);\n            memberService.saveMember(member);\n        } catch (IllegalStateException e){\n            model.addAttribute(\"errorMessage\", e.getMessage());\n            return \"member/memberForm\";\n        }\n\n        return \"redirect:/\";\n    }\n\n    @GetMapping(value = \"/login\")\n    public String loginMember(){\n        return \"/member/memberLoginForm\";\n    }\n\n    @GetMapping(value = \"/login/error\")\n    public String loginError(Model model){\n        model.addAttribute(\"loginErrorMsg\", \"아이디 또는 비밀번호를 확인해주세요\");\n        return \"/member/memberLoginForm\";\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/controller/OrderController.java",
    "content": "package com.shop.controller;\n\nimport com.shop.dto.OrderDto;\nimport com.shop.service.OrderService;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.validation.FieldError;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.ResponseBody;\n\nimport jakarta.validation.Valid;\nimport java.security.Principal;\nimport java.util.List;\n\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport com.shop.dto.OrderHistDto;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.PageRequest;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.ui.Model;\nimport java.util.Optional;\n\n@Controller\n@RequiredArgsConstructor\npublic class OrderController {\n\n    private final OrderService orderService;\n\n    @PostMapping(value = \"/order\")\n    public @ResponseBody ResponseEntity order(@RequestBody @Valid OrderDto orderDto\n            , BindingResult bindingResult, Principal principal){\n\n        if(bindingResult.hasErrors()){\n            StringBuilder sb = new StringBuilder();\n            List<FieldError> fieldErrors = bindingResult.getFieldErrors();\n\n            for (FieldError fieldError : fieldErrors) {\n                sb.append(fieldError.getDefaultMessage());\n            }\n\n            return new ResponseEntity<String>(sb.toString(), HttpStatus.BAD_REQUEST);\n        }\n\n        String email = principal.getName();\n        Long orderId;\n\n        try {\n            orderId = orderService.order(orderDto, email);\n        } catch(Exception e){\n            return new ResponseEntity<String>(e.getMessage(), HttpStatus.BAD_REQUEST);\n        }\n\n        return new ResponseEntity<Long>(orderId, HttpStatus.OK);\n    }\n\n    @GetMapping(value = {\"/orders\", \"/orders/{page}\"})\n    public String orderHist(@PathVariable(\"page\") Optional<Integer> page, Principal principal, Model model){\n\n        Pageable pageable = PageRequest.of(page.isPresent() ? page.get() : 0, 4);\n        Page<OrderHistDto> ordersHistDtoList = orderService.getOrderList(principal.getName(), pageable);\n\n        model.addAttribute(\"orders\", ordersHistDtoList);\n        model.addAttribute(\"page\", pageable.getPageNumber());\n        model.addAttribute(\"maxPage\", 5);\n\n        return \"order/orderHist\";\n    }\n\n    @PostMapping(\"/order/{orderId}/cancel\")\n    public @ResponseBody ResponseEntity cancelOrder(@PathVariable(\"orderId\") Long orderId , Principal principal){\n\n        if(!orderService.validateOrder(orderId, principal.getName())){\n            return new ResponseEntity<String>(\"주문 취소 권한이 없습니다.\", HttpStatus.FORBIDDEN);\n        }\n\n        orderService.cancelOrder(orderId);\n        return new ResponseEntity<Long>(orderId, HttpStatus.OK);\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/controller/ThymeleafExController.java",
    "content": "package com.shop.controller;\n\nimport org.springframework.stereotype.Controller;\nimport org.springframework.ui.Model;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\n\nimport com.shop.dto.ItemDto;\nimport java.time.LocalDateTime;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n@Controller\n@RequestMapping(value=\"/thymeleaf\")\npublic class ThymeleafExController {\n\n    @GetMapping(value = \"/ex01\")\n    public String thymeleafExample01(Model model){\n        model.addAttribute(\"data\", \"타임리프 예제 입니다.\");\n        return \"thymeleafEx/thymeleafEx01\";\n    }\n\n    @GetMapping(value = \"/ex02\")\n    public String thymeleafExample02(Model model){\n        ItemDto itemDto = new ItemDto();\n        itemDto.setItemDetail(\"상품 상세 설명\");\n        itemDto.setItemNm(\"테스트 상품1\");\n        itemDto.setPrice(10000);\n        itemDto.setRegTime(LocalDateTime.now());\n\n        model.addAttribute(\"itemDto\", itemDto);\n        return \"thymeleafEx/thymeleafEx02\";\n    }\n\n    @GetMapping(value = \"/ex03\")\n    public String thymeleafExample03(Model model){\n\n        List<ItemDto> itemDtoList = new ArrayList<>();\n\n        for(int i=1;i<=10;i++){\n\n            ItemDto itemDto = new ItemDto();\n            itemDto.setItemDetail(\"상품 상세 설명\"+i);\n            itemDto.setItemNm(\"테스트 상품\" + i);\n            itemDto.setPrice(1000*i);\n            itemDto.setRegTime(LocalDateTime.now());\n\n            itemDtoList.add(itemDto);\n        }\n\n        model.addAttribute(\"itemDtoList\", itemDtoList);\n        return \"thymeleafEx/thymeleafEx03\";\n    }\n\n    @GetMapping(value = \"/ex04\")\n    public String thymeleafExample04(Model model){\n\n        List<ItemDto> itemDtoList = new ArrayList<>();\n\n        for(int i=1;i<=10;i++){\n\n            ItemDto itemDto = new ItemDto();\n            itemDto.setItemDetail(\"상품 상세 설명\"+i);\n            itemDto.setItemNm(\"테스트 상품\" + i);\n            itemDto.setPrice(1000*i);\n            itemDto.setRegTime(LocalDateTime.now());\n\n            itemDtoList.add(itemDto);\n        }\n\n        model.addAttribute(\"itemDtoList\", itemDtoList);\n        return \"thymeleafEx/thymeleafEx04\";\n    }\n\n    @GetMapping(value = \"/ex05\")\n    public String thymeleafExample05(){\n        return \"thymeleafEx/thymeleafEx05\";\n    }\n\n    @GetMapping(value = \"/ex06\")\n    public String thymeleafExample06(String param1, String param2, Model model){\n        model.addAttribute(\"param1\", param1);\n        model.addAttribute(\"param2\", param2);\n        return \"thymeleafEx/thymeleafEx06\";\n    }\n\n    @GetMapping(value = \"/ex07\")\n    public String thymeleafExample07(){\n        return \"thymeleafEx/thymeleafEx07\";\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/dto/CartDetailDto.java",
    "content": "package com.shop.dto;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter @Setter\npublic class CartDetailDto {\n\n    private Long cartItemId; //장바구니 상품 아이디\n\n    private String itemNm; //상품명\n\n    private int price; //상품 금액\n\n    private int count; //수량\n\n    private String imgUrl; //상품 이미지 경로\n\n    public CartDetailDto(Long cartItemId, String itemNm, int price, int count, String imgUrl){\n        this.cartItemId = cartItemId;\n        this.itemNm = itemNm;\n        this.price = price;\n        this.count = count;\n        this.imgUrl = imgUrl;\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/dto/CartItemDto.java",
    "content": "package com.shop.dto;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport jakarta.validation.constraints.Min;\nimport jakarta.validation.constraints.NotNull;\n\n@Getter @Setter\npublic class CartItemDto {\n\n    @NotNull(message = \"상품 아이디는 필수 입력 값 입니다.\")\n    private Long itemId;\n\n    @Min(value = 1, message = \"최소 1개 이상 담아주세요\")\n    private int count;\n\n}"
  },
  {
    "path": "src/main/java/com/shop/dto/CartOrderDto.java",
    "content": "package com.shop.dto;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.util.List;\n\n@Getter\n@Setter\npublic class CartOrderDto {\n\n    private Long cartItemId;\n\n    private List<CartOrderDto> cartOrderDtoList;\n\n}"
  },
  {
    "path": "src/main/java/com/shop/dto/ItemDto.java",
    "content": "package com.shop.dto;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.time.LocalDateTime;\n\n@Getter\n@Setter\npublic class ItemDto {\n\n    private Long id;\n\n    private String itemNm;\n\n    private Integer price;\n\n    private String itemDetail;\n\n    private String sellStatCd;\n\n    private LocalDateTime regTime;\n\n    private LocalDateTime updateTime;\n\n}"
  },
  {
    "path": "src/main/java/com/shop/dto/ItemFormDto.java",
    "content": "package com.shop.dto;\n\nimport com.shop.constant.ItemSellStatus;\nimport com.shop.entity.Item;\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.modelmapper.ModelMapper;\n\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport java.util.ArrayList;\nimport java.util.List;\n\n@Getter @Setter\npublic class ItemFormDto {\n\n    private Long id;\n\n    @NotBlank(message = \"상품명은 필수 입력 값입니다.\")\n    private String itemNm;\n\n    @NotNull(message = \"가격은 필수 입력 값입니다.\")\n    private Integer price;\n\n    @NotBlank(message = \"상품 상세는 필수 입력 값입니다.\")\n    private String itemDetail;\n\n    @NotNull(message = \"재고는 필수 입력 값입니다.\")\n    private Integer stockNumber;\n\n    private ItemSellStatus itemSellStatus;\n\n    private List<ItemImgDto> itemImgDtoList = new ArrayList<>();\n\n    private List<Long> itemImgIds = new ArrayList<>();\n\n    private static ModelMapper modelMapper = new ModelMapper();\n\n    public Item createItem(){\n        return modelMapper.map(this, Item.class);\n    }\n\n    public static ItemFormDto of(Item item){\n        return modelMapper.map(item,ItemFormDto.class);\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/dto/ItemImgDto.java",
    "content": "package com.shop.dto;\n\nimport com.shop.entity.ItemImg;\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.modelmapper.ModelMapper;\n\n@Getter @Setter\npublic class ItemImgDto {\n\n    private Long id;\n\n    private String imgName;\n\n    private String oriImgName;\n\n    private String imgUrl;\n\n    private String repImgYn;\n\n    private static ModelMapper modelMapper = new ModelMapper();\n\n    public static ItemImgDto of(ItemImg itemImg) {\n        return modelMapper.map(itemImg,ItemImgDto.class);\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/dto/ItemSearchDto.java",
    "content": "package com.shop.dto;\n\nimport com.shop.constant.ItemSellStatus;\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter @Setter\npublic class ItemSearchDto {\n\n    private String searchDateType;\n\n    private ItemSellStatus searchSellStatus;\n\n    private String searchBy;\n\n    private String searchQuery = \"\";\n\n}"
  },
  {
    "path": "src/main/java/com/shop/dto/MainItemDto.java",
    "content": "package com.shop.dto;\n\nimport com.querydsl.core.annotations.QueryProjection;\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter @Setter\npublic class MainItemDto {\n\n    private Long id;\n\n    private String itemNm;\n\n    private String itemDetail;\n\n    private String imgUrl;\n\n    private Integer price;\n\n    @QueryProjection\n    public MainItemDto(Long id, String itemNm, String itemDetail, String imgUrl,Integer price){\n        this.id = id;\n        this.itemNm = itemNm;\n        this.itemDetail = itemDetail;\n        this.imgUrl = imgUrl;\n        this.price = price;\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/dto/MemberFormDto.java",
    "content": "package com.shop.dto;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.hibernate.validator.constraints.Length;\n\nimport jakarta.validation.constraints.Email;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotEmpty;\n\n@Getter @Setter\npublic class MemberFormDto {\n\n    @NotBlank(message = \"이름은 필수 입력 값입니다.\")\n    private String name;\n\n    @NotEmpty(message = \"이메일은 필수 입력 값입니다.\")\n    @Email(message = \"이메일 형식으로 입력해주세요.\")\n    private String email;\n\n    @NotEmpty(message = \"비밀번호는 필수 입력 값입니다.\")\n    @Length(min=8, max=16, message = \"비밀번호는 8자 이상, 16자 이하로 입력해주세요\")\n    private String password;\n\n    @NotEmpty(message = \"주소는 필수 입력 값입니다.\")\n    private String address;\n}"
  },
  {
    "path": "src/main/java/com/shop/dto/OrderDto.java",
    "content": "package com.shop.dto;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport jakarta.validation.constraints.Max;\nimport jakarta.validation.constraints.Min;\nimport jakarta.validation.constraints.NotNull;\n\n@Getter @Setter\npublic class OrderDto {\n\n    @NotNull(message = \"상품 아이디는 필수 입력 값입니다.\")\n    private Long itemId;\n\n    @Min(value = 1, message = \"최소 주문 수량은 1개 입니다.\")\n    @Max(value = 999, message = \"최대 주문 수량은 999개 입니다.\")\n    private int count;\n\n}"
  },
  {
    "path": "src/main/java/com/shop/dto/OrderHistDto.java",
    "content": "package com.shop.dto;\n\nimport com.shop.constant.OrderStatus;\nimport com.shop.entity.Order;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.time.format.DateTimeFormatter;\nimport java.util.ArrayList;\nimport java.util.List;\n\n@Getter @Setter\npublic class OrderHistDto {\n\n    public OrderHistDto(Order order){\n        this.orderId = order.getId();\n        this.orderDate = order.getOrderDate().format(DateTimeFormatter.ofPattern(\"yyyy-MM-dd HH:mm\"));\n        this.orderStatus = order.getOrderStatus();\n    }\n\n    private Long orderId; //주문아이디\n    private String orderDate; //주문날짜\n    private OrderStatus orderStatus; //주문 상태\n\n    private List<OrderItemDto> orderItemDtoList = new ArrayList<>();\n\n    //주문 상품리스트\n    public void addOrderItemDto(OrderItemDto orderItemDto){\n        orderItemDtoList.add(orderItemDto);\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/dto/OrderItemDto.java",
    "content": "package com.shop.dto;\n\nimport com.shop.entity.OrderItem;\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter @Setter\npublic class OrderItemDto {\n\n    public OrderItemDto(OrderItem orderItem, String imgUrl){\n        this.itemNm = orderItem.getItem().getItemNm();\n        this.count = orderItem.getCount();\n        this.orderPrice = orderItem.getOrderPrice();\n        this.imgUrl = imgUrl;\n    }\n\n    private String itemNm; //상품명\n    private int count; //주문 수량\n\n    private int orderPrice; //주문 금액\n    private String imgUrl; //상품 이미지 경로\n\n}"
  },
  {
    "path": "src/main/java/com/shop/entity/BaseEntity.java",
    "content": "package com.shop.entity;\n\nimport lombok.Getter;\nimport org.springframework.data.annotation.CreatedBy;\nimport org.springframework.data.annotation.LastModifiedBy;\nimport org.springframework.data.jpa.domain.support.AuditingEntityListener;\n\nimport jakarta.persistence.Column;\nimport jakarta.persistence.EntityListeners;\nimport jakarta.persistence.MappedSuperclass;\n\n@EntityListeners(value = {AuditingEntityListener.class})\n@MappedSuperclass\n@Getter\npublic abstract class BaseEntity extends BaseTimeEntity{\n\n    @CreatedBy\n    @Column(updatable = false)\n    private String createdBy;\n\n    @LastModifiedBy\n    private String modifiedBy;\n\n}"
  },
  {
    "path": "src/main/java/com/shop/entity/BaseTimeEntity.java",
    "content": "package com.shop.entity;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.springframework.data.annotation.CreatedDate;\nimport org.springframework.data.annotation.LastModifiedDate;\nimport org.springframework.data.jpa.domain.support.AuditingEntityListener;\n\nimport jakarta.persistence.Column;\nimport jakarta.persistence.EntityListeners;\nimport jakarta.persistence.MappedSuperclass;\nimport java.time.LocalDateTime;\n\n@EntityListeners(value = {AuditingEntityListener.class})\n@MappedSuperclass\n@Getter @Setter\npublic abstract class BaseTimeEntity {\n\n    @CreatedDate\n    @Column(updatable = false)\n    private LocalDateTime regTime;\n\n    @LastModifiedDate\n    private LocalDateTime updateTime;\n\n}"
  },
  {
    "path": "src/main/java/com/shop/entity/Cart.java",
    "content": "package com.shop.entity;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.ToString;\nimport jakarta.persistence.*;\n\n@Entity\n@Table(name = \"cart\")\n@Getter @Setter\n@ToString\npublic class Cart extends BaseEntity {\n\n    @Id\n    @Column(name = \"cart_id\")\n    @GeneratedValue(strategy = GenerationType.AUTO)\n    private Long id;\n\n    @OneToOne(fetch = FetchType.LAZY)\n    @JoinColumn(name=\"member_id\")\n    private Member member;\n\n    public static Cart createCart(Member member){\n        Cart cart = new Cart();\n        cart.setMember(member);\n        return cart;\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/entity/CartItem.java",
    "content": "package com.shop.entity;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport jakarta.persistence.*;\n\n@Entity\n@Getter @Setter\n@Table(name=\"cart_item\")\npublic class CartItem extends BaseEntity {\n\n    @Id\n    @GeneratedValue\n    @Column(name = \"cart_item_id\")\n    private Long id;\n\n    @ManyToOne(fetch = FetchType.LAZY)\n    @JoinColumn(name=\"cart_id\")\n    private Cart cart;\n\n    @ManyToOne(fetch = FetchType.LAZY)\n    @JoinColumn(name = \"item_id\")\n    private Item item;\n\n    private int count;\n\n    public static CartItem createCartItem(Cart cart, Item item, int count) {\n        CartItem cartItem = new CartItem();\n        cartItem.setCart(cart);\n        cartItem.setItem(item);\n        cartItem.setCount(count);\n        return cartItem;\n    }\n\n    public void addCount(int count){\n        this.count += count;\n    }\n\n    public void updateCount(int count){\n        this.count = count;\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/entity/Item.java",
    "content": "package com.shop.entity;\n\nimport com.shop.constant.ItemSellStatus;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.ToString;\n\nimport jakarta.persistence.*;\nimport com.shop.dto.ItemFormDto;\nimport com.shop.exception.OutOfStockException;\n\n@Entity\n@Table(name=\"item\")\n@Getter\n@Setter\n@ToString\npublic class Item extends BaseEntity {\n\n    @Id\n    @Column(name=\"item_id\")\n    @GeneratedValue(strategy = GenerationType.AUTO)\n    private Long id;       //상품 코드\n\n    @Column(nullable = false, length = 50)\n    private String itemNm; //상품명\n\n    @Column(name=\"price\", nullable = false)\n    private int price; //가격\n\n    @Column(nullable = false)\n    private int stockNumber; //재고수량\n\n    @Lob\n    @Column(nullable = false)\n    private String itemDetail; //상품 상세 설명\n\n    @Enumerated(EnumType.STRING)\n    private ItemSellStatus itemSellStatus; //상품 판매 상태\n\n    public void updateItem(ItemFormDto itemFormDto){\n        this.itemNm = itemFormDto.getItemNm();\n        this.price = itemFormDto.getPrice();\n        this.stockNumber = itemFormDto.getStockNumber();\n        this.itemDetail = itemFormDto.getItemDetail();\n        this.itemSellStatus = itemFormDto.getItemSellStatus();\n    }\n\n    public void removeStock(int stockNumber){\n        int restStock = this.stockNumber - stockNumber;\n        if(restStock<0){\n            throw new OutOfStockException(\"상품의 재고가 부족 합니다. (현재 재고 수량: \" + this.stockNumber + \")\");\n        }\n        this.stockNumber = restStock;\n    }\n\n    public void addStock(int stockNumber){\n        this.stockNumber += stockNumber;\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/entity/ItemImg.java",
    "content": "package com.shop.entity;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport jakarta.persistence.*;\n\n@Entity\n@Table(name=\"item_img\")\n@Getter @Setter\npublic class ItemImg extends BaseEntity{\n\n    @Id\n    @Column(name=\"item_img_id\")\n    @GeneratedValue(strategy = GenerationType.AUTO)\n    private Long id;\n\n    private String imgName; //이미지 파일명\n\n    private String oriImgName; //원본 이미지 파일명\n\n    private String imgUrl; //이미지 조회 경로\n\n    private String repimgYn; //대표 이미지 여부\n\n    @ManyToOne(fetch = FetchType.LAZY)\n    @JoinColumn(name = \"item_id\")\n    private Item item;\n\n    public void updateItemImg(String oriImgName, String imgName, String imgUrl){\n        this.oriImgName = oriImgName;\n        this.imgName = imgName;\n        this.imgUrl = imgUrl;\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/entity/Member.java",
    "content": "package com.shop.entity;\n\nimport com.shop.constant.Role;\nimport com.shop.dto.MemberFormDto;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.ToString;\nimport org.springframework.security.crypto.password.PasswordEncoder;\n\nimport jakarta.persistence.*;\n\n@Entity\n@Table(name=\"member\")\n@Getter @Setter\n@ToString\npublic class Member extends BaseEntity {\n\n    @Id\n    @Column(name=\"member_id\")\n    @GeneratedValue(strategy = GenerationType.AUTO)\n    private Long id;\n\n    private String name;\n\n    @Column(unique = true)\n    private String email;\n\n    private String password;\n\n    private String address;\n\n    @Enumerated(EnumType.STRING)\n    private Role role;\n\n    public static Member createMember(MemberFormDto memberFormDto, PasswordEncoder passwordEncoder){\n        Member member = new Member();\n        member.setName(memberFormDto.getName());\n        member.setEmail(memberFormDto.getEmail());\n        member.setAddress(memberFormDto.getAddress());\n        String password = passwordEncoder.encode(memberFormDto.getPassword());\n        member.setPassword(password);\n        member.setRole(Role.ADMIN);\n        return member;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/shop/entity/Order.java",
    "content": "package com.shop.entity;\n\nimport com.shop.constant.OrderStatus;\nimport lombok.Getter;\nimport lombok.Setter;\nimport jakarta.persistence.*;\nimport java.time.LocalDateTime;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n@Entity\n@Table(name = \"orders\")\n@Getter @Setter\npublic class Order extends BaseEntity {\n\n    @Id @GeneratedValue\n    @Column(name = \"order_id\")\n    private Long id;\n\n    @ManyToOne(fetch = FetchType.LAZY)\n    @JoinColumn(name = \"member_id\")\n    private Member member;\n\n    private LocalDateTime orderDate; //주문일\n\n    @Enumerated(EnumType.STRING)\n    private OrderStatus orderStatus; //주문상태\n\n    @OneToMany(mappedBy = \"order\", cascade = CascadeType.ALL\n            , orphanRemoval = true, fetch = FetchType.LAZY)\n    private List<OrderItem> orderItems = new ArrayList<>();\n\n    public void addOrderItem(OrderItem orderItem) {\n        orderItems.add(orderItem);\n        orderItem.setOrder(this);\n    }\n\n    public static Order createOrder(Member member, List<OrderItem> orderItemList) {\n        Order order = new Order();\n        order.setMember(member);\n\n        for(OrderItem orderItem : orderItemList) {\n            order.addOrderItem(orderItem);\n        }\n\n        order.setOrderStatus(OrderStatus.ORDER);\n        order.setOrderDate(LocalDateTime.now());\n        return order;\n    }\n\n    public int getTotalPrice() {\n        int totalPrice = 0;\n        for(OrderItem orderItem : orderItems){\n            totalPrice += orderItem.getTotalPrice();\n        }\n        return totalPrice;\n    }\n\n    public void cancelOrder() {\n        this.orderStatus = OrderStatus.CANCEL;\n        for (OrderItem orderItem : orderItems) {\n            orderItem.cancel();\n        }\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/entity/OrderItem.java",
    "content": "package com.shop.entity;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport jakarta.persistence.*;\n\n@Entity\n@Getter @Setter\npublic class OrderItem extends BaseEntity {\n\n    @Id @GeneratedValue\n    @Column(name = \"order_item_id\")\n    private Long id;\n\n    @ManyToOne(fetch = FetchType.LAZY)\n    @JoinColumn(name = \"item_id\")\n    private Item item;\n\n    @ManyToOne(fetch = FetchType.LAZY)\n    @JoinColumn(name = \"order_id\")\n    private Order order;\n\n    private int orderPrice; //주문가격\n\n    private int count; //수량\n\n    public static OrderItem createOrderItem(Item item, int count){\n        OrderItem orderItem = new OrderItem();\n        orderItem.setItem(item);\n        orderItem.setCount(count);\n        orderItem.setOrderPrice(item.getPrice());\n        item.removeStock(count);\n        return orderItem;\n    }\n\n    public int getTotalPrice(){\n        return orderPrice*count;\n    }\n\n    public void cancel() {\n        this.getItem().addStock(count);\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/exception/OutOfStockException.java",
    "content": "package com.shop.exception;\n\npublic class OutOfStockException extends RuntimeException{\n\n    public OutOfStockException(String message) {\n        super(message);\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/repository/CartItemRepository.java",
    "content": "package com.shop.repository;\n\nimport com.shop.entity.CartItem;\nimport org.springframework.data.jpa.repository.JpaRepository;\n\nimport com.shop.dto.CartDetailDto;\nimport org.springframework.data.jpa.repository.Query;\nimport java.util.List;\n\npublic interface CartItemRepository extends JpaRepository<CartItem, Long> {\n\n    CartItem findByCartIdAndItemId(Long cartId, Long itemId);\n\n    @Query(\"select new com.shop.dto.CartDetailDto(ci.id, i.itemNm, i.price, ci.count, im.imgUrl) \" +\n            \"from CartItem ci, ItemImg im \" +\n            \"join ci.item i \" +\n            \"where ci.cart.id = :cartId \" +\n            \"and im.item.id = ci.item.id \" +\n            \"and im.repimgYn = 'Y' \" +\n            \"order by ci.regTime desc\"\n            )\n    List<CartDetailDto> findCartDetailDtoList(Long cartId);\n\n}"
  },
  {
    "path": "src/main/java/com/shop/repository/CartRepository.java",
    "content": "package com.shop.repository;\n\nimport com.shop.entity.Cart;\nimport org.springframework.data.jpa.repository.JpaRepository;\n\npublic interface CartRepository extends JpaRepository<Cart, Long> {\n\n    Cart findByMemberId(Long memberId);\n\n}"
  },
  {
    "path": "src/main/java/com/shop/repository/ItemImgRepository.java",
    "content": "package com.shop.repository;\n\nimport com.shop.entity.ItemImg;\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport java.util.List;\n\npublic interface ItemImgRepository extends JpaRepository<ItemImg, Long> {\n\n    List<ItemImg> findByItemIdOrderByIdAsc(Long itemId);\n\n    ItemImg findByItemIdAndRepimgYn(Long itemId, String repimgYn);\n\n}"
  },
  {
    "path": "src/main/java/com/shop/repository/ItemRepository.java",
    "content": "package com.shop.repository;\n\nimport com.shop.entity.Item;\nimport org.springframework.data.jpa.repository.JpaRepository;\n\nimport java.util.List;\n\nimport org.springframework.data.jpa.repository.Query;\nimport org.springframework.data.repository.query.Param;\n\nimport org.springframework.data.querydsl.QuerydslPredicateExecutor;\n\npublic interface ItemRepository extends JpaRepository<Item, Long>,\n        QuerydslPredicateExecutor<Item>, ItemRepositoryCustom {\n\n    List<Item> findByItemNm(String itemNm);\n\n    List<Item> findByItemNmOrItemDetail(String itemNm, String itemDetail);\n\n    List<Item> findByPriceLessThan(Integer price);\n\n    List<Item> findByPriceLessThanOrderByPriceDesc(Integer price);\n\n    @Query(\"select i from Item i where i.itemDetail like \" +\n            \"%:itemDetail% order by i.price desc\")\n    List<Item> findByItemDetail(@Param(\"itemDetail\") String itemDetail);\n\n    @Query(value=\"select * from item i where i.item_detail like \" +\n            \"%:itemDetail% order by i.price desc\", nativeQuery = true)\n    List<Item> findByItemDetailByNative(@Param(\"itemDetail\") String itemDetail);\n\n}"
  },
  {
    "path": "src/main/java/com/shop/repository/ItemRepositoryCustom.java",
    "content": "package com.shop.repository;\n\nimport com.shop.dto.ItemSearchDto;\nimport com.shop.entity.Item;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport com.shop.dto.MainItemDto;\n\npublic interface ItemRepositoryCustom {\n\n    Page<Item> getAdminItemPage(ItemSearchDto itemSearchDto, Pageable pageable);\n\n    Page<MainItemDto> getMainItemPage(ItemSearchDto itemSearchDto, Pageable pageable);\n\n}"
  },
  {
    "path": "src/main/java/com/shop/repository/ItemRepositoryCustomImpl.java",
    "content": "package com.shop.repository;\n\nimport com.querydsl.core.QueryResults;\nimport com.querydsl.core.types.dsl.BooleanExpression;\nimport com.querydsl.core.types.dsl.Wildcard;\nimport com.querydsl.jpa.impl.JPAQueryFactory;\nimport com.shop.constant.ItemSellStatus;\nimport com.shop.dto.ItemSearchDto;\nimport com.shop.dto.MainItemDto;\nimport com.shop.dto.QMainItemDto;\nimport com.shop.entity.Item;\nimport com.shop.entity.QItem;\nimport com.shop.entity.QItemImg;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.PageImpl;\nimport org.springframework.data.domain.Pageable;\nimport org.thymeleaf.util.StringUtils;\n\nimport jakarta.persistence.EntityManager;\nimport java.time.LocalDateTime;\nimport java.util.List;\n\npublic class ItemRepositoryCustomImpl implements ItemRepositoryCustom{\n\n    private JPAQueryFactory queryFactory;\n\n    public ItemRepositoryCustomImpl(EntityManager em){\n        this.queryFactory = new JPAQueryFactory(em);\n    }\n\n    private BooleanExpression searchSellStatusEq(ItemSellStatus searchSellStatus){\n        return searchSellStatus == null ? null : QItem.item.itemSellStatus.eq(searchSellStatus);\n    }\n\n    private BooleanExpression regDtsAfter(String searchDateType){\n\n        LocalDateTime dateTime = LocalDateTime.now();\n\n        if(StringUtils.equals(\"all\", searchDateType) || searchDateType == null){\n            return null;\n        } else if(StringUtils.equals(\"1d\", searchDateType)){\n            dateTime = dateTime.minusDays(1);\n        } else if(StringUtils.equals(\"1w\", searchDateType)){\n            dateTime = dateTime.minusWeeks(1);\n        } else if(StringUtils.equals(\"1m\", searchDateType)){\n            dateTime = dateTime.minusMonths(1);\n        } else if(StringUtils.equals(\"6m\", searchDateType)){\n            dateTime = dateTime.minusMonths(6);\n        }\n\n        return QItem.item.regTime.after(dateTime);\n    }\n\n    private BooleanExpression searchByLike(String searchBy, String searchQuery){\n\n        if(StringUtils.equals(\"itemNm\", searchBy)){\n            return QItem.item.itemNm.like(\"%\" + searchQuery + \"%\");\n        } else if(StringUtils.equals(\"createdBy\", searchBy)){\n            return QItem.item.createdBy.like(\"%\" + searchQuery + \"%\");\n        }\n\n        return null;\n    }\n\n    @Override\n    public Page<Item> getAdminItemPage(ItemSearchDto itemSearchDto, Pageable pageable) {\n\n        List<Item> content = queryFactory\n                .selectFrom(QItem.item)\n                .where(regDtsAfter(itemSearchDto.getSearchDateType()),\n                        searchSellStatusEq(itemSearchDto.getSearchSellStatus()),\n                        searchByLike(itemSearchDto.getSearchBy(),\n                                itemSearchDto.getSearchQuery()))\n                .orderBy(QItem.item.id.desc())\n                .offset(pageable.getOffset())\n                .limit(pageable.getPageSize())\n                .fetch();\n\n        long total = queryFactory.select(Wildcard.count).from(QItem.item)\n                .where(regDtsAfter(itemSearchDto.getSearchDateType()),\n                        searchSellStatusEq(itemSearchDto.getSearchSellStatus()),\n                        searchByLike(itemSearchDto.getSearchBy(), itemSearchDto.getSearchQuery()))\n                .fetchOne()\n                ;\n\n        return new PageImpl<>(content, pageable, total);\n    }\n\n    private BooleanExpression itemNmLike(String searchQuery){\n        return StringUtils.isEmpty(searchQuery) ? null : QItem.item.itemNm.like(\"%\" + searchQuery + \"%\");\n    }\n\n    @Override\n    public Page<MainItemDto> getMainItemPage(ItemSearchDto itemSearchDto, Pageable pageable) {\n        QItem item = QItem.item;\n        QItemImg itemImg = QItemImg.itemImg;\n\n        List<MainItemDto> content = queryFactory\n                .select(\n                        new QMainItemDto(\n                                item.id,\n                                item.itemNm,\n                                item.itemDetail,\n                                itemImg.imgUrl,\n                                item.price)\n                )\n                .from(itemImg)\n                .join(itemImg.item, item)\n                .where(itemImg.repimgYn.eq(\"Y\"))\n                .where(itemNmLike(itemSearchDto.getSearchQuery()))\n                .orderBy(item.id.desc())\n                .offset(pageable.getOffset())\n                .limit(pageable.getPageSize())\n                .fetch();\n\n        long total = queryFactory\n                .select(Wildcard.count)\n                .from(itemImg)\n                .join(itemImg.item, item)\n                .where(itemImg.repimgYn.eq(\"Y\"))\n                .where(itemNmLike(itemSearchDto.getSearchQuery()))\n                .fetchOne()\n                ;\n\n        return new PageImpl<>(content, pageable, total);\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/repository/MemberRepository.java",
    "content": "package com.shop.repository;\n\nimport com.shop.entity.Member;\nimport org.springframework.data.jpa.repository.JpaRepository;\n\npublic interface MemberRepository extends JpaRepository<Member, Long> {\n\n    Member findByEmail(String email);\n\n}"
  },
  {
    "path": "src/main/java/com/shop/repository/OrderItemRepository.java",
    "content": "package com.shop.repository;\n\nimport com.shop.entity.OrderItem;\nimport org.springframework.data.jpa.repository.JpaRepository;\n\npublic interface OrderItemRepository extends JpaRepository<OrderItem, Long> {\n\n}"
  },
  {
    "path": "src/main/java/com/shop/repository/OrderRepository.java",
    "content": "package com.shop.repository;\n\nimport com.shop.entity.Order;\nimport org.springframework.data.jpa.repository.JpaRepository;\n\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.jpa.repository.Query;\nimport org.springframework.data.repository.query.Param;\n\nimport java.util.List;\n\npublic interface OrderRepository extends JpaRepository<Order, Long> {\n\n    @Query(\"select o from Order o \" +\n            \"where o.member.email = :email \" +\n            \"order by o.orderDate desc\"\n    )\n    List<Order> findOrders(@Param(\"email\") String email, Pageable pageable);\n\n    @Query(\"select count(o) from Order o \" +\n            \"where o.member.email = :email\"\n    )\n    Long countOrder(@Param(\"email\") String email);\n}"
  },
  {
    "path": "src/main/java/com/shop/service/CartService.java",
    "content": "package com.shop.service;\n\nimport com.shop.dto.CartItemDto;\nimport com.shop.entity.Cart;\nimport com.shop.entity.CartItem;\nimport com.shop.entity.Item;\nimport com.shop.entity.Member;\nimport com.shop.repository.CartItemRepository;\nimport com.shop.repository.CartRepository;\nimport com.shop.repository.ItemRepository;\nimport com.shop.repository.MemberRepository;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport jakarta.persistence.EntityNotFoundException;\n\nimport com.shop.dto.CartDetailDto;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.thymeleaf.util.StringUtils;\nimport com.shop.dto.CartOrderDto;\nimport com.shop.dto.OrderDto;\n\n@Service\n@RequiredArgsConstructor\n@Transactional\npublic class CartService {\n\n    private final ItemRepository itemRepository;\n    private final MemberRepository memberRepository;\n    private final CartRepository cartRepository;\n    private final CartItemRepository cartItemRepository;\n    private final OrderService orderService;\n\n    public Long addCart(CartItemDto cartItemDto, String email){\n\n        Item item = itemRepository.findById(cartItemDto.getItemId())\n                .orElseThrow(EntityNotFoundException::new);\n        Member member = memberRepository.findByEmail(email);\n\n        Cart cart = cartRepository.findByMemberId(member.getId());\n        if(cart == null){\n            cart = Cart.createCart(member);\n            cartRepository.save(cart);\n        }\n\n        CartItem savedCartItem = cartItemRepository.findByCartIdAndItemId(cart.getId(), item.getId());\n\n        if(savedCartItem != null){\n            savedCartItem.addCount(cartItemDto.getCount());\n            return savedCartItem.getId();\n        } else {\n            CartItem cartItem = CartItem.createCartItem(cart, item, cartItemDto.getCount());\n            cartItemRepository.save(cartItem);\n            return cartItem.getId();\n        }\n    }\n\n    @Transactional(readOnly = true)\n    public List<CartDetailDto> getCartList(String email){\n\n        List<CartDetailDto> cartDetailDtoList = new ArrayList<>();\n\n        Member member = memberRepository.findByEmail(email);\n        Cart cart = cartRepository.findByMemberId(member.getId());\n        if(cart == null){\n            return cartDetailDtoList;\n        }\n\n        cartDetailDtoList = cartItemRepository.findCartDetailDtoList(cart.getId());\n        return cartDetailDtoList;\n    }\n\n    @Transactional(readOnly = true)\n    public boolean validateCartItem(Long cartItemId, String email){\n        Member curMember = memberRepository.findByEmail(email);\n        CartItem cartItem = cartItemRepository.findById(cartItemId)\n                .orElseThrow(EntityNotFoundException::new);\n        Member savedMember = cartItem.getCart().getMember();\n\n        if(!StringUtils.equals(curMember.getEmail(), savedMember.getEmail())){\n            return false;\n        }\n\n        return true;\n    }\n\n    public void updateCartItemCount(Long cartItemId, int count){\n        CartItem cartItem = cartItemRepository.findById(cartItemId)\n                .orElseThrow(EntityNotFoundException::new);\n\n        cartItem.updateCount(count);\n    }\n\n    public void deleteCartItem(Long cartItemId) {\n        CartItem cartItem = cartItemRepository.findById(cartItemId)\n                .orElseThrow(EntityNotFoundException::new);\n        cartItemRepository.delete(cartItem);\n    }\n\n    public Long orderCartItem(List<CartOrderDto> cartOrderDtoList, String email){\n        List<OrderDto> orderDtoList = new ArrayList<>();\n\n        for (CartOrderDto cartOrderDto : cartOrderDtoList) {\n            CartItem cartItem = cartItemRepository\n                            .findById(cartOrderDto.getCartItemId())\n                            .orElseThrow(EntityNotFoundException::new);\n\n            OrderDto orderDto = new OrderDto();\n            orderDto.setItemId(cartItem.getItem().getId());\n            orderDto.setCount(cartItem.getCount());\n            orderDtoList.add(orderDto);\n        }\n\n        Long orderId = orderService.orders(orderDtoList, email);\n        for (CartOrderDto cartOrderDto : cartOrderDtoList) {\n            CartItem cartItem = cartItemRepository\n                            .findById(cartOrderDto.getCartItemId())\n                            .orElseThrow(EntityNotFoundException::new);\n            cartItemRepository.delete(cartItem);\n        }\n\n        return orderId;\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/service/FileService.java",
    "content": "package com.shop.service;\n\nimport lombok.extern.java.Log;\nimport org.springframework.stereotype.Service;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.util.UUID;\n\n@Service\n@Log\npublic class FileService {\n\n    public String uploadFile(String uploadPath, String originalFileName, byte[] fileData) throws Exception{\n        UUID uuid = UUID.randomUUID();\n        String extension = originalFileName.substring(originalFileName.lastIndexOf(\".\"));\n        String savedFileName = uuid.toString() + extension;\n        String fileUploadFullUrl = uploadPath + \"/\" + savedFileName;\n        FileOutputStream fos = new FileOutputStream(fileUploadFullUrl);\n        fos.write(fileData);\n        fos.close();\n        return savedFileName;\n    }\n\n    public void deleteFile(String filePath) throws Exception{\n        File deleteFile = new File(filePath);\n        if(deleteFile.exists()) {\n            deleteFile.delete();\n            log.info(\"파일을 삭제하였습니다.\");\n        } else {\n            log.info(\"파일이 존재하지 않습니다.\");\n        }\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/service/ItemImgService.java",
    "content": "package com.shop.service;\n\nimport com.shop.entity.ItemImg;\nimport com.shop.repository.ItemImgRepository;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.web.multipart.MultipartFile;\nimport org.thymeleaf.util.StringUtils;\nimport jakarta.persistence.EntityNotFoundException;\n\n@Service\n@RequiredArgsConstructor\n@Transactional\npublic class ItemImgService {\n\n    @Value(\"${itemImgLocation}\")\n    private String itemImgLocation;\n\n    private final ItemImgRepository itemImgRepository;\n\n    private final FileService fileService;\n\n    public void saveItemImg(ItemImg itemImg, MultipartFile itemImgFile) throws Exception{\n        String oriImgName = itemImgFile.getOriginalFilename();\n        String imgName = \"\";\n        String imgUrl = \"\";\n\n        //파일 업로드\n        if(!StringUtils.isEmpty(oriImgName)){\n            imgName = fileService.uploadFile(itemImgLocation, oriImgName,\n                    itemImgFile.getBytes());\n            imgUrl = \"/images/item/\" + imgName;\n        }\n\n        //상품 이미지 정보 저장\n        itemImg.updateItemImg(oriImgName, imgName, imgUrl);\n        itemImgRepository.save(itemImg);\n    }\n\n    public void updateItemImg(Long itemImgId, MultipartFile itemImgFile) throws Exception{\n        if(!itemImgFile.isEmpty()){\n            ItemImg savedItemImg = itemImgRepository.findById(itemImgId)\n                    .orElseThrow(EntityNotFoundException::new);\n\n            //기존 이미지 파일 삭제\n            if(!StringUtils.isEmpty(savedItemImg.getImgName())) {\n                fileService.deleteFile(itemImgLocation+\"/\"+\n                        savedItemImg.getImgName());\n            }\n\n            String oriImgName = itemImgFile.getOriginalFilename();\n            String imgName = fileService.uploadFile(itemImgLocation, oriImgName, itemImgFile.getBytes());\n            String imgUrl = \"/images/item/\" + imgName;\n            savedItemImg.updateItemImg(oriImgName, imgName, imgUrl);\n        }\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/service/ItemService.java",
    "content": "package com.shop.service;\n\nimport com.shop.dto.ItemFormDto;\nimport com.shop.entity.Item;\nimport com.shop.entity.ItemImg;\nimport com.shop.repository.ItemImgRepository;\nimport com.shop.repository.ItemRepository;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport java.util.List;\n\nimport com.shop.dto.ItemImgDto;\nimport jakarta.persistence.EntityNotFoundException;\nimport java.util.ArrayList;\n\nimport com.shop.dto.ItemSearchDto;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\n\nimport com.shop.dto.MainItemDto;\n\n@Service\n@Transactional\n@RequiredArgsConstructor\npublic class ItemService {\n\n    private final ItemRepository itemRepository;\n\n    private final ItemImgService itemImgService;\n\n    private final ItemImgRepository itemImgRepository;\n\n    public Long saveItem(ItemFormDto itemFormDto, List<MultipartFile> itemImgFileList) throws Exception{\n\n        //상품 등록\n        Item item = itemFormDto.createItem();\n        itemRepository.save(item);\n\n        //이미지 등록\n        for(int i=0;i<itemImgFileList.size();i++){\n            ItemImg itemImg = new ItemImg();\n            itemImg.setItem(item);\n\n            if(i == 0)\n                itemImg.setRepimgYn(\"Y\");\n            else\n                itemImg.setRepimgYn(\"N\");\n\n            itemImgService.saveItemImg(itemImg, itemImgFileList.get(i));\n        }\n\n        return item.getId();\n    }\n\n    @Transactional(readOnly = true)\n    public ItemFormDto getItemDtl(Long itemId){\n        List<ItemImg> itemImgList = itemImgRepository.findByItemIdOrderByIdAsc(itemId);\n        List<ItemImgDto> itemImgDtoList = new ArrayList<>();\n        for (ItemImg itemImg : itemImgList) {\n            ItemImgDto itemImgDto = ItemImgDto.of(itemImg);\n            itemImgDtoList.add(itemImgDto);\n        }\n\n        Item item = itemRepository.findById(itemId)\n                .orElseThrow(EntityNotFoundException::new);\n        ItemFormDto itemFormDto = ItemFormDto.of(item);\n        itemFormDto.setItemImgDtoList(itemImgDtoList);\n        return itemFormDto;\n    }\n\n    public Long updateItem(ItemFormDto itemFormDto, List<MultipartFile> itemImgFileList) throws Exception{\n        //상품 수정\n        Item item = itemRepository.findById(itemFormDto.getId())\n                .orElseThrow(EntityNotFoundException::new);\n        item.updateItem(itemFormDto);\n        List<Long> itemImgIds = itemFormDto.getItemImgIds();\n\n        //이미지 등록\n        for(int i=0;i<itemImgFileList.size();i++){\n            itemImgService.updateItemImg(itemImgIds.get(i),\n                    itemImgFileList.get(i));\n        }\n\n        return item.getId();\n    }\n\n    @Transactional(readOnly = true)\n    public Page<Item> getAdminItemPage(ItemSearchDto itemSearchDto, Pageable pageable){\n        return itemRepository.getAdminItemPage(itemSearchDto, pageable);\n    }\n\n    @Transactional(readOnly = true)\n    public Page<MainItemDto> getMainItemPage(ItemSearchDto itemSearchDto, Pageable pageable){\n        return itemRepository.getMainItemPage(itemSearchDto, pageable);\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/service/MemberService.java",
    "content": "package com.shop.service;\n\nimport com.shop.entity.Member;\nimport com.shop.repository.MemberRepository;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport org.springframework.security.core.userdetails.User;\nimport org.springframework.security.core.userdetails.UserDetails;\nimport org.springframework.security.core.userdetails.UserDetailsService;\nimport org.springframework.security.core.userdetails.UsernameNotFoundException;\n\n@Service\n@Transactional\n@RequiredArgsConstructor\npublic class MemberService implements UserDetailsService {\n\n    private final MemberRepository memberRepository;\n\n    public Member saveMember(Member member){\n        validateDuplicateMember(member);\n        return memberRepository.save(member);\n    }\n\n    private void validateDuplicateMember(Member member){\n        Member findMember = memberRepository.findByEmail(member.getEmail());\n        if(findMember != null){\n            throw new IllegalStateException(\"이미 가입된 회원입니다.\");\n        }\n    }\n\n    @Override\n    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {\n\n        Member member = memberRepository.findByEmail(email);\n\n        if(member == null){\n            throw new UsernameNotFoundException(email);\n        }\n\n        return User.builder()\n                .username(member.getEmail())\n                .password(member.getPassword())\n                .roles(member.getRole().toString())\n                .build();\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/shop/service/OrderService.java",
    "content": "package com.shop.service;\n\nimport com.shop.dto.OrderDto;\nimport com.shop.entity.*;\nimport com.shop.repository.ItemRepository;\nimport com.shop.repository.MemberRepository;\nimport com.shop.repository.OrderRepository;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport jakarta.persistence.EntityNotFoundException;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport com.shop.dto.OrderHistDto;\nimport com.shop.dto.OrderItemDto;\nimport com.shop.repository.ItemImgRepository;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.PageImpl;\nimport org.springframework.data.domain.Pageable;\n\nimport org.thymeleaf.util.StringUtils;\n\n@Service\n@Transactional\n@RequiredArgsConstructor\npublic class OrderService {\n\n    private final ItemRepository itemRepository;\n\n    private final MemberRepository memberRepository;\n\n    private final OrderRepository orderRepository;\n\n    private final ItemImgRepository itemImgRepository;\n\n    public Long order(OrderDto orderDto, String email){\n\n        Item item = itemRepository.findById(orderDto.getItemId())\n                .orElseThrow(EntityNotFoundException::new);\n\n        Member member = memberRepository.findByEmail(email);\n\n        List<OrderItem> orderItemList = new ArrayList<>();\n        OrderItem orderItem = OrderItem.createOrderItem(item, orderDto.getCount());\n        orderItemList.add(orderItem);\n        Order order = Order.createOrder(member, orderItemList);\n        orderRepository.save(order);\n\n        return order.getId();\n    }\n\n    @Transactional(readOnly = true)\n    public Page<OrderHistDto> getOrderList(String email, Pageable pageable) {\n\n        List<Order> orders = orderRepository.findOrders(email, pageable);\n        Long totalCount = orderRepository.countOrder(email);\n\n        List<OrderHistDto> orderHistDtos = new ArrayList<>();\n\n        for (Order order : orders) {\n            OrderHistDto orderHistDto = new OrderHistDto(order);\n            List<OrderItem> orderItems = order.getOrderItems();\n            for (OrderItem orderItem : orderItems) {\n                ItemImg itemImg = itemImgRepository.findByItemIdAndRepimgYn\n                        (orderItem.getItem().getId(), \"Y\");\n                OrderItemDto orderItemDto =\n                        new OrderItemDto(orderItem, itemImg.getImgUrl());\n                orderHistDto.addOrderItemDto(orderItemDto);\n            }\n\n            orderHistDtos.add(orderHistDto);\n        }\n\n        return new PageImpl<OrderHistDto>(orderHistDtos, pageable, totalCount);\n    }\n\n    @Transactional(readOnly = true)\n    public boolean validateOrder(Long orderId, String email){\n        Member curMember = memberRepository.findByEmail(email);\n        Order order = orderRepository.findById(orderId)\n                .orElseThrow(EntityNotFoundException::new);\n        Member savedMember = order.getMember();\n\n        if(!StringUtils.equals(curMember.getEmail(), savedMember.getEmail())){\n            return false;\n        }\n\n        return true;\n    }\n\n    public void cancelOrder(Long orderId){\n        Order order = orderRepository.findById(orderId)\n                .orElseThrow(EntityNotFoundException::new);\n        order.cancelOrder();\n    }\n\n    public Long orders(List<OrderDto> orderDtoList, String email){\n\n        Member member = memberRepository.findByEmail(email);\n        List<OrderItem> orderItemList = new ArrayList<>();\n\n        for (OrderDto orderDto : orderDtoList) {\n            Item item = itemRepository.findById(orderDto.getItemId())\n                    .orElseThrow(EntityNotFoundException::new);\n\n            OrderItem orderItem = OrderItem.createOrderItem(item, orderDto.getCount());\n            orderItemList.add(orderItem);\n        }\n\n        Order order = Order.createOrder(member, orderItemList);\n        orderRepository.save(order);\n\n        return order.getId();\n    }\n\n}"
  },
  {
    "path": "src/main/resources/application-test.properties",
    "content": "# Datasource 설정\nspring.datasource.driver-class-name=org.h2.Driver\nspring.datasource.url=jdbc:h2:mem:test\nspring.datasource.username=sa\nspring.datasource.password=\n\n# H2 데이터베이스 방언 설정\nspring.jpa.database-platform=org.hibernate.dialect.H2Dialect\n\nspring.jpa.hibernate.ddl-auto=create"
  },
  {
    "path": "src/main/resources/application.properties",
    "content": "#애플리케이션 포트 설정\nserver.port = 80\n\n#MySQL 연결 설정\nspring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver\nspring.datasource.url=jdbc:mysql://localhost:3306/shop?serverTimezone=UTC\nspring.datasource.username=root\nspring.datasource.password=1234\n\n#실행되는 쿼리 콘솔 출력\nspring.jpa.properties.hibernate.show_sql=true\n\n#콘솔창에 출력되는 쿼리를 가독성이 좋게 포맷팅\nspring.jpa.properties.hibernate.format_sql=true\n\n#쿼리에 물음표로 출력되는 바인드 파라미터 출력\nlogging.level.org.hibernate.type.descriptor.sql=trace\n\nspring.jpa.hibernate.ddl-auto=update\nspring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect\n\n#Live Reload 기능 활성화\nspring.devtools.livereload.enabled=true\n\n#Thymeleaf cache 사용 중지\nspring.thymeleaf.cache = false\n\n#파일 한 개당 최대 사이즈\nspring.servlet.multipart.maxFileSize=20MB\n#요청당 최대 파일 크기\nspring.servlet.multipart.maxRequestSize=100MB\n#상품 이미지 업로드 경로\nitemImgLocation=C:/shop/item\n#리소스 업로드 경로\nuploadPath=file:///C:/shop/\n\n#기본 batch size 설정\nspring.jpa.properties.hibernate.default_batch_fetch_size=1000"
  },
  {
    "path": "src/main/resources/static/css/layout1.css",
    "content": "html {\n    position: relative;\n    min-height: 100%;\n    margin: 0;\n}\nbody {\n    min-height: 100%;\n}\n.footer {\n    position: absolute;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    width: 100%;\n    padding: 15px 0;\n    text-align: center;\n}\n.content{\n    margin-bottom:100px;\n    margin-top: 50px;\n    margin-left: 200px;\n    margin-right: 200px;\n}"
  },
  {
    "path": "src/main/resources/templates/cart/cartList.html",
    "content": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\"\n      xmlns:layout=\"http://www.ultraq.net.nz/thymeleaf/layout\"\n      layout:decorate=\"~{layouts/layout1}\">\n\n<head>\n    <meta name=\"_csrf\" th:content=\"${_csrf.token}\"/>\n    <meta name=\"_csrf_header\" th:content=\"${_csrf.headerName}\"/>\n</head>\n\n<!-- 사용자 스크립트 추가 -->\n<th:block layout:fragment=\"script\">\n\n    <script th:inline=\"javascript\">\n\n        $(document).ready(function(){\n            $(\"input[name=cartChkBox]\").change( function(){\n                getOrderTotalPrice();\n            });\n        });\n\n        function getOrderTotalPrice(){\n            var orderTotalPrice = 0;\n            $(\"input[name=cartChkBox]:checked\").each(function() {\n                var cartItemId = $(this).val();\n                var price = $(\"#price_\" + cartItemId).attr(\"data-price\");\n                var count = $(\"#count_\" + cartItemId).val();\n                orderTotalPrice += price*count;\n            });\n\n            $(\"#orderTotalPrice\").html(orderTotalPrice+'원');\n        }\n\n        function changeCount(obj){\n            var count = obj.value;\n            var cartItemId = obj.id.split('_')[1];\n            var price = $(\"#price_\" + cartItemId).data(\"price\");\n            var totalPrice = count*price;\n            $(\"#totalPrice_\" + cartItemId).html(totalPrice+\"원\");\n            getOrderTotalPrice();\n            updateCartItemCount(cartItemId, count);\n        }\n\n        function checkAll(){\n            if($(\"#checkall\").prop(\"checked\")){\n                $(\"input[name=cartChkBox]\").prop(\"checked\",true);\n            }else{\n                $(\"input[name=cartChkBox]\").prop(\"checked\",false);\n            }\n            getOrderTotalPrice();\n        }\n\n        function updateCartItemCount(cartItemId, count){\n            var token = $(\"meta[name='_csrf']\").attr(\"content\");\n            var header = $(\"meta[name='_csrf_header']\").attr(\"content\");\n\n            var url = \"/cartItem/\" + cartItemId+\"?count=\" + count;\n\n            $.ajax({\n                url      : url,\n                type     : \"PATCH\",\n                beforeSend : function(xhr){\n                    /* 데이터를 전송하기 전에 헤더에 csrf값을 설정 */\n                    xhr.setRequestHeader(header, token);\n                },\n                dataType : \"json\",\n                cache   : false,\n                success  : function(result, status){\n                    console.log(\"cartItem count update success\");\n                },\n                error : function(jqXHR, status, error){\n\n                    if(jqXHR.status == '401'){\n                        alert('로그인 후 이용해주세요');\n                        location.href='/members/login';\n                    } else{\n                        alert(jqXHR.responseJSON.message);\n                    }\n\n                }\n            });\n        }\n\n        function deleteCartItem(obj){\n            var cartItemId = obj.dataset.id;\n            var token = $(\"meta[name='_csrf']\").attr(\"content\");\n            var header = $(\"meta[name='_csrf_header']\").attr(\"content\");\n\n            var url = \"/cartItem/\" + cartItemId;\n\n            $.ajax({\n                url      : url,\n                type     : \"DELETE\",\n                beforeSend : function(xhr){\n                    /* 데이터를 전송하기 전에 헤더에 csrf값을 설정 */\n                    xhr.setRequestHeader(header, token);\n                },\n                dataType : \"json\",\n                cache   : false,\n                success  : function(result, status){\n                    location.href='/cart';\n                },\n                error : function(jqXHR, status, error){\n\n                    if(jqXHR.status == '401'){\n                        alert('로그인 후 이용해주세요');\n                        location.href='/members/login';\n                    } else{\n                        alert(jqXHR.responseJSON.message);\n                    }\n\n                }\n            });\n        }\n\n        function orders(){\n            var token = $(\"meta[name='_csrf']\").attr(\"content\");\n            var header = $(\"meta[name='_csrf_header']\").attr(\"content\");\n\n            var url = \"/cart/orders\";\n\n            var dataList = new Array();\n            var paramData = new Object();\n\n            $(\"input[name=cartChkBox]:checked\").each(function() {\n                var cartItemId = $(this).val();\n                var data = new Object();\n                data[\"cartItemId\"] = cartItemId;\n                dataList.push(data);\n            });\n\n            paramData['cartOrderDtoList'] = dataList;\n\n            var param = JSON.stringify(paramData);\n\n            $.ajax({\n                url      : url,\n                type     : \"POST\",\n                contentType : \"application/json\",\n                data     : param,\n                beforeSend : function(xhr){\n                    /* 데이터를 전송하기 전에 헤더에 csrf값을 설정 */\n                    xhr.setRequestHeader(header, token);\n                },\n                dataType : \"json\",\n                cache   : false,\n                success  : function(result, status){\n                    alert(\"주문이 완료 되었습니다.\");\n                    location.href='/orders';\n                },\n                error : function(jqXHR, status, error){\n\n                    if(jqXHR.status == '401'){\n                        alert('로그인 후 이용해주세요');\n                        location.href='/members/login';\n                    } else{\n                        alert(jqXHR.responseJSON.message);\n                    }\n\n                }\n            });\n        }\n\n    </script>\n\n</th:block>\n\n<!-- 사용자 CSS 추가 -->\n<th:block layout:fragment=\"css\">\n    <style>\n        .content-mg{\n            margin-left:25%;\n            margin-right:25%;\n            margin-top:2%;\n            margin-bottom:100px;\n        }\n        .repImgDiv{\n            margin-right:15px;\n            margin-left:15px;\n            height:auto;\n        }\n        .repImg{\n            height:100px;\n            width:100px;\n        }\n        .fs18{\n            font-size:18px\n        }\n        .fs24{\n            font-size:24px\n        }\n    </style>\n</th:block>\n\n<div layout:fragment=\"content\" class=\"content-mg\">\n\n    <h2 class=\"mb-4\">\n        장바구니 목록\n    </h2>\n\n    <div>\n\n        <table class=\"table\">\n            <colgroup>\n                <col width=\"15%\"/>\n                <col width=\"70%\"/>\n                <col width=\"15%\"/>\n            </colgroup>\n            <thead>\n            <tr class=\"text-center\">\n                <td>\n                    <input type=\"checkbox\" id=\"checkall\" onclick=\"checkAll()\"> 전체선택\n                </td>\n                <td>상품정보</td>\n                <td>상품금액</td>\n            </tr>\n            </thead>\n            <tbody>\n            <tr th:each=\"cartItem : ${cartItems}\">\n                <td class=\"text-center align-middle\">\n                    <input type=\"checkbox\" name=\"cartChkBox\" th:value=\"${cartItem.cartItemId}\">\n                </td>\n                <td class=\"d-flex\">\n                    <div class=\"repImgDiv align-self-center\">\n                        <img th:src=\"${cartItem.imgUrl}\" class = \"rounded repImg\" th:alt=\"${cartItem.itemNm}\">\n                    </div>\n                    <div class=\"align-self-center\">\n                        <span th:text=\"${cartItem.itemNm}\" class=\"fs24 font-weight-bold\"></span>\n                        <div class=\"fs18 font-weight-light\">\n                            <span class=\"input-group mt-2\">\n                                <span th:id=\"'price_' + ${cartItem.cartItemId}\"\n                                      th:data-price=\"${cartItem.price}\"\n                                      th:text=\"${cartItem.price} + '원'\" class=\"align-self-center mr-2\">\n                                </span>\n                                <input type=\"number\" name=\"count\" th:id=\"'count_' + ${cartItem.cartItemId}\"\n                                       th:value=\"${cartItem.count}\" min=\"1\"\n                                       onchange=\"changeCount(this)\" class=\"form-control mr-2\" >\n                                <button type=\"button\" class=\"close\" aria-label=\"Close\">\n                                    <span aria-hidden=\"true\" th:data-id=\"${cartItem.cartItemId}\" onclick=\"deleteCartItem(this)\">&times;</span>\n                                </button>\n                            </span>\n                        </div>\n                    </div>\n                </td>\n                <td class=\"text-center align-middle\">\n                    <span th:id=\"'totalPrice_' + ${cartItem.cartItemId}\"\n                          name=\"totalPrice\" th:text=\"${cartItem.price * cartItem.count} + '원'\">\n                    </span>\n                </td>\n            </tr>\n            </tbody>\n        </table>\n\n        <h2 class=\"text-center\">\n            총 주문 금액 : <span id=\"orderTotalPrice\" class=\"text-danger\">0원</span>\n        </h2>\n\n        <div class=\"text-center mt-3\">\n            <button type=\"button\" class=\"btn btn-primary btn-lg\" onclick=\"orders()\">주문하기</button>\n        </div>\n\n    </div>\n\n</div>\n\n</html>"
  },
  {
    "path": "src/main/resources/templates/fragments/footer.html",
    "content": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\">\n    <div class=\"footer\" th:fragment=\"footer\">\n        <footer class=\"page-footer font-small cyan darken-3\">\n            <div class=\"footer-copyright text-center py-3\">\n                2020 Shopping Mall Example WebSite\n            </div>\n        </footer>\n    </div>\n</html>"
  },
  {
    "path": "src/main/resources/templates/fragments/header.html",
    "content": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\"\n      xmlns:sec=\"http://www.thymeleaf.org/extras/spring-security\">\n\n<div th:fragment=\"header\">\n    <nav class=\"navbar navbar-expand-sm bg-primary navbar-dark\">\n        <button class=\"navbar-toggler\" type=\"button\" data-toggle=\"collapse\"\n                data-target=\"#navbarTogglerDemo03\" aria-controls=\"navbarTogglerDemo03\"\n                aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n            <span class=\"navbar-toggler-icon\"></span>\n        </button>\n        <a class=\"navbar-brand\" href=\"/\">Shop</a>\n\n        <div class=\"collapse navbar-collapse\" id=\"navbarTogglerDemo03\">\n            <ul class=\"navbar-nav mr-auto mt-2 mt-lg-0\">\n                <li class=\"nav-item\" sec:authorize=\"hasAnyAuthority('ROLE_ADMIN')\">\n                    <a class=\"nav-link\" href=\"/admin/item/new\">상품 등록</a>\n                </li>\n                <li class=\"nav-item\" sec:authorize=\"hasAnyAuthority('ROLE_ADMIN')\">\n                    <a class=\"nav-link\" href=\"/admin/items\">상품 관리</a>\n                </li>\n                <li class=\"nav-item\" sec:authorize=\"isAuthenticated()\">\n                    <a class=\"nav-link\" href=\"/cart\">장바구니</a>\n                </li>\n                <li class=\"nav-item\" sec:authorize=\"isAuthenticated()\">\n                    <a class=\"nav-link\" href=\"/orders\">구매이력</a>\n                </li>\n                <li class=\"nav-item\" sec:authorize=\"isAnonymous()\">\n                    <a class=\"nav-link\" href=\"/members/login\">로그인</a>\n                </li>\n                <li class=\"nav-item\" sec:authorize=\"isAuthenticated()\">\n                    <a class=\"nav-link\" href=\"/members/logout\">로그아웃</a>\n                </li>\n            </ul>\n            <form class=\"form-inline my-2 my-lg-0\" th:action=\"@{/}\" method=\"get\">\n                <input name=\"searchQuery\" class=\"form-control mr-sm-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n                <button class=\"btn btn-outline-success my-2 my-sm-0\" type=\"submit\">Search</button>\n            </form>\n        </div>\n    </nav>\n</div>\n\n</html>"
  },
  {
    "path": "src/main/resources/templates/item/itemDtl.html",
    "content": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\"\n      xmlns:layout=\"http://www.ultraq.net.nz/thymeleaf/layout\"\n      layout:decorate=\"~{layouts/layout1}\">\n\n<head>\n    <meta name=\"_csrf\" th:content=\"${_csrf.token}\"/>\n    <meta name=\"_csrf_header\" th:content=\"${_csrf.headerName}\"/>\n</head>\n\n<!-- 사용자 스크립트 추가 -->\n<th:block layout:fragment=\"script\">\n    <script th:inline=\"javascript\">\n        $(document).ready(function(){\n\n            calculateToalPrice();\n\n            $(\"#count\").change( function(){\n                calculateToalPrice();\n            });\n        });\n\n        function calculateToalPrice(){\n            var count = $(\"#count\").val();\n            var price = $(\"#price\").val();\n            var totalPrice = price*count;\n            $(\"#totalPrice\").html(totalPrice + '원');\n        }\n\n        function order(){\n            var token = $(\"meta[name='_csrf']\").attr(\"content\");\n            var header = $(\"meta[name='_csrf_header']\").attr(\"content\");\n\n            var url = \"/order\";\n            var paramData = {\n                itemId : $(\"#itemId\").val(),\n                count : $(\"#count\").val()\n            };\n\n            var param = JSON.stringify(paramData);\n\n            $.ajax({\n                url      : url,\n                type     : \"POST\",\n                contentType : \"application/json\",\n                data     : param,\n                beforeSend : function(xhr){\n                    /* 데이터를 전송하기 전에 헤더에 csrf값을 설정 */\n                    xhr.setRequestHeader(header, token);\n                },\n                dataType : \"json\",\n                cache   : false,\n                success  : function(result, status){\n                    alert(\"주문이 완료 되었습니다.\");\n                    location.href='/';\n                },\n                error : function(jqXHR, status, error){\n\n                    if(jqXHR.status == '401'){\n                        alert('로그인 후 이용해주세요');\n                        location.href='/members/login';\n                    } else{\n                        alert(jqXHR.responseText);\n                    }\n\n                }\n            });\n        }\n\n        function addCart(){\n            var token = $(\"meta[name='_csrf']\").attr(\"content\");\n            var header = $(\"meta[name='_csrf_header']\").attr(\"content\");\n\n            var url = \"/cart\";\n            var paramData = {\n                itemId : $(\"#itemId\").val(),\n                count : $(\"#count\").val()\n            };\n\n            var param = JSON.stringify(paramData);\n\n            $.ajax({\n                url      : url,\n                type     : \"POST\",\n                contentType : \"application/json\",\n                data     : param,\n                beforeSend : function(xhr){\n                    /* 데이터를 전송하기 전에 헤더에 csrf값을 설정 */\n                    xhr.setRequestHeader(header, token);\n                },\n                dataType : \"json\",\n                cache   : false,\n                success  : function(result, status){\n                    alert(\"상품을 장바구니에 담았습니다.\");\n                    location.href='/';\n                },\n                error : function(jqXHR, status, error){\n\n                    if(jqXHR.status == '401'){\n                        alert('로그인 후 이용해주세요');\n                        location.href='/members/login';\n                    } else{\n                        alert(jqXHR.responseText);\n                    }\n\n                }\n            });\n        }\n\n    </script>\n</th:block>\n\n<!-- 사용자 CSS 추가 -->\n<th:block layout:fragment=\"css\">\n    <style>\n        .mgb-15{\n            margin-bottom:15px;\n        }\n        .mgt-30{\n            margin-top:30px;\n        }\n        .mgt-50{\n            margin-top:50px;\n        }\n        .repImgDiv{\n            margin-right:15px;\n            height:auto;\n            width:50%;\n        }\n        .repImg{\n            width:100%;\n            height:400px;\n        }\n        .wd50{\n            height:auto;\n            width:50%;\n        }\n    </style>\n</th:block>\n\n<div layout:fragment=\"content\" style=\"margin-left:25%;margin-right:25%\">\n\n    <input type=\"hidden\" id=\"itemId\" th:value=\"${item.id}\">\n\n    <div class=\"d-flex\">\n        <div class=\"repImgDiv\">\n            <img th:src=\"${item.itemImgDtoList[0].imgUrl}\" class = \"rounded repImg\" th:alt=\"${item.itemNm}\">\n        </div>\n        <div class=\"wd50\">\n            <span th:if=\"${item.itemSellStatus == T(com.shop.constant.ItemSellStatus).SELL}\" class=\"badge badge-primary mgb-15\">\n                판매중\n            </span>\n            <span th:unless=\"${item.itemSellStatus == T(com.shop.constant.ItemSellStatus).SELL}\" class=\"badge btn-danger mgb-15\" >\n                품절\n            </span>\n            <div class=\"h4\" th:text=\"${item.itemNm}\"></div>\n            <hr class=\"my-4\">\n\n            <div class=\"text-right\">\n                <div class=\"h4 text-danger text-left\">\n                    <input type=\"hidden\" th:value=\"${item.price}\" id=\"price\" name=\"price\">\n                    <span th:text=\"${item.price}\"></span>원\n                </div>\n                <div class=\"input-group w-50\">\n                    <div class=\"input-group-prepend\">\n                        <span class=\"input-group-text\">수량</span>\n                    </div>\n                    <input type=\"number\" name=\"count\" id=\"count\" class=\"form-control\" value=\"1\" min=\"1\">\n                </div>\n            </div>\n            <hr class=\"my-4\">\n\n            <div class=\"text-right mgt-50\">\n                <h5>결제 금액</h5>\n                <h3 name=\"totalPrice\" id=\"totalPrice\" class=\"font-weight-bold\"></h3>\n            </div>\n            <div th:if=\"${item.itemSellStatus == T(com.shop.constant.ItemSellStatus).SELL}\" class=\"text-right\">\n                <button type=\"button\" class=\"btn btn-light border border-primary btn-lg\" onclick=\"addCart()\">장바구니 담기</button>\n                <button type=\"button\" class=\"btn btn-primary btn-lg\" onclick=\"order()\">주문하기</button>\n            </div>\n            <div th:unless=\"${item.itemSellStatus == T(com.shop.constant.ItemSellStatus).SELL}\" class=\"text-right\">\n                <button type=\"button\" class=\"btn btn-danger btn-lg\">품절</button>\n            </div>\n        </div>\n    </div>\n\n    <div class=\"jumbotron jumbotron-fluid mgt-30\">\n        <div class=\"container\">\n            <h4 class=\"display-5\">상품 상세 설명</h4>\n            <hr class=\"my-4\">\n            <p class=\"lead\" th:text=\"${item.itemDetail}\"></p>\n        </div>\n    </div>\n\n    <div th:each=\"itemImg : ${item.itemImgDtoList}\" class=\"text-center\">\n        <img th:if=\"${not #strings.isEmpty(itemImg.imgUrl)}\" th:src=\"${itemImg.imgUrl}\" class=\"rounded mgb-15\" width=\"800\">\n    </div>\n\n</div>\n\n</html>"
  },
  {
    "path": "src/main/resources/templates/item/itemForm.html",
    "content": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\"\n      xmlns:layout=\"http://www.ultraq.net.nz/thymeleaf/layout\"\n      layout:decorate=\"~{layouts/layout1}\">\n\n<!-- 사용자 스크립트 추가 -->\n<th:block layout:fragment=\"script\">\n\n    <script th:inline=\"javascript\">\n        $(document).ready(function(){\n            var errorMessage = [[${errorMessage}]];\n            if(errorMessage != null){\n                alert(errorMessage);\n            }\n\n            bindDomEvent();\n\n        });\n\n        function bindDomEvent(){\n            $(\".custom-file-input\").on(\"change\", function() {\n                var fileName = $(this).val().split(\"\\\\\").pop();  //이미지 파일명\n                var fileExt = fileName.substring(fileName.lastIndexOf(\".\")+1); // 확장자 추출\n                fileExt = fileExt.toLowerCase(); //소문자 변환\n\n                if(fileExt != \"jpg\" && fileExt != \"jpeg\" && fileExt != \"gif\" && fileExt != \"png\" && fileExt != \"bmp\"){\n                    alert(\"이미지 파일만 등록이 가능합니다.\");\n                    return;\n                }\n\n                $(this).siblings(\".custom-file-label\").html(fileName);\n            });\n        }\n\n    </script>\n\n</th:block>\n\n<!-- 사용자 CSS 추가 -->\n<th:block layout:fragment=\"css\">\n    <style>\n        .input-group {\n            margin-bottom : 15px\n        }\n        .img-div {\n            margin-bottom : 10px\n        }\n        .fieldError {\n            color: #bd2130;\n        }\n    </style>\n</th:block>\n\n<div layout:fragment=\"content\">\n\n    <form role=\"form\" method=\"post\" enctype=\"multipart/form-data\" th:object=\"${itemFormDto}\">\n\n        <p class=\"h2\">\n            상품 등록\n        </p>\n\n        <input type=\"hidden\" th:field=\"*{id}\">\n\n        <div class=\"form-group\">\n            <select th:field=\"*{itemSellStatus}\" class=\"custom-select\">\n                <option value=\"SELL\">판매중</option>\n                <option value=\"SOLD_OUT\">품절</option>\n            </select>\n        </div>\n\n        <div class=\"input-group\">\n            <div class=\"input-group-prepend\">\n                <span class=\"input-group-text\">상품명</span>\n            </div>\n            <input type=\"text\" th:field=\"*{itemNm}\" class=\"form-control\" placeholder=\"상품명을 입력해주세요\">\n        </div>\n        <p th:if=\"${#fields.hasErrors('itemNm')}\" th:errors=\"*{itemNm}\" class=\"fieldError\">Incorrect data</p>\n\n        <div class=\"input-group\">\n            <div class=\"input-group-prepend\">\n                <span class=\"input-group-text\">가격</span>\n            </div>\n            <input type=\"number\" th:field=\"*{price}\" class=\"form-control\" placeholder=\"상품의 가격을 입력해주세요\">\n        </div>\n        <p th:if=\"${#fields.hasErrors('price')}\" th:errors=\"*{price}\" class=\"fieldError\">Incorrect data</p>\n\n        <div class=\"input-group\">\n            <div class=\"input-group-prepend\">\n                <span class=\"input-group-text\">재고</span>\n            </div>\n            <input type=\"number\" th:field=\"*{stockNumber}\" class=\"form-control\" placeholder=\"상품의 재고를 입력해주세요\">\n        </div>\n        <p th:if=\"${#fields.hasErrors('stockNumber')}\" th:errors=\"*{stockNumber}\" class=\"fieldError\">Incorrect data</p>\n\n        <div class=\"input-group\">\n            <div class=\"input-group-prepend\">\n                <span class=\"input-group-text\">상품 상세 내용</span>\n            </div>\n            <textarea class=\"form-control\" aria-label=\"With textarea\" th:field=\"*{itemDetail}\"></textarea>\n        </div>\n        <p th:if=\"${#fields.hasErrors('itemDetail')}\" th:errors=\"*{itemDetail}\" class=\"fieldError\">Incorrect data</p>\n\n        <div th:if=\"${#lists.isEmpty(itemFormDto.itemImgDtoList)}\">\n            <div class=\"form-group\" th:each=\"num: ${#numbers.sequence(1,5)}\">\n                <div class=\"custom-file img-div\">\n                    <input type=\"file\" class=\"custom-file-input\" name=\"itemImgFile\">\n                    <label class=\"custom-file-label\" th:text=\"상품이미지 + ${num}\"></label>\n                </div>\n            </div>\n        </div>\n\n        <div th:if = \"${not #lists.isEmpty(itemFormDto.itemImgDtoList)}\">\n            <div class=\"form-group\" th:each=\"itemImgDto, status: ${itemFormDto.itemImgDtoList}\">\n                <div class=\"custom-file img-div\">\n                    <input type=\"file\" class=\"custom-file-input\" name=\"itemImgFile\">\n                    <input type=\"hidden\" name=\"itemImgIds\" th:value=\"${itemImgDto.id}\">\n                    <label class=\"custom-file-label\" th:text=\"${not #strings.isEmpty(itemImgDto.oriImgName)} ? ${itemImgDto.oriImgName} : '상품이미지' + ${status.index+1}\"></label>\n                </div>\n            </div>\n        </div>\n\n        <div th:if=\"${#strings.isEmpty(itemFormDto.id)}\" style=\"text-align: center\">\n            <button th:formaction=\"@{/admin/item/new}\" type=\"submit\" class=\"btn btn-primary\">저장</button>\n        </div>\n        <div th:unless=\"${#strings.isEmpty(itemFormDto.id)}\" style=\"text-align: center\">\n            <button th:formaction=\"@{'/admin/item/' + ${itemFormDto.id} }\" type=\"submit\" class=\"btn btn-primary\">수정</button>\n        </div>\n        <input type=\"hidden\" th:name=\"${_csrf.parameterName}\" th:value=\"${_csrf.token}\">\n\n    </form>\n\n</div>\n\n</html>"
  },
  {
    "path": "src/main/resources/templates/item/itemMng.html",
    "content": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\"\n      xmlns:layout=\"http://www.ultraq.net.nz/thymeleaf/layout\"\n      layout:decorate=\"~{layouts/layout1}\">\n\n<!-- 사용자 스크립트 추가 -->\n<th:block layout:fragment=\"script\">\n    <script th:inline=\"javascript\">\n\n        $(document).ready(function(){\n            $(\"#searchBtn\").on(\"click\",function(e) {\n                e.preventDefault();\n                page(0);\n            });\n        });\n\n        function page(page){\n            var searchDateType = $(\"#searchDateType\").val();\n            var searchSellStatus = $(\"#searchSellStatus\").val();\n            var searchBy = $(\"#searchBy\").val();\n            var searchQuery = $(\"#searchQuery\").val();\n\n            location.href=\"/admin/items/\" + page + \"?searchDateType=\" + searchDateType\n            + \"&searchSellStatus=\" + searchSellStatus\n            + \"&searchBy=\" + searchBy\n            + \"&searchQuery=\" + searchQuery;\n        }\n\n    </script>\n</th:block>\n\n<!-- 사용자 CSS 추가 -->\n<th:block layout:fragment=\"css\">\n    <style>\n        select{\n            margin-right:10px;\n        }\n    </style>\n</th:block>\n\n<div layout:fragment=\"content\">\n\n    <form th:action=\"@{'/admin/items/' + ${items.number}}\" role=\"form\" method=\"get\" th:object=\"${items}\">\n        <table class=\"table\">\n            <thead>\n            <tr>\n                <td>상품아이디</td>\n                <td>상품명</td>\n                <td>상태</td>\n                <td>등록자</td>\n                <td>등록일</td>\n            </tr>\n            </thead>\n            <tbody>\n            <tr th:each=\"item, status: ${items.getContent()}\">\n                <td th:text=\"${item.id}\"></td>\n                <td>\n                    <a th:href=\"'/admin/item/'+${item.id}\" th:text=\"${item.itemNm}\"></a>\n                </td>\n                <td th:text=\"${item.itemSellStatus == T(com.shop.constant.ItemSellStatus).SELL} ? '판매중' : '품절'\"></td>\n                <td th:text=\"${item.createdBy}\"></td>\n                <td th:text=\"${item.regTime}\"></td>\n            </tr>\n            </tbody>\n        </table>\n\n        <div th:with=\"start=${(items.number/maxPage)*maxPage + 1}, end=(${(items.totalPages == 0) ? 1 : (start + (maxPage - 1) < items.totalPages ? start + (maxPage - 1) : items.totalPages)})\" >\n            <ul class=\"pagination justify-content-center\">\n\n                <li class=\"page-item\" th:classappend=\"${items.first}?'disabled'\">\n                    <a th:onclick=\"'javascript:page(' + ${items.number - 1} + ')'\" aria-label='Previous' class=\"page-link\">\n                        <span aria-hidden='true'>Previous</span>\n                    </a>\n                </li>\n\n                <li class=\"page-item\" th:each=\"page: ${#numbers.sequence(start, end)}\" th:classappend=\"${items.number eq page-1}?'active':''\">\n                    <a th:onclick=\"'javascript:page(' + ${page - 1} + ')'\" th:inline=\"text\" class=\"page-link\">[[${page}]]</a>\n                </li>\n\n                <li class=\"page-item\" th:classappend=\"${items.last}?'disabled'\">\n                    <a th:onclick=\"'javascript:page(' + ${items.number + 1} + ')'\" aria-label='Next' class=\"page-link\">\n                        <span aria-hidden='true'>Next</span>\n                    </a>\n                </li>\n\n            </ul>\n        </div>\n\n        <div class=\"form-inline justify-content-center\" th:object=\"${itemSearchDto}\">\n            <select th:field=\"*{searchDateType}\" class=\"form-control\" style=\"width:auto;\">\n                <option value=\"all\">전체기간</option>\n                <option value=\"1d\">1일</option>\n                <option value=\"1w\">1주</option>\n                <option value=\"1m\">1개월</option>\n                <option value=\"6m\">6개월</option>\n            </select>\n            <select th:field=\"*{searchSellStatus}\" class=\"form-control\" style=\"width:auto;\">\n                <option value=\"\">판매상태(전체)</option>\n                <option value=\"SELL\">판매</option>\n                <option value=\"SOLD_OUT\">품절</option>\n            </select>\n            <select th:field=\"*{searchBy}\" class=\"form-control\" style=\"width:auto;\">\n                <option value=\"itemNm\">상품명</option>\n                <option value=\"createdBy\">등록자</option>\n            </select>\n            <input th:field=\"*{searchQuery}\" type=\"text\" class=\"form-control\" placeholder=\"검색어를 입력해주세요\">\n            <button id=\"searchBtn\" type=\"submit\" class=\"btn btn-primary\">검색</button>\n        </div>\n    </form>\n\n</div>\n\n</html>"
  },
  {
    "path": "src/main/resources/templates/layouts/layout1.html",
    "content": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\"\n      xmlns:layout=\"http://www.ultraq.net.nz/thymeleaf/layout\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n\n    <!-- CSS only -->\n    <link rel=\"stylesheet\" href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css\">\n    <link th:href=\"@{/css/layout1.css}\" rel=\"stylesheet\">\n\n    <!-- JS, Popper.js, and jQuery -->\n    <script src=\"https://code.jquery.com/jquery-3.5.1.min.js\"></script>\n    <script src=\"https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js\"></script>\n    <script src=\"https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js\"></script>\n\n    <th:block layout:fragment=\"script\"></th:block>\n    <th:block layout:fragment=\"css\"></th:block>\n\n</head>\n<body>\n\n    <div th:replace=\"~{fragments/header::header}\"></div>\n\n    <div layout:fragment=\"content\" class=\"content\">\n\n    </div>\n\n    <div th:replace=\"~{fragments/footer::footer}\"></div>\n\n</body>\n</html>"
  },
  {
    "path": "src/main/resources/templates/main.html",
    "content": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\"\n      xmlns:layout=\"http://www.ultraq.net.nz/thymeleaf/layout\"\n      layout:decorate=\"~{layouts/layout1}\">\n\n<!-- 사용자 CSS 추가 -->\n<th:block layout:fragment=\"css\">\n    <style>\n        .carousel-inner > .item {\n            height: 350px;\n        }\n        .margin{\n            margin-bottom:30px;\n        }\n        .banner{\n            height: 300px;\n            position: absolute; top:0; left: 0;\n            width: 100%;\n            height: 100%;\n        }\n        .card-text{\n            text-overflow: ellipsis;\n            white-space: nowrap;\n            overflow: hidden;\n        }\n        a:hover{\n            text-decoration:none;\n        }\n        .center{\n            text-align:center;\n        }\n    </style>\n</th:block>\n\n<div layout:fragment=\"content\">\n\n    <div id=\"carouselControls\" class=\"carousel slide margin\" data-ride=\"carousel\">\n        <div class=\"carousel-inner\">\n            <div class=\"carousel-item active item\">\n                <img class=\"d-block w-100 banner\" src=\"https://user-images.githubusercontent.com/13268420/112147492-1ab76200-8c20-11eb-8649-3d2f282c3c02.png\" alt=\"First slide\">\n            </div>\n        </div>\n    </div>\n\n    <input type=\"hidden\" name=\"searchQuery\" th:value=\"${itemSearchDto.searchQuery}\">\n    <div th:if=\"${not #strings.isEmpty(itemSearchDto.searchQuery)}\" class=\"center\">\n        <p class=\"h3 font-weight-bold\" th:text=\"${itemSearchDto.searchQuery} + '검색 결과'\"></p>\n    </div>\n\n    <div class=\"row\">\n        <th:block th:each=\"item, status: ${items.getContent()}\">\n            <div class=\"col-md-4 margin\">\n                <div class=\"card\">\n                    <a th:href=\"'/item/' +${item.id}\" class=\"text-dark\">\n                        <img th:src=\"${item.imgUrl}\" class=\"card-img-top\" th:alt=\"${item.itemNm}\" height=\"400\">\n                        <div class=\"card-body\">\n                            <h4 class=\"card-title\">[[${item.itemNm}]]</h4>\n                            <p class=\"card-text\">[[${item.itemDetail}]]</p>\n                            <h3 class=\"card-title text-danger\">[[${item.price}]]원</h3>\n                        </div>\n                    </a>\n                </div>\n            </div>\n        </th:block>\n    </div>\n\n    <div th:with=\"start=${(items.number/maxPage)*maxPage + 1}, end=(${(items.totalPages == 0) ? 1 : (start + (maxPage - 1) < items.totalPages ? start + (maxPage - 1) : items.totalPages)})\" >\n        <ul class=\"pagination justify-content-center\">\n\n            <li class=\"page-item\" th:classappend=\"${items.number eq 0}?'disabled':''\">\n                <a th:href=\"@{'/' + '?searchQuery=' + ${itemSearchDto.searchQuery} + '&page=' + ${items.number-1}}\" aria-label='Previous' class=\"page-link\">\n                    <span aria-hidden='true'>Previous</span>\n                </a>\n            </li>\n\n            <li class=\"page-item\" th:each=\"page: ${#numbers.sequence(start, end)}\" th:classappend=\"${items.number eq page-1}?'active':''\">\n                <a th:href=\"@{'/' +'?searchQuery=' + ${itemSearchDto.searchQuery} + '&page=' + ${page-1}}\" th:inline=\"text\" class=\"page-link\">[[${page}]]</a>\n            </li>\n\n            <li class=\"page-item\" th:classappend=\"${items.number+1 ge items.totalPages}?'disabled':''\">\n                <a th:href=\"@{'/' +'?searchQuery=' + ${itemSearchDto.searchQuery} + '&page=' + ${items.number+1}}\" aria-label='Next' class=\"page-link\">\n                    <span aria-hidden='true'>Next</span>\n                </a>\n            </li>\n\n        </ul>\n    </div>\n\n</div>"
  },
  {
    "path": "src/main/resources/templates/member/memberForm.html",
    "content": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\"\n      xmlns:layout=\"http://www.ultraq.net.nz/thymeleaf/layout\"\n      layout:decorate=\"~{layouts/layout1}\">\n\n<!-- 사용자 CSS 추가 -->\n<th:block layout:fragment=\"css\">\n    <style>\n        .fieldError {\n            color: #bd2130;\n        }\n    </style>\n</th:block>\n\n<!-- 사용자 스크립트 추가 -->\n<th:block layout:fragment=\"script\">\n\n    <script th:inline=\"javascript\">\n        $(document).ready(function(){\n            var errorMessage = [[${errorMessage}]];\n            if(errorMessage != null){\n                alert(errorMessage);\n            }\n        });\n    </script>\n\n</th:block>\n\n<div layout:fragment=\"content\">\n\n    <form action=\"/members/new\" role=\"form\" method=\"post\"  th:object=\"${memberFormDto}\">\n        <div class=\"form-group\">\n            <label th:for=\"name\">이름</label>\n            <input type=\"text\" th:field=\"*{name}\" class=\"form-control\" placeholder=\"이름을 입력해주세요\">\n            <p th:if=\"${#fields.hasErrors('name')}\" th:errors=\"*{name}\" class=\"fieldError\">Incorrect data</p>\n        </div>\n        <div class=\"form-group\">\n            <label th:for=\"email\">이메일주소</label>\n            <input type=\"email\" th:field=\"*{email}\" class=\"form-control\" placeholder=\"이메일을 입력해주세요\">\n            <p th:if=\"${#fields.hasErrors('email')}\" th:errors=\"*{email}\" class=\"fieldError\">Incorrect data</p>\n        </div>\n        <div class=\"form-group\">\n            <label th:for=\"password\">비밀번호</label>\n            <input type=\"password\" th:field=\"*{password}\" class=\"form-control\" placeholder=\"비밀번호 입력\">\n            <p th:if=\"${#fields.hasErrors('password')}\" th:errors=\"*{password}\" class=\"fieldError\">Incorrect data</p>\n        </div>\n        <div class=\"form-group\">\n            <label th:for=\"address\">주소</label>\n            <input type=\"text\" th:field=\"*{address}\" class=\"form-control\" placeholder=\"주소를 입력해주세요\">\n            <p th:if=\"${#fields.hasErrors('address')}\" th:errors=\"*{address}\" class=\"fieldError\">Incorrect data</p>\n        </div>\n        <div style=\"text-align: center\">\n            <button type=\"submit\" class=\"btn btn-primary\" style=\"\">Submit</button>\n        </div>\n        <input type=\"hidden\" th:name=\"${_csrf.parameterName}\" th:value=\"${_csrf.token}\">\n    </form>\n\n</div>\n\n</html>"
  },
  {
    "path": "src/main/resources/templates/member/memberLoginForm.html",
    "content": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\"\n      xmlns:layout=\"http://www.ultraq.net.nz/thymeleaf/layout\"\n      layout:decorate=\"~{layouts/layout1}\">\n\n<!-- 사용자 CSS 추가 -->\n<th:block layout:fragment=\"css\">\n  <style>\n        .error {\n            color: #bd2130;\n        }\n    </style>\n</th:block>\n\n<div layout:fragment=\"content\">\n\n  <form role=\"form\" method=\"post\" action=\"/members/login\">\n    <div class=\"form-group\">\n      <label th:for=\"email\">이메일주소</label>\n      <input type=\"email\" name=\"email\" class=\"form-control\" placeholder=\"이메일을 입력해주세요\">\n    </div>\n    <div class=\"form-group\">\n      <label th:for=\"password\">비밀번호</label>\n      <input type=\"password\" name=\"password\" id=\"password\" class=\"form-control\" placeholder=\"비밀번호 입력\">\n    </div>\n    <p th:if=\"${loginErrorMsg}\" class=\"error\" th:text=\"${loginErrorMsg}\"></p>\n    <button class=\"btn btn-primary\">로그인</button>\n    <button type=\"button\" class=\"btn btn-primary\" onClick=\"location.href='/members/new'\">회원가입</button>\n    <input type=\"hidden\" th:name=\"${_csrf.parameterName}\" th:value=\"${_csrf.token}\">\n  </form>\n\n</div>\n\n</html>"
  },
  {
    "path": "src/main/resources/templates/order/orderHist.html",
    "content": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\"\n      xmlns:layout=\"http://www.ultraq.net.nz/thymeleaf/layout\"\n      layout:decorate=\"~{layouts/layout1}\">\n\n<head>\n    <meta name=\"_csrf\" th:content=\"${_csrf.token}\"/>\n    <meta name=\"_csrf_header\" th:content=\"${_csrf.headerName}\"/>\n</head>\n\n<!-- 사용자 스크립트 추가 -->\n<th:block layout:fragment=\"script\">\n\n    <script th:inline=\"javascript\">\n        function cancelOrder(orderId) {\n            var token = $(\"meta[name='_csrf']\").attr(\"content\");\n            var header = $(\"meta[name='_csrf_header']\").attr(\"content\");\n\n            var url = \"/order/\" + orderId + \"/cancel\";\n            var paramData = {\n                orderId : orderId,\n            };\n\n            var param = JSON.stringify(paramData);\n\n            $.ajax({\n                url      : url,\n                type     : \"POST\",\n                contentType : \"application/json\",\n                data     : param,\n                beforeSend : function(xhr){\n                    /* 데이터를 전송하기 전에 헤더에 csrf값을 설정 */\n                    xhr.setRequestHeader(header, token);\n                },\n                dataType : \"json\",\n                cache   : false,\n                success  : function(result, status){\n                    alert(\"주문이 취소 되었습니다.\");\n                    location.href='/orders/' + [[${page}]];\n                },\n                error : function(jqXHR, status, error){\n                    if(jqXHR.status == '401'){\n                        alert('로그인 후 이용해주세요');\n                        location.href='/members/login';\n                    } else{\n                        alert(jqXHR.responseText);\n                    }\n                }\n            });\n        }\n    </script>\n\n</th:block>\n\n<!-- 사용자 CSS 추가 -->\n<th:block layout:fragment=\"css\">\n    <style>\n        .content-mg{\n            margin-left:30%;\n            margin-right:30%;\n            margin-top:2%;\n            margin-bottom:100px;\n        }\n        .repImgDiv{\n            margin-right:15px;\n            margin-left:15px;\n            height:auto;\n        }\n        .repImg{\n            height:100px;\n            width:100px;\n        }\n        .card{\n            width:750px;\n            height:100%;\n            padding:30px;\n            margin-bottom:20px;\n        }\n        .fs18{\n            font-size:18px\n        }\n        .fs24{\n            font-size:24px\n        }\n    </style>\n</th:block>\n\n<div layout:fragment=\"content\" class=\"content-mg\">\n\n    <h2 class=\"mb-4\">\n        구매 이력\n    </h2>\n\n    <div th:each=\"order : ${orders.getContent()}\">\n\n        <div class=\"d-flex mb-3 align-self-center\">\n            <h4 th:text=\"${order.orderDate} + ' 주문'\"></h4>\n            <div class=\"ml-3\">\n                <th:block th:if=\"${order.orderStatus == T(com.shop.constant.OrderStatus).ORDER}\">\n                    <button type=\"button\" class=\"btn btn-outline-secondary\" th:value=\"${order.orderId}\" onclick=\"cancelOrder(this.value)\">주문취소</button>\n                </th:block>\n                <th:block th:unless=\"${order.orderStatus == T(com.shop.constant.OrderStatus).ORDER}\">\n                    <h4>(취소 완료)</h4>\n                </th:block>\n            </div>\n        </div>\n        <div class=\"card d-flex\">\n            <div th:each=\"orderItem : ${order.orderItemDtoList}\" class=\"d-flex mb-3\">\n                <div class=\"repImgDiv\">\n                    <img th:src=\"${orderItem.imgUrl}\" class = \"rounded repImg\" th:alt=\"${orderItem.itemNm}\">\n                </div>\n                <div class=\"align-self-center w-75\">\n                    <span th:text=\"${orderItem.itemNm}\" class=\"fs24 font-weight-bold\"></span>\n                    <div class=\"fs18 font-weight-light\">\n                        <span th:text=\"${orderItem.orderPrice} +'원'\"></span>\n                        <span th:text=\"${orderItem.count} +'개'\"></span>\n                    </div>\n                </div>\n            </div>\n        </div>\n\n    </div>\n\n    <div th:with=\"start=${(orders.number/maxPage)*maxPage + 1}, end=(${(orders.totalPages == 0) ? 1 : (start + (maxPage - 1) < orders.totalPages ? start + (maxPage - 1) : orders.totalPages)})\" >\n        <ul class=\"pagination justify-content-center\">\n\n            <li class=\"page-item\" th:classappend=\"${orders.number eq 0}?'disabled':''\">\n                <a th:href=\"@{'/orders/' + ${orders.number-1}}\" aria-label='Previous' class=\"page-link\">\n                    <span aria-hidden='true'>Previous</span>\n                </a>\n            </li>\n\n            <li class=\"page-item\" th:each=\"page: ${#numbers.sequence(start, end)}\" th:classappend=\"${orders.number eq page-1}?'active':''\">\n                <a th:href=\"@{'/orders/' + ${page-1}}\" th:inline=\"text\" class=\"page-link\">[[${page}]]</a>\n            </li>\n\n            <li class=\"page-item\" th:classappend=\"${orders.number+1 ge orders.totalPages}?'disabled':''\">\n                <a th:href=\"@{'/orders/' + ${orders.number+1}}\" aria-label='Next' class=\"page-link\">\n                    <span aria-hidden='true'>Next</span>\n                </a>\n            </li>\n\n        </ul>\n    </div>\n\n</div>\n\n</html>"
  },
  {
    "path": "src/main/resources/templates/thymeleafEx/thymeleafEx01.html",
    "content": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Title</title>\n</head>\n<body>\n    <p th:text=\"${data}\">Hello Thymeleaf!!</p>\n</body>\n</html>"
  },
  {
    "path": "src/main/resources/templates/thymeleafEx/thymeleafEx02.html",
    "content": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n</head>\n<body>\n    <h1>상품 데이터 출력 예제</h1>\n    <div>\n        상품명 : <span th:text=\"${itemDto.itemNm}\"></span>\n    </div>\n    <div>\n        상품상세설명 : <span th:text=\"${itemDto.itemDetail}\"></span>\n    </div>\n    <div>\n        상품등록일 : <span th:text=\"${itemDto.regTime}\"></span>\n    </div>\n    <div>\n        상품가격 : <span th:text=\"${itemDto.price}\"></span>\n    </div>\n</body>\n</html>"
  },
  {
    "path": "src/main/resources/templates/thymeleafEx/thymeleafEx03.html",
    "content": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n</head>\n<body>\n\n<h1>상품 리스트 출력 예제</h1>\n\n<table border=\"1\">\n    <thead>\n    <tr>\n        <td>순번</td>\n        <td>상품명</td>\n        <td>상품설명</td>\n        <td>가격</td>\n        <td>상품등록일</td>\n    </tr>\n    </thead>\n    <tbody>\n    <tr th:each=\"itemDto, status: ${itemDtoList}\">\n        <td th:text=\"${status.index}\"></td>\n        <td th:text=\"${itemDto.itemNm}\"></td>\n        <td th:text=\"${itemDto.itemDetail}\"></td>\n        <td th:text=\"${itemDto.price}\"></td>\n        <td th:text=\"${itemDto.regTime}\"></td>\n    </tr>\n    </tbody>\n</table>\n\n</body>\n</html>"
  },
  {
    "path": "src/main/resources/templates/thymeleafEx/thymeleafEx04.html",
    "content": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n</head>\n<body>\n\n<h1>상품 리스트 출력 예제</h1>\n\n<table border=\"1\">\n    <thead>\n    <tr>\n        <td>순번</td>\n        <td>상품명</td>\n        <td>상품설명</td>\n        <td>가격</td>\n        <td>상품등록일</td>\n    </tr>\n    </thead>\n    <tbody>\n    <tr th:each=\"itemDto, status: ${itemDtoList}\">\n        <td th:switch=\"${status.even}\">\n            <span th:case=true>짝수</span>\n            <span th:case=false>홀수</span>\n        </td>\n        <td th:text=\"${itemDto.itemNm}\"></td>\n        <td th:text=\"${itemDto.itemDetail}\"></td>\n        <td th:text=\"${itemDto.price}\"></td>\n        <td th:text=\"${itemDto.regTime}\"></td>\n    </tr>\n    </tbody>\n</table>\n\n</body>\n</html>"
  },
  {
    "path": "src/main/resources/templates/thymeleafEx/thymeleafEx05.html",
    "content": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n</head>\n<body>\n    <h1>Thymeleaf 링크처리 예제 페이지</h1>\n    <div>\n        <a th:href=\"@{/thymeleaf/ex01}\">예제1 페이지 이동</a>\n    </div>\n    <div>\n        <a th:href=\"@{https://www.thymeleaf.org/}\">thymeleaf 공식 페이지 이동</a>\n    </div>\n    <div>\n        <a th:href=\"@{/thymeleaf/ex06(param1 = '파라미터 데이터1', param2 = '파라미터 데이터2')}\">thymeleaf 파라미터 전달</a>\n    </div>\n</body>\n</html>"
  },
  {
    "path": "src/main/resources/templates/thymeleafEx/thymeleafEx06.html",
    "content": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n</head>\n<body>\n    <h1>파라미터 전달 예제</h1>\n    <div th:text=\"${param1}\"></div>\n    <div th:text=\"${param2}\"></div>\n</body>\n</html>"
  },
  {
    "path": "src/main/resources/templates/thymeleafEx/thymeleafEx07.html",
    "content": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\"\n      xmlns:layout=http://www.ultraq.net.nz/thymeleaf/layout\n      layout:decorate=\"~{layouts/layout1}\">\n\n<div layout:fragment=\"content\">\n    본문 영역 입니다.\n</div>\n\n</html>"
  },
  {
    "path": "src/test/java/com/shop/ShopApplicationTests.java",
    "content": "package com.shop;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.context.SpringBootTest;\n\n@SpringBootTest\nclass ShopApplicationTests {\n\n\t@Test\n\tvoid contextLoads() {\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/com/shop/controller/ItemControllerTest.java",
    "content": "package com.shop.controller;\n\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.security.test.context.support.WithMockUser;\nimport org.springframework.test.context.TestPropertySource;\nimport org.springframework.test.web.servlet.MockMvc;\nimport org.springframework.test.web.servlet.request.MockMvcRequestBuilders;\n\nimport static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;\n\n@SpringBootTest\n@AutoConfigureMockMvc\n@TestPropertySource(locations=\"classpath:application-test.properties\")\nclass ItemControllerTest {\n\n    @Autowired\n    MockMvc mockMvc;\n\n    @Test\n    @DisplayName(\"상품 등록 페이지 권한 테스트\")\n    @WithMockUser(username = \"admin\", roles = \"ADMIN\")\n    public void itemFormTest() throws Exception{\n        mockMvc.perform(MockMvcRequestBuilders.get(\"/admin/item/new\"))\n                .andDo(print())\n                .andExpect(status().isOk());\n    }\n\n    @Test\n    @DisplayName(\"상품 등록 페이지 일반 회원 접근 테스트\")\n    @WithMockUser(username = \"user\", roles = \"USER\")\n    public void itemFormNotAdminTest() throws Exception{\n        mockMvc.perform(MockMvcRequestBuilders.get(\"/admin/item/new\"))\n                .andDo(print())\n                .andExpect(status().isForbidden());\n    }\n}"
  },
  {
    "path": "src/test/java/com/shop/controller/MemberControllerTest.java",
    "content": "package com.shop.controller;\n\nimport com.shop.dto.MemberFormDto;\nimport com.shop.entity.Member;\nimport com.shop.service.MemberService;\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;\n\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.security.crypto.password.PasswordEncoder;\nimport org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers;\nimport org.springframework.test.context.TestPropertySource;\nimport org.springframework.test.web.servlet.MockMvc;\nimport org.springframework.transaction.annotation.Transactional;\nimport static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;\n\n@SpringBootTest\n@AutoConfigureMockMvc\n@Transactional\n@TestPropertySource(locations=\"classpath:application-test.properties\")\nclass MemberControllerTest {\n\n    @Autowired\n    private MemberService memberService;\n\n    @Autowired\n    private MockMvc mockMvc;\n\n    @Autowired\n    PasswordEncoder passwordEncoder;\n\n    public Member createMember(String email, String password){\n        MemberFormDto memberFormDto = new MemberFormDto();\n        memberFormDto.setEmail(email);\n        memberFormDto.setName(\"홍길동\");\n        memberFormDto.setAddress(\"서울시 마포구 합정동\");\n        memberFormDto.setPassword(password);\n        Member member = Member.createMember(memberFormDto, passwordEncoder);\n        return memberService.saveMember(member);\n    }\n\n    @Test\n    @DisplayName(\"로그인 성공 테스트\")\n    public void loginSuccessTest() throws Exception{\n        String email = \"test@email.com\";\n        String password = \"1234\";\n        this.createMember(email, password);\n        mockMvc.perform(formLogin().userParameter(\"email\")\n                .loginProcessingUrl(\"/members/login\")\n                .user(email).password(password))\n                .andExpect(SecurityMockMvcResultMatchers.authenticated());\n    }\n\n    @Test\n    @DisplayName(\"로그인 실패 테스트\")\n    public void loginFailTest() throws Exception{\n        String email = \"test@email.com\";\n        String password = \"1234\";\n        this.createMember(email, password);\n        mockMvc.perform(formLogin().userParameter(\"email\")\n                .loginProcessingUrl(\"/members/login\")\n                .user(email).password(\"12345\"))\n                .andExpect(SecurityMockMvcResultMatchers.unauthenticated());\n    }\n\n}"
  },
  {
    "path": "src/test/java/com/shop/entity/CartTest.java",
    "content": "package com.shop.entity;\n\nimport com.shop.dto.MemberFormDto;\nimport com.shop.repository.CartRepository;\nimport com.shop.repository.MemberRepository;\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.security.crypto.password.PasswordEncoder;\nimport org.springframework.test.context.TestPropertySource;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport jakarta.persistence.EntityManager;\nimport jakarta.persistence.EntityNotFoundException;\nimport jakarta.persistence.PersistenceContext;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@SpringBootTest\n@Transactional\n@TestPropertySource(locations=\"classpath:application-test.properties\")\n\nclass CartTest {\n\n    @Autowired\n    CartRepository cartRepository;\n\n    @Autowired\n    MemberRepository memberRepository;\n\n    @Autowired\n    PasswordEncoder passwordEncoder;\n\n    @PersistenceContext\n    EntityManager em;\n\n    public Member createMember(){\n        MemberFormDto memberFormDto = new MemberFormDto();\n        memberFormDto.setEmail(\"test@email.com\");\n        memberFormDto.setName(\"홍길동\");\n        memberFormDto.setAddress(\"서울시 마포구 합정동\");\n        memberFormDto.setPassword(\"1234\");\n        return Member.createMember(memberFormDto, passwordEncoder);\n    }\n\n    @Test\n    @DisplayName(\"장바구니 회원 엔티티 매핑 조회 테스트\")\n    public void findCartAndMemberTest(){\n        Member member = createMember();\n        memberRepository.save(member);\n        Cart cart = new Cart();\n        cart.setMember(member);\n        cartRepository.save(cart);\n\n        em.flush();\n        em.clear();\n\n        Cart savedCart = cartRepository.findById(cart.getId())\n                .orElseThrow(EntityNotFoundException::new);\n        assertEquals(savedCart.getMember().getId(), member.getId());\n    }\n\n}"
  },
  {
    "path": "src/test/java/com/shop/entity/MemberTest.java",
    "content": "package com.shop.entity;\n\nimport com.shop.repository.MemberRepository;\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.security.test.context.support.WithMockUser;\nimport org.springframework.test.context.TestPropertySource;\nimport org.springframework.transaction.annotation.Transactional;\nimport jakarta.persistence.EntityManager;\nimport jakarta.persistence.EntityNotFoundException;\nimport jakarta.persistence.PersistenceContext;\n\n@SpringBootTest\n@Transactional\n@TestPropertySource(locations=\"classpath:application-test.properties\")\npublic class MemberTest {\n\n    @Autowired\n    MemberRepository memberRepository;\n\n    @PersistenceContext\n    EntityManager em;\n\n    @Test\n    @DisplayName(\"Auditing 테스트\")\n    @WithMockUser(username = \"gildong\", roles = \"USER\")\n    public void auditingTest(){\n        Member newMember = new Member();\n        memberRepository.save(newMember);\n\n        em.flush();\n        em.clear();\n\n        Member member = memberRepository.findById(newMember.getId())\n                .orElseThrow(EntityNotFoundException::new);\n\n        System.out.println(\"register time : \" + member.getRegTime());\n        System.out.println(\"update time : \" + member.getUpdateTime());\n        System.out.println(\"create member : \" + member.getCreatedBy());\n        System.out.println(\"modify member : \" + member.getModifiedBy());\n    }\n\n}"
  },
  {
    "path": "src/test/java/com/shop/entity/OrderTest.java",
    "content": "package com.shop.entity;\n\nimport com.shop.constant.ItemSellStatus;\nimport com.shop.repository.ItemRepository;\nimport com.shop.repository.OrderRepository;\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.TestPropertySource;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport jakarta.persistence.EntityManager;\nimport jakarta.persistence.EntityNotFoundException;\nimport jakarta.persistence.PersistenceContext;\nimport java.time.LocalDateTime;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport com.shop.repository.MemberRepository;\nimport com.shop.repository.OrderItemRepository;\n\n@SpringBootTest\n@TestPropertySource(locations=\"classpath:application-test.properties\")\n@Transactional\nclass OrderTest {\n\n    @Autowired\n    OrderRepository orderRepository;\n\n    @Autowired\n    ItemRepository itemRepository;\n\n    @PersistenceContext\n    EntityManager em;\n\n    @Autowired\n    MemberRepository memberRepository;\n\n    @Autowired\n    OrderItemRepository orderItemRepository;\n\n    public Item createItem(){\n        Item item = new Item();\n        item.setItemNm(\"테스트 상품\");\n        item.setPrice(10000);\n        item.setItemDetail(\"상세설명\");\n        item.setItemSellStatus(ItemSellStatus.SELL);\n        item.setStockNumber(100);\n        item.setRegTime(LocalDateTime.now());\n\n        item.setUpdateTime(LocalDateTime.now());\n        return item;\n    }\n\n    @Test\n    @DisplayName(\"영속성 전이 테스트\")\n    public void cascadeTest() {\n\n        Order order = new Order();\n\n        for(int i=0;i<3;i++){\n            Item item = this.createItem();\n            itemRepository.save(item);\n            OrderItem orderItem = new OrderItem();\n            orderItem.setItem(item);\n            orderItem.setCount(10);\n            orderItem.setOrderPrice(1000);\n            orderItem.setOrder(order);\n            order.getOrderItems().add(orderItem);\n        }\n\n        orderRepository.saveAndFlush(order);\n        em.clear();\n\n        Order savedOrder = orderRepository.findById(order.getId())\n                .orElseThrow(EntityNotFoundException::new);\n        assertEquals(3, savedOrder.getOrderItems().size());\n    }\n\n    public Order createOrder(){\n        Order order = new Order();\n        for(int i=0;i<3;i++){\n            Item item = createItem();\n            itemRepository.save(item);\n            OrderItem orderItem = new OrderItem();\n            orderItem.setItem(item);\n            orderItem.setCount(10);\n            orderItem.setOrderPrice(1000);\n            orderItem.setOrder(order);\n            order.getOrderItems().add(orderItem);\n        }\n        Member member = new Member();\n        memberRepository.save(member);\n        order.setMember(member);\n        orderRepository.save(order);\n        return order;\n    }\n\n    @Test\n    @DisplayName(\"고아객체 제거 테스트\")\n    public void orphanRemovalTest(){\n        Order order = this.createOrder();\n        order.getOrderItems().remove(0);\n        em.flush();\n    }\n\n    @Test\n    @DisplayName(\"지연 로딩 테스트\")\n    public void lazyLoadingTest(){\n        Order order = this.createOrder();\n        Long orderItemId = order.getOrderItems().get(0).getId();\n        em.flush();\n        em.clear();\n        OrderItem orderItem = orderItemRepository.findById(orderItemId)\n                .orElseThrow(EntityNotFoundException::new);\n        System.out.println(\"Order class : \" + orderItem.getOrder().getClass());\n        System.out.println(\"===========================\");\n        orderItem.getOrder().getOrderDate();\n        System.out.println(\"===========================\");\n    }\n\n}"
  },
  {
    "path": "src/test/java/com/shop/repository/ItemRepositoryTest.java",
    "content": "package com.shop.repository;\n\nimport com.shop.constant.ItemSellStatus;\nimport com.shop.entity.Item;\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.TestPropertySource;\n\nimport java.time.LocalDateTime;\n\nimport java.util.List;\n\nimport com.querydsl.jpa.impl.JPAQueryFactory;\nimport com.querydsl.jpa.impl.JPAQuery;\nimport com.shop.entity.QItem;\nimport jakarta.persistence.PersistenceContext;\nimport jakarta.persistence.EntityManager;\n\nimport com.querydsl.core.BooleanBuilder;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.PageRequest;\nimport org.springframework.data.domain.Pageable;\nimport org.thymeleaf.util.StringUtils;\n\n@SpringBootTest\n@TestPropertySource(locations=\"classpath:application-test.properties\")\nclass ItemRepositoryTest {\n\n    @Autowired\n    ItemRepository itemRepository;\n\n    @PersistenceContext\n    EntityManager em;\n\n    @Test\n    @DisplayName(\"상품 저장 테스트\")\n    public void createItemTest(){\n        Item item = new Item();\n        item.setItemNm(\"테스트 상품\");\n        item.setPrice(10000);\n        item.setItemDetail(\"테스트 상품 상세 설명\");\n        item.setItemSellStatus(ItemSellStatus.SELL);\n        item.setStockNumber(100);\n        item.setRegTime(LocalDateTime.now());\n        item.setUpdateTime(LocalDateTime.now());\n        Item savedItem = itemRepository.save(item);\n        System.out.println(savedItem.toString());\n    }\n\n    public void createItemList(){\n        for(int i=1;i<=10;i++){\n            Item item = new Item();\n            item.setItemNm(\"테스트 상품\" + i);\n            item.setPrice(10000 + i);\n            item.setItemDetail(\"테스트 상품 상세 설명\" + i);\n            item.setItemSellStatus(ItemSellStatus.SELL);\n            item.setStockNumber(100); item.setRegTime(LocalDateTime.now());\n            item.setUpdateTime(LocalDateTime.now());\n            Item savedItem = itemRepository.save(item);\n        }\n    }\n\n    @Test\n    @DisplayName(\"상품명 조회 테스트\")\n    public void findByItemNmTest(){\n        this.createItemList();\n        List<Item> itemList = itemRepository.findByItemNm(\"테스트 상품1\");\n        for(Item item : itemList){\n            System.out.println(item.toString());\n        }\n    }\n\n    @Test\n    @DisplayName(\"상품명, 상품상세설명 or 테스트\")\n    public void findByItemNmOrItemDetailTest(){\n        this.createItemList();\n        List<Item> itemList = itemRepository.findByItemNmOrItemDetail(\"테스트 상품1\", \"테스트 상품 상세 설명5\");\n        for(Item item : itemList){\n            System.out.println(item.toString());\n        }\n    }\n\n    @Test\n    @DisplayName(\"가격 LessThan 테스트\")\n    public void findByPriceLessThanTest(){\n        this.createItemList();\n        List<Item> itemList = itemRepository.findByPriceLessThan(10005);\n        for(Item item : itemList){\n            System.out.println(item.toString());\n        }\n    }\n\n    @Test\n    @DisplayName(\"가격 내림차순 조회 테스트\")\n    public void findByPriceLessThanOrderByPriceDesc(){\n        this.createItemList();\n        List<Item> itemList = itemRepository.findByPriceLessThanOrderByPriceDesc(10005);\n        for(Item item : itemList){\n            System.out.println(item.toString());\n        }\n    }\n\n    @Test\n    @DisplayName(\"@Query를 이용한 상품 조회 테스트\")\n    public void findByItemDetailTest(){\n        this.createItemList();\n        List<Item> itemList = itemRepository.findByItemDetail(\"테스트 상품 상세 설명\");\n        for(Item item : itemList){\n            System.out.println(item.toString());\n        }\n    }\n\n    @Test\n    @DisplayName(\"Querydsl 조회 테스트1\")\n    public void queryDslTest(){\n        this.createItemList();\n        JPAQueryFactory queryFactory = new JPAQueryFactory(em);\n        QItem qItem = QItem.item;\n        JPAQuery<Item> query  = queryFactory.selectFrom(qItem)\n                .where(qItem.itemSellStatus.eq(ItemSellStatus.SELL))\n                .where(qItem.itemDetail.like(\"%\" + \"테스트 상품 상세 설명\" + \"%\"))\n                .orderBy(qItem.price.desc());\n\n        List<Item> itemList = query.fetch();\n\n        for(Item item : itemList){\n            System.out.println(item.toString());\n        }\n    }\n\n    public void createItemList2(){\n        for(int i=1;i<=5;i++){\n            Item item = new Item();\n            item.setItemNm(\"테스트 상품\" + i);\n            item.setPrice(10000 + i);\n            item.setItemDetail(\"테스트 상품 상세 설명\" + i);\n            item.setItemSellStatus(ItemSellStatus.SELL);\n            item.setStockNumber(100);\n            item.setRegTime(LocalDateTime.now());\n            item.setUpdateTime(LocalDateTime.now());\n            itemRepository.save(item);\n        }\n\n        for(int i=6;i<=10;i++){\n            Item item = new Item();\n            item.setItemNm(\"테스트 상품\" + i);\n            item.setPrice(10000 + i);\n            item.setItemDetail(\"테스트 상품 상세 설명\" + i);\n            item.setItemSellStatus(ItemSellStatus.SOLD_OUT);\n            item.setStockNumber(0);\n            item.setRegTime(LocalDateTime.now());\n            item.setUpdateTime(LocalDateTime.now());\n            itemRepository.save(item);\n        }\n    }\n\n    @Test\n    @DisplayName(\"상품 Querydsl 조회 테스트 2\")\n    public void queryDslTest2(){\n\n        this.createItemList2();\n\n        BooleanBuilder booleanBuilder = new BooleanBuilder();\n        QItem item = QItem.item;\n        String itemDetail = \"테스트 상품 상세 설명\";\n        int price = 10003;\n        String itemSellStat = \"SELL\";\n\n        booleanBuilder.and(item.itemDetail.like(\"%\" + itemDetail + \"%\"));\n        booleanBuilder.and(item.price.gt(price));\n        System.out.println(ItemSellStatus.SELL);\n        if(StringUtils.equals(itemSellStat, ItemSellStatus.SELL)){\n            booleanBuilder.and(item.itemSellStatus.eq(ItemSellStatus.SELL));\n        }\n\n        Pageable pageable = PageRequest.of(0, 5);\n        Page<Item> itemPagingResult = itemRepository.findAll(booleanBuilder, pageable);\n        System.out.println(\"total elements : \" + itemPagingResult. getTotalElements ());\n\n        List<Item> resultItemList = itemPagingResult.getContent();\n        for(Item resultItem: resultItemList){\n            System.out.println(resultItem.toString());\n        }\n    }\n\n}"
  },
  {
    "path": "src/test/java/com/shop/service/CartServiceTest.java",
    "content": "package com.shop.service;\n\nimport com.shop.constant.ItemSellStatus;\nimport com.shop.dto.CartItemDto;\nimport com.shop.entity.CartItem;\nimport com.shop.entity.Item;\nimport com.shop.entity.Member;\nimport com.shop.repository.CartItemRepository;\nimport com.shop.repository.ItemRepository;\nimport com.shop.repository.MemberRepository;\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.TestPropertySource;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport jakarta.persistence.EntityNotFoundException;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@SpringBootTest\n@Transactional\n@TestPropertySource(locations=\"classpath:application-test.properties\")\nclass CartServiceTest {\n\n    @Autowired\n    ItemRepository itemRepository;\n\n    @Autowired\n    MemberRepository memberRepository;\n\n    @Autowired\n    CartService cartService;\n\n    @Autowired\n    CartItemRepository cartItemRepository;\n\n    public Item saveItem(){\n        Item item = new Item();\n        item.setItemNm(\"테스트 상품\");\n        item.setPrice(10000);\n        item.setItemDetail(\"테스트 상품 상세 설명\");\n        item.setItemSellStatus(ItemSellStatus.SELL);\n        item.setStockNumber(100);\n        return itemRepository.save(item);\n    }\n\n    public Member saveMember(){\n        Member member = new Member();\n        member.setEmail(\"test@test.com\");\n        return memberRepository.save(member);\n    }\n\n    @Test\n    @DisplayName(\"장바구니 담기 테스트\")\n    public void addCart(){\n        Item item = saveItem();\n        Member member = saveMember();\n\n        CartItemDto cartItemDto = new CartItemDto();\n        cartItemDto.setCount(5);\n        cartItemDto.setItemId(item.getId());\n\n        Long cartItemId = cartService.addCart(cartItemDto, member.getEmail());\n        CartItem cartItem = cartItemRepository.findById(cartItemId)\n                .orElseThrow(EntityNotFoundException::new);\n\n        assertEquals(item.getId(), cartItem.getItem().getId());\n        assertEquals(cartItemDto.getCount(), cartItem.getCount());\n    }\n\n}"
  },
  {
    "path": "src/test/java/com/shop/service/ItemServiceTest.java",
    "content": "package com.shop.service;\n\nimport com.shop.constant.ItemSellStatus;\nimport com.shop.dto.ItemFormDto;\nimport com.shop.entity.Item;\nimport com.shop.entity.ItemImg;\nimport com.shop.repository.ItemImgRepository;\nimport com.shop.repository.ItemRepository;\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.mock.web.MockMultipartFile;\nimport org.springframework.security.test.context.support.WithMockUser;\nimport org.springframework.test.context.TestPropertySource;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.web.multipart.MultipartFile;\nimport jakarta.persistence.EntityNotFoundException;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@SpringBootTest\n@Transactional\n@TestPropertySource(locations=\"classpath:application-test.properties\")\nclass ItemServiceTest {\n\n    @Autowired\n    ItemService itemService;\n\n    @Autowired\n    ItemRepository itemRepository;\n\n    @Autowired\n    ItemImgRepository itemImgRepository;\n\n    List<MultipartFile> createMultipartFiles() throws Exception{\n\n        List<MultipartFile> multipartFileList = new ArrayList<>();\n\n        for(int i=0;i<5;i++){\n            String path = \"C:/shop/item/\";\n            String imageName = \"image\" + i + \".jpg\";\n            MockMultipartFile multipartFile =\n                    new MockMultipartFile(path, imageName, \"image/jpg\", new byte[]{1,2,3,4});\n            multipartFileList.add(multipartFile);\n        }\n\n        return multipartFileList;\n    }\n\n    @Test\n    @DisplayName(\"상품 등록 테스트\")\n    @WithMockUser(username = \"admin\", roles = \"ADMIN\")\n    void saveItem() throws Exception {\n        ItemFormDto itemFormDto = new ItemFormDto();\n        itemFormDto.setItemNm(\"테스트상품\");\n        itemFormDto.setItemSellStatus(ItemSellStatus.SELL);\n        itemFormDto.setItemDetail(\"테스트 상품 입니다.\");\n        itemFormDto.setPrice(1000);\n        itemFormDto.setStockNumber(100);\n\n        List<MultipartFile> multipartFileList = createMultipartFiles();\n        Long itemId = itemService.saveItem(itemFormDto, multipartFileList);\n        List<ItemImg> itemImgList = itemImgRepository.findByItemIdOrderByIdAsc(itemId);\n\n        Item item = itemRepository.findById(itemId)\n                .orElseThrow(EntityNotFoundException::new);\n\n        assertEquals(itemFormDto.getItemNm(), item.getItemNm());\n        assertEquals(itemFormDto.getItemSellStatus(), item.getItemSellStatus());\n        assertEquals(itemFormDto.getItemDetail(), item.getItemDetail());\n        assertEquals(itemFormDto.getPrice(), item.getPrice());\n        assertEquals(itemFormDto.getStockNumber(), item.getStockNumber());\n        assertEquals(multipartFileList.get(0).getOriginalFilename(), itemImgList.get(0).getOriImgName());\n    }\n\n}"
  },
  {
    "path": "src/test/java/com/shop/service/MemberServiceTest.java",
    "content": "package com.shop.service;\n\nimport com.shop.dto.MemberFormDto;\nimport com.shop.entity.Member;\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.security.crypto.password.PasswordEncoder;\nimport org.springframework.test.context.TestPropertySource;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\n@SpringBootTest\n@Transactional\n@TestPropertySource(locations=\"classpath:application-test.properties\")\nclass MemberServiceTest {\n\n    @Autowired\n    MemberService memberService;\n\n    @Autowired\n    PasswordEncoder passwordEncoder;\n\n    public Member createMember(){\n        MemberFormDto memberFormDto = new MemberFormDto();\n        memberFormDto.setEmail(\"test@email.com\");\n        memberFormDto.setName(\"홍길동\");\n        memberFormDto.setAddress(\"서울시 마포구 합정동\");\n        memberFormDto.setPassword(\"1234\");\n        return Member.createMember(memberFormDto, passwordEncoder);\n    }\n\n    @Test\n    @DisplayName(\"회원가입 테스트\")\n    public void saveMemberTest(){\n        Member member = createMember();\n        Member savedMember = memberService.saveMember(member);\n        assertEquals(member.getEmail(), savedMember.getEmail());\n        assertEquals(member.getName(), savedMember.getName());\n        assertEquals(member.getAddress(), savedMember.getAddress());\n        assertEquals(member.getPassword(), savedMember.getPassword());\n        assertEquals(member.getRole(), savedMember.getRole());\n    }\n\n    @Test\n    @DisplayName(\"중복 회원 가입 테스트\")\n    public void saveDuplicateMemberTest(){\n        Member member1 = createMember();\n        Member member2 = createMember();\n        memberService.saveMember(member1);\n        Throwable e = assertThrows(IllegalStateException.class, () -> {\n            memberService.saveMember(member2);});\n        assertEquals(\"이미 가입된 회원입니다.\", e.getMessage());\n    }\n}"
  },
  {
    "path": "src/test/java/com/shop/service/OrderServiceTest.java",
    "content": "package com.shop.service;\n\nimport com.shop.constant.ItemSellStatus;\nimport com.shop.constant.OrderStatus;\nimport com.shop.dto.OrderDto;\nimport com.shop.entity.Item;\nimport com.shop.entity.Member;\nimport com.shop.entity.Order;\nimport com.shop.entity.OrderItem;\nimport com.shop.repository.ItemRepository;\nimport com.shop.repository.MemberRepository;\nimport com.shop.repository.OrderRepository;\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.TestPropertySource;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport jakarta.persistence.EntityNotFoundException;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@SpringBootTest\n@Transactional\n@TestPropertySource(locations=\"classpath:application-test.properties\")\nclass OrderServiceTest {\n\n    @Autowired\n    private OrderService orderService;\n\n    @Autowired\n    private OrderRepository orderRepository;\n\n    @Autowired\n    ItemRepository itemRepository;\n\n    @Autowired\n    MemberRepository memberRepository;\n\n    public Item saveItem(){\n        Item item = new Item();\n        item.setItemNm(\"테스트 상품\");\n        item.setPrice(10000);\n        item.setItemDetail(\"테스트 상품 상세 설명\");\n        item.setItemSellStatus(ItemSellStatus.SELL);\n        item.setStockNumber(100);\n        return itemRepository.save(item);\n    }\n\n    public Member saveMember(){\n        Member member = new Member();\n        member.setEmail(\"test@test.com\");\n        return memberRepository.save(member);\n\n    }\n\n    @Test\n    @DisplayName(\"주문 테스트\")\n    public void order(){\n        Item item = saveItem();\n        Member member = saveMember();\n\n        OrderDto orderDto = new OrderDto();\n        orderDto.setCount(10);\n        orderDto.setItemId(item.getId());\n\n        Long orderId = orderService.order(orderDto, member.getEmail());\n        Order order = orderRepository.findById(orderId)\n                .orElseThrow(EntityNotFoundException::new);\n\n        List<OrderItem> orderItems = order.getOrderItems();\n\n        int totalPrice = orderDto.getCount()*item.getPrice();\n\n        assertEquals(totalPrice, order.getTotalPrice());\n    }\n\n    @Test\n    @DisplayName(\"주문 취소 테스트\")\n    public void cancelOrder(){\n        Item item = saveItem();\n        Member member = saveMember();\n\n        OrderDto orderDto = new OrderDto();\n        orderDto.setCount(10);\n        orderDto.setItemId(item.getId());\n        Long orderId = orderService.order(orderDto, member.getEmail());\n\n        Order order = orderRepository.findById(orderId)\n                .orElseThrow(EntityNotFoundException::new);\n        orderService.cancelOrder(orderId);\n\n        assertEquals(OrderStatus.CANCEL, order.getOrderStatus());\n        assertEquals(100, item.getStockNumber());\n    }\n\n}"
  }
]