Full Code of roadbook2/shop for AI

master 175815c81986 cached
94 files
162.0 KB
41.2k tokens
210 symbols
1 requests
Download .txt
Repository: roadbook2/shop
Branch: master
Commit: 175815c81986
Files: 94
Total size: 162.0 KB

Directory structure:
gitextract_kfsect3j/

├── .gitignore
├── .mvn/
│   └── wrapper/
│       ├── MavenWrapperDownloader.java
│       ├── maven-wrapper.jar
│       └── maven-wrapper.properties
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src/
    ├── main/
    │   ├── java/
    │   │   └── com/
    │   │       └── shop/
    │   │           ├── ShopApplication.java
    │   │           ├── config/
    │   │           │   ├── AuditConfig.java
    │   │           │   ├── AuditorAwareImpl.java
    │   │           │   ├── CsrfCookieFilter.java
    │   │           │   ├── CustomAuthenticationEntryPoint.java
    │   │           │   ├── FormLoginAuthenticationFailureHandler.java
    │   │           │   ├── SecurityConfig.java
    │   │           │   └── WebMvcConfig.java
    │   │           ├── constant/
    │   │           │   ├── ItemSellStatus.java
    │   │           │   ├── OrderStatus.java
    │   │           │   └── Role.java
    │   │           ├── controller/
    │   │           │   ├── CartController.java
    │   │           │   ├── ItemController.java
    │   │           │   ├── MainController.java
    │   │           │   ├── MemberController.java
    │   │           │   ├── OrderController.java
    │   │           │   └── ThymeleafExController.java
    │   │           ├── dto/
    │   │           │   ├── CartDetailDto.java
    │   │           │   ├── CartItemDto.java
    │   │           │   ├── CartOrderDto.java
    │   │           │   ├── ItemDto.java
    │   │           │   ├── ItemFormDto.java
    │   │           │   ├── ItemImgDto.java
    │   │           │   ├── ItemSearchDto.java
    │   │           │   ├── MainItemDto.java
    │   │           │   ├── MemberFormDto.java
    │   │           │   ├── OrderDto.java
    │   │           │   ├── OrderHistDto.java
    │   │           │   └── OrderItemDto.java
    │   │           ├── entity/
    │   │           │   ├── BaseEntity.java
    │   │           │   ├── BaseTimeEntity.java
    │   │           │   ├── Cart.java
    │   │           │   ├── CartItem.java
    │   │           │   ├── Item.java
    │   │           │   ├── ItemImg.java
    │   │           │   ├── Member.java
    │   │           │   ├── Order.java
    │   │           │   └── OrderItem.java
    │   │           ├── exception/
    │   │           │   └── OutOfStockException.java
    │   │           ├── repository/
    │   │           │   ├── CartItemRepository.java
    │   │           │   ├── CartRepository.java
    │   │           │   ├── ItemImgRepository.java
    │   │           │   ├── ItemRepository.java
    │   │           │   ├── ItemRepositoryCustom.java
    │   │           │   ├── ItemRepositoryCustomImpl.java
    │   │           │   ├── MemberRepository.java
    │   │           │   ├── OrderItemRepository.java
    │   │           │   └── OrderRepository.java
    │   │           └── service/
    │   │               ├── CartService.java
    │   │               ├── FileService.java
    │   │               ├── ItemImgService.java
    │   │               ├── ItemService.java
    │   │               ├── MemberService.java
    │   │               └── OrderService.java
    │   └── resources/
    │       ├── application-test.properties
    │       ├── application.properties
    │       ├── static/
    │       │   └── css/
    │       │       └── layout1.css
    │       └── templates/
    │           ├── cart/
    │           │   └── cartList.html
    │           ├── fragments/
    │           │   ├── footer.html
    │           │   └── header.html
    │           ├── item/
    │           │   ├── itemDtl.html
    │           │   ├── itemForm.html
    │           │   └── itemMng.html
    │           ├── layouts/
    │           │   └── layout1.html
    │           ├── main.html
    │           ├── member/
    │           │   ├── memberForm.html
    │           │   └── memberLoginForm.html
    │           ├── order/
    │           │   └── orderHist.html
    │           └── thymeleafEx/
    │               ├── thymeleafEx01.html
    │               ├── thymeleafEx02.html
    │               ├── thymeleafEx03.html
    │               ├── thymeleafEx04.html
    │               ├── thymeleafEx05.html
    │               ├── thymeleafEx06.html
    │               └── thymeleafEx07.html
    └── test/
        └── java/
            └── com/
                └── shop/
                    ├── ShopApplicationTests.java
                    ├── controller/
                    │   ├── ItemControllerTest.java
                    │   └── MemberControllerTest.java
                    ├── entity/
                    │   ├── CartTest.java
                    │   ├── MemberTest.java
                    │   └── OrderTest.java
                    ├── repository/
                    │   └── ItemRepositoryTest.java
                    └── service/
                        ├── CartServiceTest.java
                        ├── ItemServiceTest.java
                        ├── MemberServiceTest.java
                        └── OrderServiceTest.java

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/

### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache

### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/

### VS Code ###
.vscode/


================================================
FILE: .mvn/wrapper/MavenWrapperDownloader.java
================================================
/*
 * Copyright 2007-present the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Properties;

public class MavenWrapperDownloader {

    private static final String WRAPPER_VERSION = "0.5.6";
    /**
     * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
     */
    private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
        + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";

    /**
     * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
     * use instead of the default one.
     */
    private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
            ".mvn/wrapper/maven-wrapper.properties";

    /**
     * Path where the maven-wrapper.jar will be saved to.
     */
    private static final String MAVEN_WRAPPER_JAR_PATH =
            ".mvn/wrapper/maven-wrapper.jar";

    /**
     * Name of the property which should be used to override the default download url for the wrapper.
     */
    private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";

    public static void main(String args[]) {
        System.out.println("- Downloader started");
        File baseDirectory = new File(args[0]);
        System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());

        // If the maven-wrapper.properties exists, read it and check if it contains a custom
        // wrapperUrl parameter.
        File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
        String url = DEFAULT_DOWNLOAD_URL;
        if(mavenWrapperPropertyFile.exists()) {
            FileInputStream mavenWrapperPropertyFileInputStream = null;
            try {
                mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
                Properties mavenWrapperProperties = new Properties();
                mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
                url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
            } catch (IOException e) {
                System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
            } finally {
                try {
                    if(mavenWrapperPropertyFileInputStream != null) {
                        mavenWrapperPropertyFileInputStream.close();
                    }
                } catch (IOException e) {
                    // Ignore ...
                }
            }
        }
        System.out.println("- Downloading from: " + url);

        File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
        if(!outputFile.getParentFile().exists()) {
            if(!outputFile.getParentFile().mkdirs()) {
                System.out.println(
                        "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
            }
        }
        System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
        try {
            downloadFileFromURL(url, outputFile);
            System.out.println("Done");
            System.exit(0);
        } catch (Throwable e) {
            System.out.println("- Error downloading");
            e.printStackTrace();
            System.exit(1);
        }
    }

    private static void downloadFileFromURL(String urlString, File destination) throws Exception {
        if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
            String username = System.getenv("MVNW_USERNAME");
            char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
            Authenticator.setDefault(new Authenticator() {
                @Override
                protected PasswordAuthentication getPasswordAuthentication() {
                    return new PasswordAuthentication(username, password);
                }
            });
        }
        URL website = new URL(urlString);
        ReadableByteChannel rbc;
        rbc = Channels.newChannel(website.openStream());
        FileOutputStream fos = new FileOutputStream(destination);
        fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
        fos.close();
        rbc.close();
    }

}


================================================
FILE: .mvn/wrapper/maven-wrapper.properties
================================================
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar


================================================
FILE: README.md
================================================
# 1.변경사항
- 스프링 시큐리티 설정 수정 (2025-02-13)
  - 애플리케이션 기동 후 첫 post에서 csrf 토큰이 생성되지 않는 문제 발견
  - CsrfCookieFilter에서 csrfToken.getToken() 을 호출하여 csrf 토큰이 없는 경우 강제로 생성
  - SecurityConfig에 CsrfCookieFilter 추가
- 스프링 시큐리티 설정 수정 (2025-02-03)
  - 로그인 후 redirect 오류로 인한 SecurityConfig -> defaultSucceeUrl 항상 "/"로 가도록 true 설정 
  - SecurityConfig에서 csrfTokenRepository로 CookieCsrfTokenRepository 를 사용하도록 수정. 
    -> 디폴트로 HttpSessionCsrfTokenRepository를 사용하는데 세션이 생성되기 전에 csrf가 필요한 페이지 접근 시 csrf 값을 가져올 수 없는 문제가 있음
    -> CookieCsrfTokenRepository를 사용하여 http only cookie에 csrf 토큰을 저장하도록 수정 
- 스프링 시큐리티 설정 수정 (2025-01-26)
  - 상품 상세 페이지에서 order() 실행 시 비로그인 상태의 경우 http status 401로 응답이 되지 않는 버그 발견
  - CustomAuthenticationEntryPoint 클래스 신규 생성 및 SecurityConfig 설정 추가
  - CustomAuthenticationFailureHandler -> FormLoginAuthenticationFailureHandler 클래스명 리팩토링 진행
- 스프링부트3(2024-08-17) branch
  - 스프링부트 3.3.2 버전으로 업데이트
  - 스프링시큐리티 설정 수정 (SecurityConfig, CustomAuthenticationEntryPoint -> CustomAuthenticationFailureHandler 수정)
  - thymeleaf-layout-dialect 3.2.1 -> 3.3.0 버전업 진행
- 스프링부트3(2023-07-17) branch
  - 스프링부트 3.1.1 버전으로 업데이트
  - 스프링부트3 버전부터는 자바 17버전 이상을 사용해야합니다.
  - pom.xml 버전 정보가 많이 바뀌었습니다. 최신 pom.xml 버전을 참고해주세요
  - javax에서 jakarta로 변경됨에 따라서 많은 import 들이 jakarta로 수정되었습니다.
    - javax.validation => jakarta.validation
    - javax.persistence => jakarta.persistence
    - CustomAuthenticationEntryPoint.java (javax.servlet => jakarta.servlet)
    - Security 버전이 수정됨에 따라 기존 메소드가 deprecated 됐습니다. 최신 설정은 추후 올려두도록하겠습니다.
  - layout1.html 파일 내용 수정
    - thymeleaf layout 버전 증가에 따른 코드 수정 
    - th:replace="fragments/header::header">   =>   th:replace="~{fragments/header::header}">
    - th:replace="fragments/footer::footer">   =>   th:replace="~{fragments/footer::footer}">
- 2022-06-26 branch 
   - 스프링부트 2.7.1 버전으로 업데이트
   - querydsl 5.0.0 버전으로 pom.xml 업데이트 및 ItemRepositoryCustomImpl의 fetchResults() deprecated 대응
     (list 조회 및 count 조회 쿼리 분리)
   - WebSecurityConfigurerAdapter deprecated로 인한 SecurityConfig 파일 수정
   - thymeleaf-layout-dialect 3.1.0 버전으로 업데이트
   - modelmapper 3.1.0 버전으로 업데이트
  
# 2.안내사항
 - 스프링부트3로 진행 시 기존 소스코드와 import 등 코드가 많이 달라져서 스프링부트 2.7.1 버전 기준으로 진행을 권장드립니다.
 - 백견불여일타 스프링부트 with JPA 질의응답 게시판 공지사항을 보시면 자주 겪으시는 오류 사항들도 계속 업데이트 중입니다. 참고부탁드립니다. (https://cafe.naver.com/codefirst)


================================================
FILE: mvnw
================================================
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#    https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------

# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
#   JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
#   M2_HOME - location of maven2's installed home dir
#   MAVEN_OPTS - parameters passed to the Java VM when running Maven
#     e.g. to debug Maven itself, use
#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------

if [ -z "$MAVEN_SKIP_RC" ] ; then

  if [ -f /etc/mavenrc ] ; then
    . /etc/mavenrc
  fi

  if [ -f "$HOME/.mavenrc" ] ; then
    . "$HOME/.mavenrc"
  fi

fi

# OS specific support.  $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
  CYGWIN*) cygwin=true ;;
  MINGW*) mingw=true;;
  Darwin*) darwin=true
    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
    if [ -z "$JAVA_HOME" ]; then
      if [ -x "/usr/libexec/java_home" ]; then
        export JAVA_HOME="`/usr/libexec/java_home`"
      else
        export JAVA_HOME="/Library/Java/Home"
      fi
    fi
    ;;
esac

if [ -z "$JAVA_HOME" ] ; then
  if [ -r /etc/gentoo-release ] ; then
    JAVA_HOME=`java-config --jre-home`
  fi
fi

if [ -z "$M2_HOME" ] ; then
  ## resolve links - $0 may be a link to maven's home
  PRG="$0"

  # need this for relative symlinks
  while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
      PRG="$link"
    else
      PRG="`dirname "$PRG"`/$link"
    fi
  done

  saveddir=`pwd`

  M2_HOME=`dirname "$PRG"`/..

  # make it fully qualified
  M2_HOME=`cd "$M2_HOME" && pwd`

  cd "$saveddir"
  # echo Using m2 at $M2_HOME
fi

# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
  [ -n "$M2_HOME" ] &&
    M2_HOME=`cygpath --unix "$M2_HOME"`
  [ -n "$JAVA_HOME" ] &&
    JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
  [ -n "$CLASSPATH" ] &&
    CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi

# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
  [ -n "$M2_HOME" ] &&
    M2_HOME="`(cd "$M2_HOME"; pwd)`"
  [ -n "$JAVA_HOME" ] &&
    JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi

if [ -z "$JAVA_HOME" ]; then
  javaExecutable="`which javac`"
  if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
    # readlink(1) is not available as standard on Solaris 10.
    readLink=`which readlink`
    if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
      if $darwin ; then
        javaHome="`dirname \"$javaExecutable\"`"
        javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
      else
        javaExecutable="`readlink -f \"$javaExecutable\"`"
      fi
      javaHome="`dirname \"$javaExecutable\"`"
      javaHome=`expr "$javaHome" : '\(.*\)/bin'`
      JAVA_HOME="$javaHome"
      export JAVA_HOME
    fi
  fi
fi

if [ -z "$JAVACMD" ] ; then
  if [ -n "$JAVA_HOME"  ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
      # IBM's JDK on AIX uses strange locations for the executables
      JAVACMD="$JAVA_HOME/jre/sh/java"
    else
      JAVACMD="$JAVA_HOME/bin/java"
    fi
  else
    JAVACMD="`which java`"
  fi
fi

if [ ! -x "$JAVACMD" ] ; then
  echo "Error: JAVA_HOME is not defined correctly." >&2
  echo "  We cannot execute $JAVACMD" >&2
  exit 1
fi

if [ -z "$JAVA_HOME" ] ; then
  echo "Warning: JAVA_HOME environment variable is not set."
fi

CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher

# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {

  if [ -z "$1" ]
  then
    echo "Path not specified to find_maven_basedir"
    return 1
  fi

  basedir="$1"
  wdir="$1"
  while [ "$wdir" != '/' ] ; do
    if [ -d "$wdir"/.mvn ] ; then
      basedir=$wdir
      break
    fi
    # workaround for JBEAP-8937 (on Solaris 10/Sparc)
    if [ -d "${wdir}" ]; then
      wdir=`cd "$wdir/.."; pwd`
    fi
    # end of workaround
  done
  echo "${basedir}"
}

# concatenates all lines of a file
concat_lines() {
  if [ -f "$1" ]; then
    echo "$(tr -s '\n' ' ' < "$1")"
  fi
}

BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
  exit 1;
fi

##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
    if [ "$MVNW_VERBOSE" = true ]; then
      echo "Found .mvn/wrapper/maven-wrapper.jar"
    fi
else
    if [ "$MVNW_VERBOSE" = true ]; then
      echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
    fi
    if [ -n "$MVNW_REPOURL" ]; then
      jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
    else
      jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
    fi
    while IFS="=" read key value; do
      case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
      esac
    done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
    if [ "$MVNW_VERBOSE" = true ]; then
      echo "Downloading from: $jarUrl"
    fi
    wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
    if $cygwin; then
      wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
    fi

    if command -v wget > /dev/null; then
        if [ "$MVNW_VERBOSE" = true ]; then
          echo "Found wget ... using wget"
        fi
        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
            wget "$jarUrl" -O "$wrapperJarPath"
        else
            wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
        fi
    elif command -v curl > /dev/null; then
        if [ "$MVNW_VERBOSE" = true ]; then
          echo "Found curl ... using curl"
        fi
        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
            curl -o "$wrapperJarPath" "$jarUrl" -f
        else
            curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
        fi

    else
        if [ "$MVNW_VERBOSE" = true ]; then
          echo "Falling back to using Java to download"
        fi
        javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
        # For Cygwin, switch paths to Windows format before running javac
        if $cygwin; then
          javaClass=`cygpath --path --windows "$javaClass"`
        fi
        if [ -e "$javaClass" ]; then
            if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
                if [ "$MVNW_VERBOSE" = true ]; then
                  echo " - Compiling MavenWrapperDownloader.java ..."
                fi
                # Compiling the Java class
                ("$JAVA_HOME/bin/javac" "$javaClass")
            fi
            if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
                # Running the downloader
                if [ "$MVNW_VERBOSE" = true ]; then
                  echo " - Running MavenWrapperDownloader.java ..."
                fi
                ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
            fi
        fi
    fi
fi
##########################################################################################
# End of extension
##########################################################################################

export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
  echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"

# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
  [ -n "$M2_HOME" ] &&
    M2_HOME=`cygpath --path --windows "$M2_HOME"`
  [ -n "$JAVA_HOME" ] &&
    JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
  [ -n "$CLASSPATH" ] &&
    CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
  [ -n "$MAVEN_PROJECTBASEDIR" ] &&
    MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi

# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS

WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain

exec "$JAVACMD" \
  $MAVEN_OPTS \
  -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
  "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"


================================================
FILE: mvnw.cmd
================================================
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements.  See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership.  The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License.  You may obtain a copy of the License at
@REM
@REM    https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied.  See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------

@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM     e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------

@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on"  echo %MAVEN_BATCH_ECHO%

@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")

@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre

@setlocal

set ERROR_CODE=0

@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal

@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome

echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error

:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init

echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error

@REM ==== END VALIDATION ====

:init

@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.

set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir

set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir

:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir

:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"

:endDetectBaseDir

IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig

@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%

:endReadAdditionalConfig

SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain

set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"

FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
    IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)

@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
    if "%MVNW_VERBOSE%" == "true" (
        echo Found %WRAPPER_JAR%
    )
) else (
    if not "%MVNW_REPOURL%" == "" (
        SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
    )
    if "%MVNW_VERBOSE%" == "true" (
        echo Couldn't find %WRAPPER_JAR%, downloading it ...
        echo Downloading from: %DOWNLOAD_URL%
    )

    powershell -Command "&{"^
		"$webclient = new-object System.Net.WebClient;"^
		"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
		"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
		"}"^
		"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
		"}"
    if "%MVNW_VERBOSE%" == "true" (
        echo Finished downloading %WRAPPER_JAR%
    )
)
@REM End of extension

@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*

%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end

:error
set ERROR_CODE=1

:end
@endlocal & set ERROR_CODE=%ERROR_CODE%

if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost

@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause

if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%

exit /B %ERROR_CODE%


================================================
FILE: pom.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.3.2</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.shop</groupId>
	<artifactId>shop</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>shop</name>
	<description>Shop Project for Spring Boot</description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.33</version>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>

		<dependency>
			<groupId>com.querydsl</groupId>
			<artifactId>querydsl-jpa</artifactId>
			<version>5.0.0</version>
			<classifier>jakarta</classifier>
		</dependency>

		<dependency>
			<groupId>com.querydsl</groupId>
			<artifactId>querydsl-apt</artifactId>
			<version>5.0.0</version>
			<classifier>jakarta</classifier>
		</dependency>

		<dependency>
			<groupId>com.querydsl</groupId>
			<artifactId>querydsl-core</artifactId>
			<version>5.0.0</version>
		</dependency>

		<dependency>
			<groupId>nz.net.ultraq.thymeleaf</groupId>
			<artifactId>thymeleaf-layout-dialect</artifactId>
			<version>3.3.0</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-test</artifactId>
			<scope>test</scope>
			<version>${spring-security.version}</version>
		</dependency>

		<dependency>
			<groupId>org.thymeleaf.extras</groupId>
			<artifactId>thymeleaf-extras-springsecurity6</artifactId>
			<version>3.1.1.RELEASE</version>
		</dependency>

        <dependency>
            <groupId>org.modelmapper</groupId>
            <artifactId>modelmapper</artifactId>
			<version>3.1.0</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>


================================================
FILE: src/main/java/com/shop/ShopApplication.java
================================================
package com.shop;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ShopApplication {

	public static void main(String[] args) {
		SpringApplication.run(ShopApplication.class, args);
	}

}


================================================
FILE: src/main/java/com/shop/config/AuditConfig.java
================================================
package com.shop.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@Configuration
@EnableJpaAuditing
public class AuditConfig {

    @Bean
    public AuditorAware<String> auditorProvider() {
        return new AuditorAwareImpl();
    }

}

================================================
FILE: src/main/java/com/shop/config/AuditorAwareImpl.java
================================================
package com.shop.config;

import org.springframework.data.domain.AuditorAware;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.Optional;

public class AuditorAwareImpl implements AuditorAware<String> {

    @Override
    public Optional<String> getCurrentAuditor() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String userId = "";
        if(authentication != null){
            userId = authentication.getName();
        }
        return Optional.of(userId);
    }

}

================================================
FILE: src/main/java/com/shop/config/CsrfCookieFilter.java
================================================
package com.shop.config;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

public class CsrfCookieFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // HttpServletRequest에서 CsrfToken 속성 조회
        CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
        if (csrfToken != null) {
            // csrf 토큰 강제 생성
            csrfToken.getToken();
        }
        filterChain.doFilter(request, response);
    }
}

================================================
FILE: src/main/java/com/shop/config/CustomAuthenticationEntryPoint.java
================================================
package com.shop.config;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

import java.io.IOException;

public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
    }

}


================================================
FILE: src/main/java/com/shop/config/FormLoginAuthenticationFailureHandler.java
================================================
package com.shop.config;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

import java.io.IOException;

public class FormLoginAuthenticationFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.sendRedirect("/members/login/error");
    }

}


================================================
FILE: src/main/java/com/shop/config/SecurityConfig.java
================================================
package com.shop.config;

