change directory name
This commit is contained in:
33
react+springboot/book/book-backend/.gitignore
vendored
Normal file
33
react+springboot/book/book-backend/.gitignore
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
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/
|
||||
118
react+springboot/book/book-backend/.mvn/wrapper/MavenWrapperDownloader.java
vendored
Normal file
118
react+springboot/book/book-backend/.mvn/wrapper/MavenWrapperDownloader.java
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
BIN
react+springboot/book/book-backend/.mvn/wrapper/maven-wrapper.jar
vendored
Normal file
BIN
react+springboot/book/book-backend/.mvn/wrapper/maven-wrapper.jar
vendored
Normal file
Binary file not shown.
2
react+springboot/book/book-backend/.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
2
react+springboot/book/book-backend/.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
|
||||
322
react+springboot/book/book-backend/mvnw
vendored
Normal file
322
react+springboot/book/book-backend/mvnw
vendored
Normal file
@@ -0,0 +1,322 @@
|
||||
#!/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 "$@"
|
||||
182
react+springboot/book/book-backend/mvnw.cmd
vendored
Normal file
182
react+springboot/book/book-backend/mvnw.cmd
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
@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%
|
||||
75
react+springboot/book/book-backend/pom.xml
Normal file
75
react+springboot/book/book-backend/pom.xml
Normal file
@@ -0,0 +1,75 @@
|
||||
<?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>2.4.2</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>book</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>book</name>
|
||||
<description>react - spring boot</description>
|
||||
<properties>
|
||||
<java.version>11</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-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<scope>runtime</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<scope>runtime</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>
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.example.book;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class BookApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(BookApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.example.book.domain;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@Accessors(chain = true)
|
||||
@Entity
|
||||
public class Book {
|
||||
|
||||
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
private String title;
|
||||
private String author;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.example.book.domain;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository // 생략가능
|
||||
public interface BookRepository extends JpaRepository<Book, Long> {
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.example.book.service;
|
||||
|
||||
import com.example.book.domain.Book;
|
||||
import com.example.book.domain.BookRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true) // readOnly : JPA 변경감지 내부기능 활성화 X , update 시의 정합성 유지 , insert 시의 팬텀현상은 못막음
|
||||
public class BookService {
|
||||
|
||||
private final BookRepository bookRepository;
|
||||
|
||||
@Transactional
|
||||
public Book 저장하기(Book book) {
|
||||
return bookRepository.save(book);
|
||||
}
|
||||
|
||||
public Book 한건가져오기(Long id){
|
||||
return bookRepository.findById(id)
|
||||
.orElseThrow(() -> new IllegalArgumentException("id를 확인해 주세요"));
|
||||
}
|
||||
|
||||
public List<Book> 모두가져오기(){
|
||||
return bookRepository.findAll();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Book 수정하기(Long id, Book book){
|
||||
Book bookEntity = bookRepository.findById(id)
|
||||
.orElseThrow(() -> new IllegalArgumentException("id를 확인해 주세요"));
|
||||
bookEntity
|
||||
.setTitle(book.getTitle())
|
||||
.setAuthor(book.getAuthor());
|
||||
return bookEntity;
|
||||
} // 함수 종료 => 트랜잭션 종료 => 영속화 되어있는 데이터를 DB로 갱신(flush) => commit : 더티체킹
|
||||
|
||||
@Transactional
|
||||
public String 삭제하기(Long id){
|
||||
bookRepository.deleteById(id); // 오류 발생시 익셉션 발생
|
||||
return "ok";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.example.book.web;
|
||||
|
||||
import com.example.book.domain.Book;
|
||||
import com.example.book.service.BookService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@CrossOrigin
|
||||
public class BookController {
|
||||
|
||||
private final BookService bookService;
|
||||
|
||||
@GetMapping("/book")
|
||||
public ResponseEntity<?> findAll() {
|
||||
return new ResponseEntity<>(bookService.모두가져오기(), HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PostMapping ("/book")
|
||||
public ResponseEntity<?> save(@RequestBody Book book) {
|
||||
return new ResponseEntity<>(bookService.저장하기(book), HttpStatus.CREATED);
|
||||
}
|
||||
|
||||
@GetMapping("/book/{id}")
|
||||
public ResponseEntity<?> findById(@PathVariable Long id) {
|
||||
return new ResponseEntity<>(bookService.한건가져오기(id), HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PutMapping ("/book/{id}")
|
||||
public ResponseEntity<?> updateById(@PathVariable Long id, @RequestBody Book book) {
|
||||
return new ResponseEntity<>(bookService.수정하기(id, book), HttpStatus.OK);
|
||||
}
|
||||
|
||||
@DeleteMapping ("/book/{id}")
|
||||
public ResponseEntity<?> updateById(@PathVariable Long id) {
|
||||
return new ResponseEntity<>(bookService.삭제하기(id), HttpStatus.OK);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
server:
|
||||
servlet:
|
||||
encoding:
|
||||
charset: utf-8
|
||||
enabled: true
|
||||
|
||||
spring:
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://localhost:3306/book?serverTimezone=Asia/Seoul
|
||||
username: book
|
||||
password: 1234
|
||||
|
||||
jpa:
|
||||
open-in-view: true
|
||||
hibernate:
|
||||
ddl-auto: update
|
||||
show-sql: true
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.example.book;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class BookApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.example.book.domain;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
|
||||
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
// 단위테스트 ( DB 관련 Bean 만 IoC에 등록되면 됨 )
|
||||
|
||||
@Transactional
|
||||
@AutoConfigureTestDatabase(replace = Replace.ANY) // Replace.ANY 가짜 DB로 테스트, Replace.NONE 실제 DB로 테스트
|
||||
@DataJpaTest() // Repository 들을 spring container 에 등록해줌
|
||||
class BookRepositoryUnitTest {
|
||||
|
||||
@Autowired
|
||||
private BookRepository bookRepository;
|
||||
|
||||
@Test
|
||||
public void save_테스트() throws Exception {
|
||||
// given
|
||||
Book book = new Book(null, "제목", "저자");
|
||||
|
||||
// when
|
||||
Book bookEntity = bookRepository.save(book);
|
||||
|
||||
// then
|
||||
Assertions.assertEquals(1L, bookEntity.getId());
|
||||
Assertions.assertEquals("제목", bookEntity.getTitle());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.example.book.service;
|
||||
|
||||
import com.example.book.domain.Book;
|
||||
import com.example.book.domain.BookRepository;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
/**
|
||||
* 단위테스트 ( Service 관련 Bean 만 메모리에 띄움 )
|
||||
* BookService 는 BookRepository 에 의존적이다.
|
||||
* 단위테스트를 위해 BookRepository 를 가짜 객체로 만들어 테스트 ( Mockito )
|
||||
*/
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class BookServiceUnitTest {
|
||||
|
||||
@InjectMocks // BookService 객체가 만들어질 때 BookServiceUnitTest 파일에 @Mock 으로 등록된 객체를 DI
|
||||
private BookService bookService;
|
||||
|
||||
@Mock
|
||||
private BookRepository bookRepository;
|
||||
|
||||
@Test
|
||||
public void 저장하기_테스트() throws Exception {
|
||||
// BODMockito 방식
|
||||
// given
|
||||
Book book = new Book();
|
||||
book.setTitle("제목");
|
||||
book.setAuthor("저자");
|
||||
Mockito.when(bookRepository.save(book)).thenReturn(book);
|
||||
|
||||
// when
|
||||
Book bookEntity = bookService.저장하기(book);
|
||||
|
||||
// then
|
||||
Assertions.assertEquals(bookEntity, book);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package com.example.book.web;
|
||||
|
||||
import com.example.book.domain.Book;
|
||||
import com.example.book.domain.BookRepository;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
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.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.ResultActions;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
import static 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.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* 통합테스트 ( 모든 Bean 들을 IoC에 올리고 테스트 하는 것 )
|
||||
* WebEnvironment.MOCK = 실제 톰캣이 아닌 다른 톰캣으로 테스트
|
||||
* WebEnvironment.RANDOM_PORT = 실제 톰캣으로 테스트
|
||||
* @AutoConfigureMockMvc = MockMvc 를 IoC에 등록
|
||||
* @Transactional = 각 테스트 함수가 종료되면 트랜잭션을 rollback
|
||||
*
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
@Transactional
|
||||
@AutoConfigureMockMvc
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
|
||||
public class BookControllerIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private BookRepository bookRepository;
|
||||
|
||||
@Autowired
|
||||
private EntityManager entityManager;
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
// entityManager.createNativeQuery("alter table book alter column id restart with 1").executeUpdate();
|
||||
entityManager.createNativeQuery("alter table book auto_increment=1").executeUpdate();
|
||||
}
|
||||
|
||||
// BDDMockito 패턴 given, when, then
|
||||
@Test
|
||||
public void save_테스트() throws Exception {
|
||||
log.info("save 테스트 시작 ==================================");
|
||||
// given ( 테스트를 하기 위한 준비 )
|
||||
Book book = new Book(null, "스프링", "kim");
|
||||
String content = new ObjectMapper().writeValueAsString(book);
|
||||
|
||||
// when ( 테스트 실행 )
|
||||
ResultActions resultActions = mockMvc.perform(post("/book")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(content)
|
||||
.accept(MediaType.APPLICATION_JSON_UTF8));
|
||||
|
||||
// then ( 검증 )
|
||||
resultActions
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.title").value("스프링"))
|
||||
.andDo(print());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findAll_테스트() throws Exception {
|
||||
// given
|
||||
List<Book> books = new ArrayList<>();
|
||||
books.add(new Book(null, "스프링", "kim"));
|
||||
books.add(new Book(null, "리액트", "kim"));
|
||||
books.add(new Book(null, "JUnit", "kim"));
|
||||
|
||||
bookRepository.saveAll(books);
|
||||
|
||||
// when
|
||||
ResultActions resultActions = mockMvc.perform(get("/book")
|
||||
.accept(MediaType.APPLICATION_JSON_UTF8));
|
||||
|
||||
// then
|
||||
resultActions
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$", Matchers.hasSize(3)))
|
||||
.andExpect(jsonPath("$.[0].title").value("스프링"))
|
||||
.andDo(print());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findById_테스트() throws Exception {
|
||||
// given
|
||||
Long id = 2L;
|
||||
List<Book> books = new ArrayList<>();
|
||||
books.add(new Book(null, "스프링", "kim"));
|
||||
books.add(new Book(null, "리액트", "kim"));
|
||||
books.add(new Book(null, "JUnit", "kim"));
|
||||
|
||||
bookRepository.saveAll(books);
|
||||
|
||||
// when
|
||||
ResultActions resultActions = mockMvc.perform(get("/book/{id}", id)
|
||||
.accept(MediaType.APPLICATION_JSON_UTF8));
|
||||
|
||||
// then
|
||||
resultActions
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.title").value("리액트"))
|
||||
.andDo(print());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void update_테스트() throws Exception {
|
||||
// given
|
||||
List<Book> books = new ArrayList<>();
|
||||
books.add(new Book(null, "스프링", "kim"));
|
||||
books.add(new Book(null, "리액트", "kim"));
|
||||
books.add(new Book(null, "JUnit", "kim"));
|
||||
|
||||
bookRepository.saveAll(books);
|
||||
|
||||
Long id = 2L;
|
||||
Book book = new Book(null, "SQL", "kim");
|
||||
String content = new ObjectMapper().writeValueAsString(book);
|
||||
|
||||
// when
|
||||
ResultActions resultActions = mockMvc.perform(put("/book/{id}", id)
|
||||
.contentType(MediaType.APPLICATION_JSON_UTF8)
|
||||
.content(content)
|
||||
.accept(MediaType.APPLICATION_JSON_UTF8));
|
||||
|
||||
// then
|
||||
resultActions
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.id").value(2L))
|
||||
.andExpect(jsonPath("$.title").value("SQL"))
|
||||
.andDo(print());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void delete_테스트() throws Exception {
|
||||
// given
|
||||
List<Book> books = new ArrayList<>();
|
||||
books.add(new Book(null, "스프링", "kim"));
|
||||
books.add(new Book(null, "리액트", "kim"));
|
||||
books.add(new Book(null, "JUnit", "kim"));
|
||||
|
||||
bookRepository.saveAll(books);
|
||||
|
||||
Long id = 1L;
|
||||
|
||||
// when
|
||||
ResultActions resultActions = mockMvc.perform(delete("/book/{id}", id)
|
||||
.accept(MediaType.TEXT_PLAIN));
|
||||
|
||||
// then
|
||||
resultActions
|
||||
.andExpect(status().isOk())
|
||||
.andDo(print());
|
||||
|
||||
String result = resultActions.andReturn()
|
||||
.getResponse().getContentAsString();
|
||||
|
||||
Assertions.assertEquals("ok", result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package com.example.book.web;
|
||||
|
||||
import com.example.book.domain.Book;
|
||||
import com.example.book.service.BookService;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.ResultActions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
import static 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.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
// 단위테스트 ( Controller 관련 로직만 테스트 - Filter, ControllerAdvice , ... )
|
||||
|
||||
@Slf4j
|
||||
@WebMvcTest
|
||||
class BookControllerUnitTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@MockBean // IoC 환경에 등록
|
||||
private BookService bookService;
|
||||
|
||||
// BDDMockito 패턴 given, when, then
|
||||
@Test
|
||||
public void save_테스트() throws Exception {
|
||||
// given ( 테스트를 하기 위한 준비 )
|
||||
Book book = new Book(null, "스프링", "kim");
|
||||
String content = new ObjectMapper().writeValueAsString(book);
|
||||
when(bookService.저장하기(book)).thenReturn(new Book(1L, "스프링", "kim"));
|
||||
|
||||
// when ( 테스트 실행 )
|
||||
ResultActions resultActions = mockMvc.perform(post("/book")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(content)
|
||||
.accept(MediaType.APPLICATION_JSON));
|
||||
|
||||
// then ( 검증 )
|
||||
resultActions
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.title").value("스프링"))
|
||||
.andDo(print());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findAll_테스트() throws Exception {
|
||||
// given
|
||||
List<Book> books = new ArrayList<>();
|
||||
books.add(new Book(1L, "스프링", "kim"));
|
||||
books.add(new Book(2L, "리액트", "kim"));
|
||||
when(bookService.모두가져오기()).thenReturn(books);
|
||||
|
||||
// when
|
||||
ResultActions resultActions = mockMvc.perform(get("/book")
|
||||
.accept(MediaType.APPLICATION_JSON_UTF8));
|
||||
|
||||
// then
|
||||
resultActions
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$", Matchers.hasSize(2)))
|
||||
.andExpect(jsonPath("$.[0].title").value("스프링"))
|
||||
.andDo(print());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findById_테스트() throws Exception {
|
||||
// given
|
||||
Long id = 1L;
|
||||
when(bookService.한건가져오기(id)).thenReturn(new Book(1L, "자바", "kim"));
|
||||
|
||||
// when
|
||||
ResultActions resultActions = mockMvc.perform(get("/book/{id}", id)
|
||||
.accept(MediaType.APPLICATION_JSON_UTF8));
|
||||
|
||||
// then
|
||||
resultActions
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.title").value("자바"))
|
||||
.andDo(print());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void update_테스트() throws Exception {
|
||||
// given
|
||||
Long id = 1L;
|
||||
Book book = new Book(null, "SQL", "kim");
|
||||
String content = new ObjectMapper().writeValueAsString(book);
|
||||
when(bookService.수정하기(id, book)).thenReturn(new Book(1L, "SQL", "kim"));
|
||||
|
||||
// when
|
||||
ResultActions resultActions = mockMvc.perform(put("/book/{id}", id)
|
||||
.contentType(MediaType.APPLICATION_JSON_UTF8)
|
||||
.content(content)
|
||||
.accept(MediaType.APPLICATION_JSON_UTF8));
|
||||
|
||||
// then
|
||||
resultActions
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.title").value("SQL"))
|
||||
.andDo(print());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void delete_테스트() throws Exception {
|
||||
// given
|
||||
Long id = 1L;
|
||||
when(bookService.삭제하기(id)).thenReturn("ok");
|
||||
|
||||
// when
|
||||
ResultActions resultActions = mockMvc.perform(delete("/book/{id}", id)
|
||||
.accept(MediaType.TEXT_PLAIN));
|
||||
|
||||
// then
|
||||
resultActions
|
||||
.andExpect(status().isOk())
|
||||
.andDo(print());
|
||||
|
||||
String result = resultActions.andReturn()
|
||||
.getResponse().getContentAsString();
|
||||
|
||||
Assertions.assertEquals("ok", result);
|
||||
}
|
||||
}
|
||||
23
react+springboot/book/book-frontend/.gitignore
vendored
Normal file
23
react+springboot/book/book-frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
7
react+springboot/book/book-frontend/.prettierrc
Normal file
7
react+springboot/book/book-frontend/.prettierrc
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"semi": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 80
|
||||
}
|
||||
43
react+springboot/book/book-frontend/package.json
Normal file
43
react+springboot/book/book-frontend/package.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "book-frontend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.11.4",
|
||||
"@testing-library/react": "^11.1.0",
|
||||
"@testing-library/user-event": "^12.1.10",
|
||||
"bootstrap": "^4.6.0",
|
||||
"react": "^17.0.1",
|
||||
"react-bootstrap": "^1.4.3",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "4.0.2",
|
||||
"redux": "^4.0.5",
|
||||
"web-vitals": "^1.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
BIN
react+springboot/book/book-frontend/public/favicon.ico
Normal file
BIN
react+springboot/book/book-frontend/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
43
react+springboot/book/book-frontend/public/index.html
Normal file
43
react+springboot/book/book-frontend/public/index.html
Normal file
@@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
BIN
react+springboot/book/book-frontend/public/logo192.png
Normal file
BIN
react+springboot/book/book-frontend/public/logo192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
BIN
react+springboot/book/book-frontend/public/logo512.png
Normal file
BIN
react+springboot/book/book-frontend/public/logo512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
25
react+springboot/book/book-frontend/public/manifest.json
Normal file
25
react+springboot/book/book-frontend/public/manifest.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
3
react+springboot/book/book-frontend/public/robots.txt
Normal file
3
react+springboot/book/book-frontend/public/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
28
react+springboot/book/book-frontend/src/App.js
Normal file
28
react+springboot/book/book-frontend/src/App.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { Container } from 'react-bootstrap';
|
||||
import { Route } from 'react-router-dom';
|
||||
import Header from './components/Header';
|
||||
import Detail from './pages/book/Detail';
|
||||
import Home from './pages/book/Home';
|
||||
import SaveForm from './pages/book/SaveForm';
|
||||
import UpdateForm from './pages/book/UpdateForm';
|
||||
import JoinForm from './pages/user/JoinForm';
|
||||
import LoginForm from './pages/user/LoginForm';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<Header />
|
||||
<Container>
|
||||
<Route path="/" exact={true} component={Home} />
|
||||
<Route path="/saveForm" exact={true} component={SaveForm} />
|
||||
<Route path="/book/:id" exact={true} component={Detail} />
|
||||
<Route path="/loginForm" exact={true} component={LoginForm} />
|
||||
<Route path="/joinForm" exact={true} component={JoinForm} />
|
||||
<Route path="/updateForm/:id" exact={true} component={UpdateForm} />
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { Card } from 'react-bootstrap';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const BookItem = (props) => {
|
||||
const { id, title } = props.book;
|
||||
return (
|
||||
<Card>
|
||||
<Card.Body>
|
||||
<Card.Title>{title}</Card.Title>
|
||||
<Link to={'/book/' + id} className="btn btn-primary">
|
||||
상세보기
|
||||
</Link>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default BookItem;
|
||||
33
react+springboot/book/book-frontend/src/components/Header.js
Normal file
33
react+springboot/book/book-frontend/src/components/Header.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import { Button, Form, FormControl, Nav, Navbar } from 'react-bootstrap';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const Header = () => {
|
||||
return (
|
||||
<>
|
||||
<Navbar bg="dark" variant="dark">
|
||||
<Link to="/" className="navbar-brand">
|
||||
홈
|
||||
</Link>
|
||||
<Nav className="mr-auto">
|
||||
<Link to="/joinForm" className="nav-link">
|
||||
회원가입
|
||||
</Link>
|
||||
<Link to="/loginForm" className="nav-link">
|
||||
로그인
|
||||
</Link>
|
||||
<Link to="/saveForm" className="nav-link">
|
||||
글쓰기
|
||||
</Link>
|
||||
</Nav>
|
||||
<Form inline>
|
||||
<FormControl type="text" placeholder="Search" className="mr-sm-2" />
|
||||
<Button variant="outline-info">Search</Button>
|
||||
</Form>
|
||||
</Navbar>
|
||||
<br />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
14
react+springboot/book/book-frontend/src/index.js
Normal file
14
react+springboot/book/book-frontend/src/index.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root'),
|
||||
);
|
||||
51
react+springboot/book/book-frontend/src/pages/book/Detail.js
Normal file
51
react+springboot/book/book-frontend/src/pages/book/Detail.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button } from 'react-bootstrap';
|
||||
|
||||
const Detail = (props) => {
|
||||
const id = props.match.params.id;
|
||||
|
||||
const [book, setBook] = useState({
|
||||
id: '',
|
||||
title: '',
|
||||
author: '',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
fetch('http://localhost:8080/book/' + id)
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
setBook(res);
|
||||
});
|
||||
}, [id]);
|
||||
|
||||
const deleteBook = () => {
|
||||
fetch('http://localhost:8080/book/' + id, {
|
||||
method: 'delete',
|
||||
})
|
||||
.then((res) => res.text())
|
||||
.then((res) => {
|
||||
res === 'ok' ? props.history.push('/') : alert('삭제실패');
|
||||
});
|
||||
};
|
||||
|
||||
const updateBook = () => {
|
||||
props.history.push('/updateForm/' + id);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>책 상세보기</h1>
|
||||
<Button variant="warning" onClick={updateBook}>
|
||||
수정
|
||||
</Button>{' '}
|
||||
<Button variant="danger" onClick={deleteBook}>
|
||||
삭제
|
||||
</Button>
|
||||
<hr />
|
||||
<h3>{book.author}</h3>
|
||||
<h1>{book.title}</h1>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Detail;
|
||||
24
react+springboot/book/book-frontend/src/pages/book/Home.js
Normal file
24
react+springboot/book/book-frontend/src/pages/book/Home.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import BookItem from '../../components/BookItem';
|
||||
|
||||
const Home = () => {
|
||||
const [books, setBooks] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('http://localhost:8080/book')
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
setBooks(res);
|
||||
}); // 비동기 함수
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{books.map((book) => (
|
||||
<BookItem key={book.id} book={book} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
@@ -0,0 +1,70 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Button, Form } from 'react-bootstrap';
|
||||
|
||||
const SaveForm = (props) => {
|
||||
const [book, setBook] = useState({
|
||||
title: '',
|
||||
author: '',
|
||||
});
|
||||
|
||||
const changeValue = (e) => {
|
||||
setBook({
|
||||
...book,
|
||||
[e.target.name]: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const submitBook = (e) => {
|
||||
e.preventDefault();
|
||||
fetch('http://localhost:8080/book', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
},
|
||||
body: JSON.stringify(book),
|
||||
})
|
||||
.then((res) => {
|
||||
return res.status === 201 ? res.json() : null;
|
||||
})
|
||||
.then((res) => {
|
||||
res !== null ? props.history.push('/') : alert('책 등록 실패');
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form onSubmit={submitBook}>
|
||||
<Form.Group controlId="formBasicEmail">
|
||||
<Form.Label>Title</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
placeholder="Enter title"
|
||||
onChange={changeValue}
|
||||
name="title"
|
||||
/>
|
||||
<Form.Text className="text-muted"></Form.Text>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group controlId="formBasicEmail">
|
||||
<Form.Label>Author</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
placeholder="Enter author"
|
||||
onChange={changeValue}
|
||||
name="author"
|
||||
/>
|
||||
<Form.Text className="text-muted"></Form.Text>
|
||||
</Form.Group>
|
||||
|
||||
<Button variant="primary" type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SaveForm;
|
||||
@@ -0,0 +1,83 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Form } from 'react-bootstrap';
|
||||
|
||||
const UpdateForm = (props) => {
|
||||
const id = props.match.params.id;
|
||||
const [book, setBook] = useState({
|
||||
title: '',
|
||||
author: '',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
fetch('http://localhost:8080/book/' + id)
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
setBook(res);
|
||||
});
|
||||
}, [id]);
|
||||
|
||||
const changeValue = (e) => {
|
||||
setBook({
|
||||
...book,
|
||||
[e.target.name]: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const submitBook = (e) => {
|
||||
e.preventDefault();
|
||||
fetch('http://localhost:8080/book/' + id, {
|
||||
method: 'put',
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
},
|
||||
body: JSON.stringify(book),
|
||||
})
|
||||
.then((res) => {
|
||||
return res.status === 200 ? res.json() : null;
|
||||
})
|
||||
.then((res) => {
|
||||
res !== null
|
||||
? props.history.push('/book/' + id)
|
||||
: alert('책 수정 실패');
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form onSubmit={submitBook}>
|
||||
<Form.Group controlId="formBasicEmail">
|
||||
<Form.Label>Title</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
placeholder="Enter title"
|
||||
onChange={changeValue}
|
||||
name="title"
|
||||
value={book.title}
|
||||
/>
|
||||
<Form.Text className="text-muted"></Form.Text>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group controlId="formBasicEmail">
|
||||
<Form.Label>Author</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
placeholder="Enter author"
|
||||
onChange={changeValue}
|
||||
name="author"
|
||||
value={book.author}
|
||||
/>
|
||||
<Form.Text className="text-muted"></Form.Text>
|
||||
</Form.Group>
|
||||
|
||||
<Button variant="primary" type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdateForm;
|
||||
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
const JoinForm = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>회원가입 화면</h1>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default JoinForm;
|
||||
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
const LoginForm = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>로그인 화면</h1>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginForm;
|
||||
11670
react+springboot/book/book-frontend/yarn.lock
Normal file
11670
react+springboot/book/book-frontend/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
1
react+springboot/react-practice/.eslintcache
Normal file
1
react+springboot/react-practice/.eslintcache
Normal file
@@ -0,0 +1 @@
|
||||
[{"C:\\Users\\Woojin\\Desktop\\study\\Study\\react-springboot\\my-app\\src\\index.js":"1","C:\\Users\\Woojin\\Desktop\\study\\Study\\react-springboot\\my-app\\src\\App.js":"2","C:\\Users\\Woojin\\Desktop\\study\\Study\\react-springboot\\my-app\\src\\components\\Top.js":"3","C:\\Users\\Woojin\\Desktop\\study\\Study\\react-springboot\\my-app\\src\\components\\Bottom.js":"4","C:\\Users\\Woojin\\Desktop\\study\\Study\\react-springboot\\my-app\\src\\store.js":"5"},{"size":534,"mtime":1612422022110,"results":"6","hashOfConfig":"7"},{"size":261,"mtime":1612422137381,"results":"8","hashOfConfig":"7"},{"size":346,"mtime":1612423047106,"results":"9","hashOfConfig":"7"},{"size":464,"mtime":1612422810996,"results":"10","hashOfConfig":"7"},{"size":637,"mtime":1612422959111,"results":"11","hashOfConfig":"7"},{"filePath":"12","messages":"13","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"11g3wnd",{"filePath":"14","messages":"15","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"16","messages":"17","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"18","messages":"19","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"20","messages":"21","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"C:\\Users\\Woojin\\Desktop\\study\\Study\\react-springboot\\my-app\\src\\index.js",[],"C:\\Users\\Woojin\\Desktop\\study\\Study\\react-springboot\\my-app\\src\\App.js",[],"C:\\Users\\Woojin\\Desktop\\study\\Study\\react-springboot\\my-app\\src\\components\\Top.js",[],"C:\\Users\\Woojin\\Desktop\\study\\Study\\react-springboot\\my-app\\src\\components\\Bottom.js",[],"C:\\Users\\Woojin\\Desktop\\study\\Study\\react-springboot\\my-app\\src\\store.js",[]]
|
||||
23
react+springboot/react-practice/.gitignore
vendored
Normal file
23
react+springboot/react-practice/.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
7
react+springboot/react-practice/.prettierrc
Normal file
7
react+springboot/react-practice/.prettierrc
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"semi": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 80
|
||||
}
|
||||
16666
react+springboot/react-practice/package-lock.json
generated
Normal file
16666
react+springboot/react-practice/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
46
react+springboot/react-practice/package.json
Normal file
46
react+springboot/react-practice/package.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "my-app",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.11.9",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@testing-library/user-event": "^12.6.3",
|
||||
"bootstrap": "^4.6.0",
|
||||
"jquery": "^3.5.1",
|
||||
"popper.js": "^1.16.1",
|
||||
"react": "^17.0.1",
|
||||
"react-bootstrap": "^1.4.3",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "4.0.1",
|
||||
"redux": "^4.0.5",
|
||||
"styled-components": "^5.2.1",
|
||||
"web-vitals": "^0.2.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
3
react+springboot/react-practice/practice/01/App.css
Normal file
3
react+springboot/react-practice/practice/01/App.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.box-style {
|
||||
color: blue;
|
||||
}
|
||||
19
react+springboot/react-practice/practice/01/App.js
vendored
Normal file
19
react+springboot/react-practice/practice/01/App.js
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Route } from 'react-router-dom';
|
||||
import './App.css';
|
||||
import Footer from './components/Footer';
|
||||
import Header from './components/Header';
|
||||
import HomePage from './pages/HomePage';
|
||||
import LoginPage from './pages/LoginPage';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<Route path="/" exact={true} component={HomePage} />
|
||||
<Route path="/login/:id" exact={true} component={LoginPage} />
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
9
react+springboot/react-practice/practice/01/Style.js
vendored
Normal file
9
react+springboot/react-practice/practice/01/Style.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Title = styled.h1`
|
||||
font-size: 1.5em;
|
||||
text-align: center;
|
||||
color: palevioletred;
|
||||
`;
|
||||
|
||||
export { Title };
|
||||
15
react+springboot/react-practice/practice/01/Sub.js
vendored
Normal file
15
react+springboot/react-practice/practice/01/Sub.js
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
|
||||
let num = 10;
|
||||
|
||||
const Sub = () => {
|
||||
console.log('sub');
|
||||
return (
|
||||
<div>
|
||||
<h1>Sub</h1>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { num };
|
||||
export default Sub;
|
||||
22
react+springboot/react-practice/practice/01/components/Footer.js
vendored
Normal file
22
react+springboot/react-practice/practice/01/components/Footer.js
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
// 하나의 컴포넌트 생성 (재사용)
|
||||
// 하나의 컴포넌트에 js css를 파일 하나로 관리
|
||||
const StyledFooterDiv = styled.div`
|
||||
border: 1px solid black;
|
||||
height: 300px;
|
||||
`;
|
||||
|
||||
const Footer = () => {
|
||||
return (
|
||||
<StyledFooterDiv>
|
||||
<ul>
|
||||
<li>오시는길 : 서울</li>
|
||||
<li>전화번호 : 021321231</li>
|
||||
</ul>
|
||||
</StyledFooterDiv>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
52
react+springboot/react-practice/practice/01/components/Header.js
vendored
Normal file
52
react+springboot/react-practice/practice/01/components/Header.js
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { Navbar, Nav, Form, FormControl, Button } from 'react-bootstrap';
|
||||
|
||||
// 하나의 컴포넌트 생성 (재사용)
|
||||
// 하나의 컴포넌트에 js css를 파일 하나로 관리
|
||||
const StyledHeaderDiv = styled.div`
|
||||
border: 1px solid black;
|
||||
height: 300px;
|
||||
background-color: ${(props) => props.backgroundColor};
|
||||
`;
|
||||
|
||||
const StyledHeaderLink = styled(Link)`
|
||||
color: red;
|
||||
`;
|
||||
|
||||
const Header = () => {
|
||||
return (
|
||||
<>
|
||||
<StyledHeaderDiv backgroundColor="blue">
|
||||
<ul>
|
||||
<li>
|
||||
<StyledHeaderLink to="/">홈</StyledHeaderLink>
|
||||
</li>
|
||||
<li>
|
||||
<StyledHeaderLink to="/login/10">로그인</StyledHeaderLink>
|
||||
</li>
|
||||
</ul>
|
||||
</StyledHeaderDiv>
|
||||
<>
|
||||
<Navbar bg="dark" variant="dark">
|
||||
<Navbar.Brand href="#home">Navbar</Navbar.Brand>
|
||||
<Nav className="mr-auto">
|
||||
<Link to="/" className="nav-link">
|
||||
Home
|
||||
</Link>
|
||||
<Link to="/login" className="nav-link">
|
||||
Login
|
||||
</Link>
|
||||
</Nav>
|
||||
<Form inline>
|
||||
<FormControl type="text" placeholder="Search" className="mr-sm-2" />
|
||||
<Button variant="outline-info">Search</Button>
|
||||
</Form>
|
||||
</Navbar>
|
||||
</>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
58
react+springboot/react-practice/practice/01/components/home/Home.js
vendored
Normal file
58
react+springboot/react-practice/practice/01/components/home/Home.js
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledDeleteButton = styled.button`
|
||||
color: ${(props) => (props.user.username === 'kim' ? 'blue' : 'red')};
|
||||
`;
|
||||
|
||||
const StyleAddButton = styled(StyledDeleteButton)`
|
||||
background-color: green;
|
||||
`;
|
||||
|
||||
const Home = (props) => {
|
||||
// 구조분할 할당
|
||||
const { board, setBoard, user } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>홈</h1>
|
||||
<Button variant="primary">Primary</Button>{' '}
|
||||
<StyleAddButton user={user}>더하기</StyleAddButton>
|
||||
<StyledDeleteButton user={user} onClick={() => setBoard([])}>
|
||||
전체삭제
|
||||
</StyledDeleteButton>
|
||||
{board.map((p) => (
|
||||
<h3 key={board.id}>
|
||||
제목: {p.title} 내용: {p.content}
|
||||
</h3>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
|
||||
// props
|
||||
// const Home = (props) => {
|
||||
// // const board = props.board;
|
||||
// // const id = props.id;
|
||||
|
||||
// // 구조분할 할당
|
||||
// const { board, setBoard, number, setNumber } = props;
|
||||
|
||||
// return (
|
||||
// <div>
|
||||
// <h1>홈 : {number} </h1>
|
||||
// <button onClick={() => setNumber(number + 1)}>번호증가</button>
|
||||
// <button onClick={() => setBoard([])}>전체삭제</button>
|
||||
// {board.map((p) => (
|
||||
// <h3>
|
||||
// 제목: {p.title} 내용: {p.content}
|
||||
// </h3>
|
||||
// ))}
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
|
||||
// export default Home;
|
||||
16
react+springboot/react-practice/practice/01/components/login/Login.js
vendored
Normal file
16
react+springboot/react-practice/practice/01/components/login/Login.js
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledLoginDiv = styled.div`
|
||||
padding: 30px;
|
||||
`;
|
||||
|
||||
const Login = () => {
|
||||
return (
|
||||
<StyledLoginDiv>
|
||||
<h1>로그인 페이지 입니다.</h1>
|
||||
</StyledLoginDiv>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
13
react+springboot/react-practice/practice/01/index.css
Normal file
13
react+springboot/react-practice/practice/01/index.css
Normal file
@@ -0,0 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
15
react+springboot/react-practice/practice/01/index.js
vendored
Normal file
15
react+springboot/react-practice/practice/01/index.js
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root'),
|
||||
);
|
||||
63
react+springboot/react-practice/practice/01/pages/HomePage.js
vendored
Normal file
63
react+springboot/react-practice/practice/01/pages/HomePage.js
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import Home from '../components/home/Home';
|
||||
|
||||
const HomePage = () => {
|
||||
// Http 요청 (fetch, axios)
|
||||
const [board, setBoard] = useState([]);
|
||||
const [user, setUser] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
// 데이터를 받았다고 가정
|
||||
// fetch(), axios()
|
||||
let data = [
|
||||
{ id: 1, title: '제목1', content: '내용1' },
|
||||
{ id: 2, title: '제목2', content: '내용2' },
|
||||
{ id: 3, title: '제목3', content: '내용3' },
|
||||
];
|
||||
|
||||
setBoard([...data]);
|
||||
setUser({ id: 1, username: 'lee' });
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Home board={board} setBoard={setBoard} user={user} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HomePage;
|
||||
|
||||
// props
|
||||
// const HomePage = () => {
|
||||
// // Http 요청 (fetch, axios)
|
||||
// const [board, setBoard] = useState([]);
|
||||
// const [number, setNumber] = useState(0);
|
||||
|
||||
// useEffect(() => {
|
||||
// // 데이터를 받았다고 가정
|
||||
// // fetch(), axios()
|
||||
// let data = [
|
||||
// { id: 1, title: '제목1', content: '내용1' },
|
||||
// { id: 2, title: '제목2', content: '내용2' },
|
||||
// { id: 3, title: '제목3', content: '내용3' },
|
||||
// ];
|
||||
|
||||
// setBoard([...data]);
|
||||
// }, []);
|
||||
|
||||
// return (
|
||||
// <div>
|
||||
// <Header />
|
||||
// <Home
|
||||
// number={number}
|
||||
// board={board}
|
||||
// setBoard={setBoard}
|
||||
// setNumber={setNumber}
|
||||
// />
|
||||
// <Footer />
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
|
||||
// export default HomePage;
|
||||
16
react+springboot/react-practice/practice/01/pages/LoginPage.js
vendored
Normal file
16
react+springboot/react-practice/practice/01/pages/LoginPage.js
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
|
||||
import Login from '../components/Login';
|
||||
|
||||
const LoginPage = (props) => {
|
||||
console.log(props);
|
||||
console.log(props.match.params.id);
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => props.history.goBack()}>뒤로가기</button>
|
||||
<Login />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginPage;
|
||||
@@ -0,0 +1,3 @@
|
||||
.box-style {
|
||||
color: blue;
|
||||
}
|
||||
18
react+springboot/react-practice/practice/02 Route, Link/App.js
vendored
Normal file
18
react+springboot/react-practice/practice/02 Route, Link/App.js
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Route } from 'react-router-dom';
|
||||
import Navigation from './components/Navigation';
|
||||
import ListPage from './pages/ListPage';
|
||||
import WritePage from './pages/WritePage';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<Navigation />
|
||||
<ListPage />
|
||||
|
||||
{/* <Route path="/" exact={true} component={ListPage}></Route>
|
||||
<Route path="/write" exact={true} component={WritePage}></Route> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
17
react+springboot/react-practice/practice/02 Route, Link/components/Navigation.js
vendored
Normal file
17
react+springboot/react-practice/practice/02 Route, Link/components/Navigation.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const Navigation = () => {
|
||||
return (
|
||||
<ul>
|
||||
<li>
|
||||
<Link to="/">리스트</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/write">글쓰기</Link>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
export default Navigation;
|
||||
@@ -0,0 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
15
react+springboot/react-practice/practice/02 Route, Link/index.js
vendored
Normal file
15
react+springboot/react-practice/practice/02 Route, Link/index.js
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root'),
|
||||
);
|
||||
77
react+springboot/react-practice/practice/02 Route, Link/pages/ListPage.js
vendored
Normal file
77
react+springboot/react-practice/practice/02 Route, Link/pages/ListPage.js
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
import React from 'react';
|
||||
import { useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledItemBoxDiv = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border: 1px solid black;
|
||||
padding: 10px;
|
||||
height: 100px;
|
||||
margin: 20px;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const ListPage = () => {
|
||||
const [post, setPost] = useState({
|
||||
id: 0,
|
||||
title: '',
|
||||
content: '',
|
||||
});
|
||||
|
||||
const [posts, setPosts] = useState([
|
||||
{ id: 1, title: '제목1', content: '내용1' },
|
||||
{ id: 2, title: '제목2', content: '내용2' },
|
||||
{ id: 3, title: '제목3', content: '내용3' },
|
||||
{ id: 4, title: '제목4', content: '내용4' },
|
||||
{ id: 5, title: '제목5', content: '내용5' },
|
||||
]);
|
||||
|
||||
const handleWrite = (e) => {
|
||||
// ListPage의 setPosts
|
||||
e.preventDefault();
|
||||
setPosts([...posts, post]);
|
||||
};
|
||||
|
||||
const handleForm = (e) => {
|
||||
// computed property names 문법 (키값 동적 할당)
|
||||
setPost({ ...post, [e.target.name]: e.target.value });
|
||||
};
|
||||
|
||||
const handleDelete = (props) => {
|
||||
setPosts(posts.filter((v) => v.id !== props));
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>글목록 페이지</h1>
|
||||
<form onSubmit={handleWrite}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="제목을 입력하세요"
|
||||
value={post.title}
|
||||
onChange={handleForm}
|
||||
name="title"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="내용을 입력하세요"
|
||||
value={post.content}
|
||||
onChange={handleForm}
|
||||
name="content"
|
||||
/>
|
||||
<button>글쓰기</button>
|
||||
</form>
|
||||
{posts.map((post) => (
|
||||
<StyledItemBoxDiv>
|
||||
<div>
|
||||
번호 : {post.id} / 제목 : {post.title} / 내용 : {post.content}
|
||||
</div>
|
||||
<button onClick={() => handleDelete(post.id)}>삭제</button>
|
||||
</StyledItemBoxDiv>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ListPage;
|
||||
23
react+springboot/react-practice/practice/02 Route, Link/pages/WritePage.js
vendored
Normal file
23
react+springboot/react-practice/practice/02 Route, Link/pages/WritePage.js
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
|
||||
const WritePage = () => {
|
||||
const handleWrite = () => {
|
||||
// ListPage의 setPosts
|
||||
let post = { id: 6, title: 'input value' };
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>글쓰기 페이지</h1>
|
||||
<hr />
|
||||
<form>
|
||||
<input type="text" placeholder="제목을 입력하세요" />
|
||||
<button type="button" onClick={handleWrite}>
|
||||
글쓰기
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WritePage;
|
||||
BIN
react+springboot/react-practice/public/favicon.ico
Normal file
BIN
react+springboot/react-practice/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
44
react+springboot/react-practice/public/index.html
Normal file
44
react+springboot/react-practice/public/index.html
Normal file
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
BIN
react+springboot/react-practice/public/logo192.png
Normal file
BIN
react+springboot/react-practice/public/logo192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
BIN
react+springboot/react-practice/public/logo512.png
Normal file
BIN
react+springboot/react-practice/public/logo512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
25
react+springboot/react-practice/public/manifest.json
Normal file
25
react+springboot/react-practice/public/manifest.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
3
react+springboot/react-practice/public/robots.txt
Normal file
3
react+springboot/react-practice/public/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
13
react+springboot/react-practice/src/App.css
Normal file
13
react+springboot/react-practice/src/App.css
Normal file
@@ -0,0 +1,13 @@
|
||||
.container {
|
||||
height: 700px;
|
||||
border: 2px solid black;
|
||||
}
|
||||
|
||||
.sub_container {
|
||||
height: 250px;
|
||||
border: 2px solid black;
|
||||
}
|
||||
|
||||
div {
|
||||
padding: 10px;
|
||||
}
|
||||
15
react+springboot/react-practice/src/App.js
vendored
Normal file
15
react+springboot/react-practice/src/App.js
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import './App.css';
|
||||
import Top from './components/Top';
|
||||
import Bottom from './components/Bottom';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="container">
|
||||
<h1>최상단 화면</h1>
|
||||
<Top />
|
||||
<Bottom />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
17
react+springboot/react-practice/src/components/Bottom.js
vendored
Normal file
17
react+springboot/react-practice/src/components/Bottom.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import '../App.css';
|
||||
import { decrease, increase } from '../store';
|
||||
|
||||
const Bottom = () => {
|
||||
const dispatch = useDispatch();
|
||||
return (
|
||||
<div className="sub_container">
|
||||
<h1>Bottom</h1>
|
||||
<button onClick={() => dispatch(increase('kim'))}>증가</button>
|
||||
<button onClick={() => dispatch(decrease())}>감소</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Bottom;
|
||||
16
react+springboot/react-practice/src/components/Top.js
vendored
Normal file
16
react+springboot/react-practice/src/components/Top.js
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import '../App.css';
|
||||
|
||||
const Top = () => {
|
||||
const { number, username } = useSelector((store) => store);
|
||||
return (
|
||||
<div className="sub_container">
|
||||
<h1>Top</h1>
|
||||
번호 : {number}
|
||||
이름 : {username}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Top;
|
||||
21
react+springboot/react-practice/src/index.js
vendored
Normal file
21
react+springboot/react-practice/src/index.js
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
import { Provider } from 'react-redux';
|
||||
import reducer from './store';
|
||||
import { createStore } from 'redux';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
|
||||
const store = createStore(reducer);
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root'),
|
||||
);
|
||||
26
react+springboot/react-practice/src/store.js
vendored
Normal file
26
react+springboot/react-practice/src/store.js
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
// 액션
|
||||
export const increase = (username) => ({
|
||||
type: 'INCREMENT',
|
||||
payload: username,
|
||||
});
|
||||
export const decrease = () => ({ type: 'DECREMENT' });
|
||||
|
||||
// 상태
|
||||
const initstate = {
|
||||
username: '?',
|
||||
number: 1,
|
||||
};
|
||||
|
||||
// 액션의 결과를 걸러냄
|
||||
const reducer = (state = initstate, action) => {
|
||||
switch (action.type) {
|
||||
case 'INCREMENT':
|
||||
return { number: state.number + 1, username: action.payload }; // return 되는 순간 state가 변경이 되므로 ui 변경됨
|
||||
case 'DECREMENT':
|
||||
return { number: state.number - 1 };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default reducer;
|
||||
11763
react+springboot/react-practice/yarn.lock
Normal file
11763
react+springboot/react-practice/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user