import com.shop.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    MemberService memberService;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .csrf(csrf -> csrf
                        .csrfTokenRepository(new CookieCsrfTokenRepository())   // csrf 토큰 저장소를 http only cookie로 설정
                )
                .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class)
                .authorizeHttpRequests(authorizeHttpRequestsCustomizer -> authorizeHttpRequestsCustomizer
                        .requestMatchers("/css/**", "/js/**", "/img/**").permitAll()
                        .requestMatchers("/", "/members/**", "/item/**", "/images/**").permitAll()
                        .requestMatchers("/admin/**").hasRole("ADMIN")
                        .anyRequest()
                        .authenticated()
                ).formLogin(formLoginCustomizer -> formLoginCustomizer
                        .loginPage("/members/login")
                        .defaultSuccessUrl("/", true)
                        .usernameParameter("email")
                        .failureHandler(new FormLoginAuthenticationFailureHandler())
                ).logout( logoutCustomizer -> logoutCustomizer
                        .logoutRequestMatcher(new AntPathRequestMatcher("/members/logout"))
                        .logoutSuccessUrl("/")
                ).exceptionHandling(e -> e
                        .authenticationEntryPoint(new CustomAuthenticationEntryPoint())
                )
                .build()
        ;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}


================================================
FILE: src/main/java/com/shop/config/WebMvcConfig.java
================================================
package com.shop.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Value("${uploadPath}")
    String uploadPath;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/images/**")
                .addResourceLocations(uploadPath);
    }

}

================================================
FILE: src/main/java/com/shop/constant/ItemSellStatus.java
================================================
package com.shop.constant;

public enum ItemSellStatus {
    SELL, SOLD_OUT
}

================================================
FILE: src/main/java/com/shop/constant/OrderStatus.java
================================================
package com.shop.constant;

public enum OrderStatus {
    ORDER, CANCEL
}

================================================
FILE: src/main/java/com/shop/constant/Role.java
================================================
package com.shop.constant;

public enum Role {
    USER, ADMIN
}

================================================
FILE: src/main/java/com/shop/controller/CartController.java
================================================
package com.shop.controller;
import com.shop.dto.CartItemDto;
import com.shop.service.CartService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import jakarta.validation.Valid;
import java.security.Principal;
import java.util.List;

import com.shop.dto.CartDetailDto;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.DeleteMapping;

import com.shop.dto.CartOrderDto;

@Controller
@RequiredArgsConstructor
public class CartController {

    private final CartService cartService;

    @PostMapping(value = "/cart")
    public @ResponseBody ResponseEntity order(@RequestBody @Valid CartItemDto cartItemDto, BindingResult bindingResult, Principal principal){

        if(bindingResult.hasErrors()){
            StringBuilder sb = new StringBuilder();
            List<FieldError> fieldErrors = bindingResult.getFieldErrors();

            for (FieldError fieldError : fieldErrors) {
                sb.append(fieldError.getDefaultMessage());
            }

            return new ResponseEntity<String>(sb.toString(), HttpStatus.BAD_REQUEST);
        }

        String email = principal.getName();
        Long cartItemId;

        try {
            cartItemId = cartService.addCart(cartItemDto, email);
        } catch(Exception e){
            return new ResponseEntity<String>(e.getMessage(), HttpStatus.BAD_REQUEST);
        }

        return new ResponseEntity<Long>(cartItemId, HttpStatus.OK);
    }

    @GetMapping(value = "/cart")
    public String orderHist(Principal principal, Model model){
        List<CartDetailDto> cartDetailList = cartService.getCartList(principal.getName());
        model.addAttribute("cartItems", cartDetailList);
        return "cart/cartList";
    }

    @PatchMapping(value = "/cartItem/{cartItemId}")
    public @ResponseBody ResponseEntity updateCartItem(@PathVariable("cartItemId") Long cartItemId, int count, Principal principal){

        if(count <= 0){
            return new ResponseEntity<String>("최소 1개 이상 담아주세요", HttpStatus.BAD_REQUEST);
        } else if(!cartService.validateCartItem(cartItemId, principal.getName())){
            return new ResponseEntity<String>("수정 권한이 없습니다.", HttpStatus.FORBIDDEN);
        }

        cartService.updateCartItemCount(cartItemId, count);
        return new ResponseEntity<Long>(cartItemId, HttpStatus.OK);
    }

    @DeleteMapping(value = "/cartItem/{cartItemId}")
    public @ResponseBody ResponseEntity deleteCartItem(@PathVariable("cartItemId") Long cartItemId, Principal principal){

        if(!cartService.validateCartItem(cartItemId, principal.getName())){
            return new ResponseEntity<String>("수정 권한이 없습니다.", HttpStatus.FORBIDDEN);
        }

        cartService.deleteCartItem(cartItemId);

        return new ResponseEntity<Long>(cartItemId, HttpStatus.OK);
    }

    @PostMapping(value = "/cart/orders")
    public @ResponseBody ResponseEntity orderCartItem(@RequestBody CartOrderDto cartOrderDto, Principal principal){

        List<CartOrderDto> cartOrderDtoList = cartOrderDto.getCartOrderDtoList();

        if(cartOrderDtoList == null || cartOrderDtoList.size() == 0){
            return new ResponseEntity<String>("주문할 상품을 선택해주세요", HttpStatus.FORBIDDEN);
        }

        for (CartOrderDto cartOrder : cartOrderDtoList) {
            if(!cartService.validateCartItem(cartOrder.getCartItemId(), principal.getName())){
                return new ResponseEntity<String>("주문 권한이 없습니다.", HttpStatus.FORBIDDEN);
            }
        }

        Long orderId = cartService.orderCartItem(cartOrderDtoList, principal.getName());
        return new ResponseEntity<Long>(orderId, HttpStatus.OK);
    }

}

================================================
FILE: src/main/java/com/shop/controller/ItemController.java
================================================
package com.shop.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.ui.Model;
import com.shop.dto.ItemFormDto;

import com.shop.service.ItemService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import jakarta.validation.Valid;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;

import org.springframework.web.bind.annotation.PathVariable;
import jakarta.persistence.EntityNotFoundException;

import com.shop.dto.ItemSearchDto;
import com.shop.entity.Item;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import java.util.Optional;

@Controller
@RequiredArgsConstructor
public class ItemController {

    private final ItemService itemService;

    @GetMapping(value = "/admin/item/new")
    public String itemForm(Model model){
        model.addAttribute("itemFormDto", new ItemFormDto());
        return "item/itemForm";
    }

    @PostMapping(value = "/admin/item/new")
    public String itemNew(@Valid ItemFormDto itemFormDto, BindingResult bindingResult,
                          Model model, @RequestParam("itemImgFile") List<MultipartFile> itemImgFileList){

        if(bindingResult.hasErrors()){
            return "item/itemForm";
        }

        if(itemImgFileList.get(0).isEmpty() && itemFormDto.getId() == null){
            model.addAttribute("errorMessage", "첫번째 상품 이미지는 필수 입력 값 입니다.");
            return "item/itemForm";
        }

        try {
            itemService.saveItem(itemFormDto, itemImgFileList);
        } catch (Exception e){
            model.addAttribute("errorMessage", "상품 등록 중 에러가 발생하였습니다.");
            return "item/itemForm";
        }

        return "redirect:/";
    }

    @GetMapping(value = "/admin/item/{itemId}")
    public String itemDtl(@PathVariable("itemId") Long itemId, Model model){

        try {
            ItemFormDto itemFormDto = itemService.getItemDtl(itemId);
            model.addAttribute("itemFormDto", itemFormDto);
        } catch(EntityNotFoundException e){
            model.addAttribute("errorMessage", "존재하지 않는 상품 입니다.");
            model.addAttribute("itemFormDto", new ItemFormDto());
            return "item/itemForm";
        }

        return "item/itemForm";
    }

    @PostMapping(value = "/admin/item/{itemId}")
    public String itemUpdate(@Valid ItemFormDto itemFormDto, BindingResult bindingResult,
                             @RequestParam("itemImgFile") List<MultipartFile> itemImgFileList, Model model){
        if(bindingResult.hasErrors()){
            return "item/itemForm";
        }

        if(itemImgFileList.get(0).isEmpty() && itemFormDto.getId() == null){
            model.addAttribute("errorMessage", "첫번째 상품 이미지는 필수 입력 값 입니다.");
            return "item/itemForm";
        }

        try {
            itemService.updateItem(itemFormDto, itemImgFileList);
        } catch (Exception e){
            model.addAttribute("errorMessage", "상품 수정 중 에러가 발생하였습니다.");
            return "item/itemForm";
        }

        return "redirect:/";
    }

    @GetMapping(value = {"/admin/items", "/admin/items/{page}"})
    public String itemManage(ItemSearchDto itemSearchDto, @PathVariable("page") Optional<Integer> page, Model model){

        Pageable pageable = PageRequest.of(page.isPresent() ? page.get() : 0, 3);
        Page<Item> items = itemService.getAdminItemPage(itemSearchDto, pageable);

        model.addAttribute("items", items);
        model.addAttribute("itemSearchDto", itemSearchDto);
        model.addAttribute("maxPage", 5);

        return "item/itemMng";
    }

    @GetMapping(value = "/item/{itemId}")
    public String itemDtl(Model model, @PathVariable("itemId") Long itemId){
        ItemFormDto itemFormDto = itemService.getItemDtl(itemId);
        model.addAttribute("item", itemFormDto);
        return "item/itemDtl";
    }

}

================================================
FILE: src/main/java/com/shop/controller/MainController.java
================================================
package com.shop.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import com.shop.dto.ItemSearchDto;
import com.shop.dto.MainItemDto;
import com.shop.service.ItemService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.Optional;

@Controller
@RequiredArgsConstructor
public class MainController {

    private final ItemService itemService;

    @GetMapping(value = "/")
    public String main(ItemSearchDto itemSearchDto, Optional<Integer> page, Model model){

        Pageable pageable = PageRequest.of(page.isPresent() ? page.get() : 0, 6);
        Page<MainItemDto> items = itemService.getMainItemPage(itemSearchDto, pageable);

        model.addAttribute("items", items);
        model.addAttribute("itemSearchDto", itemSearchDto);
        model.addAttribute("maxPage", 5);

        return "main";
    }

}

================================================
FILE: src/main/java/com/shop/controller/MemberController.java
================================================
package com.shop.controller;

import com.shop.dto.MemberFormDto;
import com.shop.service.MemberService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.shop.entity.Member;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;

import org.springframework.validation.BindingResult;
import jakarta.validation.Valid;

@RequestMapping("/members")
@Controller
@RequiredArgsConstructor
public class MemberController {

    private final MemberService memberService;
    private final PasswordEncoder passwordEncoder;

    @GetMapping(value = "/new")
    public String memberForm(Model model){
        model.addAttribute("memberFormDto", new MemberFormDto());
        return "member/memberForm";
    }

    @PostMapping(value = "/new")
    public String newMember(@Valid MemberFormDto memberFormDto, BindingResult bindingResult, Model model){

        if(bindingResult.hasErrors()){
            return "member/memberForm";
        }

        try {
            Member member = Member.createMember(memberFormDto, passwordEncoder);
            memberService.saveMember(member);
        } catch (IllegalStateException e){
            model.addAttribute("errorMessage", e.getMessage());
            return "member/memberForm";
        }

        return "redirect:/";
    }

    @GetMapping(value = "/login")
    public String loginMember(){
        return "/member/memberLoginForm";
    }

    @GetMapping(value = "/login/error")
    public String loginError(Model model){
        model.addAttribute("loginErrorMsg", "아이디 또는 비밀번호를 확인해주세요");
        return "/member/memberLoginForm";
    }

}

================================================
FILE: src/main/java/com/shop/controller/OrderController.java
================================================
package com.shop.controller;

import com.shop.dto.OrderDto;
import com.shop.service.OrderService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;

import jakarta.validation.Valid;
import java.security.Principal;
import java.util.List;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import com.shop.dto.OrderHistDto;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.ui.Model;
import java.util.Optional;

@Controller
@RequiredArgsConstructor
public class OrderController {

    private final OrderService orderService;

    @PostMapping(value = "/order")
    public @ResponseBody ResponseEntity order(@RequestBody @Valid OrderDto orderDto
            , BindingResult bindingResult, Principal principal){

        if(bindingResult.hasErrors()){
            StringBuilder sb = new StringBuilder();
            List<FieldError> fieldErrors = bindingResult.getFieldErrors();

            for (FieldError fieldError : fieldErrors) {
                sb.append(fieldError.getDefaultMessage());
            }

            return new ResponseEntity<String>(sb.toString(), HttpStatus.BAD_REQUEST);
        }

        String email = principal.getName();
        Long orderId;

        try {
            orderId = orderService.order(orderDto, email);
        } catch(Exception e){
            return new ResponseEntity<String>(e.getMessage(), HttpStatus.BAD_REQUEST);
        }

        return new ResponseEntity<Long>(orderId, HttpStatus.OK);
    }

    @GetMapping(value = {"/orders", "/orders/{page}"})
    public String orderHist(@PathVariable("page") Optional<Integer> page, Principal principal, Model model){

        Pageable pageable = PageRequest.of(page.isPresent() ? page.get() : 0, 4);
        Page<OrderHistDto> ordersHistDtoList = orderService.getOrderList(principal.getName(), pageable);

        model.addAttribute("orders", ordersHistDtoList);
        model.addAttribute("page", pageable.getPageNumber());
        model.addAttribute("maxPage", 5);

        return "order/orderHist";
    }

    @PostMapping("/order/{orderId}/cancel")
    public @ResponseBody ResponseEntity cancelOrder(@PathVariable("orderId") Long orderId , Principal principal){

        if(!orderService.validateOrder(orderId, principal.getName())){
            return new ResponseEntity<String>("주문 취소 권한이 없습니다.", HttpStatus.FORBIDDEN);
        }

        orderService.cancelOrder(orderId);
        return new ResponseEntity<Long>(orderId, HttpStatus.OK);
    }

}

================================================
FILE: src/main/java/com/shop/controller/ThymeleafExController.java
================================================
package com.shop.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.shop.dto.ItemDto;
import java.time.LocalDateTime;

import java.util.ArrayList;
import java.util.List;

@Controller
@RequestMapping(value="/thymeleaf")
public class ThymeleafExController {

    @GetMapping(value = "/ex01")
    public String thymeleafExample01(Model model){
        model.addAttribute("data", "타임리프 예제 입니다.");
        return "thymeleafEx/thymeleafEx01";
    }

    @GetMapping(value = "/ex02")
    public String thymeleafExample02(Model model){
        ItemDto itemDto = new ItemDto();
        itemDto.setItemDetail("상품 상세 설명");
        itemDto.setItemNm("테스트 상품1");
        itemDto.setPrice(10000);
        itemDto.setRegTime(LocalDateTime.now());

        model.addAttribute("itemDto", itemDto);
        return "thymeleafEx/thymeleafEx02";
    }

    @GetMapping(value = "/ex03")
    public String thymeleafExample03(Model model){

        List<ItemDto> itemDtoList = new ArrayList<>();

        for(int i=1;i<=10;i++){

            ItemDto itemDto = new ItemDto();
            itemDto.setItemDetail("상품 상세 설명"+i);
            itemDto.setItemNm("테스트 상품" + i);
            itemDto.setPrice(1000*i);
            itemDto.setRegTime(LocalDateTime.now());

            itemDtoList.add(itemDto);
        }

        model.addAttribute("itemDtoList", itemDtoList);
        return "thymeleafEx/thymeleafEx03";
    }

    @GetMapping(value = "/ex04")
    public String thymeleafExample04(Model model){

        List<ItemDto> itemDtoList = new ArrayList<>();

        for(int i=1;i<=10;i++){

            ItemDto itemDto = new ItemDto();
            itemDto.setItemDetail("상품 상세 설명"+i);
            itemDto.setItemNm("테스트 상품" + i);
            itemDto.setPrice(1000*i);
            itemDto.setRegTime(LocalDateTime.now());

            itemDtoList.add(itemDto);
        }

        model.addAttribute("itemDtoList", itemDtoList);
        return "thymeleafEx/thymeleafEx04";
    }

    @GetMapping(value = "/ex05")
    public String thymeleafExample05(){
        return "thymeleafEx/thymeleafEx05";
    }

    @GetMapping(value = "/ex06")
    public String thymeleafExample06(String param1, String param2, Model model){
        model.addAttribute("param1", param1);
        model.addAttribute("param2", param2);
        return "thymeleafEx/thymeleafEx06";
    }

    @GetMapping(value = "/ex07")
    public String thymeleafExample07(){
        return "thymeleafEx/thymeleafEx07";
    }

}

================================================
FILE: src/main/java/com/shop/dto/CartDetailDto.java
================================================
package com.shop.dto;

import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class CartDetailDto {

    private Long cartItemId; //장바구니 상품 아이디

    private String itemNm; //상품명

    private int price; //상품 금액

    private int count; //수량

    private String imgUrl; //상품 이미지 경로

    public CartDetailDto(Long cartItemId, String itemNm, int price, int count, String imgUrl){
        this.cartItemId = cartItemId;
        this.itemNm = itemNm;
        this.price = price;
        this.count = count;
        this.imgUrl = imgUrl;
    }

}

================================================
FILE: src/main/java/com/shop/dto/CartItemDto.java
================================================
package com.shop.dto;

import lombok.Getter;
import lombok.Setter;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;

@Getter @Setter
public class CartItemDto {

    @NotNull(message = "상품 아이디는 필수 입력 값 입니다.")
    private Long itemId;

    @Min(value = 1, message = "최소 1개 이상 담아주세요")
    private int count;

}

================================================
FILE: src/main/java/com/shop/dto/CartOrderDto.java
================================================
package com.shop.dto;

import lombok.Getter;
import lombok.Setter;

import java.util.List;

@Getter
@Setter
public class CartOrderDto {

    private Long cartItemId;

    private List<CartOrderDto> cartOrderDtoList;

}

================================================
FILE: src/main/java/com/shop/dto/ItemDto.java
================================================
package com.shop.dto;

import lombok.Getter;
import lombok.Setter;

import java.time.LocalDateTime;

@Getter
@Setter
public class ItemDto {

    private Long id;

    private String itemNm;

    private Integer price;

    private String itemDetail;

    private String sellStatCd;

    private LocalDateTime regTime;

    private LocalDateTime updateTime;

}

================================================
FILE: src/main/java/com/shop/dto/ItemFormDto.java
================================================
package com.shop.dto;

import com.shop.constant.ItemSellStatus;
import com.shop.entity.Item;
import lombok.Getter;
import lombok.Setter;
import org.modelmapper.ModelMapper;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;

@Getter @Setter
public class ItemFormDto {

    private Long id;

    @NotBlank(message = "상품명은 필수 입력 값입니다.")
    private String itemNm;

    @NotNull(message = "가격은 필수 입력 값입니다.")
    private Integer price;

    @NotBlank(message = "상품 상세는 필수 입력 값입니다.")
    private String itemDetail;

    @NotNull(message = "재고는 필수 입력 값입니다.")
    private Integer stockNumber;

    private ItemSellStatus itemSellStatus;

    private List<ItemImgDto> itemImgDtoList = new ArrayList<>();

    private List<Long> itemImgIds = new ArrayList<>();

    private static ModelMapper modelMapper = new ModelMapper();

    public Item createItem(){
        return modelMapper.map(this, Item.class);
    }

    public static ItemFormDto of(Item item){
        return modelMapper.map(item,ItemFormDto.class);
    }

}

================================================
FILE: src/main/java/com/shop/dto/ItemImgDto.java
================================================
package com.shop.dto;

import com.shop.entity.ItemImg;
import lombok.Getter;
import lombok.Setter;
import org.modelmapper.ModelMapper;

@Getter @Setter
public class ItemImgDto {

    private Long id;

    private String imgName;

    private String oriImgName;

    private String imgUrl;

    private String repImgYn;

    private static ModelMapper modelMapper = new ModelMapper();

    public static ItemImgDto of(ItemImg itemImg) {
        return modelMapper.map(itemImg,ItemImgDto.class);
    }

}

================================================
FILE: src/main/java/com/shop/dto/ItemSearchDto.java
================================================
package com.shop.dto;

import com.shop.constant.ItemSellStatus;
import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class ItemSearchDto {

    private String searchDateType;

    private ItemSellStatus searchSellStatus;

    private String searchBy;

    private String searchQuery = "";

}

================================================
FILE: src/main/java/com/shop/dto/MainItemDto.java
================================================
package com.shop.dto;

import com.querydsl.core.annotations.QueryProjection;
import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class MainItemDto {

    private Long id;

    private String itemNm;

    private String itemDetail;

    private String imgUrl;

    private Integer price;

    @QueryProjection
    public MainItemDto(Long id, String itemNm, String itemDetail, String imgUrl,Integer price){
        this.id = id;
        this.itemNm = itemNm;
        this.itemDetail = itemDetail;
        this.imgUrl = imgUrl;
        this.price = price;
    }

}

================================================
FILE: src/main/java/com/shop/dto/MemberFormDto.java
================================================
package com.shop.dto;

import lombok.Getter;
import lombok.Setter;
import org.hibernate.validator.constraints.Length;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;

@Getter @Setter
public class MemberFormDto {

    @NotBlank(message = "이름은 필수 입력 값입니다.")
    private String name;

    @NotEmpty(message = "이메일은 필수 입력 값입니다.")
    @Email(message = "이메일 형식으로 입력해주세요.")
    private String email;

    @NotEmpty(message = "비밀번호는 필수 입력 값입니다.")
    @Length(min=8, max=16, message = "비밀번호는 8자 이상, 16자 이하로 입력해주세요")
    private String password;

    @NotEmpty(message = "주소는 필수 입력 값입니다.")
    private String address;
}

================================================
FILE: src/main/java/com/shop/dto/OrderDto.java
================================================
package com.shop.dto;

import lombok.Getter;
import lombok.Setter;

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;

@Getter @Setter
public class OrderDto {

    @NotNull(message = "상품 아이디는 필수 입력 값입니다.")
    private Long itemId;

    @Min(value = 1, message = "최소 주문 수량은 1개 입니다.")
    @Max(value = 999, message = "최대 주문 수량은 999개 입니다.")
    private int count;

}

================================================
FILE: src/main/java/com/shop/dto/OrderHistDto.java
================================================
package com.shop.dto;

import com.shop.constant.OrderStatus;
import com.shop.entity.Order;
import lombok.Getter;
import lombok.Setter;

import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

@Getter @Setter
public class OrderHistDto {

    public OrderHistDto(Order order){
        this.orderId = order.getId();
        this.orderDate = order.getOrderDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
        this.orderStatus = order.getOrderStatus();
    }

    private Long orderId; //주문아이디
    private String orderDate; //주문날짜
    private OrderStatus orderStatus; //주문 상태

    private List<OrderItemDto> orderItemDtoList = new ArrayList<>();

    //주문 상품리스트
    public void addOrderItemDto(OrderItemDto orderItemDto){
        orderItemDtoList.add(orderItemDto);
    }

}

================================================
FILE: src/main/java/com/shop/dto/OrderItemDto.java
================================================
package com.shop.dto;

import com.shop.entity.OrderItem;
import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class OrderItemDto {

    public OrderItemDto(OrderItem orderItem, String imgUrl){
        this.itemNm = orderItem.getItem().getItemNm();
        this.count = orderItem.getCount();
        this.orderPrice = orderItem.getOrderPrice();
        this.imgUrl = imgUrl;
    }

    private String itemNm; //상품명
    private int count; //주문 수량

    private int orderPrice; //주문 금액
    private String imgUrl; //상품 이미지 경로

}

================================================
FILE: src/main/java/com/shop/entity/BaseEntity.java
================================================
package com.shop.entity;

import lombok.Getter;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;

@EntityListeners(value = {AuditingEntityListener.class})
@MappedSuperclass
@Getter
public abstract class BaseEntity extends BaseTimeEntity{

    @CreatedBy
    @Column(updatable = false)
    private String createdBy;

    @LastModifiedBy
    private String modifiedBy;

}

================================================
FILE: src/main/java/com/shop/entity/BaseTimeEntity.java
================================================
package com.shop.entity;

import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import java.time.LocalDateTime;

@EntityListeners(value = {AuditingEntityListener.class})
@MappedSuperclass
@Getter @Setter
public abstract class BaseTimeEntity {

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime regTime;

    @LastModifiedDate
    private LocalDateTime updateTime;

}

================================================
FILE: src/main/java/com/shop/entity/Cart.java
================================================
package com.shop.entity;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import jakarta.persistence.*;

@Entity
@Table(name = "cart")
@Getter @Setter
@ToString
public class Cart extends BaseEntity {

    @Id
    @Column(name = "cart_id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="member_id")
    private Member member;

    public static Cart createCart(Member member){
        Cart cart = new Cart();
        cart.setMember(member);
        return cart;
    }

}

================================================
FILE: src/main/java/com/shop/entity/CartItem.java
================================================
package com.shop.entity;

import lombok.Getter;
import lombok.Setter;
import jakarta.persistence.*;

@Entity
@Getter @Setter
@Table(name="cart_item")
public class CartItem extends BaseEntity {

    @Id
    @GeneratedValue
    @Column(name = "cart_item_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="cart_id")
    private Cart cart;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "item_id")
    private Item item;

    private int count;

    public static CartItem createCartItem(Cart cart, Item item, int count) {
        CartItem cartItem = new CartItem();
        cartItem.setCart(cart);
        cartItem.setItem(item);
        cartItem.setCount(count);
        return cartItem;
    }

    public void addCount(int count){
        this.count += count;
    }

    public void updateCount(int count){
        this.count = count;
    }

}

================================================
FILE: src/main/java/com/shop/entity/Item.java
================================================
package com.shop.entity;

import com.shop.constant.ItemSellStatus;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import jakarta.persistence.*;
import com.shop.dto.ItemFormDto;
import com.shop.exception.OutOfStockException;

@Entity
@Table(name="item")
@Getter
@Setter
@ToString
public class Item extends BaseEntity {

    @Id
    @Column(name="item_id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;       //상품 코드

    @Column(nullable = false, length = 50)
    private String itemNm; //상품명

    @Column(name="price", nullable = false)
    private int price; //가격

    @Column(nullable = false)
    private int stockNumber; //재고수량

    @Lob
    @Column(nullable = false)
    private String itemDetail; //상품 상세 설명

    @Enumerated(EnumType.STRING)
    private ItemSellStatus itemSellStatus; //상품 판매 상태

    public void updateItem(ItemFormDto itemFormDto){
        this.itemNm = itemFormDto.getItemNm();
        this.price = itemFormDto.getPrice();
        this.stockNumber = itemFormDto.getStockNumber();
        this.itemDetail = itemFormDto.getItemDetail();
        this.itemSellStatus = itemFormDto.getItemSellStatus();
    }

    public void removeStock(int stockNumber){
        int restStock = this.stockNumber - stockNumber;
        if(restStock<0){
            throw new OutOfStockException("상품의 재고가 부족 합니다. (현재 재고 수량: " + this.stockNumber + ")");
        }
        this.stockNumber = restStock;
    }

    public void addStock(int stockNumber){
        this.stockNumber += stockNumber;
    }

}

================================================
FILE: src/main/java/com/shop/entity/ItemImg.java
================================================
package com.shop.entity;

import lombok.Getter;
import lombok.Setter;
import jakarta.persistence.*;

@Entity
@Table(name="item_img")
@Getter @Setter
public class ItemImg extends BaseEntity{

    @Id
    @Column(name="item_img_id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String imgName; //이미지 파일명

    private String oriImgName; //원본 이미지 파일명

    private String imgUrl; //이미지 조회 경로

    private String repimgYn; //대표 이미지 여부

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "item_id")
    private Item item;

    public void updateItemImg(String oriImgName, String imgName, String imgUrl){
        this.oriImgName = oriImgName;
        this.imgName = imgName;
        this.imgUrl = imgUrl;
    }

}

================================================
FILE: src/main/java/com/shop/entity/Member.java
================================================
package com.shop.entity;

import com.shop.constant.Role;
import com.shop.dto.MemberFormDto;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.security.crypto.password.PasswordEncoder;

import jakarta.persistence.*;

@Entity
@Table(name="member")
@Getter @Setter
@ToString
public class Member extends BaseEntity {

    @Id
    @Column(name="member_id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;

    @Column(unique = true)
    private String email;

    private String password;

    private String address;

    @Enumerated(EnumType.STRING)
    private Role role;

    public static Member createMember(MemberFormDto memberFormDto, PasswordEncoder passwordEncoder){
        Member member = new Member();
        member.setName(memberFormDto.getName());
        member.setEmail(memberFormDto.getEmail());
        member.setAddress(memberFormDto.getAddress());
        String password = passwordEncoder.encode(memberFormDto.getPassword());
        member.setPassword(password);
        member.setRole(Role.ADMIN);
        return member;
    }

}


================================================
FILE: src/main/java/com/shop/entity/Order.java
================================================
package com.shop.entity;

import com.shop.constant.OrderStatus;
import lombok.Getter;
import lombok.Setter;
import jakarta.persistence.*;
import java.time.LocalDateTime;

import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "orders")
@Getter @Setter
public class Order extends BaseEntity {

    @Id @GeneratedValue
    @Column(name = "order_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    private LocalDateTime orderDate; //주문일

    @Enumerated(EnumType.STRING)
    private OrderStatus orderStatus; //주문상태

    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL
            , orphanRemoval = true, fetch = FetchType.LAZY)
    private List<OrderItem> orderItems = new ArrayList<>();

    public void addOrderItem(OrderItem orderItem) {
        orderItems.add(orderItem);
        orderItem.setOrder(this);
    }

    public static Order createOrder(Member member, List<OrderItem> orderItemList) {
        Order order = new Order();
        order.setMember(member);

        for(OrderItem orderItem : orderItemList) {
            order.addOrderItem(orderItem);
        }

        order.setOrderStatus(OrderStatus.ORDER);
        order.setOrderDate(LocalDateTime.now());
        return order;
    }

    public int getTotalPrice() {
        int totalPrice = 0;
        for(OrderItem orderItem : orderItems){
            totalPrice += orderItem.getTotalPrice();
        }
        return totalPrice;
    }

    public void cancelOrder() {
        this.orderStatus = OrderStatus.CANCEL;
        for (OrderItem orderItem : orderItems) {
            orderItem.cancel();
        }
    }

}

================================================
FILE: src/main/java/com/shop/entity/OrderItem.java
================================================
package com.shop.entity;

import lombok.Getter;
import lombok.Setter;
import jakarta.persistence.*;

@Entity
@Getter @Setter
public class OrderItem extends BaseEntity {

    @Id @GeneratedValue
    @Column(name = "order_item_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "item_id")
    private Item item;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "order_id")
    private Order order;

    private int orderPrice; //주문가격

    private int count; //수량

    public static OrderItem createOrderItem(Item item, int count){
        OrderItem orderItem = new OrderItem();
        orderItem.setItem(item);
        orderItem.setCount(count);
        orderItem.setOrderPrice(item.getPrice());
        item.removeStock(count);
        return orderItem;
    }

    public int getTotalPrice(){
        return orderPrice*count;
    }

    public void cancel() {
        this.getItem().addStock(count);
    }

}

================================================
FILE: src/main/java/com/shop/exception/OutOfStockException.java
================================================
package com.shop.exception;

public class OutOfStockException extends RuntimeException{

    public OutOfStockException(String message) {
        super(message);
    }

}

================================================
FILE: src/main/java/com/shop/repository/CartItemRepository.java
================================================
package com.shop.repository;

import com.shop.entity.CartItem;
import org.springframework.data.jpa.repository.JpaRepository;

import com.shop.dto.CartDetailDto;
import org.springframework.data.jpa.repository.Query;
import java.util.List;

public interface CartItemRepository extends JpaRepository<CartItem, Long> {

    CartItem findByCartIdAndItemId(Long cartId, Long itemId);

    @Query("select new com.shop.dto.CartDetailDto(ci.id, i.itemNm, i.price, ci.count, im.imgUrl) " +
            "from CartItem ci, ItemImg im " +
            "join ci.item i " +
            "where ci.cart.id = :cartId " +
            "and im.item.id = ci.item.id " +
            "and im.repimgYn = 'Y' " +
            "order by ci.regTime desc"
            )
    List<CartDetailDto> findCartDetailDtoList(Long cartId);

}

================================================
FILE: src/main/java/com/shop/repository/CartRepository.java
================================================
package com.shop.repository;

import com.shop.entity.Cart;
import org.springframework.data.jpa.repository.JpaRepository;

public interface CartRepository extends JpaRepository<Cart, Long> {

    Cart findByMemberId(Long memberId);

}

================================================
FILE: src/main/java/com/shop/repository/ItemImgRepository.java
================================================
package com.shop.repository;

import com.shop.entity.ItemImg;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;

public interface ItemImgRepository extends JpaRepository<ItemImg, Long> {

    List<ItemImg> findByItemIdOrderByIdAsc(Long itemId);

    ItemImg findByItemIdAndRepimgYn(Long itemId, String repimgYn);

}

================================================
FILE: src/main/java/com/shop/repository/ItemRepository.java
================================================
package com.shop.repository;

import com.shop.entity.Item;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import org.springframework.data.querydsl.QuerydslPredicateExecutor;

public interface ItemRepository extends JpaRepository<Item, Long>,
        QuerydslPredicateExecutor<Item>, ItemRepositoryCustom {

    List<Item> findByItemNm(String itemNm);

    List<Item> findByItemNmOrItemDetail(String itemNm, String itemDetail);

    List<Item> findByPriceLessThan(Integer price);

    List<Item> findByPriceLessThanOrderByPriceDesc(Integer price);

    @Query("select i from Item i where i.itemDetail like " +
            "%:itemDetail% order by i.price desc")
    List<Item> findByItemDetail(@Param("itemDetail") String itemDetail);

    @Query(value="select * from item i where i.item_detail like " +
            "%:itemDetail% order by i.price desc", nativeQuery = true)
    List<Item> findByItemDetailByNative(@Param("itemDetail") String itemDetail);

}

================================================
FILE: src/main/java/com/shop/repository/ItemRepositoryCustom.java
================================================
package com.shop.repository;

import com.shop.dto.ItemSearchDto;
import com.shop.entity.Item;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import com.shop.dto.MainItemDto;

public interface ItemRepositoryCustom {

    Page<Item> getAdminItemPage(ItemSearchDto itemSearchDto, Pageable pageable);

    Page<MainItemDto> getMainItemPage(ItemSearchDto itemSearchDto, Pageable pageable);

}

================================================
FILE: src/main/java/com/shop/repository/ItemRepositoryCustomImpl.java
================================================
package com.shop.repository;

import com.querydsl.core.QueryResults;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.Wildcard;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.shop.constant.ItemSellStatus;
import com.shop.dto.ItemSearchDto;
import com.shop.dto.MainItemDto;
import com.shop.dto.QMainItemDto;
import com.shop.entity.Item;
import com.shop.entity.QItem;
import com.shop.entity.QItemImg;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.thymeleaf.util.StringUtils;

import jakarta.persistence.EntityManager;
import java.time.LocalDateTime;
import java.util.List;

public class ItemRepositoryCustomImpl implements ItemRepositoryCustom{

    private JPAQueryFactory queryFactory;

    public ItemRepositoryCustomImpl(EntityManager em){
        this.queryFactory = new JPAQueryFactory(em);
    }

    private BooleanExpression searchSellStatusEq(ItemSellStatus searchSellStatus){
        return searchSellStatus == null ? null : QItem.item.itemSellStatus.eq(searchSellStatus);
    }

    private BooleanExpression regDtsAfter(String searchDateType){

        LocalDateTime dateTime = LocalDateTime.now();

        if(StringUtils.equals("all", searchDateType) || searchDateType == null){
            return null;
        } else if(StringUtils.equals("1d", searchDateType)){
            dateTime = dateTime.minusDays(1);
        } else if(StringUtils.equals("1w", searchDateType)){
            dateTime = dateTime.minusWeeks(1);
        } else if(StringUtils.equals("1m", searchDateType)){
            dateTime = dateTime.minusMonths(1);
        } else if(StringUtils.equals("6m", searchDateType)){
            dateTime = dateTime.minusMonths(6);
        }

        return QItem.item.regTime.after(dateTime);
    }

    private BooleanExpression searchByLike(String searchBy, String searchQuery){

        if(StringUtils.equals("itemNm", searchBy)){
            return QItem.item.itemNm.like("%" + searchQuery + "%");
        } else if(StringUtils.equals("createdBy", searchBy)){
            return QItem.item.createdBy.like("%" + searchQuery + "%");
        }

        return null;
    }

    @Override
    public Page<Item> getAdminItemPage(ItemSearchDto itemSearchDto, Pageable pageable) {

        List<Item> content = queryFactory
                .selectFrom(QItem.item)
                .where(regDtsAfter(itemSearchDto.getSearchDateType()),
                        searchSellStatusEq(itemSearchDto.getSearchSellStatus()),
                        searchByLike(itemSearchDto.getSearchBy(),
                                itemSearchDto.getSearchQuery()))
                .orderBy(QItem.item.id.desc())
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetch();

        long total = queryFactory.select(Wildcard.count).from(QItem.item)
                .where(regDtsAfter(itemSearchDto.getSearchDateType()),
                        searchSellStatusEq(itemSearchDto.getSearchSellStatus()),
                        searchByLike(itemSearchDto.getSearchBy(), itemSearchDto.getSearchQuery()))
                .fetchOne()
                ;

        return new PageImpl<>(content, pageable, total);
    }

    private BooleanExpression itemNmLike(String searchQuery){
        return StringUtils.isEmpty(searchQuery) ? null : QItem.item.itemNm.like("%" + searchQuery + "%");
    }

    @Override
    public Page<MainItemDto> getMainItemPage(ItemSearchDto itemSearchDto, Pageable pageable) {
        QItem item = QItem.item;
        QItemImg itemImg = QItemImg.itemImg;

        List<MainItemDto> content = queryFactory
                .select(
                        new QMainItemDto(
                                item.id,
                                item.itemNm,
                                item.itemDetail,
                                itemImg.imgUrl,
                                item.price)
                )
                .from(itemImg)
                .join(itemImg.item, item)
                .where(itemImg.repimgYn.eq("Y"))
                .where(itemNmLike(itemSearchDto.getSearchQuery()))
                .orderBy(item.id.desc())
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetch();

        long total = queryFactory
                .select(Wildcard.count)
                .from(itemImg)
                .join(itemImg.item, item)
                .where(itemImg.repimgYn.eq("Y"))
                .where(itemNmLike(itemSearchDto.getSearchQuery()))
                .fetchOne()
                ;

        return new PageImpl<>(content, pageable, total);
    }

}

================================================
FILE: src/main/java/com/shop/repository/MemberRepository.java
================================================
package com.shop.repository;

import com.shop.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;

public interface MemberRepository extends JpaRepository<Member, Long> {

    Member findByEmail(String email);

}

================================================
FILE: src/main/java/com/shop/repository/OrderItemRepository.java
================================================
package com.shop.repository;

import com.shop.entity.OrderItem;
import org.springframework.data.jpa.repository.JpaRepository;

public interface OrderItemRepository extends JpaRepository<OrderItem, Long> {

}

================================================
FILE: src/main/java/com/shop/repository/OrderRepository.java
================================================
package com.shop.repository;

import com.shop.entity.Order;
import org.springframework.data.jpa.repository.JpaRepository;

import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;

public interface OrderRepository extends JpaRepository<Order, Long> {

    @Query("select o from Order o " +
            "where o.member.email = :email " +
            "order by o.orderDate desc"
    )
    List<Order> findOrders(@Param("email") String email, Pageable pageable);

    @Query("select count(o) from Order o " +
            "where o.member.email = :email"
    )
    Long countOrder(@Param("email") String email);
}

================================================
FILE: src/main/java/com/shop/service/CartService.java
================================================
package com.shop.service;

import com.shop.dto.CartItemDto;
import com.shop.entity.Cart;
import com.shop.entity.CartItem;
import com.shop.entity.Item;
import com.shop.entity.Member;
import com.shop.repository.CartItemRepository;
import com.shop.repository.CartRepository;
import com.shop.repository.ItemRepository;
import com.shop.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import jakarta.persistence.EntityNotFoundException;

import com.shop.dto.CartDetailDto;
import java.util.ArrayList;
import java.util.List;

import org.thymeleaf.util.StringUtils;
import com.shop.dto.CartOrderDto;
import com.shop.dto.OrderDto;

@Service
@RequiredArgsConstructor
@Transactional
public class CartService {

    private final ItemRepository itemRepository;
    private final MemberRepository memberRepository;
    private final CartRepository cartRepository;
    private final CartItemRepository cartItemRepository;
    private final OrderService orderService;

    public Long addCart(CartItemDto cartItemDto, String email){

        Item item = itemRepository.findById(cartItemDto.getItemId())
                .orElseThrow(EntityNotFoundException::new);
        Member member = memberRepository.findByEmail(email);

        Cart cart = cartRepository.findByMemberId(member.getId());
        if(cart == null){
            cart = Cart.createCart(member);
            cartRepository.save(cart);
        }

        CartItem savedCartItem = cartItemRepository.findByCartIdAndItemId(cart.getId(), item.getId());

        if(savedCartItem != null){
            savedCartItem.addCount(cartItemDto.getCount());
            return savedCartItem.getId();
        } else {
            CartItem cartItem = CartItem.createCartItem(cart, item, cartItemDto.getCount());
            cartItemRepository.save(cartItem);
            return cartItem.getId();
        }
    }

    @Transactional(readOnly = true)
    public List<CartDetailDto> getCartList(String email){

        List<CartDetailDto> cartDetailDtoList = new ArrayList<>();

        Member member = memberRepository.findByEmail(email);
        Cart cart = cartRepository.findByMemberId(member.getId());
        if(cart == null){
            return cartDetailDtoList;
        }

        cartDetailDtoList = cartItemRepository.findCartDetailDtoList(cart.getId());
        return cartDetailDtoList;
    }

    @Transactional(readOnly = true)
    public boolean validateCartItem(Long cartItemId, String email){
        Member curMember = memberRepository.findByEmail(email);
        CartItem cartItem = cartItemRepository.findById(cartItemId)
                .orElseThrow(EntityNotFoundException::new);
        Member savedMember = cartItem.getCart().getMember();

        if(!StringUtils.equals(curMember.getEmail(), savedMember.getEmail())){
            return false;
        }

        return true;
    }

    public void updateCartItemCount(Long cartItemId, int count){
        CartItem cartItem = cartItemRepository.findById(cartItemId)
                .orElseThrow(EntityNotFoundException::new);

        cartItem.updateCount(count);
    }

    public void deleteCartItem(Long cartItemId) {
        CartItem cartItem = cartItemRepository.findById(cartItemId)
                .orElseThrow(EntityNotFoundException::new);
        cartItemRepository.delete(cartItem);
    }

    public Long orderCartItem(List<CartOrderDto> cartOrderDtoList, String email){
        List<OrderDto> orderDtoList = new ArrayList<>();

        for (CartOrderDto cartOrderDto : cartOrderDtoList) {
            CartItem cartItem = cartItemRepository
                            .findById(cartOrderDto.getCartItemId())
                            .orElseThrow(EntityNotFoundException::new);

            OrderDto orderDto = new OrderDto();
            orderDto.setItemId(cartItem.getItem().getId());
            orderDto.setCount(cartItem.getCount());
            orderDtoList.add(orderDto);
        }

        Long orderId = orderService.orders(orderDtoList, email);
        for (CartOrderDto cartOrderDto : cartOrderDtoList) {
            CartItem cartItem = cartItemRepository
                            .findById(cartOrderDto.getCartItemId())
                            .orElseThrow(EntityNotFoundException::new);
            cartItemRepository.delete(cartItem);
        }

        return orderId;
    }

}

================================================
FILE: src/main/java/com/shop/service/FileService.java
================================================
package com.shop.service;

import lombok.extern.java.Log;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileOutputStream;
import java.util.UUID;

@Service
@Log
public class FileService {

    public String uploadFile(String uploadPath, String originalFileName, byte[] fileData) throws Exception{
        UUID uuid = UUID.randomUUID();
        String extension = originalFileName.substring(originalFileName.lastIndexOf("."));
        String savedFileName = uuid.toString() + extension;
        String fileUploadFullUrl = uploadPath + "/" + savedFileName;
        FileOutputStream fos = new FileOutputStream(fileUploadFullUrl);
        fos.write(fileData);
        fos.close();
        return savedFileName;
    }

    public void deleteFile(String filePath) throws Exception{
        File deleteFile = new File(filePath);
        if(deleteFile.exists()) {
            deleteFile.delete();
            log.info("파일을 삭제하였습니다.");
        } else {
            log.info("파일이 존재하지 않습니다.");
        }
    }

}

================================================
FILE: src/main/java/com/shop/service/ItemImgService.java
================================================
package com.shop.service;

import com.shop.entity.ItemImg;
import com.shop.repository.ItemImgRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import org.thymeleaf.util.StringUtils;
import jakarta.persistence.EntityNotFoundException;

@Service
@RequiredArgsConstructor
@Transactional
public class ItemImgService {

    @Value("${itemImgLocation}")
    private String itemImgLocation;

    private final ItemImgRepository itemImgRepository;

    private final FileService fileService;

    public void saveItemImg(ItemImg itemImg, MultipartFile itemImgFile) throws Exception{
        String oriImgName = itemImgFile.getOriginalFilename();
        String imgName = "";
        String imgUrl = "";

        //파일 업로드
        if(!StringUtils.isEmpty(oriImgName)){
            imgName = fileService.uploadFile(itemImgLocation, oriImgName,
                    itemImgFile.getBytes());
            imgUrl = "/images/item/" + imgName;
        }

        //상품 이미지 정보 저장
        itemImg.updateItemImg(oriImgName, imgName, imgUrl);
        itemImgRepository.save(itemImg);
    }

    public void updateItemImg(Long itemImgId, MultipartFile itemImgFile) throws Exception{
        if(!itemImgFile.isEmpty()){
            ItemImg savedItemImg = itemImgRepository.findById(itemImgId)
                    .orElseThrow(EntityNotFoundException::new);

            //기존 이미지 파일 삭제
            if(!StringUtils.isEmpty(savedItemImg.getImgName())) {
                fileService.deleteFile(itemImgLocation+"/"+
                        savedItemImg.getImgName());
            }

            String oriImgName = itemImgFile.getOriginalFilename();
            String imgName = fileService.uploadFile(itemImgLocation, oriImgName, itemImgFile.getBytes());
            String imgUrl = "/images/item/" + imgName;
            savedItemImg.updateItemImg(oriImgName, imgName, imgUrl);
        }
    }

}

================================================
FILE: src/main/java/com/shop/service/ItemService.java
================================================
package com.shop.service;

import com.shop.dto.ItemFormDto;
import com.shop.entity.Item;
import com.shop.entity.ItemImg;
import com.shop.repository.ItemImgRepository;
import com.shop.repository.ItemRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

import com.shop.dto.ItemImgDto;
import jakarta.persistence.EntityNotFoundException;
import java.util.ArrayList;

import com.shop.dto.ItemSearchDto;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import com.shop.dto.MainItemDto;

@Service
@Transactional
@RequiredArgsConstructor
public class ItemService {

    private final ItemRepository itemRepository;

    private final ItemImgService itemImgService;

    private final ItemImgRepository itemImgRepository;

    public Long saveItem(ItemFormDto itemFormDto, List<MultipartFile> itemImgFileList) throws Exception{

        //상품 등록
        Item item = itemFormDto.createItem();
        itemRepository.save(item);

        //이미지 등록
        for(int i=0;i<itemImgFileList.size();i++){
            ItemImg itemImg = new ItemImg();
            itemImg.setItem(item);

            if(i == 0)
                itemImg.setRepimgYn("Y");
            else
                itemImg.setRepimgYn("N");

            itemImgService.saveItemImg(itemImg, itemImgFileList.get(i));
        }

        return item.getId();
    }

    @Transactional(readOnly = true)
    public ItemFormDto getItemDtl(Long itemId){
        List<ItemImg> itemImgList = itemImgRepository.findByItemIdOrderByIdAsc(itemId);
        List<ItemImgDto> itemImgDtoList = new ArrayList<>();
        for (ItemImg itemImg : itemImgList) {
            ItemImgDto itemImgDto = ItemImgDto.of(itemImg);
            itemImgDtoList.add(itemImgDto);
        }

        Item item = itemRepository.findById(itemId)
                .orElseThrow(EntityNotFoundException::new);
        ItemFormDto itemFormDto = ItemFormDto.of(item);
        itemFormDto.setItemImgDtoList(itemImgDtoList);
        return itemFormDto;
    }

    public Long updateItem(ItemFormDto itemFormDto, List<MultipartFile> itemImgFileList) throws Exception{
        //상품 수정
        Item item = itemRepository.findById(itemFormDto.getId())
                .orElseThrow(EntityNotFoundException::new);
        item.updateItem(itemFormDto);
        List<Long> itemImgIds = itemFormDto.getItemImgIds();

        //이미지 등록
        for(int i=0;i<itemImgFileList.size();i++){
            itemImgService.updateItemImg(itemImgIds.get(i),
                    itemImgFileList.get(i));
        }

        return item.getId();
    }

    @Transactional(readOnly = true)
    public Page<Item> getAdminItemPage(ItemSearchDto itemSearchDto, Pageable pageable){
        return itemRepository.getAdminItemPage(itemSearchDto, pageable);
    }

    @Transactional(readOnly = true)
    public Page<MainItemDto> getMainItemPage(ItemSearchDto itemSearchDto, Pageable pageable){
        return itemRepository.getMainItemPage(itemSearchDto, pageable);
    }

}

================================================
FILE: src/main/java/com/shop/service/MemberService.java
================================================
package com.shop.service;

import com.shop.entity.Member;
import com.shop.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

@Service
@Transactional
@RequiredArgsConstructor
public class MemberService implements UserDetailsService {

    private final MemberRepository memberRepository;

    public Member saveMember(Member member){
        validateDuplicateMember(member);
        return memberRepository.save(member);
    }

    private void validateDuplicateMember(Member member){
        Member findMember = memberRepository.findByEmail(member.getEmail());
        if(findMember != null){
            throw new IllegalStateException("이미 가입된 회원입니다.");
        }
    }

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {

        Member member = memberRepository.findByEmail(email);

        if(member == null){
            throw new UsernameNotFoundException(email);
        }

        return User.builder()
                .username(member.getEmail())
                .password(member.getPassword())
                .roles(member.getRole().toString())
                .build();
    }

}

================================================
FILE: src/main/java/com/shop/service/OrderService.java
================================================
package com.shop.service;

import com.shop.dto.OrderDto;
import com.shop.entity.*;
import com.shop.repository.ItemRepository;
import com.shop.repository.MemberRepository;
import com.shop.repository.OrderRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import jakarta.persistence.EntityNotFoundException;
import java.util.ArrayList;
import java.util.List;

import com.shop.dto.OrderHistDto;
import com.shop.dto.OrderItemDto;
import com.shop.repository.ItemImgRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;

import org.thymeleaf.util.StringUtils;

@Service
@Transactional
@RequiredArgsConstructor
public class OrderService {

    private final ItemRepository itemRepository;

    private final MemberRepository memberRepository;

    private final OrderRepository orderRepository;

    private final ItemImgRepository itemImgRepository;

    public Long order(OrderDto orderDto, String email){

        Item item = itemRepository.findById(orderDto.getItemId())
                .orElseThrow(EntityNotFoundException::new);

        Member member = memberRepository.findByEmail(email);

        List<OrderItem> orderItemList = new ArrayList<>();
        OrderItem orderItem = OrderItem.createOrderItem(item, orderDto.getCount());
        orderItemList.add(orderItem);
        Order order = Order.createOrder(member, orderItemList);
        orderRepository.save(order);

        return order.getId();
    }

    @Transactional(readOnly = true)
    public Page<OrderHistDto> getOrderList(String email, Pageable pageable) {

        List<Order> orders = orderRepository.findOrders(email, pageable);
        Long totalCount = orderRepository.countOrder(email);

        List<OrderHistDto> orderHistDtos = new ArrayList<>();

        for (Order order : orders) {
            OrderHistDto orderHistDto = new OrderHistDto(order);
            List<OrderItem> orderItems = order.getOrderItems();
            for (OrderItem orderItem : orderItems) {
                ItemImg itemImg = itemImgRepository.findByItemIdAndRepimgYn
                        (orderItem.getItem().getId(), "Y");
                OrderItemDto orderItemDto =
                        new OrderItemDto(orderItem, itemImg.getImgUrl());
                orderHistDto.addOrderItemDto(orderItemDto);
            }

            orderHistDtos.add(orderHistDto);
        }

        return new PageImpl<OrderHistDto>(orderHistDtos, pageable, totalCount);
    }

    @Transactional(readOnly = true)
    public boolean validateOrder(Long orderId, String email){
        Member curMember = memberRepository.findByEmail(email);
        Order order = orderRepository.findById(orderId)
                .orElseThrow(EntityNotFoundException::new);
        Member savedMember = order.getMember();

        if(!StringUtils.equals(curMember.getEmail(), savedMember.getEmail())){
            return false;
        }

        return true;
    }

    public void cancelOrder(Long orderId){
        Order order = orderRepository.findById(orderId)
                .orElseThrow(EntityNotFoundException::new);
        order.cancelOrder();
    }

    public Long orders(List<OrderDto> orderDtoList, String email){

        Member member = memberRepository.findByEmail(email);
        List<OrderItem> orderItemList = new ArrayList<>();

        for (OrderDto orderDto : orderDtoList) {
            Item item = itemRepository.findById(orderDto.getItemId())
                    .orElseThrow(EntityNotFoundException::new);

            OrderItem orderItem = OrderItem.createOrderItem(item, orderDto.getCount());
            orderItemList.add(orderItem);
        }

        Order order = Order.createOrder(member, orderItemList);
        orderRepository.save(order);

        return order.getId();
    }

}

================================================
FILE: src/main/resources/application-test.properties
================================================
# Datasource 설정
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:test
spring.datasource.username=sa
spring.datasource.password=

# H2 데이터베이스 방언 설정
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

spring.jpa.hibernate.ddl-auto=create

================================================
FILE: src/main/resources/application.properties
================================================
#애플리케이션 포트 설정
server.port = 80

#MySQL 연결 설정
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shop?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=1234

#실행되는 쿼리 콘솔 출력
spring.jpa.properties.hibernate.show_sql=true

#콘솔창에 출력되는 쿼리를 가독성이 좋게 포맷팅
spring.jpa.properties.hibernate.format_sql=true

#쿼리에 물음표로 출력되는 바인드 파라미터 출력
logging.level.org.hibernate.type.descriptor.sql=trace

spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect

#Live Reload 기능 활성화
spring.devtools.livereload.enabled=true

#Thymeleaf cache 사용 중지
spring.thymeleaf.cache = false

#파일 한 개당 최대 사이즈
spring.servlet.multipart.maxFileSize=20MB
#요청당 최대 파일 크기
spring.servlet.multipart.maxRequestSize=100MB
#상품 이미지 업로드 경로
itemImgLocation=C:/shop/item
#리소스 업로드 경로
uploadPath=file:///C:/shop/

#기본 batch size 설정
spring.jpa.properties.hibernate.default_batch_fetch_size=1000

================================================
FILE: src/main/resources/static/css/layout1.css
================================================
html {
    position: relative;
    min-height: 100%;
    margin: 0;
}
body {
    min-height: 100%;
}
.footer {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    padding: 15px 0;
    text-align: center;
}
.content{
    margin-bottom:100px;
    margin-top: 50px;
    margin-left: 200px;
    margin-right: 200px;
}

================================================
FILE: src/main/resources/templates/cart/cartList.html
================================================
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layouts/layout1}">

<head>
    <meta name="_csrf" th:content="${_csrf.token}"/>
    <meta name="_csrf_header" th:content="${_csrf.headerName}"/>
</head>

<!-- 사용자 스크립트 추가 -->
<th:block layout:fragment="script">

    <script th:inline="javascript">

        $(document).ready(function(){
            $("input[name=cartChkBox]").change( function(){
                getOrderTotalPrice();
            });
        });

        function getOrderTotalPrice(){
            var orderTotalPrice = 0;
            $("input[name=cartChkBox]:checked").each(function() {
                var cartItemId = $(this).val();
                var price = $("#price_" + cartItemId).attr("data-price");
                var count = $("#count_" + cartItemId).val();
                orderTotalPrice += price*count;
            });

            $("#orderTotalPrice").html(orderTotalPrice+'원');
        }

        function changeCount(obj){
            var count = obj.value;
            var cartItemId = obj.id.split('_')[1];
            var price = $("#price_" + cartItemId).data("price");
            var totalPrice = count*price;
            $("#totalPrice_" + cartItemId).html(totalPrice+"원");
            getOrderTotalPrice();
            updateCartItemCount(cartItemId, count);
        }

        function checkAll(){
            if($("#checkall").prop("checked")){
                $("input[name=cartChkBox]").prop("checked",true);
            }else{
                $("input[name=cartChkBox]").prop("checked",false);
            }
            getOrderTotalPrice();
        }

        function updateCartItemCount(cartItemId, count){
            var token = $("meta[name='_csrf']").attr("content");
            var header = $("meta[name='_csrf_header']").attr("content");

            var url = "/cartItem/" + cartItemId+"?count=" + count;

            $.ajax({
                url      : url,
                type     : "PATCH",
                beforeSend : function(xhr){
                    /* 데이터를 전송하기 전에 헤더에 csrf값을 설정 */
                    xhr.setRequestHeader(header, token);
                },
                dataType : "json",
                cache   : false,
                success  : function(result, status){
                    console.log("cartItem count update success");
                },
                error : function(jqXHR, status, error){

                    if(jqXHR.status == '401'){
                        alert('로그인 후 이용해주세요');
                        location.href='/members/login';
                    } else{
                        alert(jqXHR.responseJSON.message);
                    }

                }
            });
        }

        function deleteCartItem(obj){
            var cartItemId = obj.dataset.id;
            var token = $("meta[name='_csrf']").attr("content");
            var header = $("meta[name='_csrf_header']").attr("content");

            var url = "/cartItem/" + cartItemId;

            $.ajax({
                url      : url,
                type     : "DELETE",
                beforeSend : function(xhr){
                    /* 데이터를 전송하기 전에 헤더에 csrf값을 설정 */
                    xhr.setRequestHeader(header, token);
                },
                dataType : "json",
                cache   : false,
                success  : function(result, status){
                    location.href='/cart';
                },
                error : function(jqXHR, status, error){

                    if(jqXHR.status == '401'){
                        alert('로그인 후 이용해주세요');
                        location.href='/members/login';
                    } else{
                        alert(jqXHR.responseJSON.message);
                    }

                }
            });
        }

        function orders(){
            var token = $("meta[name='_csrf']").attr("content");
            var header = $("meta[name='_csrf_header']").attr("content");

            var url = "/cart/orders";

            var dataList = new Array();
            var paramData = new Object();

            $("input[name=cartChkBox]:checked").each(function() {
                var cartItemId = $(this).val();
                var data = new Object();
                data["cartItemId"] = cartItemId;
                dataList.push(data);
            });

            paramData['cartOrderDtoList'] = dataList;

            var param = JSON.stringify(paramData);

            $.ajax({
                url      : url,
                type     : "POST",
                contentType : "application/json",
                data     : param,
                beforeSend : function(xhr){
                    /* 데이터를 전송하기 전에 헤더에 csrf값을 설정 */
                    xhr.setRequestHeader(header, token);
                },
                dataType : "json",
                cache   : false,
                success  : function(result, status){
                    alert("주문이 완료 되었습니다.");
                    location.href='/orders';
                },
                error : function(jqXHR, status, error){

                    if(jqXHR.status == '401'){
                        alert('로그인 후 이용해주세요');
                        location.href='/members/login';
                    } else{
                        alert(jqXHR.responseJSON.message);
                    }

                }
            });
        }

    </script>

</th:block>

<!-- 사용자 CSS 추가 -->
<th:block layout:fragment="css">
    <style>
        .content-mg{
            margin-left:25%;
            margin-right:25%;
            margin-top:2%;
            margin-bottom:100px;
        }
        .repImgDiv{
            margin-right:15px;
            margin-left:15px;
            height:auto;
        }
        .repImg{
            height:100px;
            width:100px;
        }
        .fs18{
            font-size:18px
        }
        .fs24{
            font-size:24px
        }
    </style>
</th:block>

<div layout:fragment="content" class="content-mg">

    <h2 class="mb-4">
        장바구니 목록
    </h2>

    <div>

        <table class="table">
            <colgroup>
                <col width="15%"/>
                <col width="70%"/>
                <col width="15%"/>
            </colgroup>
            <thead>
            <tr class="text-center">
                <td>
                    <input type="checkbox" id="checkall" onclick="checkAll()"> 전체선택
                </td>
                <td>상품정보</td>
                <td>상품금액</td>
            </tr>
            </thead>
            <tbody>
            <tr th:each="cartItem : ${cartItems}">
                <td class="text-center align-middle">
                    <input type="checkbox" name="cartChkBox" th:value="${cartItem.cartItemId}">
                </td>
                <td class="d-flex">
                    <div class="repImgDiv align-self-center">
                        <img th:src="${cartItem.imgUrl}" class = "rounded repImg" th:alt="${cartItem.itemNm}">
                    </div>
                    <div class="align-self-center">
                        <span th:text="${cartItem.itemNm}" class="fs24 font-weight-bold"></span>
                        <div class="fs18 font-weight-light">
                            <span class="input-group mt-2">
                                <span th:id="'price_' + ${cartItem.cartItemId}"
                                      th:data-price="${cartItem.price}"
                                      th:text="${cartItem.price} + '원'" class="align-self-center mr-2">
                                </span>
                                <input type="number" name="count" th:id="'count_' + ${cartItem.cartItemId}"
                                       th:value="${cartItem.count}" min="1"
                                       onchange="changeCount(this)" class="form-control mr-2" >
                                <button type="button" class="close" aria-label="Close">
                                    <span aria-hidden="true" th:data-id="${cartItem.cartItemId}" onclick="deleteCartItem(this)">&times;</span>
                                </button>
                            </span>
                        </div>
                    </div>
                </td>
                <td class="text-center align-middle">
                    <span th:id="'totalPrice_' + ${cartItem.cartItemId}"
                          name="totalPrice" th:text="${cartItem.price * cartItem.count} + '원'">
                    </span>
                </td>
            </tr>
            </tbody>
        </table>

        <h2 class="text-center">
            총 주문 금액 : <span id="orderTotalPrice" class="text-danger">0원</span>
        </h2>

        <div class="text-center mt-3">
            <button type="button" class="btn btn-primary btn-lg" onclick="orders()">주문하기</button>
        </div>

    </div>

</div>

</html>

================================================
FILE: src/main/resources/templates/fragments/footer.html
================================================
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <div class="footer" th:fragment="footer">
        <footer class="page-footer font-small cyan darken-3">
            <div class="footer-copyright text-center py-3">
                2020 Shopping Mall Example WebSite
            </div>
        </footer>
    </div>
</html>

================================================
FILE: src/main/resources/templates/fragments/header.html
================================================
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

<div th:fragment="header">
    <nav class="navbar navbar-expand-sm bg-primary navbar-dark">
        <button class="navbar-toggler" type="button" data-toggle="collapse"
                data-target="#navbarTogglerDemo03" aria-controls="navbarTogglerDemo03"
                aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <a class="navbar-brand" href="/">Shop</a>

        <div class="collapse navbar-collapse" id="navbarTogglerDemo03">
            <ul class="navbar-nav mr-auto mt-2 mt-lg-0">
                <li class="nav-item" sec:authorize="hasAnyAuthority('ROLE_ADMIN')">
                    <a class="nav-link" href="/admin/item/new">상품 등록</a>
                </li>
                <li class="nav-item" sec:authorize="hasAnyAuthority('ROLE_ADMIN')">
                    <a class="nav-link" href="/admin/items">상품 관리</a>
                </li>
                <li class="nav-item" sec:authorize="isAuthenticated()">
                    <a class="nav-link" href="/cart">장바구니</a>
                </li>
                <li class="nav-item" sec:authorize="isAuthenticated()">
                    <a class="nav-link" href="/orders">구매이력</a>
                </li>
                <li class="nav-item" sec:authorize="isAnonymous()">
                    <a class="nav-link" href="/members/login">로그인</a>
                </li>
                <li class="nav-item" sec:authorize="isAuthenticated()">
                    <a class="nav-link" href="/members/logout">로그아웃</a>
                </li>
            </ul>
            <form class="form-inline my-2 my-lg-0" th:action="@{/}" method="get">
                <input name="searchQuery" class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
                <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
            </form>
        </div>
    </nav>
</div>

</html>

================================================
FILE: src/main/resources/templates/item/itemDtl.html
================================================
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layouts/layout1}">

<head>
    <meta name="_csrf" th:content="${_csrf.token}"/>
    <meta name="_csrf_header" th:content="${_csrf.headerName}"/>
</head>

<!-- 사용자 스크립트 추가 -->
<th:block layout:fragment="script">
    <script th:inline="javascript">
        $(document).ready(function(){

            calculateToalPrice();

            $("#count").change( function(){
                calculateToalPrice();
            });
        });

        function calculateToalPrice(){
            var count = $("#count").val();
            var price = $("#price").val();
            var totalPrice = price*count;
            $("#totalPrice").html(totalPrice + '원');
        }

        function order(){
            var token = $("meta[name='_csrf']").attr("content");
            var header = $("meta[name='_csrf_header']").attr("content");

            var url = "/order";
            var paramData = {
                itemId : $("#itemId").val(),
                count : $("#count").val()
            };

            var param = JSON.stringify(paramData);

            $.ajax({
                url      : url,
                type     : "POST",
                contentType : "application/json",
                data     : param,
                beforeSend : function(xhr){
                    /* 데이터를 전송하기 전에 헤더에 csrf값을 설정 */
                    xhr.setRequestHeader(header, token);
                },
                dataType : "json",
                cache   : false,
                success  : function(result, status){
                    alert("주문이 완료 되었습니다.");
                    location.href='/';
                },
                error : function(jqXHR, status, error){

                    if(jqXHR.status == '401'){
                        alert('로그인 후 이용해주세요');
                        location.href='/members/login';
                    } else{
                        alert(jqXHR.responseText);
                    }

                }
            });
        }

        function addCart(){
            var token = $("meta[name='_csrf']").attr("content");
            var header = $("meta[name='_csrf_header']").attr("content");

            var url = "/cart";
            var paramData = {
                itemId : $("#itemId").val(),
                count : $("#count").val()
            };

            var param = JSON.stringify(paramData);

            $.ajax({
                url      : url,
                type     : "POST",
                contentType : "application/json",
                data     : param,
                beforeSend : function(xhr){
                    /* 데이터를 전송하기 전에 헤더에 csrf값을 설정 */
                    xhr.setRequestHeader(header, token);
                },
                dataType : "json",
                cache   : false,
                success  : function(result, status){
                    alert("상품을 장바구니에 담았습니다.");
                    location.href='/';
                },
                error : function(jqXHR, status, error){

                    if(jqXHR.status == '401'){
                        alert('로그인 후 이용해주세요');
                        location.href='/members/login';
                    } else{
                        alert(jqXHR.responseText);
                    }

                }
            });
        }

    </script>
</th:block>

<!-- 사용자 CSS 추가 -->
<th:block layout:fragment="css">
    <style>
        .mgb-15{
            margin-bottom:15px;
        }
        .mgt-30{
            margin-top:30px;
        }
        .mgt-50{
            margin-top:50px;
        }
        .repImgDiv{
            margin-right:15px;
            height:auto;
            width:50%;
        }
        .repImg{
            width:100%;
            height:400px;
        }
        .wd50{
            height:auto;
            width:50%;
        }
    </style>
</th:block>

<div layout:fragment="content" style="margin-left:25%;margin-right:25%">

    <input type="hidden" id="itemId" th:value="${item.id}">

    <div class="d-flex">
        <div class="repImgDiv">
            <img th:src="${item.itemImgDtoList[0].imgUrl}" class = "rounded repImg" th:alt="${item.itemNm}">
        </div>
        <div class="wd50">
            <span th:if="${item.itemSellStatus == T(com.shop.constant.ItemSellStatus).SELL}" class="badge badge-primary mgb-15">
                판매중
            </span>
            <span th:unless="${item.itemSellStatus == T(com.shop.constant.ItemSellStatus).SELL}" class="badge btn-danger mgb-15" >
                품절
            </span>
            <div class="h4" th:text="${item.itemNm}"></div>
            <hr class="my-4">

            <div class="text-right">
                <div class="h4 text-danger text-left">
                    <input type="hidden" th:value="${item.price}" id="price" name="price">
                    <span th:text="${item.price}"></span>원
                </div>
                <div class="input-group w-50">
                    <div class="input-group-prepend">
                        <span class="input-group-text">수량</span>
                    </div>
                    <input type="number" name="count" id="count" class="form-control" value="1" min="1">
                </div>
            </div>
            <hr class="my-4">

            <div class="text-right mgt-50">
                <h5>결제 금액</h5>
                <h3 name="totalPrice" id="totalPrice" class="font-weight-bold"></h3>
            </div>
            <div th:if="${item.itemSellStatus == T(com.shop.constant.ItemSellStatus).SELL}" class="text-right">
                <button type="button" class="btn btn-light border border-primary btn-lg" onclick="addCart()">장바구니 담기</button>
                <button type="button" class="btn btn-primary btn-lg" onclick="order()">주문하기</button>
            </div>
            <div th:unless="${item.itemSellStatus == T(com.shop.constant.ItemSellStatus).SELL}" class="text-right">
                <button type="button" class="btn btn-danger btn-lg">품절</button>
            </div>
        </div>
    </div>

    <div class="jumbotron jumbotron-fluid mgt-30">
        <div class="container">
            <h4 class="display-5">상품 상세 설명</h4>
            <hr class="my-4">
            <p class="lead" th:text="${item.itemDetail}"></p>
        </div>
    </div>

    <div th:each="itemImg : ${item.itemImgDtoList}" class="text-center">
        <img th:if="${not #strings.isEmpty(itemImg.imgUrl)}" th:src="${itemImg.imgUrl}" class="rounded mgb-15" width="800">
    </div>

</div>

</html>

================================================
FILE: src/main/resources/templates/item/itemForm.html
================================================
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layouts/layout1}">

<!-- 사용자 스크립트 추가 -->
<th:block layout:fragment="script">

    <script th:inline="javascript">
        $(document).ready(function(){
            var errorMessage = [[${errorMessage}]];
            if(errorMessage != null){
                alert(errorMessage);
            }

            bindDomEvent();

        });

        function bindDomEvent(){
            $(".custom-file-input").on("change", function() {
                var fileName = $(this).val().split("\\").pop();  //이미지 파일명
                var fileExt = fileName.substring(fileName.lastIndexOf(".")+1); // 확장자 추출
                fileExt = fileExt.toLowerCase(); //소문자 변환

                if(fileExt != "jpg" && fileExt != "jpeg" && fileExt != "gif" && fileExt != "png" && fileExt != "bmp"){
                    alert("이미지 파일만 등록이 가능합니다.");
                    return;
                }

                $(this).siblings(".custom-file-label").html(fileName);
            });
        }

    </script>

</th:block>

<!-- 사용자 CSS 추가 -->
<th:block layout:fragment="css">
    <style>
        .input-group {
            margin-bottom : 15px
        }
        .img-div {
            margin-bottom : 10px
        }
        .fieldError {
            color: #bd2130;
        }
    </style>
</th:block>

<div layout:fragment="content">

    <form role="form" method="post" enctype="multipart/form-data" th:object="${itemFormDto}">

        <p class="h2">
            상품 등록
        </p>

        <input type="hidden" th:field="*{id}">

        <div class="form-group">
            <select th:field="*{itemSellStatus}" class="custom-select">
                <option value="SELL">판매중</option>
                <option value="SOLD_OUT">품절</option>
            </select>
        </div>

        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text">상품명</span>
            </div>
            <input type="text" th:field="*{itemNm}" class="form-control" placeholder="상품명을 입력해주세요">
        </div>
        <p th:if="${#fields.hasErrors('itemNm')}" th:errors="*{itemNm}" class="fieldError">Incorrect data</p>

        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text">가격</span>
            </div>
            <input type="number" th:field="*{price}" class="form-control" placeholder="상품의 가격을 입력해주세요">
        </div>
        <p th:if="${#fields.hasErrors('price')}" th:errors="*{price}" class="fieldError">Incorrect data</p>

        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text">재고</span>
            </div>
            <input type="number" th:field="*{stockNumber}" class="form-control" placeholder="상품의 재고를 입력해주세요">
        </div>
        <p th:if="${#fields.hasErrors('stockNumber')}" th:errors="*{stockNumber}" class="fieldError">Incorrect data</p>

        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text">상품 상세 내용</span>
            </div>
            <textarea class="form-control" aria-label="With textarea" th:field="*{itemDetail}"></textarea>
        </div>
        <p th:if="${#fields.hasErrors('itemDetail')}" th:errors="*{itemDetail}" class="fieldError">Incorrect data</p>

        <div th:if="${#lists.isEmpty(itemFormDto.itemImgDtoList)}">
            <div class="form-group" th:each="num: ${#numbers.sequence(1,5)}">
                <div class="custom-file img-div">
                    <input type="file" class="custom-file-input" name="itemImgFile">
                    <label class="custom-file-label" th:text="상품이미지 + ${num}"></label>
                </div>
            </div>
        </div>

        <div th:if = "${not #lists.isEmpty(itemFormDto.itemImgDtoList)}">
            <div class="form-group" th:each="itemImgDto, status: ${itemFormDto.itemImgDtoList}">
                <div class="custom-file img-div">
                    <input type="file" class="custom-file-input" name="itemImgFile">
                    <input type="hidden" name="itemImgIds" th:value="${itemImgDto.id}">
                    <label class="custom-file-label" th:text="${not #strings.isEmpty(itemImgDto.oriImgName)} ? ${itemImgDto.oriImgName} : '상품이미지' + ${status.index+1}"></label>
                </div>
            </div>
        </div>

        <div th:if="${#strings.isEmpty(itemFormDto.id)}" style="text-align: center">
            <button th:formaction="@{/admin/item/new}" type="submit" class="btn btn-primary">저장</button>
        </div>
        <div th:unless="${#strings.isEmpty(itemFormDto.id)}" style="text-align: center">
            <button th:formaction="@{'/admin/item/' + ${itemFormDto.id} }" type="submit" class="btn btn-primary">수정</button>
        </div>
        <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">

    </form>

</div>

</html>

================================================
FILE: src/main/resources/templates/item/itemMng.html
================================================
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layouts/layout1}">

<!-- 사용자 스크립트 추가 -->
<th:block layout:fragment="script">
    <script th:inline="javascript">

        $(document).ready(function(){
            $("#searchBtn").on("click",function(e) {
                e.preventDefault();
                page(0);
            });
        });

        function page(page){
            var searchDateType = $("#searchDateType").val();
            var searchSellStatus = $("#searchSellStatus").val();
            var searchBy = $("#searchBy").val();
            var searchQuery = $("#searchQuery").val();

            location.href="/admin/items/" + page + "?searchDateType=" + searchDateType
            + "&searchSellStatus=" + searchSellStatus
            + "&searchBy=" + searchBy
            + "&searchQuery=" + searchQuery;
        }

    </script>
</th:block>

<!-- 사용자 CSS 추가 -->
<th:block layout:fragment="css">
    <style>
        select{
            margin-right:10px;
        }
    </style>
</th:block>

<div layout:fragment="content">

    <form th:action="@{'/admin/items/' + ${items.number}}" role="form" method="get" th:object="${items}">
        <table class="table">
            <thead>
            <tr>
                <td>상품아이디</td>
                <td>상품명</td>
                <td>상태</td>
                <td>등록자</td>
                <td>등록일</td>
            </tr>
            </thead>
            <tbody>
            <tr th:each="item, status: ${items.getContent()}">
                <td th:text="${item.id}"></td>
                <td>
                    <a th:href="'/admin/item/'+${item.id}" th:text="${item.itemNm}"></a>
                </td>
                <td th:text="${item.itemSellStatus == T(com.shop.constant.ItemSellStatus).SELL} ? '판매중' : '품절'"></td>
                <td th:text="${item.createdBy}"></td>
                <td th:text="${item.regTime}"></td>
            </tr>
            </tbody>
        </table>

        <div th:with="start=${(items.number/maxPage)*maxPage + 1}, end=(${(items.totalPages == 0) ? 1 : (start + (maxPage - 1) < items.totalPages ? start + (maxPage - 1) : items.totalPages)})" >
            <ul class="pagination justify-content-center">

                <li class="page-item" th:classappend="${items.first}?'disabled'">
                    <a th:onclick="'javascript:page(' + ${items.number - 1} + ')'" aria-label='Previous' class="page-link">
                        <span aria-hidden='true'>Previous</span>
                    </a>
                </li>

                <li class="page-item" th:each="page: ${#numbers.sequence(start, end)}" th:classappend="${items.number eq page-1}?'active':''">
                    <a th:onclick="'javascript:page(' + ${page - 1} + ')'" th:inline="text" class="page-link">[[${page}]]</a>
                </li>

                <li class="page-item" th:classappend="${items.last}?'disabled'">
                    <a th:onclick="'javascript:page(' + ${items.number + 1} + ')'" aria-label='Next' class="page-link">
                        <span aria-hidden='true'>Next</span>
                    </a>
                </li>

            </ul>
        </div>

        <div class="form-inline justify-content-center" th:object="${itemSearchDto}">
            <select th:field="*{searchDateType}" class="form-control" style="width:auto;">
                <option value="all">전체기간</option>
                <option value="1d">1일</option>
                <option value="1w">1주</option>
                <option value="1m">1개월</option>
                <option value="6m">6개월</option>
            </select>
            <select th:field="*{searchSellStatus}" class="form-control" style="width:auto;">
                <option value="">판매상태(전체)</option>
                <option value="SELL">판매</option>
                <option value="SOLD_OUT">품절</option>
            </select>
            <select th:field="*{searchBy}" class="form-control" style="width:auto;">
                <option value="itemNm">상품명</option>
                <option value="createdBy">등록자</option>
            </select>
            <input th:field="*{searchQuery}" type="text" class="form-control" placeholder="검색어를 입력해주세요">
            <button id="searchBtn" type="submit" class="btn btn-primary">검색</button>
        </div>
    </form>

</div>

</html>

================================================
FILE: src/main/resources/templates/layouts/layout1.html
================================================
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

    <!-- CSS only -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <link th:href="@{/css/layout1.css}" rel="stylesheet">

    <!-- JS, Popper.js, and jQuery -->
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>

    <th:block layout:fragment="script"></th:block>
    <th:block layout:fragment="css"></th:block>

</head>
<body>

    <div th:replace="~{fragments/header::header}"></div>

    <div layout:fragment="content" class="content">

    </div>

    <div th:replace="~{fragments/footer::footer}"></div>

</body>
</html>

================================================
FILE: src/main/resources/templates/main.html
================================================
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layouts/layout1}">

<!-- 사용자 CSS 추가 -->
<th:block layout:fragment="css">
    <style>
        .carousel-inner > .item {
            height: 350px;
        }
        .margin{
            margin-bottom:30px;
        }
        .banner{
            height: 300px;
            position: absolute; top:0; left: 0;
            width: 100%;
            height: 100%;
        }
        .card-text{
            text-overflow: ellipsis;
            white-space: nowrap;
            overflow: hidden;
        }
        a:hover{
            text-decoration:none;
        }
        .center{
            text-align:center;
        }
    </style>
</th:block>

<div layout:fragment="content">

    <div id="carouselControls" class="carousel slide margin" data-ride="carousel">
        <div class="carousel-inner">
            <div class="carousel-item active item">
                <img class="d-block w-100 banner" src="https://user-images.githubusercontent.com/13268420/112147492-1ab76200-8c20-11eb-8649-3d2f282c3c02.png" alt="First slide">
            </div>
        </div>
    </div>

    <input type="hidden" name="searchQuery" th:value="${itemSearchDto.searchQuery}">
    <div th:if="${not #strings.isEmpty(itemSearchDto.searchQuery)}" class="center">
        <p class="h3 font-weight-bold" th:text="${itemSearchDto.searchQuery} + '검색 결과'"></p>
    </div>

    <div class="row">
        <th:block th:each="item, status: ${items.getContent()}">
            <div class="col-md-4 margin">
                <div class="card">
                    <a th:href="'/item/' +${item.id}" class="text-dark">
                        <img th:src="${item.imgUrl}" class="card-img-top" th:alt="${item.itemNm}" height="400">
                        <div class="card-body">
                            <h4 class="card-title">[[${item.itemNm}]]</h4>
                            <p class="card-text">[[${item.itemDetail}]]</p>
                            <h3 class="card-title text-danger">[[${item.price}]]원</h3>
                        </div>
                    </a>
                </div>
            </div>
        </th:block>
    </div>

    <div th:with="start=${(items.number/maxPage)*maxPage + 1}, end=(${(items.totalPages == 0) ? 1 : (start + (maxPage - 1) < items.totalPages ? start + (maxPage - 1) : items.totalPages)})" >
        <ul class="pagination justify-content-center">

            <li class="page-item" th:classappend="${items.number eq 0}?'disabled':''">
                <a th:href="@{'/' + '?searchQuery=' + ${itemSearchDto.searchQuery} + '&page=' + ${items.number-1}}" aria-label='Previous' class="page-link">
                    <span aria-hidden='true'>Previous</span>
                </a>
            </li>

            <li class="page-item" th:each="page: ${#numbers.sequence(start, end)}" th:classappend="${items.number eq page-1}?'active':''">
                <a th:href="@{'/' +'?searchQuery=' + ${itemSearchDto.searchQuery} + '&page=' + ${page-1}}" th:inline="text" class="page-link">[[${page}]]</a>
            </li>

            <li class="page-item" th:classappend="${items.number+1 ge items.totalPages}?'disabled':''">
                <a th:href="@{'/' +'?searchQuery=' + ${itemSearchDto.searchQuery} + '&page=' + ${items.number+1}}" aria-label='Next' class="page-link">
                    <span aria-hidden='true'>Next</span>
                </a>
            </li>

        </ul>
    </div>

</div>

================================================
FILE: src/main/resources/templates/member/memberForm.html
================================================
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layouts/layout1}">

<!-- 사용자 CSS 추가 -->
<th:block layout:fragment="css">
    <style>
        .fieldError {
            color: #bd2130;
        }
    </style>
</th:block>

<!-- 사용자 스크립트 추가 -->
<th:block layout:fragment="script">

    <script th:inline="javascript">
        $(document).ready(function(){
            var errorMessage = [[${errorMessage}]];
            if(errorMessage != null){
                alert(errorMessage);
            }
        });
    </script>

</th:block>

<div layout:fragment="content">

    <form action="/members/new" role="form" method="post"  th:object="${memberFormDto}">
        <div class="form-group">
            <label th:for="name">이름</label>
            <input type="text" th:field="*{name}" class="form-control" placeholder="이름을 입력해주세요">
            <p th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="fieldError">Incorrect data</p>
        </div>
        <div class="form-group">
            <label th:for="email">이메일주소</label>
            <input type="email" th:field="*{email}" class="form-control" placeholder="이메일을 입력해주세요">
            <p th:if="${#fields.hasErrors('email')}" th:errors="*{email}" class="fieldError">Incorrect data</p>
        </div>
        <div class="form-group">
            <label th:for="password">비밀번호</label>
            <input type="password" th:field="*{password}" class="form-control" placeholder="비밀번호 입력">
            <p th:if="${#fields.hasErrors('password')}" th:errors="*{password}" class="fieldError">Incorrect data</p>
        </div>
        <div class="form-group">
            <label th:for="address">주소</label>
            <input type="text" th:field="*{address}" class="form-control" placeholder="주소를 입력해주세요">
            <p th:if="${#fields.hasErrors('address')}" th:errors="*{address}" class="fieldError">Incorrect data</p>
        </div>
        <div style="text-align: center">
            <button type="submit" class="btn btn-primary" style="">Submit</button>
        </div>
        <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
    </form>

</div>

</html>

================================================
FILE: src/main/resources/templates/member/memberLoginForm.html
================================================
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layouts/layout1}">

<!-- 사용자 CSS 추가 -->
<th:block layout:fragment="css">
  <style>
        .error {
            color: #bd2130;
        }
    </style>
</th:block>

<div layout:fragment="content">

  <form role="form" method="post" action="/members/login">
    <div class="form-group">
      <label th:for="email">이메일주소</label>
      <input type="email" name="email" class="form-control" placeholder="이메일을 입력해주세요">
    </div>
    <div class="form-group">
      <label th:for="password">비밀번호</label>
      <input type="password" name="password" id="password" class="form-control" placeholder="비밀번호 입력">
    </div>
    <p th:if="${loginErrorMsg}" class="error" th:text="${loginErrorMsg}"></p>
    <button class="btn btn-primary">로그인</button>
    <button type="button" class="btn btn-primary" onClick="location.href='/members/new'">회원가입</button>
    <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
  </form>

</div>

</html>

================================================
FILE: src/main/resources/templates/order/orderHist.html
================================================
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layouts/layout1}">

<head>
    <meta name="_csrf" th:content="${_csrf.token}"/>
    <meta name="_csrf_header" th:content="${_csrf.headerName}"/>
</head>

<!-- 사용자 스크립트 추가 -->
<th:block layout:fragment="script">

    <script th:inline="javascript">
        function cancelOrder(orderId) {
            var token = $("meta[name='_csrf']").attr("content");
            var header = $("meta[name='_csrf_header']").attr("content");

            var url = "/order/" + orderId + "/cancel";
            var paramData = {
                orderId : orderId,
            };

            var param = JSON.stringify(paramData);

            $.ajax({
                url      : url,
                type     : "POST",
                contentType : "application/json",
                data     : param,
                beforeSend : function(xhr){
                    /* 데이터를 전송하기 전에 헤더에 csrf값을 설정 */
                    xhr.setRequestHeader(header, token);
                },
                dataType : "json",
                cache   : false,
                success  : function(result, status){
                    alert("주문이 취소 되었습니다.");
                    location.href='/orders/' + [[${page}]];
                },
                error : function(jqXHR, status, error){
                    if(jqXHR.status == '401'){
                        alert('로그인 후 이용해주세요');
                        location.href='/members/login';
                    } else{
                        alert(jqXHR.responseText);
                    }
                }
            });
        }
    </script>

</th:block>

<!-- 사용자 CSS 추가 -->
<th:block layout:fragment="css">
    <style>
        .content-mg{
            margin-left:30%;
            margin-right:30%;
            margin-top:2%;
            margin-bottom:100px;
        }
        .repImgDiv{
            margin-right:15px;
            margin-left:15px;
            height:auto;
        }
        .repImg{
            height:100px;
            width:100px;
        }
        .card{
            width:750px;
            height:100%;
            padding:30px;
            margin-bottom:20px;
        }
        .fs18{
            font-size:18px
        }
        .fs24{
            font-size:24px
        }
    </style>
</th:block>

<div layout:fragment="content" class="content-mg">

    <h2 class="mb-4">
        구매 이력
    </h2>

    <div th:each="order : ${orders.getContent()}">

        <div class="d-flex mb-3 align-self-center">
            <h4 th:text="${order.orderDate} + ' 주문'"></h4>
            <div class="ml-3">
                <th:block th:if="${order.orderStatus == T(com.shop.constant.OrderStatus).ORDER}">
                    <button type="button" class="btn btn-outline-secondary" th:value="${order.orderId}" onclick="cancelOrder(this.value)">주문취소</button>
                </th:block>
                <th:block th:unless="${order.orderStatus == T(com.shop.constant.OrderStatus).ORDER}">
                    <h4>(취소 완료)</h4>
                </th:block>
            </div>
        </div>
        <div class="card d-flex">
            <div th:each="orderItem : ${order.orderItemDtoList}" class="d-flex mb-3">
                <div class="repImgDiv">
                    <img th:src="${orderItem.imgUrl}" class = "rounded repImg" th:alt="${orderItem.itemNm}">
                </div>
                <div class="align-self-center w-75">
                    <span th:text="${orderItem.itemNm}" class="fs24 font-weight-bold"></span>
                    <div class="fs18 font-weight-light">
                        <span th:text="${orderItem.orderPrice} +'원'"></span>
                        <span th:text="${orderItem.count} +'개'"></span>
                    </div>
                </div>
            </div>
        </div>

    </div>

    <div th:with="start=${(orders.number/maxPage)*maxPage + 1}, end=(${(orders.totalPages == 0) ? 1 : (start + (maxPage - 1) < orders.totalPages ? start + (maxPage - 1) : orders.totalPages)})" >
        <ul class="pagination justify-content-center">

            <li class="page-item" th:classappend="${orders.number eq 0}?'disabled':''">
                <a th:href="@{'/orders/' + ${orders.number-1}}" aria-label='Previous' class="page-link">
                    <span aria-hidden='true'>Previous</span>
                </a>
            </li>

            <li class="page-item" th:each="page: ${#numbers.sequence(start, end)}" th:classappend="${orders.number eq page-1}?'active':''">
                <a th:href="@{'/orders/' + ${page-1}}" th:inline="text" class="page-link">[[${page}]]</a>
            </li>

            <li class="page-item" th:classappend="${orders.number+1 ge orders.totalPages}?'disabled':''">
                <a th:href="@{'/orders/' + ${orders.number+1}}" aria-label='Next' class="page-link">
                    <span aria-hidden='true'>Next</span>
                </a>
            </li>

        </ul>
    </div>

</div>

</html>

================================================
FILE: src/main/resources/templates/thymeleafEx/thymeleafEx01.html
================================================
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
    <p th:text="${data}">Hello Thymeleaf!!</p>
</body>
</html>

================================================
FILE: src/main/resources/templates/thymeleafEx/thymeleafEx02.html
================================================
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>상품 데이터 출력 예제</h1>
    <div>
        상품명 : <span th:text="${itemDto.itemNm}"></span>
    </div>
    <div>
        상품상세설명 : <span th:text="${itemDto.itemDetail}"></span>
    </div>
    <div>
        상품등록일 : <span th:text="${itemDto.regTime}"></span>
    </div>
    <div>
        상품가격 : <span th:text="${itemDto.price}"></span>
    </div>
</body>
</html>

================================================
FILE: src/main/resources/templates/thymeleafEx/thymeleafEx03.html
================================================
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>상품 리스트 출력 예제</h1>

<table border="1">
    <thead>
    <tr>
        <td>순번</td>
        <td>상품명</td>
        <td>상품설명</td>
        <td>가격</td>
        <td>상품등록일</td>
    </tr>
    </thead>
    <tbody>
    <tr th:each="itemDto, status: ${itemDtoList}">
        <td th:text="${status.index}"></td>
        <td th:text="${itemDto.itemNm}"></td>
        <td th:text="${itemDto.itemDetail}"></td>
        <td th:text="${itemDto.price}"></td>
        <td th:text="${itemDto.regTime}"></td>
    </tr>
    </tbody>
</table>

</body>
</html>

================================================
FILE: src/main/resources/templates/thymeleafEx/thymeleafEx04.html
================================================
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>상품 리스트 출력 예제</h1>

<table border="1">
    <thead>
    <tr>
        <td>순번</td>
        <td>상품명</td>
        <td>상품설명</td>
        <td>가격</td>
        <td>상품등록일</td>
    </tr>
    </thead>
    <tbody>
    <tr th:each="itemDto, status: ${itemDtoList}">
        <td th:switch="${status.even}">
            <span th:case=true>짝수</span>
            <span th:case=false>홀수</span>
        </td>
        <td th:text="${itemDto.itemNm}"></td>
        <td th:text="${itemDto.itemDetail}"></td>
        <td th:text="${itemDto.price}"></td>
        <td th:text="${itemDto.regTime}"></td>
    </tr>
    </tbody>
</table>

</body>
</html>

================================================
FILE: src/main/resources/templates/thymeleafEx/thymeleafEx05.html
================================================
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>Thymeleaf 링크처리 예제 페이지</h1>
    <div>
        <a th:href="@{/thymeleaf/ex01}">예제1 페이지 이동</a>
    </div>
    <div>
        <a th:href="@{https://www.thymeleaf.org/}">thymeleaf 공식 페이지 이동</a>
    </div>
    <div>
        <a th:href="@{/thymeleaf/ex06(param1 = '파라미터 데이터1', param2 = '파라미터 데이터2')}">thymeleaf 파라미터 전달</a>
    </div>
</body>
</html>

================================================
FILE: src/main/resources/templates/thymeleafEx/thymeleafEx06.html
================================================
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>파라미터 전달 예제</h1>
    <div th:text="${param1}"></div>
    <div th:text="${param2}"></div>
</body>
</html>

================================================
FILE: src/main/resources/templates/thymeleafEx/thymeleafEx07.html
================================================
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout=http://www.ultraq.net.nz/thymeleaf/layout
      layout:decorate="~{layouts/layout1}">

<div layout:fragment="content">
    본문 영역 입니다.
</div>

</html>

================================================
FILE: src/test/java/com/shop/ShopApplicationTests.java
================================================
package com.shop;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class ShopApplicationTests {

	@Test
	void contextLoads() {
	}

}


================================================
FILE: src/test/java/com/shop/controller/ItemControllerTest.java
================================================
package com.shop.controller;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
@TestPropertySource(locations="classpath:application-test.properties")
class ItemControllerTest {

    @Autowired
    MockMvc mockMvc;

    @Test
    @DisplayName("상품 등록 페이지 권한 테스트")
    @WithMockUser(username = "admin", roles = "ADMIN")
    public void itemFormTest() throws Exception{
        mockMvc.perform(MockMvcRequestBuilders.get("/admin/item/new"))
                .andDo(print())
                .andExpect(status().isOk());
    }

    @Test
    @DisplayName("상품 등록 페이지 일반 회원 접근 테스트")
    @WithMockUser(username = "user", roles = "USER")
    public void itemFormNotAdminTest() throws Exception{
        mockMvc.perform(MockMvcRequestBuilders.get("/admin/item/new"))
                .andDo(print())
                .andExpect(status().isForbidden());
    }
}

================================================
FILE: src/test/java/com/shop/controller/MemberControllerTest.java
================================================
package com.shop.controller;

import com.shop.dto.MemberFormDto;
import com.shop.entity.Member;
import com.shop.service.MemberService;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;

@SpringBootTest
@AutoConfigureMockMvc
@Transactional
@TestPropertySource(locations="classpath:application-test.properties")
class MemberControllerTest {

    @Autowired
    private MemberService memberService;

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    PasswordEncoder passwordEncoder;

    public Member createMember(String email, String password){
        MemberFormDto memberFormDto = new MemberFormDto();
        memberFormDto.setEmail(email);
        memberFormDto.setName("홍길동");
        memberFormDto.setAddress("서울시 마포구 합정동");
        memberFormDto.setPassword(password);
        Member member = Member.createMember(memberFormDto, passwordEncoder);
        return memberService.saveMember(member);
    }

    @Test
    @DisplayName("로그인 성공 테스트")
    public void loginSuccessTest() throws Exception{
        String email = "test@email.com";
        String password = "1234";
        this.createMember(email, password);
        mockMvc.perform(formLogin().userParameter("email")
                .loginProcessingUrl("/members/login")
                .user(email).password(password))
                .andExpect(SecurityMockMvcResultMatchers.authenticated());
    }

    @Test
    @DisplayName("로그인 실패 테스트")
    public void loginFailTest() throws Exception{
        String email = "test@email.com";
        String password = "1234";
        this.createMember(email, password);
        mockMvc.perform(formLogin().userParameter("email")
                .loginProcessingUrl("/members/login")
                .user(email).password("12345"))
                .andExpect(SecurityMockMvcResultMatchers.unauthenticated());
    }

}

================================================
FILE: src/test/java/com/shop/entity/CartTest.java
================================================
package com.shop.entity;

import com.shop.dto.MemberFormDto;
import com.shop.repository.CartRepository;
import com.shop.repository.MemberRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.annotation.Transactional;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityNotFoundException;
import jakarta.persistence.PersistenceContext;

import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBootTest
@Transactional
@TestPropertySource(locations="classpath:application-test.properties")

class CartTest {

    @Autowired
    CartRepository cartRepository;

    @Autowired
    MemberRepository memberRepository;

    @Autowired
    PasswordEncoder passwordEncoder;

    @PersistenceContext
    EntityManager em;

    public Member createMember(){
        MemberFormDto memberFormDto = new MemberFormDto();
        memberFormDto.setEmail("test@email.com");
        memberFormDto.setName("홍길동");
        memberFormDto.setAddress("서울시 마포구 합정동");
        memberFormDto.setPassword("1234");
        return Member.createMember(memberFormDto, passwordEncoder);
    }

    @Test
    @DisplayName("장바구니 회원 엔티티 매핑 조회 테스트")
    public void findCartAndMemberTest(){
        Member member = createMember();
        memberRepository.save(member);
        Cart cart = new Cart();
        cart.setMember(member);
        cartRepository.save(cart);

        em.flush();
        em.clear();

        Cart savedCart = cartRepository.findById(cart.getId())
                .orElseThrow(EntityNotFoundException::new);
        assertEquals(savedCart.getMember().getId(), member.getId());
    }

}

================================================
FILE: src/test/java/com/shop/entity/MemberTest.java
================================================
package com.shop.entity;

import com.shop.repository.MemberRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.annotation.Transactional;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityNotFoundException;
import jakarta.persistence.PersistenceContext;

@SpringBootTest
@Transactional
@TestPropertySource(locations="classpath:application-test.properties")
public class MemberTest {

    @Autowired
    MemberRepository memberRepository;

    @PersistenceContext
    EntityManager em;

    @Test
    @DisplayName("Auditing 테스트")
    @WithMockUser(username = "gildong", roles = "USER")
    public void auditingTest(){
        Member newMember = new Member();
        memberRepository.save(newMember);

        em.flush();
        em.clear();

        Member member = memberRepository.findById(newMember.getId())
                .orElseThrow(EntityNotFoundException::new);

        System.out.println("register time : " + member.getRegTime());
        System.out.println("update time : " + member.getUpdateTime());
        System.out.println("create member : " + member.getCreatedBy());
        System.out.println("modify member : " + member.getModifiedBy());
    }

}

================================================
FILE: src/test/java/com/shop/entity/OrderTest.java
================================================
package com.shop.entity;

import com.shop.constant.ItemSellStatus;
import com.shop.repository.ItemRepository;
import com.shop.repository.OrderRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.annotation.Transactional;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityNotFoundException;
import jakarta.persistence.PersistenceContext;
import java.time.LocalDateTime;

import static org.junit.jupiter.api.Assertions.assertEquals;
import com.shop.repository.MemberRepository;
import com.shop.repository.OrderItemRepository;

@SpringBootTest
@TestPropertySource(locations="classpath:application-test.properties")
@Transactional
class OrderTest {

    @Autowired
    OrderRepository orderRepository;

    @Autowired
    ItemRepository itemRepository;

    @PersistenceContext
    EntityManager em;

    @Autowired
    MemberRepository memberRepository;

    @Autowired
    OrderItemRepository orderItemRepository;

    public Item createItem(){
        Item item = new Item();
        item.setItemNm("테스트 상품");
        item.setPrice(10000);
        item.setItemDetail("상세설명");
        item.setItemSellStatus(ItemSellStatus.SELL);
        item.setStockNumber(100);
        item.setRegTime(LocalDateTime.now());

        item.setUpdateTime(LocalDateTime.now());
        return item;
    }

    @Test
    @DisplayName("영속성 전이 테스트")
    public void cascadeTest() {

        Order order = new Order();

        for(int i=0;i<3;i++){
            Item item = this.createItem();
            itemRepository.save(item);
            OrderItem orderItem = new OrderItem();
            orderItem.setItem(item);
            orderItem.setCount(10);
            orderItem.setOrderPrice(1000);
            orderItem.setOrder(order);
            order.getOrderItems().add(orderItem);
        }

        orderRepository.saveAndFlush(order);
        em.clear();

        Order savedOrder = orderRepository.findById(order.getId())
                .orElseThrow(EntityNotFoundException::new);
        assertEquals(3, savedOrder.getOrderItems().size());
    }

    public Order createOrder(){
        Order order = new Order();
        for(int i=0;i<3;i++){
            Item item = createItem();
            itemRepository.save(item);
            OrderItem orderItem = new OrderItem();
            orderItem.setItem(item);
            orderItem.setCount(10);
            orderItem.setOrderPrice(1000);
            orderItem.setOrder(order);
            order.getOrderItems().add(orderItem);
        }
        Member member = new Member();
        memberRepository.save(member);
        order.setMember(member);
        orderRepository.save(order);
        return order;
    }

    @Test
    @DisplayName("고아객체 제거 테스트")
    public void orphanRemovalTest(){
        Order order = this.createOrder();
        order.getOrderItems().remove(0);
        em.flush();
    }

    @Test
    @DisplayName("지연 로딩 테스트")
    public void lazyLoadingTest(){
        Order order = this.createOrder();
        Long orderItemId = order.getOrderItems().get(0).getId();
        em.flush();
        em.clear();
        OrderItem orderItem = orderItemRepository.findById(orderItemId)
                .orElseThrow(EntityNotFoundException::new);
        System.out.println("Order class : " + orderItem.getOrder().getClass());
        System.out.println("===========================");
        orderItem.getOrder().getOrderDate();
        System.out.println("===========================");
    }

}

================================================
FILE: src/test/java/com/shop/repository/ItemRepositoryTest.java
================================================
package com.shop.repository;

import com.shop.constant.ItemSellStatus;
import com.shop.entity.Item;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;

import java.time.LocalDateTime;

import java.util.List;

import com.querydsl.jpa.impl.JPAQueryFactory;
import com.querydsl.jpa.impl.JPAQuery;
import com.shop.entity.QItem;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.EntityManager;

import com.querydsl.core.BooleanBuilder;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.thymeleaf.util.StringUtils;

@SpringBootTest
@TestPropertySource(locations="classpath:application-test.properties")
class ItemRepositoryTest {

    @Autowired
    ItemRepository itemRepository;

    @PersistenceContext
    EntityManager em;

    @Test
    @DisplayName("상품 저장 테스트")
    public void createItemTest(){
        Item item = new Item();
        item.setItemNm("테스트 상품");
        item.setPrice(10000);
        item.setItemDetail("테스트 상품 상세 설명");
        item.setItemSellStatus(ItemSellStatus.SELL);
        item.setStockNumber(100);
        item.setRegTime(LocalDateTime.now());
        item.setUpdateTime(LocalDateTime.now());
        Item savedItem = itemRepository.save(item);
        System.out.println(savedItem.toString());
    }

    public void createItemList(){
        for(int i=1;i<=10;i++){
            Item item = new Item();
            item.setItemNm("테스트 상품" + i);
            item.setPrice(10000 + i);
            item.setItemDetail("테스트 상품 상세 설명" + i);
            item.setItemSellStatus(ItemSellStatus.SELL);
            item.setStockNumber(100); item.setRegTime(LocalDateTime.now());
            item.setUpdateTime(LocalDateTime.now());
            Item savedItem = itemRepository.save(item);
        }
    }

    @Test
    @DisplayName("상품명 조회 테스트")
    public void findByItemNmTest(){
        this.createItemList();
        List<Item> itemList = itemRepository.findByItemNm("테스트 상품1");
        for(Item item : itemList){
            System.out.println(item.toString());
        }
    }

    @Test
    @DisplayName("상품명, 상품상세설명 or 테스트")
    public void findByItemNmOrItemDetailTest(){
        this.createItemList();
        List<Item> itemList = itemRepository.findByItemNmOrItemDetail("테스트 상품1", "테스트 상품 상세 설명5");
        for(Item item : itemList){
            System.out.println(item.toString());
        }
    }

    @Test
    @DisplayName("가격 LessThan 테스트")
    public void findByPriceLessThanTest(){
        this.createItemList();
        List<Item> itemList = itemRepository.findByPriceLessThan(10005);
        for(Item item : itemList){
            System.out.println(item.toString());
        }
    }

    @Test
    @DisplayName("가격 내림차순 조회 테스트")
    public void findByPriceLessThanOrderByPriceDesc(){
        this.createItemList();
        List<Item> itemList = itemRepository.findByPriceLessThanOrderByPriceDesc(10005);
        for(Item item : itemList){
            System.out.println(item.toString());
        }
    }

    @Test
    @DisplayName("@Query를 이용한 상품 조회 테스트")
    public void findByItemDetailTest(){
        this.createItemList();
        List<Item> itemList = itemRepository.findByItemDetail("테스트 상품 상세 설명");
        for(Item item : itemList){
            System.out.println(item.toString());
        }
    }

    @Test
    @DisplayName("Querydsl 조회 테스트1")
    public void queryDslTest(){
        this.createItemList();
        JPAQueryFactory queryFactory = new JPAQueryFactory(em);
        QItem qItem = QItem.item;
        JPAQuery<Item> query  = queryFactory.selectFrom(qItem)
                .where(qItem.itemSellStatus.eq(ItemSellStatus.SELL))
                .where(qItem.itemDetail.like("%" + "테스트 상품 상세 설명" + "%"))
                .orderBy(qItem.price.desc());

        List<Item> itemList = query.fetch();

        for(Item item : itemList){
            System.out.println(item.toString());
        }
    }

    public void createItemList2(){
        for(int i=1;i<=5;i++){
            Item item = new Item();
            item.setItemNm("테스트 상품" + i);
            item.setPrice(10000 + i);
            item.setItemDetail("테스트 상품 상세 설명" + i);
            item.setItemSellStatus(ItemSellStatus.SELL);
            item.setStockNumber(100);
            item.setRegTime(LocalDateTime.now());
            item.setUpdateTime(LocalDateTime.now());
            itemRepository.save(item);
        }

        for(int i=6;i<=10;i++){
            Item item = new Item();
            item.setItemNm("테스트 상품" + i);
            item.setPrice(10000 + i);
            item.setItemDetail("테스트 상품 상세 설명" + i);
            item.setItemSellStatus(ItemSellStatus.SOLD_OUT);
            item.setStockNumber(0);
            item.setRegTime(LocalDateTime.now());
            item.setUpdateTime(LocalDateTime.now());
            itemRepository.save(item);
        }
    }

    @Test
    @DisplayName("상품 Querydsl 조회 테스트 2")
    public void queryDslTest2(){

        this.createItemList2();

        BooleanBuilder booleanBuilder = new BooleanBuilder();
        QItem item = QItem.item;
        String itemDetail = "테스트 상품 상세 설명";
        int price = 10003;
        String itemSellStat = "SELL";

        booleanBuilder.and(item.itemDetail.like("%" + itemDetail + "%"));
        booleanBuilder.and(item.price.gt(price));
        System.out.println(ItemSellStatus.SELL);
        if(StringUtils.equals(itemSellStat, ItemSellStatus.SELL)){
            booleanBuilder.and(item.itemSellStatus.eq(ItemSellStatus.SELL));
        }

        Pageable pageable = PageRequest.of(0, 5);
        Page<Item> itemPagingResult = itemRepository.findAll(booleanBuilder, pageable);
        System.out.println("total elements : " + itemPagingResult. getTotalElements ());

        List<Item> resultItemList = itemPagingResult.getContent();
        for(Item resultItem: resultItemList){
            System.out.println(resultItem.toString());
        }
    }

}

================================================
FILE: src/test/java/com/shop/service/CartServiceTest.java
================================================
package com.shop.service;

import com.shop.constant.ItemSellStatus;
import com.shop.dto.CartItemDto;
import com.shop.entity.CartItem;
import com.shop.entity.Item;
import com.shop.entity.Member;
import com.shop.repository.CartItemRepository;
import com.shop.repository.ItemRepository;
import com.shop.repository.MemberRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.annotation.Transactional;

import jakarta.persistence.EntityNotFoundException;

import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBootTest
@Transactional
@TestPropertySource(locations="classpath:application-test.properties")
class CartServiceTest {

    @Autowired
    ItemRepository itemRepository;

    @Autowired
    MemberRepository memberRepository;

    @Autowired
    CartService cartService;

    @Autowired
    CartItemRepository cartItemRepository;

    public Item saveItem(){
        Item item = new Item();
        item.setItemNm("테스트 상품");
        item.setPrice(10000);
        item.setItemDetail("테스트 상품 상세 설명");
        item.setItemSellStatus(ItemSellStatus.SELL);
        item.setStockNumber(100);
        return itemRepository.save(item);
    }

    public Member saveMember(){
        Member member = new Member();
        member.setEmail("test@test.com");
        return memberRepository.save(member);
    }

    @Test
    @DisplayName("장바구니 담기 테스트")
    public void addCart(){
        Item item = saveItem();
        Member member = saveMember();

        CartItemDto cartItemDto = new CartItemDto();
        cartItemDto.setCount(5);
        cartItemDto.setItemId(item.getId());

        Long cartItemId = cartService.addCart(cartItemDto, member.getEmail());
        CartItem cartItem = cartItemRepository.findById(cartItemId)
                .orElseThrow(EntityNotFoundException::new);

        assertEquals(item.getId(), cartItem.getItem().getId());
        assertEquals(cartItemDto.getCount(), cartItem.getCount());
    }

}

================================================
FILE: src/test/java/com/shop/service/ItemServiceTest.java
================================================
package com.shop.service;

import com.shop.constant.ItemSellStatus;
import com.shop.dto.ItemFormDto;
import com.shop.entity.Item;
import com.shop.entity.ItemImg;
import com.shop.repository.ItemImgRepository;
import com.shop.repository.ItemRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import jakarta.persistence.EntityNotFoundException;
import java.util.ArrayList;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBootTest
@Transactional
@TestPropertySource(locations="classpath:application-test.properties")
class ItemServiceTest {

    @Autowired
    ItemService itemService;

    @Autowired
    ItemRepository itemRepository;

    @Autowired
    ItemImgRepository itemImgRepository;

    List<MultipartFile> createMultipartFiles() throws Exception{

        List<MultipartFile> multipartFileList = new ArrayList<>();

        for(int i=0;i<5;i++){
            String path = "C:/shop/item/";
            String imageName = "image" + i + ".jpg";
            MockMultipartFile multipartFile =
                    new MockMultipartFile(path, imageName, "image/jpg", new byte[]{1,2,3,4});
            multipartFileList.add(multipartFile);
        }

        return multipartFileList;
    }

    @Test
    @DisplayName("상품 등록 테스트")
    @WithMockUser(username = "admin", roles = "ADMIN")
    void saveItem() throws Exception {
        ItemFormDto itemFormDto = new ItemFormDto();
        itemFormDto.setItemNm("테스트상품");
        itemFormDto.setItemSellStatus(ItemSellStatus.SELL);
        itemFormDto.setItemDetail("테스트 상품 입니다.");
        itemFormDto.setPrice(1000);
        itemFormDto.setStockNumber(100);

        List<MultipartFile> multipartFileList = createMultipartFiles();
        Long itemId = itemService.saveItem(itemFormDto, multipartFileList);
        List<ItemImg> itemImgList = itemImgRepository.findByItemIdOrderByIdAsc(itemId);

        Item item = itemRepository.findById(itemId)
                .orElseThrow(EntityNotFoundException::new);

        assertEquals(itemFormDto.getItemNm(), item.getItemNm());
        assertEquals(itemFormDto.getItemSellStatus(), item.getItemSellStatus());
        assertEquals(itemFormDto.getItemDetail(), item.getItemDetail());
        assertEquals(itemFormDto.getPrice(), item.getPrice());
        assertEquals(itemFormDto.getStockNumber(), item.getStockNumber());
        assertEquals(multipartFileList.get(0).getOriginalFilename(), itemImgList.get(0).getOriImgName());
    }

}

================================================
FILE: src/test/java/com/shop/service/MemberServiceTest.java
================================================
package com.shop.service;

import com.shop.dto.MemberFormDto;
import com.shop.entity.Member;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.annotation.Transactional;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

@SpringBootTest
@Transactional
@TestPropertySource(locations="classpath:application-test.properties")
class MemberServiceTest {

    @Autowired
    MemberService memberService;

    @Autowired
    PasswordEncoder passwordEncoder;

    public Member createMember(){
        MemberFormDto memberFormDto = new MemberFormDto();
        memberFormDto.setEmail("test@email.com");
        memberFormDto.setName("홍길동");
        memberFormDto.setAddress("서울시 마포구 합정동");
        memberFormDto.setPassword("1234");
        return Member.createMember(memberFormDto, passwordEncoder);
    }

    @Test
    @DisplayName("회원가입 테스트")
    public void saveMemberTest(){
        Member member = createMember();
        Member savedMember = memberService.saveMember(member);
        assertEquals(member.getEmail(), savedMember.getEmail());
        assertEquals(member.getName(), savedMember.getName());
        assertEquals(member.getAddress(), savedMember.getAddress());
        assertEquals(member.getPassword(), savedMember.getPassword());
        assertEquals(member.getRole(), savedMember.getRole());
    }

    @Test
    @DisplayName("중복 회원 가입 테스트")
    public void saveDuplicateMemberTest(){
        Member member1 = createMember();
        Member member2 = createMember();
        memberService.saveMember(member1);
        Throwable e = assertThrows(IllegalStateException.class, () -> {
            memberService.saveMember(member2);});
        assertEquals("이미 가입된 회원입니다.", e.getMessage());
    }
}

================================================
FILE: src/test/java/com/shop/service/OrderServiceTest.java
================================================
package com.shop.service;

import com.shop.constant.ItemSellStatus;
import com.shop.constant.OrderStatus;
import com.shop.dto.OrderDto;
import com.shop.entity.Item;
import com.shop.entity.Member;
import com.shop.entity.Order;
import com.shop.entity.OrderItem;
import com.shop.repository.ItemRepository;
import com.shop.repository.MemberRepository;
import com.shop.repository.OrderRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.annotation.Transactional;

import jakarta.persistence.EntityNotFoundException;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBootTest
@Transactional
@TestPropertySource(locations="classpath:application-test.properties")
class OrderServiceTest {

    @Autowired
    private OrderService orderService;

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    ItemRepository itemRepository;

    @Autowired
    MemberRepository memberRepository;

    public Item saveItem(){
        Item item = new Item();
        item.setItemNm("테스트 상품");
        item.setPrice(10000);
        item.setItemDetail("테스트 상품 상세 설명");
        item.setItemSellStatus(ItemSellStatus.SELL);
        item.setStockNumber(100);
        return itemRepository.save(item);
    }

    public Member saveMember(){
        Member member = new Member();
        member.setEmail("test@test.com");
        return memberRepository.save(member);

    }

    @Test
    @DisplayName("주문 테스트")
    public void order(){
        Item item = saveItem();
        Member member = saveMember();

        OrderDto orderDto = new OrderDto();
        orderDto.setCount(10);
        orderDto.setItemId(item.getId());

        Long orderId = orderService.order(orderDto, member.getEmail());
        Order order = orderRepository.findById(orderId)
                .orElseThrow(EntityNotFoundException::new);

        List<OrderItem> orderItems = order.getOrderItems();

        int totalPrice = orderDto.getCount()*item.getPrice();

        assertEquals(totalPrice, order.getTotalPrice());
    }

    @Test
    @DisplayName("주문 취소 테스트")
    public void cancelOrder(){
        Item item = saveItem();
        Member member = saveMember();

        OrderDto orderDto = new OrderDto();
        orderDto.setCount(10);
        orderDto.setItemId(item.getId());
        Long orderId = orderService.order(orderDto, member.getEmail());

        Order order = orderRepository.findById(orderId)
                .orElseThrow(EntityNotFoundException::new);
        orderService.cancelOrder(orderId);

        assertEquals(OrderStatus.CANCEL, order.getOrderStatus());
        assertEquals(100, item.getStockNumber());
    }

}
Download .txt
gitextract_kfsect3j/

├── .gitignore
├── .mvn/
│   └── wrapper/
│       ├── MavenWrapperDownloader.java
│       ├── maven-wrapper.jar
│       └── maven-wrapper.properties
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src/
    ├── main/
    │   ├── java/
    │   │   └── com/
    │   │       └── shop/
    │   │           ├── ShopApplication.java
    │   │           ├── config/
    │   │           │   ├── AuditConfig.java
    │   │           │   ├── AuditorAwareImpl.java
    │   │           │   ├── CsrfCookieFilter.java
    │   │           │   ├── CustomAuthenticationEntryPoint.java
    │   │           │   ├── FormLoginAuthenticationFailureHandler.java
    │   │           │   ├── SecurityConfig.java
    │   │           │   └── WebMvcConfig.java
    │   │           ├── constant/
    │   │           │   ├── ItemSellStatus.java
    │   │           │   ├── OrderStatus.java
    │   │           │   └── Role.java
    │   │           ├── controller/
    │   │           │   ├── CartController.java
    │   │           │   ├── ItemController.java
    │   │           │   ├── MainController.java
    │   │           │   ├── MemberController.java
    │   │           │   ├── OrderController.java
    │   │           │   └── ThymeleafExController.java
    │   │           ├── dto/
    │   │           │   ├── CartDetailDto.java
    │   │           │   ├── CartItemDto.java
    │   │           │   ├── CartOrderDto.java
    │   │           │   ├── ItemDto.java
    │   │           │   ├── ItemFormDto.java
    │   │           │   ├── ItemImgDto.java
    │   │           │   ├── ItemSearchDto.java
    │   │           │   ├── MainItemDto.java
    │   │           │   ├── MemberFormDto.java
    │   │           │   ├── OrderDto.java
    │   │           │   ├── OrderHistDto.java
    │   │           │   └── OrderItemDto.java
    │   │           ├── entity/
    │   │           │   ├── BaseEntity.java
    │   │           │   ├── BaseTimeEntity.java
    │   │           │   ├── Cart.java
    │   │           │   ├── CartItem.java
    │   │           │   ├── Item.java
    │   │           │   ├── ItemImg.java
    │   │           │   ├── Member.java
    │   │           │   ├── Order.java
    │   │           │   └── OrderItem.java
    │   │           ├── exception/
    │   │           │   └── OutOfStockException.java
    │   │           ├── repository/
    │   │           │   ├── CartItemRepository.java
    │   │           │   ├── CartRepository.java
    │   │           │   ├── ItemImgRepository.java
    │   │           │   ├── ItemRepository.java
    │   │           │   ├── ItemRepositoryCustom.java
    │   │           │   ├── ItemRepositoryCustomImpl.java
    │   │           │   ├── MemberRepository.java
    │   │           │   ├── OrderItemRepository.java
    │   │           │   └── OrderRepository.java
    │   │           └── service/
    │   │               ├── CartService.java
    │   │               ├── FileService.java
    │   │               ├── ItemImgService.java
    │   │               ├── ItemService.java
    │   │               ├── MemberService.java
    │   │               └── OrderService.java
    │   └── resources/
    │       ├── application-test.properties
    │       ├── application.properties
    │       ├── static/
    │       │   └── css/
    │       │       └── layout1.css
    │       └── templates/
    │           ├── cart/
    │           │   └── cartList.html
    │           ├── fragments/
    │           │   ├── footer.html
    │           │   └── header.html
    │           ├── item/
    │           │   ├── itemDtl.html
    │           │   ├── itemForm.html
    │           │   └── itemMng.html
    │           ├── layouts/
    │           │   └── layout1.html
    │           ├── main.html
    │           ├── member/
    │           │   ├── memberForm.html
    │           │   └── memberLoginForm.html
    │           ├── order/
    │           │   └── orderHist.html
    │           └── thymeleafEx/
    │               ├── thymeleafEx01.html
    │               ├── thymeleafEx02.html
    │               ├── thymeleafEx03.html
    │               ├── thymeleafEx04.html
    │               ├── thymeleafEx05.html
    │               ├── thymeleafEx06.html
    │               └── thymeleafEx07.html
    └── test/
        └── java/
            └── com/
                └── shop/
                    ├── ShopApplicationTests.java
                    ├── controller/
                    │   ├── ItemControllerTest.java
                    │   └── MemberControllerTest.java
                    ├── entity/
                    │   ├── CartTest.java
                    │   ├── MemberTest.java
                    │   └── OrderTest.java
                    ├── repository/
                    │   └── ItemRepositoryTest.java
                    └── service/
                        ├── CartServiceTest.java
                        ├── ItemServiceTest.java
                        ├── MemberServiceTest.java
                        └── OrderServiceTest.java
Download .txt
SYMBOL INDEX (210 symbols across 66 files)

FILE: .mvn/wrapper/MavenWrapperDownloader.java
  class MavenWrapperDownloader (line 21) | public class MavenWrapperDownloader {
    method main (line 48) | public static void main(String args[]) {
    method downloadFileFromURL (line 97) | private static void downloadFileFromURL(String urlString, File destina...

FILE: src/main/java/com/shop/ShopApplication.java
  class ShopApplication (line 6) | @SpringBootApplication
    method main (line 9) | public static void main(String[] args) {

FILE: src/main/java/com/shop/config/AuditConfig.java
  class AuditConfig (line 8) | @Configuration
    method auditorProvider (line 12) | @Bean

FILE: src/main/java/com/shop/config/AuditorAwareImpl.java
  class AuditorAwareImpl (line 8) | public class AuditorAwareImpl implements AuditorAware<String> {
    method getCurrentAuditor (line 10) | @Override

FILE: src/main/java/com/shop/config/CsrfCookieFilter.java
  class CsrfCookieFilter (line 12) | public class CsrfCookieFilter extends OncePerRequestFilter {
    method doFilterInternal (line 14) | @Override

FILE: src/main/java/com/shop/config/CustomAuthenticationEntryPoint.java
  class CustomAuthenticationEntryPoint (line 11) | public class CustomAuthenticationEntryPoint implements AuthenticationEnt...
    method commence (line 13) | @Override

FILE: src/main/java/com/shop/config/FormLoginAuthenticationFailureHandler.java
  class FormLoginAuthenticationFailureHandler (line 11) | public class FormLoginAuthenticationFailureHandler implements Authentica...
    method onAuthenticationFailure (line 13) | @Override

FILE: src/main/java/com/shop/config/SecurityConfig.java
  class SecurityConfig (line 16) | @Configuration
    method filterChain (line 23) | @Bean
    method passwordEncoder (line 51) | @Bean

FILE: src/main/java/com/shop/config/WebMvcConfig.java
  class WebMvcConfig (line 8) | @Configuration
    method addResourceHandlers (line 14) | @Override

FILE: src/main/java/com/shop/constant/ItemSellStatus.java
  type ItemSellStatus (line 3) | public enum ItemSellStatus {

FILE: src/main/java/com/shop/constant/OrderStatus.java
  type OrderStatus (line 3) | public enum OrderStatus {

FILE: src/main/java/com/shop/constant/Role.java
  type Role (line 3) | public enum Role {

FILE: src/main/java/com/shop/controller/CartController.java
  class CartController (line 28) | @Controller
    method order (line 34) | @PostMapping(value = "/cart")
    method orderHist (line 60) | @GetMapping(value = "/cart")
    method updateCartItem (line 67) | @PatchMapping(value = "/cartItem/{cartItemId}")
    method deleteCartItem (line 80) | @DeleteMapping(value = "/cartItem/{cartItemId}")
    method orderCartItem (line 92) | @PostMapping(value = "/cart/orders")

FILE: src/main/java/com/shop/controller/ItemController.java
  class ItemController (line 28) | @Controller
    method itemForm (line 34) | @GetMapping(value = "/admin/item/new")
    method itemNew (line 40) | @PostMapping(value = "/admin/item/new")
    method itemDtl (line 63) | @GetMapping(value = "/admin/item/{itemId}")
    method itemUpdate (line 78) | @PostMapping(value = "/admin/item/{itemId}")
    method itemManage (line 100) | @GetMapping(value = {"/admin/items", "/admin/items/{page}"})
    method itemDtl (line 113) | @GetMapping(value = "/item/{itemId}")

FILE: src/main/java/com/shop/controller/MainController.java
  class MainController (line 18) | @Controller
    method main (line 24) | @GetMapping(value = "/")

FILE: src/main/java/com/shop/controller/MemberController.java
  class MemberController (line 18) | @RequestMapping("/members")
    method memberForm (line 26) | @GetMapping(value = "/new")
    method newMember (line 32) | @PostMapping(value = "/new")
    method loginMember (line 50) | @GetMapping(value = "/login")
    method loginError (line 55) | @GetMapping(value = "/login/error")

FILE: src/main/java/com/shop/controller/OrderController.java
  class OrderController (line 28) | @Controller
    method order (line 34) | @PostMapping(value = "/order")
    method orderHist (line 61) | @GetMapping(value = {"/orders", "/orders/{page}"})
    method cancelOrder (line 74) | @PostMapping("/order/{orderId}/cancel")

FILE: src/main/java/com/shop/controller/ThymeleafExController.java
  class ThymeleafExController (line 14) | @Controller
    method thymeleafExample01 (line 18) | @GetMapping(value = "/ex01")
    method thymeleafExample02 (line 24) | @GetMapping(value = "/ex02")
    method thymeleafExample03 (line 36) | @GetMapping(value = "/ex03")
    method thymeleafExample04 (line 56) | @GetMapping(value = "/ex04")
    method thymeleafExample05 (line 76) | @GetMapping(value = "/ex05")
    method thymeleafExample06 (line 81) | @GetMapping(value = "/ex06")
    method thymeleafExample07 (line 88) | @GetMapping(value = "/ex07")

FILE: src/main/java/com/shop/dto/CartDetailDto.java
  class CartDetailDto (line 6) | @Getter @Setter
    method CartDetailDto (line 19) | public CartDetailDto(Long cartItemId, String itemNm, int price, int co...

FILE: src/main/java/com/shop/dto/CartItemDto.java
  class CartItemDto (line 9) | @Getter @Setter

FILE: src/main/java/com/shop/dto/CartOrderDto.java
  class CartOrderDto (line 8) | @Getter

FILE: src/main/java/com/shop/dto/ItemDto.java
  class ItemDto (line 8) | @Getter

FILE: src/main/java/com/shop/dto/ItemFormDto.java
  class ItemFormDto (line 14) | @Getter @Setter
    method createItem (line 39) | public Item createItem(){
    method of (line 43) | public static ItemFormDto of(Item item){

FILE: src/main/java/com/shop/dto/ItemImgDto.java
  class ItemImgDto (line 8) | @Getter @Setter
    method of (line 23) | public static ItemImgDto of(ItemImg itemImg) {

FILE: src/main/java/com/shop/dto/ItemSearchDto.java
  class ItemSearchDto (line 7) | @Getter @Setter

FILE: src/main/java/com/shop/dto/MainItemDto.java
  class MainItemDto (line 7) | @Getter @Setter
    method MainItemDto (line 20) | @QueryProjection

FILE: src/main/java/com/shop/dto/MemberFormDto.java
  class MemberFormDto (line 11) | @Getter @Setter

FILE: src/main/java/com/shop/dto/OrderDto.java
  class OrderDto (line 10) | @Getter @Setter

FILE: src/main/java/com/shop/dto/OrderHistDto.java
  class OrderHistDto (line 12) | @Getter @Setter
    method OrderHistDto (line 15) | public OrderHistDto(Order order){
    method addOrderItemDto (line 28) | public void addOrderItemDto(OrderItemDto orderItemDto){

FILE: src/main/java/com/shop/dto/OrderItemDto.java
  class OrderItemDto (line 7) | @Getter @Setter
    method OrderItemDto (line 10) | public OrderItemDto(OrderItem orderItem, String imgUrl){

FILE: src/main/java/com/shop/entity/BaseEntity.java
  class BaseEntity (line 12) | @EntityListeners(value = {AuditingEntityListener.class})

FILE: src/main/java/com/shop/entity/BaseTimeEntity.java
  class BaseTimeEntity (line 14) | @EntityListeners(value = {AuditingEntityListener.class})

FILE: src/main/java/com/shop/entity/Cart.java
  class Cart (line 8) | @Entity
    method createCart (line 23) | public static Cart createCart(Member member){

FILE: src/main/java/com/shop/entity/CartItem.java
  class CartItem (line 7) | @Entity
    method createCartItem (line 27) | public static CartItem createCartItem(Cart cart, Item item, int count) {
    method addCount (line 35) | public void addCount(int count){
    method updateCount (line 39) | public void updateCount(int count){

FILE: src/main/java/com/shop/entity/Item.java
  class Item (line 12) | @Entity
    method updateItem (line 40) | public void updateItem(ItemFormDto itemFormDto){
    method removeStock (line 48) | public void removeStock(int stockNumber){
    method addStock (line 56) | public void addStock(int stockNumber){

FILE: src/main/java/com/shop/entity/ItemImg.java
  class ItemImg (line 7) | @Entity
    method updateItemImg (line 29) | public void updateItemImg(String oriImgName, String imgName, String im...

FILE: src/main/java/com/shop/entity/Member.java
  class Member (line 12) | @Entity
    method createMember (line 35) | public static Member createMember(MemberFormDto memberFormDto, Passwor...

FILE: src/main/java/com/shop/entity/Order.java
  class Order (line 12) | @Entity
    method addOrderItem (line 34) | public void addOrderItem(OrderItem orderItem) {
    method createOrder (line 39) | public static Order createOrder(Member member, List<OrderItem> orderIt...
    method getTotalPrice (line 52) | public int getTotalPrice() {
    method cancelOrder (line 60) | public void cancelOrder() {

FILE: src/main/java/com/shop/entity/OrderItem.java
  class OrderItem (line 7) | @Entity
    method createOrderItem (line 27) | public static OrderItem createOrderItem(Item item, int count){
    method getTotalPrice (line 36) | public int getTotalPrice(){
    method cancel (line 40) | public void cancel() {

FILE: src/main/java/com/shop/exception/OutOfStockException.java
  class OutOfStockException (line 3) | public class OutOfStockException extends RuntimeException{
    method OutOfStockException (line 5) | public OutOfStockException(String message) {

FILE: src/main/java/com/shop/repository/CartItemRepository.java
  type CartItemRepository (line 10) | public interface CartItemRepository extends JpaRepository<CartItem, Long> {
    method findByCartIdAndItemId (line 12) | CartItem findByCartIdAndItemId(Long cartId, Long itemId);
    method findCartDetailDtoList (line 14) | @Query("select new com.shop.dto.CartDetailDto(ci.id, i.itemNm, i.price...

FILE: src/main/java/com/shop/repository/CartRepository.java
  type CartRepository (line 6) | public interface CartRepository extends JpaRepository<Cart, Long> {
    method findByMemberId (line 8) | Cart findByMemberId(Long memberId);

FILE: src/main/java/com/shop/repository/ItemImgRepository.java
  type ItemImgRepository (line 7) | public interface ItemImgRepository extends JpaRepository<ItemImg, Long> {
    method findByItemIdOrderByIdAsc (line 9) | List<ItemImg> findByItemIdOrderByIdAsc(Long itemId);
    method findByItemIdAndRepimgYn (line 11) | ItemImg findByItemIdAndRepimgYn(Long itemId, String repimgYn);

FILE: src/main/java/com/shop/repository/ItemRepository.java
  type ItemRepository (line 13) | public interface ItemRepository extends JpaRepository<Item, Long>,
    method findByItemNm (line 16) | List<Item> findByItemNm(String itemNm);
    method findByItemNmOrItemDetail (line 18) | List<Item> findByItemNmOrItemDetail(String itemNm, String itemDetail);
    method findByPriceLessThan (line 20) | List<Item> findByPriceLessThan(Integer price);
    method findByPriceLessThanOrderByPriceDesc (line 22) | List<Item> findByPriceLessThanOrderByPriceDesc(Integer price);
    method findByItemDetail (line 24) | @Query("select i from Item i where i.itemDetail like " +
    method findByItemDetailByNative (line 28) | @Query(value="select * from item i where i.item_detail like " +

FILE: src/main/java/com/shop/repository/ItemRepositoryCustom.java
  type ItemRepositoryCustom (line 9) | public interface ItemRepositoryCustom {
    method getAdminItemPage (line 11) | Page<Item> getAdminItemPage(ItemSearchDto itemSearchDto, Pageable page...
    method getMainItemPage (line 13) | Page<MainItemDto> getMainItemPage(ItemSearchDto itemSearchDto, Pageabl...

FILE: src/main/java/com/shop/repository/ItemRepositoryCustomImpl.java
  class ItemRepositoryCustomImpl (line 23) | public class ItemRepositoryCustomImpl implements ItemRepositoryCustom{
    method ItemRepositoryCustomImpl (line 27) | public ItemRepositoryCustomImpl(EntityManager em){
    method searchSellStatusEq (line 31) | private BooleanExpression searchSellStatusEq(ItemSellStatus searchSell...
    method regDtsAfter (line 35) | private BooleanExpression regDtsAfter(String searchDateType){
    method searchByLike (line 54) | private BooleanExpression searchByLike(String searchBy, String searchQ...
    method getAdminItemPage (line 65) | @Override
    method itemNmLike (line 89) | private BooleanExpression itemNmLike(String searchQuery){
    method getMainItemPage (line 93) | @Override

FILE: src/main/java/com/shop/repository/MemberRepository.java
  type MemberRepository (line 6) | public interface MemberRepository extends JpaRepository<Member, Long> {
    method findByEmail (line 8) | Member findByEmail(String email);

FILE: src/main/java/com/shop/repository/OrderItemRepository.java
  type OrderItemRepository (line 6) | public interface OrderItemRepository extends JpaRepository<OrderItem, Lo...

FILE: src/main/java/com/shop/repository/OrderRepository.java
  type OrderRepository (line 12) | public interface OrderRepository extends JpaRepository<Order, Long> {
    method findOrders (line 14) | @Query("select o from Order o " +
    method countOrder (line 20) | @Query("select count(o) from Order o " +

FILE: src/main/java/com/shop/service/CartService.java
  class CartService (line 25) | @Service
    method addCart (line 36) | public Long addCart(CartItemDto cartItemDto, String email){
    method getCartList (line 60) | @Transactional(readOnly = true)
    method validateCartItem (line 75) | @Transactional(readOnly = true)
    method updateCartItemCount (line 89) | public void updateCartItemCount(Long cartItemId, int count){
    method deleteCartItem (line 96) | public void deleteCartItem(Long cartItemId) {
    method orderCartItem (line 102) | public Long orderCartItem(List<CartOrderDto> cartOrderDtoList, String ...

FILE: src/main/java/com/shop/service/FileService.java
  class FileService (line 9) | @Service
    method uploadFile (line 13) | public String uploadFile(String uploadPath, String originalFileName, b...
    method deleteFile (line 24) | public void deleteFile(String filePath) throws Exception{

FILE: src/main/java/com/shop/service/ItemImgService.java
  class ItemImgService (line 13) | @Service
    method saveItemImg (line 25) | public void saveItemImg(ItemImg itemImg, MultipartFile itemImgFile) th...
    method updateItemImg (line 42) | public void updateItemImg(Long itemImgId, MultipartFile itemImgFile) t...

FILE: src/main/java/com/shop/service/ItemService.java
  class ItemService (line 25) | @Service
    method saveItem (line 36) | public Long saveItem(ItemFormDto itemFormDto, List<MultipartFile> item...
    method getItemDtl (line 58) | @Transactional(readOnly = true)
    method updateItem (line 74) | public Long updateItem(ItemFormDto itemFormDto, List<MultipartFile> it...
    method getAdminItemPage (line 90) | @Transactional(readOnly = true)
    method getMainItemPage (line 95) | @Transactional(readOnly = true)

FILE: src/main/java/com/shop/service/MemberService.java
  class MemberService (line 14) | @Service
    method saveMember (line 21) | public Member saveMember(Member member){
    method validateDuplicateMember (line 26) | private void validateDuplicateMember(Member member){
    method loadUserByUsername (line 33) | @Override

FILE: src/main/java/com/shop/service/OrderService.java
  class OrderService (line 25) | @Service
    method order (line 38) | public Long order(OrderDto orderDto, String email){
    method getOrderList (line 54) | @Transactional(readOnly = true)
    method validateOrder (line 79) | @Transactional(readOnly = true)
    method cancelOrder (line 93) | public void cancelOrder(Long orderId){
    method orders (line 99) | public Long orders(List<OrderDto> orderDtoList, String email){

FILE: src/test/java/com/shop/ShopApplicationTests.java
  class ShopApplicationTests (line 6) | @SpringBootTest
    method contextLoads (line 9) | @Test

FILE: src/test/java/com/shop/controller/ItemControllerTest.java
  class ItemControllerTest (line 16) | @SpringBootTest
    method itemFormTest (line 24) | @Test
    method itemFormNotAdminTest (line 33) | @Test

FILE: src/test/java/com/shop/controller/MemberControllerTest.java
  class MemberControllerTest (line 19) | @SpringBootTest
    method createMember (line 34) | public Member createMember(String email, String password){
    method loginSuccessTest (line 44) | @Test
    method loginFailTest (line 56) | @Test

FILE: src/test/java/com/shop/entity/CartTest.java
  class CartTest (line 20) | @SpringBootTest
    method createMember (line 38) | public Member createMember(){
    method findCartAndMemberTest (line 47) | @Test

FILE: src/test/java/com/shop/entity/MemberTest.java
  class MemberTest (line 15) | @SpringBootTest
    method auditingTest (line 26) | @Test

FILE: src/test/java/com/shop/entity/OrderTest.java
  class OrderTest (line 22) | @SpringBootTest
    method createItem (line 42) | public Item createItem(){
    method cascadeTest (line 55) | @Test
    method createOrder (line 80) | public Order createOrder(){
    method orphanRemovalTest (line 99) | @Test
    method lazyLoadingTest (line 107) | @Test

FILE: src/test/java/com/shop/repository/ItemRepositoryTest.java
  class ItemRepositoryTest (line 27) | @SpringBootTest
    method createItemTest (line 37) | @Test
    method createItemList (line 52) | public void createItemList(){
    method findByItemNmTest (line 65) | @Test
    method findByItemNmOrItemDetailTest (line 75) | @Test
    method findByPriceLessThanTest (line 85) | @Test
    method findByPriceLessThanOrderByPriceDesc (line 95) | @Test
    method findByItemDetailTest (line 105) | @Test
    method queryDslTest (line 115) | @Test
    method createItemList2 (line 133) | public void createItemList2(){
    method queryDslTest2 (line 159) | @Test

FILE: src/test/java/com/shop/service/CartServiceTest.java
  class CartServiceTest (line 22) | @SpringBootTest
    method saveItem (line 39) | public Item saveItem(){
    method saveMember (line 49) | public Member saveMember(){
    method addCart (line 55) | @Test

FILE: src/test/java/com/shop/service/ItemServiceTest.java
  class ItemServiceTest (line 24) | @SpringBootTest
    method createMultipartFiles (line 38) | List<MultipartFile> createMultipartFiles() throws Exception{
    method saveItem (line 53) | @Test

FILE: src/test/java/com/shop/service/MemberServiceTest.java
  class MemberServiceTest (line 16) | @SpringBootTest
    method createMember (line 27) | public Member createMember(){
    method saveMemberTest (line 36) | @Test
    method saveDuplicateMemberTest (line 48) | @Test

FILE: src/test/java/com/shop/service/OrderServiceTest.java
  class OrderServiceTest (line 25) | @SpringBootTest
    method saveItem (line 42) | public Item saveItem(){
    method saveMember (line 52) | public Member saveMember(){
    method order (line 59) | @Test
    method cancelOrder (line 80) | @Test
Condensed preview — 94 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (186K chars).
[
  {
    "path": ".gitignore",
    "chars": 395,
    "preview": "HELP.md\ntarget/\n!.mvn/wrapper/maven-wrapper.jar\n!**/src/main/**/target/\n!**/src/test/**/target/\n\n### STS ###\n.apt_genera"
  },
  {
    "path": ".mvn/wrapper/MavenWrapperDownloader.java",
    "chars": 4942,
    "preview": "/*\n * Copyright 2007-present the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": ".mvn/wrapper/maven-wrapper.properties",
    "chars": 218,
    "preview": "distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip\nwrap"
  },
  {
    "path": "README.md",
    "chars": 2287,
    "preview": "# 1.변경사항\n- 스프링 시큐리티 설정 수정 (2025-02-13)\n  - 애플리케이션 기동 후 첫 post에서 csrf 토큰이 생성되지 않는 문제 발견\n  - CsrfCookieFilter에서 csrfToken."
  },
  {
    "path": "mvnw",
    "chars": 10070,
    "preview": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Softwa"
  },
  {
    "path": "mvnw.cmd",
    "chars": 6608,
    "preview": "@REM ----------------------------------------------------------------------------\n@REM Licensed to the Apache Software F"
  },
  {
    "path": "pom.xml",
    "chars": 3538,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2"
  },
  {
    "path": "src/main/java/com/shop/ShopApplication.java",
    "chars": 297,
    "preview": "package com.shop;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.Spri"
  },
  {
    "path": "src/main/java/com/shop/config/AuditConfig.java",
    "chars": 437,
    "preview": "package com.shop.config;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotat"
  },
  {
    "path": "src/main/java/com/shop/config/AuditorAwareImpl.java",
    "chars": 626,
    "preview": "package com.shop.config;\n\nimport org.springframework.data.domain.AuditorAware;\nimport org.springframework.security.core."
  },
  {
    "path": "src/main/java/com/shop/config/CsrfCookieFilter.java",
    "chars": 881,
    "preview": "package com.shop.config;\n\nimport jakarta.servlet.FilterChain;\nimport jakarta.servlet.ServletException;\nimport jakarta.se"
  },
  {
    "path": "src/main/java/com/shop/config/CustomAuthenticationEntryPoint.java",
    "chars": 674,
    "preview": "package com.shop.config;\n\nimport jakarta.servlet.ServletException;\nimport jakarta.servlet.http.HttpServletRequest;\nimpor"
  },
  {
    "path": "src/main/java/com/shop/config/FormLoginAuthenticationFailureHandler.java",
    "chars": 689,
    "preview": "package com.shop.config;\n\nimport jakarta.servlet.ServletException;\nimport jakarta.servlet.http.HttpServletRequest;\nimpor"
  },
  {
    "path": "src/main/java/com/shop/config/SecurityConfig.java",
    "chars": 2592,
    "preview": "package com.shop.config;\n\nimport com.shop.service.MemberService;\nimport org.springframework.beans.factory.annotation.Aut"
  },
  {
    "path": "src/main/java/com/shop/config/WebMvcConfig.java",
    "chars": 623,
    "preview": "package com.shop.config;\n\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context."
  },
  {
    "path": "src/main/java/com/shop/constant/ItemSellStatus.java",
    "chars": 77,
    "preview": "package com.shop.constant;\n\npublic enum ItemSellStatus {\n    SELL, SOLD_OUT\n}"
  },
  {
    "path": "src/main/java/com/shop/constant/OrderStatus.java",
    "chars": 73,
    "preview": "package com.shop.constant;\n\npublic enum OrderStatus {\n    ORDER, CANCEL\n}"
  },
  {
    "path": "src/main/java/com/shop/constant/Role.java",
    "chars": 64,
    "preview": "package com.shop.constant;\n\npublic enum Role {\n    USER, ADMIN\n}"
  },
  {
    "path": "src/main/java/com/shop/controller/CartController.java",
    "chars": 4248,
    "preview": "package com.shop.controller;\nimport com.shop.dto.CartItemDto;\nimport com.shop.service.CartService;\nimport lombok.Require"
  },
  {
    "path": "src/main/java/com/shop/controller/ItemController.java",
    "chars": 4151,
    "preview": "package com.shop.controller;\n\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.anno"
  },
  {
    "path": "src/main/java/com/shop/controller/MainController.java",
    "chars": 1185,
    "preview": "package com.shop.controller;\n\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.anno"
  },
  {
    "path": "src/main/java/com/shop/controller/MemberController.java",
    "chars": 1865,
    "preview": "package com.shop.controller;\n\nimport com.shop.dto.MemberFormDto;\nimport com.shop.service.MemberService;\nimport lombok.Re"
  },
  {
    "path": "src/main/java/com/shop/controller/OrderController.java",
    "chars": 3053,
    "preview": "package com.shop.controller;\n\nimport com.shop.dto.OrderDto;\nimport com.shop.service.OrderService;\nimport lombok.Required"
  },
  {
    "path": "src/main/java/com/shop/controller/ThymeleafExController.java",
    "chars": 2643,
    "preview": "package com.shop.controller;\n\nimport org.springframework.stereotype.Controller;\nimport org.springframework.ui.Model;\nimp"
  },
  {
    "path": "src/main/java/com/shop/dto/CartDetailDto.java",
    "chars": 550,
    "preview": "package com.shop.dto;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter @Setter\npublic class CartDetailDto {\n\n    pr"
  },
  {
    "path": "src/main/java/com/shop/dto/CartItemDto.java",
    "chars": 349,
    "preview": "package com.shop.dto;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport jakarta.validation.constraints.Min;\nimport ja"
  },
  {
    "path": "src/main/java/com/shop/dto/CartOrderDto.java",
    "chars": 218,
    "preview": "package com.shop.dto;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.util.List;\n\n@Getter\n@Setter\npublic class"
  },
  {
    "path": "src/main/java/com/shop/dto/ItemDto.java",
    "chars": 359,
    "preview": "package com.shop.dto;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.time.LocalDateTime;\n\n@Getter\n@Setter\npub"
  },
  {
    "path": "src/main/java/com/shop/dto/ItemFormDto.java",
    "chars": 1110,
    "preview": "package com.shop.dto;\n\nimport com.shop.constant.ItemSellStatus;\nimport com.shop.entity.Item;\nimport lombok.Getter;\nimpor"
  },
  {
    "path": "src/main/java/com/shop/dto/ItemImgDto.java",
    "chars": 502,
    "preview": "package com.shop.dto;\n\nimport com.shop.entity.ItemImg;\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.modelmappe"
  },
  {
    "path": "src/main/java/com/shop/dto/ItemSearchDto.java",
    "chars": 306,
    "preview": "package com.shop.dto;\n\nimport com.shop.constant.ItemSellStatus;\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter @Se"
  },
  {
    "path": "src/main/java/com/shop/dto/MainItemDto.java",
    "chars": 577,
    "preview": "package com.shop.dto;\n\nimport com.querydsl.core.annotations.QueryProjection;\nimport lombok.Getter;\nimport lombok.Setter;"
  },
  {
    "path": "src/main/java/com/shop/dto/MemberFormDto.java",
    "chars": 703,
    "preview": "package com.shop.dto;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.hibernate.validator.constraints.Length;\n\ni"
  },
  {
    "path": "src/main/java/com/shop/dto/OrderDto.java",
    "chars": 446,
    "preview": "package com.shop.dto;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport jakarta.validation.constraints.Max;\nimport ja"
  },
  {
    "path": "src/main/java/com/shop/dto/OrderHistDto.java",
    "chars": 827,
    "preview": "package com.shop.dto;\n\nimport com.shop.constant.OrderStatus;\nimport com.shop.entity.Order;\nimport lombok.Getter;\nimport "
  },
  {
    "path": "src/main/java/com/shop/dto/OrderItemDto.java",
    "chars": 538,
    "preview": "package com.shop.dto;\n\nimport com.shop.entity.OrderItem;\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter @Setter\npu"
  },
  {
    "path": "src/main/java/com/shop/entity/BaseEntity.java",
    "chars": 633,
    "preview": "package com.shop.entity;\n\nimport lombok.Getter;\nimport org.springframework.data.annotation.CreatedBy;\nimport org.springf"
  },
  {
    "path": "src/main/java/com/shop/entity/BaseTimeEntity.java",
    "chars": 697,
    "preview": "package com.shop.entity;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.springframework.data.annotation.Created"
  },
  {
    "path": "src/main/java/com/shop/entity/Cart.java",
    "chars": 576,
    "preview": "package com.shop.entity;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.ToString;\nimport jakarta.persistence"
  },
  {
    "path": "src/main/java/com/shop/entity/CartItem.java",
    "chars": 895,
    "preview": "package com.shop.entity;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport jakarta.persistence.*;\n\n@Entity\n@Getter @Se"
  },
  {
    "path": "src/main/java/com/shop/entity/Item.java",
    "chars": 1552,
    "preview": "package com.shop.entity;\n\nimport com.shop.constant.ItemSellStatus;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lo"
  },
  {
    "path": "src/main/java/com/shop/entity/ItemImg.java",
    "chars": 757,
    "preview": "package com.shop.entity;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport jakarta.persistence.*;\n\n@Entity\n@Table(name"
  },
  {
    "path": "src/main/java/com/shop/entity/Member.java",
    "chars": 1146,
    "preview": "package com.shop.entity;\n\nimport com.shop.constant.Role;\nimport com.shop.dto.MemberFormDto;\nimport lombok.Getter;\nimport"
  },
  {
    "path": "src/main/java/com/shop/entity/Order.java",
    "chars": 1692,
    "preview": "package com.shop.entity;\n\nimport com.shop.constant.OrderStatus;\nimport lombok.Getter;\nimport lombok.Setter;\nimport jakar"
  },
  {
    "path": "src/main/java/com/shop/entity/OrderItem.java",
    "chars": 959,
    "preview": "package com.shop.entity;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport jakarta.persistence.*;\n\n@Entity\n@Getter @Se"
  },
  {
    "path": "src/main/java/com/shop/exception/OutOfStockException.java",
    "chars": 170,
    "preview": "package com.shop.exception;\n\npublic class OutOfStockException extends RuntimeException{\n\n    public OutOfStockException("
  },
  {
    "path": "src/main/java/com/shop/repository/CartItemRepository.java",
    "chars": 801,
    "preview": "package com.shop.repository;\n\nimport com.shop.entity.CartItem;\nimport org.springframework.data.jpa.repository.JpaReposit"
  },
  {
    "path": "src/main/java/com/shop/repository/CartRepository.java",
    "chars": 233,
    "preview": "package com.shop.repository;\n\nimport com.shop.entity.Cart;\nimport org.springframework.data.jpa.repository.JpaRepository;"
  },
  {
    "path": "src/main/java/com/shop/repository/ItemImgRepository.java",
    "chars": 350,
    "preview": "package com.shop.repository;\n\nimport com.shop.entity.ItemImg;\nimport org.springframework.data.jpa.repository.JpaReposito"
  },
  {
    "path": "src/main/java/com/shop/repository/ItemRepository.java",
    "chars": 1107,
    "preview": "package com.shop.repository;\n\nimport com.shop.entity.Item;\nimport org.springframework.data.jpa.repository.JpaRepository;"
  },
  {
    "path": "src/main/java/com/shop/repository/ItemRepositoryCustom.java",
    "chars": 434,
    "preview": "package com.shop.repository;\n\nimport com.shop.dto.ItemSearchDto;\nimport com.shop.entity.Item;\nimport org.springframework"
  },
  {
    "path": "src/main/java/com/shop/repository/ItemRepositoryCustomImpl.java",
    "chars": 4772,
    "preview": "package com.shop.repository;\n\nimport com.querydsl.core.QueryResults;\nimport com.querydsl.core.types.dsl.BooleanExpressio"
  },
  {
    "path": "src/main/java/com/shop/repository/MemberRepository.java",
    "chars": 237,
    "preview": "package com.shop.repository;\n\nimport com.shop.entity.Member;\nimport org.springframework.data.jpa.repository.JpaRepositor"
  },
  {
    "path": "src/main/java/com/shop/repository/OrderItemRepository.java",
    "chars": 207,
    "preview": "package com.shop.repository;\n\nimport com.shop.entity.OrderItem;\nimport org.springframework.data.jpa.repository.JpaReposi"
  },
  {
    "path": "src/main/java/com/shop/repository/OrderRepository.java",
    "chars": 734,
    "preview": "package com.shop.repository;\n\nimport com.shop.entity.Order;\nimport org.springframework.data.jpa.repository.JpaRepository"
  },
  {
    "path": "src/main/java/com/shop/service/CartService.java",
    "chars": 4455,
    "preview": "package com.shop.service;\n\nimport com.shop.dto.CartItemDto;\nimport com.shop.entity.Cart;\nimport com.shop.entity.CartItem"
  },
  {
    "path": "src/main/java/com/shop/service/FileService.java",
    "chars": 1038,
    "preview": "package com.shop.service;\n\nimport lombok.extern.java.Log;\nimport org.springframework.stereotype.Service;\nimport java.io."
  },
  {
    "path": "src/main/java/com/shop/service/ItemImgService.java",
    "chars": 2096,
    "preview": "package com.shop.service;\n\nimport com.shop.entity.ItemImg;\nimport com.shop.repository.ItemImgRepository;\nimport lombok.R"
  },
  {
    "path": "src/main/java/com/shop/service/ItemService.java",
    "chars": 3184,
    "preview": "package com.shop.service;\n\nimport com.shop.dto.ItemFormDto;\nimport com.shop.entity.Item;\nimport com.shop.entity.ItemImg;"
  },
  {
    "path": "src/main/java/com/shop/service/MemberService.java",
    "chars": 1553,
    "preview": "package com.shop.service;\n\nimport com.shop.entity.Member;\nimport com.shop.repository.MemberRepository;\nimport lombok.Req"
  },
  {
    "path": "src/main/java/com/shop/service/OrderService.java",
    "chars": 3950,
    "preview": "package com.shop.service;\n\nimport com.shop.dto.OrderDto;\nimport com.shop.entity.*;\nimport com.shop.repository.ItemReposi"
  },
  {
    "path": "src/main/resources/application-test.properties",
    "chars": 280,
    "preview": "# Datasource 설정\nspring.datasource.driver-class-name=org.h2.Driver\nspring.datasource.url=jdbc:h2:mem:test\nspring.datasour"
  },
  {
    "path": "src/main/resources/application.properties",
    "chars": 966,
    "preview": "#애플리케이션 포트 설정\nserver.port = 80\n\n#MySQL 연결 설정\nspring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver\nspring.datasou"
  },
  {
    "path": "src/main/resources/static/css/layout1.css",
    "chars": 348,
    "preview": "html {\n    position: relative;\n    min-height: 100%;\n    margin: 0;\n}\nbody {\n    min-height: 100%;\n}\n.footer {\n    posit"
  },
  {
    "path": "src/main/resources/templates/cart/cartList.html",
    "chars": 8958,
    "preview": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\"\n      xmlns:layout=\"http://www.ultraq.net.nz/thymeleaf/layout\""
  },
  {
    "path": "src/main/resources/templates/fragments/footer.html",
    "chars": 333,
    "preview": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\">\n    <div class=\"footer\" th:fragment=\"footer\">\n        <footer"
  },
  {
    "path": "src/main/resources/templates/fragments/header.html",
    "chars": 2085,
    "preview": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\"\n      xmlns:sec=\"http://www.thymeleaf.org/extras/spring-securi"
  },
  {
    "path": "src/main/resources/templates/item/itemDtl.html",
    "chars": 6631,
    "preview": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\"\n      xmlns:layout=\"http://www.ultraq.net.nz/thymeleaf/layout\""
  },
  {
    "path": "src/main/resources/templates/item/itemForm.html",
    "chars": 5075,
    "preview": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\"\n      xmlns:layout=\"http://www.ultraq.net.nz/thymeleaf/layout\""
  },
  {
    "path": "src/main/resources/templates/item/itemMng.html",
    "chars": 4409,
    "preview": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\"\n      xmlns:layout=\"http://www.ultraq.net.nz/thymeleaf/layout\""
  },
  {
    "path": "src/main/resources/templates/layouts/layout1.html",
    "chars": 991,
    "preview": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\"\n      xmlns:layout=\"http://www.ultraq.net.nz/thymeleaf/layout\""
  },
  {
    "path": "src/main/resources/templates/main.html",
    "chars": 3549,
    "preview": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\"\n      xmlns:layout=\"http://www.ultraq.net.nz/thymeleaf/layout\""
  },
  {
    "path": "src/main/resources/templates/member/memberForm.html",
    "chars": 2244,
    "preview": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\"\n      xmlns:layout=\"http://www.ultraq.net.nz/thymeleaf/layout\""
  },
  {
    "path": "src/main/resources/templates/member/memberLoginForm.html",
    "chars": 1099,
    "preview": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\"\n      xmlns:layout=\"http://www.ultraq.net.nz/thymeleaf/layout\""
  },
  {
    "path": "src/main/resources/templates/order/orderHist.html",
    "chars": 5070,
    "preview": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\"\n      xmlns:layout=\"http://www.ultraq.net.nz/thymeleaf/layout\""
  },
  {
    "path": "src/main/resources/templates/thymeleafEx/thymeleafEx01.html",
    "chars": 191,
    "preview": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Title</title>\n</head"
  },
  {
    "path": "src/main/resources/templates/thymeleafEx/thymeleafEx02.html",
    "chars": 492,
    "preview": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n</"
  },
  {
    "path": "src/main/resources/templates/thymeleafEx/thymeleafEx03.html",
    "chars": 669,
    "preview": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n</"
  },
  {
    "path": "src/main/resources/templates/thymeleafEx/thymeleafEx04.html",
    "chars": 762,
    "preview": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n</"
  },
  {
    "path": "src/main/resources/templates/thymeleafEx/thymeleafEx05.html",
    "chars": 482,
    "preview": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n</"
  },
  {
    "path": "src/main/resources/templates/thymeleafEx/thymeleafEx06.html",
    "chars": 244,
    "preview": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n</"
  },
  {
    "path": "src/main/resources/templates/thymeleafEx/thymeleafEx07.html",
    "chars": 226,
    "preview": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\"\n      xmlns:layout=http://www.ultraq.net.nz/thymeleaf/layout\n "
  },
  {
    "path": "src/test/java/com/shop/ShopApplicationTests.java",
    "chars": 198,
    "preview": "package com.shop;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.context.SpringBootTest;\n\n@Spr"
  },
  {
    "path": "src/test/java/com/shop/controller/ItemControllerTest.java",
    "chars": 1558,
    "preview": "package com.shop.controller;\n\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Test;\nimport org.sp"
  },
  {
    "path": "src/test/java/com/shop/controller/MemberControllerTest.java",
    "chars": 2521,
    "preview": "package com.shop.controller;\n\nimport com.shop.dto.MemberFormDto;\nimport com.shop.entity.Member;\nimport com.shop.service."
  },
  {
    "path": "src/test/java/com/shop/entity/CartTest.java",
    "chars": 1937,
    "preview": "package com.shop.entity;\n\nimport com.shop.dto.MemberFormDto;\nimport com.shop.repository.CartRepository;\nimport com.shop."
  },
  {
    "path": "src/test/java/com/shop/entity/MemberTest.java",
    "chars": 1521,
    "preview": "package com.shop.entity;\n\nimport com.shop.repository.MemberRepository;\nimport org.junit.jupiter.api.DisplayName;\nimport "
  },
  {
    "path": "src/test/java/com/shop/entity/OrderTest.java",
    "chars": 3730,
    "preview": "package com.shop.entity;\n\nimport com.shop.constant.ItemSellStatus;\nimport com.shop.repository.ItemRepository;\nimport com"
  },
  {
    "path": "src/test/java/com/shop/repository/ItemRepositoryTest.java",
    "chars": 6198,
    "preview": "package com.shop.repository;\n\nimport com.shop.constant.ItemSellStatus;\nimport com.shop.entity.Item;\nimport org.junit.jup"
  },
  {
    "path": "src/test/java/com/shop/service/CartServiceTest.java",
    "chars": 2196,
    "preview": "package com.shop.service;\n\nimport com.shop.constant.ItemSellStatus;\nimport com.shop.dto.CartItemDto;\nimport com.shop.ent"
  },
  {
    "path": "src/test/java/com/shop/service/ItemServiceTest.java",
    "chars": 2943,
    "preview": "package com.shop.service;\n\nimport com.shop.constant.ItemSellStatus;\nimport com.shop.dto.ItemFormDto;\nimport com.shop.ent"
  },
  {
    "path": "src/test/java/com/shop/service/MemberServiceTest.java",
    "chars": 2100,
    "preview": "package com.shop.service;\n\nimport com.shop.dto.MemberFormDto;\nimport com.shop.entity.Member;\nimport org.junit.jupiter.ap"
  },
  {
    "path": "src/test/java/com/shop/service/OrderServiceTest.java",
    "chars": 2921,
    "preview": "package com.shop.service;\n\nimport com.shop.constant.ItemSellStatus;\nimport com.shop.constant.OrderStatus;\nimport com.sho"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the roadbook2/shop GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 94 files (162.0 KB), approximately 41.2k tokens, and a symbol index with 210 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!