Compare commits
27 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
0a86d54ff6 | ||
5bfc441e59 | |||
a1b1f97ee6 | |||
12ac235025 | |||
|
cf46765fb9 | ||
|
44d9f2b935 | ||
|
0a3a5aedbb | ||
c6889698bf | |||
4f93068cce | |||
a9112f5e06 | |||
2b40c29f36 | |||
|
5364651da2 | ||
816d2d1284 | |||
ac0427a16e | |||
09dd2f7ae2 | |||
|
2babc111b9 | ||
07bd46c0ac | |||
7a1916bff3 | |||
|
6c9581c820 | ||
b91f23a39e | |||
11173e07c4 | |||
|
89fcb3cbca | ||
|
9876a17856 | ||
|
128fd2ff2b | ||
|
1e958a8b94 | ||
|
ca7afdf94e | ||
ba48211be0 |
39
demo/.gitignore
vendored
Normal file
39
demo/.gitignore
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
HELP.md
|
||||
.gradle
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
bin/
|
||||
!**/src/main/**/bin/
|
||||
!**/src/test/**/bin/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
out/
|
||||
!**/src/main/**/out/
|
||||
!**/src/test/**/out/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
*.db
|
43
demo/build.gradle
Normal file
43
demo/build.gradle
Normal file
@ -0,0 +1,43 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '2.6.3'
|
||||
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
|
||||
}
|
||||
|
||||
group = 'com.example'
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
sourceCompatibility = '17'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
jar{
|
||||
enabled = false
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||
implementation 'org.springframework.boot:spring-boot-devtools'
|
||||
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
|
||||
|
||||
implementation 'org.webjars:bootstrap:5.1.3'
|
||||
implementation 'org.webjars:jquery:3.6.0'
|
||||
implementation 'org.webjars:font-awesome:6.1.0'
|
||||
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
implementation 'com.h2database:h2:2.1.210'
|
||||
|
||||
implementation 'org.hibernate.validator:hibernate-validator'
|
||||
|
||||
implementation 'org.springdoc:springdoc-openapi-ui:1.6.5'
|
||||
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
useJUnitPlatform()
|
||||
}
|
BIN
demo/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
demo/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
demo/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
demo/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
240
demo/gradlew
vendored
Normal file
240
demo/gradlew
vendored
Normal file
@ -0,0 +1,240 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
91
demo/gradlew.bat
vendored
Normal file
91
demo/gradlew.bat
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
1
demo/settings.gradle
Normal file
1
demo/settings.gradle
Normal file
@ -0,0 +1 @@
|
||||
rootProject.name = 'demo'
|
12
demo/src/main/java/com/example/demo/DemoApplication.java
Normal file
12
demo/src/main/java/com/example/demo/DemoApplication.java
Normal file
@ -0,0 +1,12 @@
|
||||
package com.example.demo;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
|
||||
public class DemoApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(DemoApplication.class, args);
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package com.example.demo.configuration;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
@Configuration
|
||||
public class PasswordEncoderConfiguration {
|
||||
@Bean
|
||||
public PasswordEncoder createPasswordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package com.example.demo.configuration;
|
||||
|
||||
import com.example.demo.supply.User.UserRole;
|
||||
import com.example.demo.supply.User.UserService;
|
||||
import com.example.demo.supply.User.UserSignupMvcController;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
|
||||
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||
private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class);
|
||||
private static final String LOGIN_URL = "/login";
|
||||
private final UserService userService;
|
||||
|
||||
public SecurityConfiguration(UserService userService) {
|
||||
this.userService = userService;
|
||||
createAdminOnStartup();
|
||||
}
|
||||
|
||||
private void createAdminOnStartup() {
|
||||
final String admin = "admin";
|
||||
if (userService.findByLogin(admin) == null) {
|
||||
log.info("Admin user successfully created");
|
||||
userService.createUser(admin, admin, admin, UserRole.ADMIN);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.headers().frameOptions().sameOrigin().and()
|
||||
.cors().and()
|
||||
.csrf().disable()
|
||||
.authorizeRequests()
|
||||
.antMatchers(UserSignupMvcController.SIGNUP_URL).permitAll()
|
||||
.antMatchers(HttpMethod.GET, LOGIN_URL).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.formLogin()
|
||||
.loginPage(LOGIN_URL).permitAll()
|
||||
.and()
|
||||
.logout().permitAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.userDetailsService(userService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(WebSecurity web) {
|
||||
web.ignoring()
|
||||
.antMatchers("/css/**")
|
||||
.antMatchers("/js/**")
|
||||
.antMatchers("/templates/**")
|
||||
.antMatchers("/webjars/**");
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package com.example.demo.configuration;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class WebConfiguration implements WebMvcConfigurer {
|
||||
public static final String REST_API = "/api";
|
||||
|
||||
@Override
|
||||
public void addViewControllers(ViewControllerRegistry registry) {
|
||||
WebMvcConfigurer.super.addViewControllers(registry);
|
||||
registry.addViewController("rest-test");
|
||||
registry.addViewController("login");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**").allowedMethods("*");
|
||||
}
|
||||
}
|
||||
|
110
demo/src/main/java/com/example/demo/supply/Order/Order.java
Normal file
110
demo/src/main/java/com/example/demo/supply/Order/Order.java
Normal file
@ -0,0 +1,110 @@
|
||||
package com.example.demo.supply.Order;
|
||||
|
||||
import com.example.demo.supply.Product.Product;
|
||||
import com.example.demo.supply.Supplier.Supplier;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.Date;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
@Table(name = "tab_order")
|
||||
public class Order {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false)
|
||||
@Temporal(TemporalType.DATE)
|
||||
private Date dateOfOrder;
|
||||
|
||||
@JsonIgnore
|
||||
@ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE})
|
||||
@JoinColumn(name = "supplier_fk")
|
||||
private Supplier supplier;
|
||||
@ManyToMany(fetch = FetchType.EAGER, cascade = {
|
||||
CascadeType.PERSIST,
|
||||
CascadeType.MERGE
|
||||
})
|
||||
@JoinTable(name = "ordersAndProducts",joinColumns = @JoinColumn(name = "order_fk"),
|
||||
inverseJoinColumns = @JoinColumn(name = "product_fk"))
|
||||
private List<Product> products;
|
||||
|
||||
public Long getId(){
|
||||
return id;
|
||||
}
|
||||
public Order(Date dateOfOrder) {
|
||||
this.dateOfOrder = dateOfOrder;
|
||||
products = new ArrayList<>();
|
||||
}
|
||||
|
||||
public Order() {
|
||||
}
|
||||
|
||||
public Date getDateOfOrder() {
|
||||
return dateOfOrder;
|
||||
}
|
||||
public void setDateOfOrder(Date dateOfOrder) {
|
||||
this.dateOfOrder = dateOfOrder;
|
||||
}
|
||||
public Supplier getSupplier() {
|
||||
return supplier;
|
||||
}
|
||||
public void setSupplier(Supplier supplier) {
|
||||
this.supplier = supplier;
|
||||
supplier.getOrders().add(this);
|
||||
}
|
||||
public List<Product> getProducts() {
|
||||
return products;
|
||||
}
|
||||
public void setProducts(List<Product> products) {
|
||||
this.products = products;
|
||||
}
|
||||
public void addProduct(Product product){
|
||||
if(!products.contains(product)){
|
||||
products.add(product);
|
||||
product.getOrders().add(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteProduct(Product product){
|
||||
if(products.contains(product)){
|
||||
products.remove(product);
|
||||
product.getOrders().remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Order order = (Order) o;
|
||||
if(!Objects.equals(id, order.id)) return false;
|
||||
|
||||
if(!Objects.equals(dateOfOrder.toString(), order.dateOfOrder.toString())) return false;
|
||||
if(!Objects.equals(supplier, order.supplier)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Product{" +
|
||||
"id=" + id +
|
||||
", date='" + dateOfOrder + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package com.example.demo.supply.Order;
|
||||
|
||||
import com.example.demo.supply.Product.Product;
|
||||
import com.example.demo.supply.Product.ProductDto;
|
||||
import com.example.demo.supply.Supplier.Supplier;
|
||||
import com.example.demo.supply.Supplier.SupplierDto;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@CrossOrigin
|
||||
@RequestMapping("/api/order")
|
||||
public class OrderController {
|
||||
private OrderService orderService;
|
||||
|
||||
public OrderController(OrderService orderService){
|
||||
this.orderService = orderService;
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public OrderDto getOrder(@PathVariable Long id) {
|
||||
return new OrderDto(orderService.findOrder(id));
|
||||
}
|
||||
|
||||
@GetMapping("/")
|
||||
public List<OrderDto> getOrders() {
|
||||
return orderService.findAllOrders().stream().map(OrderDto::new).toList();
|
||||
}
|
||||
|
||||
@GetMapping("/getProducts/{id}")
|
||||
public List<ProductDto> getOrderProducts(@PathVariable Long id) {
|
||||
return orderService.findAllOrderProducts(id).stream().map(ProductDto::new).toList();
|
||||
}
|
||||
|
||||
@GetMapping("/someSuppliers/{id}")
|
||||
public List<SupplierDto> getSomeSuppliers(@PathVariable Long id) {
|
||||
return orderService.suppliers(id).stream().map(SupplierDto::new).toList();
|
||||
}
|
||||
|
||||
@PostMapping("/")
|
||||
public Long createOrder(@RequestParam() Long supplierId) {
|
||||
return new OrderDto(orderService.addOrder(supplierId)).getId();
|
||||
}
|
||||
|
||||
@PatchMapping("/addProduct/{id}")
|
||||
public OrderDto addProduct(@PathVariable Long id,
|
||||
@RequestParam() Long productId){
|
||||
return new OrderDto(orderService.addProduct(id, productId));
|
||||
}
|
||||
|
||||
@PatchMapping("/removeProduct/{id}")
|
||||
public OrderDto removeProduct(@PathVariable Long id,
|
||||
@RequestParam() Long productId){
|
||||
return new OrderDto(orderService.removeProduct(id, productId));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public OrderDto deleteOrder(@PathVariable Long id) {
|
||||
return new OrderDto(orderService.deleteOrder(id));
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package com.example.demo.supply.Order;
|
||||
|
||||
import com.example.demo.supply.Product.Product;
|
||||
import com.example.demo.supply.Supplier.Supplier;
|
||||
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class OrderDto {
|
||||
|
||||
private Long id;
|
||||
private Date dateOfOrder;
|
||||
private Supplier supplier;
|
||||
private List<Product> products;
|
||||
|
||||
public OrderDto(){
|
||||
}
|
||||
public OrderDto(Order order){
|
||||
this.id = order.getId();
|
||||
this.dateOfOrder = order.getDateOfOrder();
|
||||
this.supplier = order.getSupplier();
|
||||
this.products = order.getProducts();
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Date getDateOfOrder() {
|
||||
return dateOfOrder;
|
||||
}
|
||||
|
||||
public Supplier getSupplier() {
|
||||
return supplier;
|
||||
}
|
||||
|
||||
public List<Product> getProducts() {
|
||||
return products;
|
||||
}
|
||||
public void setDateOfOrder(Date dateOfOrder) {
|
||||
this.dateOfOrder = dateOfOrder;
|
||||
}
|
||||
|
||||
public void setSupplier(Supplier supplier) {
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
public void setProducts(List<Product> products) {
|
||||
this.products = products;
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package com.example.demo.supply.Order;
|
||||
|
||||
import com.example.demo.supply.Product.Product;
|
||||
import com.example.demo.supply.Supplier.Supplier;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class OrderDtoForCreate {
|
||||
private Long id;
|
||||
private Date dateOfOrder;
|
||||
private long supplierId;
|
||||
private List<Long> productsId;
|
||||
|
||||
public OrderDtoForCreate(){
|
||||
}
|
||||
|
||||
public OrderDtoForCreate(Order order){
|
||||
this.id = order.getId();
|
||||
this.dateOfOrder = order.getDateOfOrder();
|
||||
this.supplierId = order.getSupplier().getId();
|
||||
this.productsId = order.getProducts().stream().map(Product::getId).toList();
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Date getDateOfOrder() {
|
||||
return dateOfOrder;
|
||||
}
|
||||
|
||||
public void setDateOfOrder(Date dateOfOrder) {
|
||||
this.dateOfOrder = dateOfOrder;
|
||||
}
|
||||
|
||||
public long getSupplierId() {
|
||||
return supplierId;
|
||||
}
|
||||
|
||||
public void setSupplierId(long supplierId) {
|
||||
this.supplierId = supplierId;
|
||||
}
|
||||
|
||||
public List<Long> getProductsId() {
|
||||
return productsId;
|
||||
}
|
||||
|
||||
public void setProductsId(List<Long> productsId) {
|
||||
this.productsId = productsId;
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package com.example.demo.supply.Order;
|
||||
|
||||
import com.example.demo.supply.Product.ProductDto;
|
||||
import com.example.demo.supply.Product.ProductService;
|
||||
import com.example.demo.supply.Supplier.SupplierDto;
|
||||
import com.example.demo.supply.Supplier.SupplierService;
|
||||
import com.example.demo.supply.User.UserRole;
|
||||
import org.springframework.security.access.annotation.Secured;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/order")
|
||||
public class OrderMvcController {
|
||||
private OrderService orderService;
|
||||
private ProductService productService;
|
||||
private SupplierService supplierService;
|
||||
|
||||
public OrderMvcController(OrderService orderService,
|
||||
ProductService productService,
|
||||
SupplierService supplierService){
|
||||
this.orderService = orderService;
|
||||
this.productService = productService;
|
||||
this.supplierService = supplierService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public String getOrders(Model model) {
|
||||
model.addAttribute("orders",
|
||||
orderService.findAllOrders().stream()
|
||||
.map(OrderDto::new)
|
||||
.toList());
|
||||
return "order";
|
||||
}
|
||||
|
||||
@GetMapping("/dop")
|
||||
public String getSuppliers(Model model,
|
||||
@RequestParam(required = false) Long id) {
|
||||
if(id == null || id <= 0) {
|
||||
model.addAttribute("productDto", new ProductDto());
|
||||
model.addAttribute("suppliers", null);
|
||||
model.addAttribute("selectedProduct", null);
|
||||
}
|
||||
else{
|
||||
ProductDto product = new ProductDto(productService.findProduct(id));
|
||||
model.addAttribute("productDto", product);
|
||||
model.addAttribute("selectedProduct", null);
|
||||
model.addAttribute("suppliers", orderService.suppliers(id).stream().map(SupplierDto::new).toList());
|
||||
}
|
||||
model.addAttribute("products", productService.findAllProducts().stream().map(ProductDto::new).toList());
|
||||
// model.addAttribute("orderDto", new OrderDto(orderService.findOrder(id)));
|
||||
return "order-dop";
|
||||
}
|
||||
|
||||
@GetMapping("/add")
|
||||
@Secured({UserRole.AsString.ADMIN})
|
||||
public String addOrder(Model model) {
|
||||
model.addAttribute("orderDto", new OrderDtoForCreate());
|
||||
model.addAttribute("selectedSupplier", null);
|
||||
model.addAttribute("suppliers", supplierService.findAllSuppliers().stream().map(SupplierDto::new).toList());
|
||||
model.addAttribute("products", productService.findAllProducts().stream().map(ProductDto::new).toList());
|
||||
return "order-add";
|
||||
}
|
||||
|
||||
@PostMapping("/create")
|
||||
@Secured({UserRole.AsString.ADMIN})
|
||||
public String saveOrder(Model model,
|
||||
@ModelAttribute("orderDto") @Valid OrderDtoForCreate order,
|
||||
BindingResult bindingResult) {
|
||||
if (bindingResult.hasErrors()) {
|
||||
model.addAttribute("errors", bindingResult.getAllErrors());
|
||||
return "order-add";
|
||||
}
|
||||
Order newOrder = orderService.addOrder(supplierService.findSupplier(order.getSupplierId()).getId());
|
||||
for(Long obj: order.getProductsId())
|
||||
orderService.addProduct(newOrder.getId(), obj);
|
||||
return "redirect:/order";
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.example.demo.supply.Order;
|
||||
|
||||
public class OrderNotFoundException extends RuntimeException{
|
||||
public OrderNotFoundException(Long id){
|
||||
super(String.format("Order with id [%s] is not found", id));
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.example.demo.supply.Order;
|
||||
|
||||
import com.example.demo.supply.Product.Product;
|
||||
import com.example.demo.supply.Supplier.Supplier;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface OrderRepository extends JpaRepository<Order, Long> {
|
||||
@Query("SELECT o.supplier FROM Order o where ?1 member of o.products")
|
||||
List<Supplier> getSomeSuppliers(Product product);
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package com.example.demo.supply.Order;
|
||||
|
||||
import com.example.demo.supply.Product.Product;
|
||||
import com.example.demo.supply.Product.ProductService;
|
||||
import com.example.demo.supply.Supplier.Supplier;
|
||||
import com.example.demo.supply.Supplier.SupplierService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class OrderService {
|
||||
|
||||
private final OrderRepository orderRepository;
|
||||
private final ProductService productService;
|
||||
private final SupplierService supplierService;
|
||||
|
||||
public OrderService(OrderRepository orderRepository,
|
||||
ProductService productService,
|
||||
SupplierService supplierService)
|
||||
{
|
||||
this.orderRepository = orderRepository;
|
||||
this.productService = productService;
|
||||
this.supplierService = supplierService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Order addOrder(Long supplierId){
|
||||
final Order order = new Order(new Date());
|
||||
order.setSupplier(supplierService.findSupplier(supplierId));
|
||||
return orderRepository.save(order);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public List<Supplier> suppliers(Long productId){
|
||||
final Product product = productService.findProduct(productId);
|
||||
return orderRepository.getSomeSuppliers(product);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Order addProduct(Long id, Long productId) {
|
||||
final Order currentOrder = findOrder(id);
|
||||
currentOrder.addProduct(productService.findProduct(productId));
|
||||
return orderRepository.save(currentOrder);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Order removeProduct(Long id, Long productId) {
|
||||
final Order currentOrder = findOrder(id);
|
||||
currentOrder.addProduct(productService.findProduct(productId));
|
||||
return orderRepository.save(currentOrder);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public Order findOrder(Long id) {
|
||||
final Optional<Order> order = orderRepository.findById(id);
|
||||
return order.orElseThrow(() -> new OrderNotFoundException(id));
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<Order> findAllOrders() {
|
||||
return orderRepository.findAll();
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<Product> findAllOrderProducts(Long orderId) {
|
||||
final Optional<Order> order = orderRepository.findById(orderId);
|
||||
return order.orElseThrow(() -> new OrderNotFoundException(orderId)).getProducts();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Order deleteOrder(Long id) {
|
||||
final Order currentOrder = findOrder(id);
|
||||
orderRepository.delete(currentOrder);
|
||||
return currentOrder;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteAll(){
|
||||
orderRepository.deleteAll();
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package com.example.demo.supply.Product;
|
||||
|
||||
import com.example.demo.supply.Order.Order;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.Objects;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
public class Product {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private long id;
|
||||
|
||||
@Column(nullable = false)
|
||||
// @NotBlank(message = "name cant be null or empty")
|
||||
private String name;
|
||||
// @NotBlank(message = "cost cant be < 0")
|
||||
@Column(nullable = false)
|
||||
private double cost;
|
||||
@JsonIgnore
|
||||
@ManyToMany(fetch = FetchType.EAGER, mappedBy = "products")
|
||||
private List<Order> orders;
|
||||
|
||||
public Product(){}
|
||||
|
||||
public Product(String name, double cost){
|
||||
this.name = name;
|
||||
this.cost = cost;
|
||||
orders = new ArrayList<>();
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
public double getCost() {
|
||||
return cost;
|
||||
}
|
||||
public void setCost(double cost) {
|
||||
this.cost = cost;
|
||||
}
|
||||
|
||||
public List<Order> getOrders() {
|
||||
return orders;
|
||||
}
|
||||
|
||||
public void setOrders(List<Order> orders) {
|
||||
this.orders = orders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Product product = (Product) o;
|
||||
return Objects.equals(id, product.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Product{" +
|
||||
"id=" + id +
|
||||
", name='" + name + '\'' +
|
||||
", cost='" + cost + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package com.example.demo.supply.Product;
|
||||
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@CrossOrigin
|
||||
@RequestMapping("/api/product")
|
||||
public class ProductController {
|
||||
|
||||
private final ProductService productService;
|
||||
|
||||
public ProductController(ProductService productService) {
|
||||
this.productService = productService;
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ProductDto getProduct(@PathVariable Long id) {
|
||||
return new ProductDto(productService.findProduct(id));
|
||||
}
|
||||
|
||||
@GetMapping("/")
|
||||
public List<ProductDto> getProducts() {
|
||||
return productService.findAllProducts().stream().map(ProductDto::new).toList();
|
||||
}
|
||||
|
||||
@PostMapping("/")
|
||||
public ProductDto createProduct(@RequestParam() String name,
|
||||
@RequestParam() double cost) {
|
||||
return new ProductDto(productService.addProduct(name, cost));
|
||||
}
|
||||
|
||||
@PatchMapping("/{id}")
|
||||
public ProductDto updateProduct(@PathVariable Long id,
|
||||
@RequestParam String name,
|
||||
@RequestParam double cost) {
|
||||
return new ProductDto(productService.updateProduct(id, name, cost));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ProductDto deleteProduct(@PathVariable Long id) {
|
||||
return new ProductDto(productService.deleteProduct(id));
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package com.example.demo.supply.Product;
|
||||
|
||||
import com.example.demo.supply.Order.Order;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ProductDto {
|
||||
private long id;
|
||||
private String name;
|
||||
private double cost;
|
||||
private List<Order> orders;
|
||||
|
||||
public ProductDto() {}
|
||||
|
||||
public ProductDto(Product product) {
|
||||
this.id = product.getId();
|
||||
this.name = product.getName();
|
||||
this.cost = product.getCost();
|
||||
this.orders = product.getOrders();
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
public double getCost() {
|
||||
return cost;
|
||||
}
|
||||
public List<Order> getOrders() {
|
||||
return orders;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setCost(double cost) {
|
||||
this.cost = cost;
|
||||
}
|
||||
|
||||
public void setOrders(List<Order> orders) {
|
||||
this.orders = orders;
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package com.example.demo.supply.Product;
|
||||
|
||||
import com.example.demo.supply.User.UserRole;
|
||||
import org.springframework.security.access.annotation.Secured;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.security.Principal;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/product")
|
||||
public class ProductMvcController {
|
||||
private final ProductService productService;
|
||||
|
||||
public ProductMvcController(ProductService productService){ this.productService = productService;}
|
||||
|
||||
@GetMapping
|
||||
public String getProducts(Model model) {
|
||||
model.addAttribute("products",
|
||||
productService.findAllProducts().stream()
|
||||
.map(ProductDto::new)
|
||||
.toList());
|
||||
return "product";
|
||||
}
|
||||
|
||||
@GetMapping(value = {"/edit", "/edit/{id}"})
|
||||
public String editProduct(@PathVariable(required = false) Long id,
|
||||
Model model, Principal principal) {
|
||||
String roleName = ((Authentication)principal).getAuthorities().toArray()[0].toString();
|
||||
if(UserRole.ADMIN.toString().equals(roleName)) {
|
||||
if (id == null || id <= 0) {
|
||||
model.addAttribute("productDto", new ProductDto());
|
||||
} else {
|
||||
model.addAttribute("productId", id);
|
||||
model.addAttribute("productDto", new ProductDto(productService.findProduct(id)));
|
||||
}
|
||||
return "product-edit";
|
||||
}
|
||||
else return "redirect:/product";
|
||||
}
|
||||
|
||||
@PostMapping(value = {"", "/{id}"})
|
||||
public String saveProduct(@PathVariable(required = false) Long id,
|
||||
@ModelAttribute @Valid ProductDto productDto,
|
||||
BindingResult bindingResult,
|
||||
Model model,
|
||||
Principal principal) {
|
||||
String roleName = ((Authentication)principal).getAuthorities().toArray()[0].toString();
|
||||
if(UserRole.ADMIN.toString().equals(roleName)) {
|
||||
if (bindingResult.hasErrors()) {
|
||||
model.addAttribute("errors", bindingResult.getAllErrors());
|
||||
return "product-edit";
|
||||
}
|
||||
if (id == null || id <= 0) {
|
||||
productService.addProduct(productDto.getName(), productDto.getCost());
|
||||
} else {
|
||||
productService.updateProduct(id, productDto.getName(), productDto.getCost());
|
||||
}
|
||||
return "redirect:/product";
|
||||
}
|
||||
else return "redirect:/product";
|
||||
}
|
||||
|
||||
@PostMapping("/delete/{id}")
|
||||
public String deleteProduct(@PathVariable Long id, Principal principal) {
|
||||
String roleName = ((Authentication)principal).getAuthorities().toArray()[0].toString();
|
||||
if(UserRole.ADMIN.toString().equals(roleName)) {
|
||||
productService.deleteProduct(id);
|
||||
return "redirect:/product";
|
||||
}
|
||||
else return "redirect:/product";
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.example.demo.supply.Product;
|
||||
|
||||
public class ProductNotFoundException extends RuntimeException{
|
||||
public ProductNotFoundException(Long id){
|
||||
super(String.format("Product with id [%s] is not found", id));
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package com.example.demo.supply.Product;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface ProductRepository extends JpaRepository<Product, Long> {
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package com.example.demo.supply.Product;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class ProductService {
|
||||
private final ProductRepository productRepository;
|
||||
public ProductService(ProductRepository productRepository){
|
||||
this.productRepository = productRepository;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Product addProduct(String name, double cost){
|
||||
final Product product = new Product(name, cost);
|
||||
return productRepository.save(product);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public Product findProduct(Long id) {
|
||||
final Optional<Product> product = productRepository.findById(id);
|
||||
return product.orElseThrow(() -> new ProductNotFoundException(id));
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<Product> findAllProducts() {
|
||||
return productRepository.findAll();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Product updateProduct(Long id, String name, double cost) {
|
||||
final Product currentProduct = findProduct(id);
|
||||
currentProduct.setName(name);
|
||||
currentProduct.setCost(cost);
|
||||
return productRepository.save(currentProduct);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Product deleteProduct(Long id) {
|
||||
final Product product = findProduct(id);
|
||||
productRepository.delete(product);
|
||||
return product;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteAll(){
|
||||
productRepository.deleteAll();
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package com.example.demo.supply.Supplier;
|
||||
|
||||
import com.example.demo.supply.Order.Order;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.Objects;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
public class Supplier {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
@Column(nullable = false)
|
||||
private int license;
|
||||
|
||||
@JsonIgnore
|
||||
@OneToMany(fetch = FetchType.EAGER, mappedBy = "supplier", cascade = CascadeType.REMOVE)
|
||||
private List<Order> orders;
|
||||
|
||||
public Supplier(){}
|
||||
|
||||
public Supplier( String name, int license) {
|
||||
this.name = name;
|
||||
this.license = license;
|
||||
orders = new ArrayList<>();
|
||||
}
|
||||
|
||||
public Long getId() { return id; }
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getLicense() {
|
||||
return license;
|
||||
}
|
||||
|
||||
public void setLicense(int license) {
|
||||
this.license = license;
|
||||
}
|
||||
|
||||
public List<Order> getOrders() {
|
||||
return orders;
|
||||
}
|
||||
|
||||
public void setOrders(List<Order> orders) {
|
||||
this.orders = orders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Supplier supplier = (Supplier) o;
|
||||
return Objects.equals(id, supplier.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Product{" +
|
||||
"id=" + id +
|
||||
", name='" + name + '\'' +
|
||||
", license='" + license + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package com.example.demo.supply.Supplier;
|
||||
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@CrossOrigin
|
||||
@RequestMapping("/api/supplier")
|
||||
public class SupplierController {
|
||||
private final SupplierService supplierService;
|
||||
|
||||
public SupplierController(SupplierService supplierService) {
|
||||
this.supplierService = supplierService;
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public SupplierDto getSupplier(@PathVariable Long id) {
|
||||
return new SupplierDto(supplierService.findSupplier(id));
|
||||
}
|
||||
|
||||
@GetMapping("/")
|
||||
public List<SupplierDto> getSuppliers() {
|
||||
return supplierService.findAllSuppliers().stream().map(SupplierDto::new).toList();
|
||||
}
|
||||
|
||||
@PostMapping("/")
|
||||
public SupplierDto createSupplier(@RequestParam() String name,
|
||||
@RequestParam() int license) {
|
||||
return new SupplierDto(supplierService.addSupplier(name, license));
|
||||
}
|
||||
|
||||
@PatchMapping("/{id}")
|
||||
public SupplierDto updateSupplier(@PathVariable Long id,
|
||||
@RequestParam() String name,
|
||||
@RequestParam() int license) {
|
||||
return new SupplierDto(supplierService.updateSupplier(id, name, license));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public SupplierDto deleteSupplier(@PathVariable Long id) {
|
||||
return new SupplierDto(supplierService.deleteSupplier(id));
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package com.example.demo.supply.Supplier;
|
||||
|
||||
import com.example.demo.supply.Order.Order;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SupplierDto {
|
||||
private Long id;
|
||||
private String name;
|
||||
private int license;
|
||||
private List<Order> orders;
|
||||
|
||||
public SupplierDto(){
|
||||
}
|
||||
|
||||
public SupplierDto(Supplier supplier){
|
||||
this.id = supplier.getId();
|
||||
this.name = supplier.getName();
|
||||
this.license = supplier.getLicense();
|
||||
this.orders = supplier.getOrders();
|
||||
}
|
||||
|
||||
public Long getId() { return id; }
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
public int getLicense() {
|
||||
return license;
|
||||
}
|
||||
public List<Order> getOrders() {
|
||||
return orders;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setLicense(int license) {
|
||||
this.license = license;
|
||||
}
|
||||
|
||||
public void setOrders(List<Order> orders) {
|
||||
this.orders = orders;
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package com.example.demo.supply.Supplier;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/supplier")
|
||||
public class SupplierMvcController {
|
||||
private final SupplierService supplierService;
|
||||
|
||||
public SupplierMvcController(SupplierService supplierService){ this.supplierService = supplierService;}
|
||||
|
||||
@GetMapping
|
||||
public String getSuppliers(Model model) {
|
||||
model.addAttribute("suppliers",
|
||||
supplierService.findAllSuppliers().stream()
|
||||
.map(SupplierDto::new)
|
||||
.toList());
|
||||
return "supplier";
|
||||
}
|
||||
|
||||
@GetMapping(value = {"/edit", "/edit/{id}"})
|
||||
public String editSuppliers(@PathVariable(required = false) Long id,
|
||||
Model model) {
|
||||
if (id == null || id <= 0) {
|
||||
model.addAttribute("supplierDto", new SupplierDto());
|
||||
} else {
|
||||
model.addAttribute("supplierId", id);
|
||||
model.addAttribute("supplierDto", new SupplierDto(supplierService.findSupplier(id)));
|
||||
}
|
||||
return "supplier-edit";
|
||||
}
|
||||
|
||||
@PostMapping(value = {"", "/{id}"})
|
||||
public String saveSuppliers(@PathVariable(required = false) Long id,
|
||||
@ModelAttribute @Valid SupplierDto supplierDto,
|
||||
BindingResult bindingResult,
|
||||
Model model) {
|
||||
if (bindingResult.hasErrors()) {
|
||||
model.addAttribute("errors", bindingResult.getAllErrors());
|
||||
return "supplier-edit";
|
||||
}
|
||||
if (id == null || id <= 0) {
|
||||
supplierService.addSupplier(supplierDto.getName(), supplierDto.getLicense());
|
||||
} else {
|
||||
supplierService.updateSupplier(id, supplierDto.getName(), supplierDto.getLicense());
|
||||
}
|
||||
return "redirect:/supplier";
|
||||
}
|
||||
|
||||
@PostMapping("/delete/{id}")
|
||||
public String deleteSuppliers(@PathVariable Long id) {
|
||||
supplierService.deleteSupplier(id);
|
||||
return "redirect:/supplier";
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package com.example.demo.supply.Supplier;
|
||||
|
||||
public class SupplierNotFoundException extends RuntimeException{
|
||||
public SupplierNotFoundException(Long id){
|
||||
super(String.format("Supplier with id [%s] is not found", id));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package com.example.demo.supply.Supplier;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface SupplierRepository extends JpaRepository<Supplier, Long> {
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package com.example.demo.supply.Supplier;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class SupplierService {
|
||||
private final SupplierRepository supplierRepository;
|
||||
|
||||
public SupplierService(SupplierRepository supplierRepository){
|
||||
this.supplierRepository = supplierRepository;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Supplier addSupplier(String name, int license){
|
||||
final Supplier supplier = new Supplier(name, license);
|
||||
return supplierRepository.save(supplier);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public Supplier findSupplier(Long id) {
|
||||
final Optional<Supplier> supplier = supplierRepository.findById(id);
|
||||
return supplier.orElseThrow(() -> new SupplierNotFoundException(id));
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<Supplier> findAllSuppliers() {
|
||||
return supplierRepository.findAll();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Supplier updateSupplier(Long id, String name, int license) {
|
||||
final Supplier currentSupplier = findSupplier(id);
|
||||
currentSupplier.setName(name);
|
||||
currentSupplier.setLicense(license);
|
||||
return supplierRepository.save(currentSupplier);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Supplier deleteSupplier(Long id) {
|
||||
final Supplier currentSupplier = findSupplier(id);
|
||||
supplierRepository.delete(currentSupplier);
|
||||
return currentSupplier;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteAll(){
|
||||
supplierRepository.deleteAll();
|
||||
}
|
||||
}
|
73
demo/src/main/java/com/example/demo/supply/User/User.java
Normal file
73
demo/src/main/java/com/example/demo/supply/User/User.java
Normal file
@ -0,0 +1,73 @@
|
||||
package com.example.demo.supply.User;
|
||||
|
||||
import javax.persistence.*;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.util.Objects;
|
||||
|
||||
@Entity
|
||||
@Table(name = "users")
|
||||
public class User {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
@Column(nullable = false, unique = true, length = 64)
|
||||
@NotBlank
|
||||
@Size(min = 3, max = 64)
|
||||
private String login;
|
||||
@Column(nullable = false, length = 64)
|
||||
@NotBlank
|
||||
@Size(min = 4, max = 64)
|
||||
private String password;
|
||||
private UserRole role;
|
||||
|
||||
public User() {
|
||||
}
|
||||
|
||||
public User(String login, String password) {
|
||||
this(login, password, UserRole.USER);
|
||||
}
|
||||
|
||||
public User(String login, String password, UserRole role) {
|
||||
this.login = login;
|
||||
this.password = password;
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getLogin() {
|
||||
return login;
|
||||
}
|
||||
|
||||
public void setLogin(String login) {
|
||||
this.login = login;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public UserRole getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
User user = (User) o;
|
||||
return Objects.equals(id, user.id) && Objects.equals(login, user.login);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, login);
|
||||
}
|
||||
}
|
25
demo/src/main/java/com/example/demo/supply/User/UserDto.java
Normal file
25
demo/src/main/java/com/example/demo/supply/User/UserDto.java
Normal file
@ -0,0 +1,25 @@
|
||||
package com.example.demo.supply.User;
|
||||
|
||||
public class UserDto {
|
||||
private final long id;
|
||||
private final String login;
|
||||
private final UserRole role;
|
||||
|
||||
public UserDto(User user) {
|
||||
this.id = user.getId();
|
||||
this.login = user.getLogin();
|
||||
this.role = user.getRole();
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getLogin() {
|
||||
return login;
|
||||
}
|
||||
|
||||
public UserRole getRole() {
|
||||
return role;
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package com.example.demo.supply.User;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.security.access.annotation.Secured;
|
||||
import org.springframework.security.core.Authentication;
|
||||
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 org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/users")
|
||||
public class UserMvcController {
|
||||
private final UserService userService;
|
||||
|
||||
public UserMvcController(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public String getUsers(@RequestParam(defaultValue = "1") int page,
|
||||
@RequestParam(defaultValue = "5") int size,
|
||||
Model model, Principal principal) {
|
||||
|
||||
String roleName = ((Authentication)principal).getAuthorities().toArray()[0].toString();
|
||||
if(UserRole.ADMIN.toString().equals(roleName)) {
|
||||
final Page<UserDto> users = userService.findAllPages(page, size)
|
||||
.map(UserDto::new);
|
||||
model.addAttribute("users", users);
|
||||
final int totalPages = users.getTotalPages();
|
||||
final List<Integer> pageNumbers = IntStream.rangeClosed(1, totalPages)
|
||||
.boxed()
|
||||
.toList();
|
||||
model.addAttribute("pages", pageNumbers);
|
||||
model.addAttribute("totalPages", totalPages);
|
||||
return "users";
|
||||
}
|
||||
else{
|
||||
model.addAttribute("error", "Доступ запрещен");
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.example.demo.supply.User;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
User findOneByLoginIgnoreCase(String login);
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.example.demo.supply.User;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
public enum UserRole implements GrantedAuthority {
|
||||
ADMIN,
|
||||
USER;
|
||||
|
||||
private static final String PREFIX = "ROLE_";
|
||||
|
||||
@Override
|
||||
public String getAuthority() {
|
||||
return PREFIX + this.name();
|
||||
}
|
||||
|
||||
public static final class AsString {
|
||||
public static final String ADMIN = PREFIX + "ADMIN";
|
||||
public static final String USER = PREFIX + "USER";
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package com.example.demo.supply.User;
|
||||
|
||||
import com.example.demo.supply.util.validation.ValidationException;
|
||||
import com.example.demo.supply.util.validation.ValidatorUtil;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
|
||||
@Service
|
||||
public class UserService implements UserDetailsService {
|
||||
private final UserRepository userRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final ValidatorUtil validatorUtil;
|
||||
|
||||
public UserService(UserRepository userRepository,
|
||||
PasswordEncoder passwordEncoder,
|
||||
ValidatorUtil validatorUtil) {
|
||||
this.userRepository = userRepository;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.validatorUtil = validatorUtil;
|
||||
}
|
||||
|
||||
public Page<User> findAllPages(int page, int size) {
|
||||
return userRepository.findAll(PageRequest.of(page - 1, size, Sort.by("id").ascending()));
|
||||
}
|
||||
|
||||
public User findByLogin(String login) {
|
||||
return userRepository.findOneByLoginIgnoreCase(login);
|
||||
}
|
||||
|
||||
public User createUser(String login, String password, String passwordConfirm) {
|
||||
return createUser(login, password, passwordConfirm, UserRole.USER);
|
||||
}
|
||||
|
||||
public User createUser(String login, String password, String passwordConfirm, UserRole role) {
|
||||
if (findByLogin(login) != null) {
|
||||
throw new ValidationException(String.format("User '%s' already exists", login));
|
||||
}
|
||||
final User user = new User(login, passwordEncoder.encode(password), role);
|
||||
validatorUtil.validate(user);
|
||||
if (!Objects.equals(password, passwordConfirm)) {
|
||||
throw new ValidationException("Passwords not equals");
|
||||
}
|
||||
return userRepository.save(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
final User userEntity = findByLogin(username);
|
||||
if (userEntity == null) {
|
||||
throw new UsernameNotFoundException(username);
|
||||
}
|
||||
return new org.springframework.security.core.userdetails.User(
|
||||
userEntity.getLogin(), userEntity.getPassword(), Collections.singleton(userEntity.getRole()));
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package com.example.demo.supply.User;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
public class UserSignupDto {
|
||||
@NotBlank
|
||||
@Size(min = 3, max = 64)
|
||||
private String login;
|
||||
@NotBlank
|
||||
@Size(min = 6, max = 64)
|
||||
private String password;
|
||||
@NotBlank
|
||||
@Size(min = 6, max = 64)
|
||||
private String passwordConfirm;
|
||||
|
||||
public String getLogin() {
|
||||
return login;
|
||||
}
|
||||
|
||||
public void setLogin(String login) {
|
||||
this.login = login;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getPasswordConfirm() {
|
||||
return passwordConfirm;
|
||||
}
|
||||
|
||||
public void setPasswordConfirm(String passwordConfirm) {
|
||||
this.passwordConfirm = passwordConfirm;
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package com.example.demo.supply.User;
|
||||
|
||||
import com.example.demo.supply.util.validation.ValidationException;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
@Controller
|
||||
@RequestMapping(UserSignupMvcController.SIGNUP_URL)
|
||||
public class UserSignupMvcController {
|
||||
public static final String SIGNUP_URL = "/signup";
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
public UserSignupMvcController(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public String showSignupForm(Model model) {
|
||||
model.addAttribute("userDto", new UserSignupDto());
|
||||
return "signup";
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public String signup(@ModelAttribute("userDto") @Valid UserSignupDto userSignupDto,
|
||||
BindingResult bindingResult,
|
||||
Model model) {
|
||||
if (bindingResult.hasErrors()) {
|
||||
model.addAttribute("errors", bindingResult.getAllErrors());
|
||||
return "signup";
|
||||
}
|
||||
try {
|
||||
final User user = userService.createUser(
|
||||
userSignupDto.getLogin(), userSignupDto.getPassword(), userSignupDto.getPasswordConfirm());
|
||||
return "redirect:/login?created=" + user.getLogin();
|
||||
} catch (ValidationException e) {
|
||||
model.addAttribute("errors", e.getMessage());
|
||||
return "signup";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.example.demo.supply.util;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@Controller
|
||||
public class StaticPagesMvcController {
|
||||
@RequestMapping("/")
|
||||
public String indexPage(){
|
||||
return "index";
|
||||
}
|
||||
@RequestMapping("/forum")
|
||||
public String forumPage(){
|
||||
return "forum";
|
||||
}
|
||||
@RequestMapping("/login")
|
||||
public String loginPage(){
|
||||
return "login";
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package com.example.demo.supply.util.error;
|
||||
|
||||
|
||||
import com.example.demo.supply.Order.OrderNotFoundException;
|
||||
import com.example.demo.supply.Product.ProductNotFoundException;
|
||||
import com.example.demo.supply.Supplier.SupplierNotFoundException;
|
||||
import com.example.demo.supply.util.validation.ValidationException;
|
||||
import org.springframework.context.support.DefaultMessageSourceResolvable;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ControllerAdvice(annotations = RestController.class)
|
||||
public class AdviceController {
|
||||
@ExceptionHandler({
|
||||
OrderNotFoundException.class,
|
||||
ProductNotFoundException.class,
|
||||
SupplierNotFoundException.class,
|
||||
ValidationException.class
|
||||
})
|
||||
public ResponseEntity<Object> handleException(Throwable e) {
|
||||
return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<Object> handleBindException(MethodArgumentNotValidException e) {
|
||||
final ValidationException validationException = new ValidationException(
|
||||
e.getBindingResult().getAllErrors().stream()
|
||||
.map(DefaultMessageSourceResolvable::getDefaultMessage)
|
||||
.collect(Collectors.toSet()));
|
||||
return handleException(validationException);
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<Object> handleUnknownException(Throwable e) {
|
||||
e.printStackTrace();
|
||||
return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.example.demo.supply.util.validation;
|
||||
import java.util.Set;
|
||||
public class ValidationException extends RuntimeException{
|
||||
public <T> ValidationException(Set<String> errors) {
|
||||
super(String.join("\n", errors));
|
||||
}
|
||||
|
||||
public <T> ValidationException(String error) {
|
||||
super(error);
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.example.demo.supply.util.validation;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import javax.validation.ValidatorFactory;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
public class ValidatorUtil {
|
||||
private final Validator validator;
|
||||
public ValidatorUtil() {
|
||||
try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) {
|
||||
this.validator = factory.getValidator();
|
||||
}
|
||||
}
|
||||
public <T> void validate(T object) {
|
||||
final Set<ConstraintViolation<T>> errors = validator.validate(object);
|
||||
if (!errors.isEmpty()) {
|
||||
throw new ValidationException(errors.stream()
|
||||
.map(ConstraintViolation::getMessage)
|
||||
.collect(Collectors.toSet()));
|
||||
}
|
||||
}
|
||||
}
|
11
demo/src/main/resources/application.properties
Normal file
11
demo/src/main/resources/application.properties
Normal file
@ -0,0 +1,11 @@
|
||||
spring.main.banner-mode=off
|
||||
server.port=8080
|
||||
spring.datasource.url=jdbc:h2:file:./data
|
||||
spring.datasource.driverClassName=org.h2.Driver
|
||||
spring.datasource.username=sa
|
||||
spring.datasource.password=password
|
||||
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.h2.console.enabled=true
|
||||
spring.h2.console.settings.trace=false
|
||||
spring.h2.console.settings.web-allow-others=false
|
15
demo/src/main/resources/public/css/style.css
Normal file
15
demo/src/main/resources/public/css/style.css
Normal file
@ -0,0 +1,15 @@
|
||||
.container-padding {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.margin-bottom {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.button-fixed {
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.button-sm {
|
||||
padding: 1px;
|
||||
}
|
4
demo/src/main/resources/public/favicon.svg
Normal file
4
demo/src/main/resources/public/favicon.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 448 512"><!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
|
||||
<path d="M448 48V384c-63.09 22.54-82.34 32-119.5 32c-62.82 0-86.6-32-149.3-32C158.6 384 142.6 387.6 128 392.2v-64C142.6 323.6 158.6 320 179.2 320c62.73 0 86.51 32 149.3 32C348.9 352 364.1 349 384 342.7v-208C364.1 141 348.9 144 328.5 144c-62.82 0-86.6-32-149.3-32C128.4 112 104.3 132.6 64 140.7v307.3C64 465.7 49.67 480 32 480S0 465.7 0 448V63.1C0 46.33 14.33 32 31.1 32S64 46.33 64 63.1V76.66C104.3 68.63 128.4 48 179.2 48c62.73 0 86.51 32 149.3 32C365.7 80 384.9 70.54 448 48z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 727 B |
10
demo/src/main/resources/templates/common/header.html
Normal file
10
demo/src/main/resources/templates/common/header.html
Normal file
@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
57
demo/src/main/resources/templates/default.html
Normal file
57
demo/src/main/resources/templates/default.html
Normal file
@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru"
|
||||
xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity6"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<title>Поставки</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<link rel="icon" href="/favicon.svg">
|
||||
<script type="text/javascript" src="/webjars/bootstrap/5.1.3/js/bootstrap.bundle.min.js"></script>
|
||||
<link rel="stylesheet" href="/webjars/bootstrap/5.1.3/css/bootstrap.min.css"/>
|
||||
<link rel="stylesheet" href="/webjars/font-awesome/6.1.0/css/all.min.css"/>
|
||||
<link rel="stylesheet" href="/css/style.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">
|
||||
<i class="fa-solid fa-font-awesome"></i>
|
||||
Поставки
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button"
|
||||
data-bs-toggle="collapse" data-bs-target="#navbarNav"
|
||||
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav" th:with="activeLink=${#request.requestURI}" sec:authorize="isAuthenticated()">
|
||||
<a class="nav-link" href="/"
|
||||
th:classappend="${#strings.equals(activeLink, '/')} ? 'active' : ''">Главная</a>
|
||||
<a class="nav-link" href="/product"
|
||||
th:classappend="${#strings.equals(activeLink, '/product')} ? 'active' : ''">Продукты</a>
|
||||
<a class="nav-link" href="/supplier"
|
||||
th:classappend="${#strings.equals(activeLink, '/supplier')} ? 'active' : ''">Поставщики</a>
|
||||
<a class="nav-link" href="/order"
|
||||
th:classappend="${#strings.equals(activeLink, '/order')} ? 'active' : ''">Заказы</a>
|
||||
<a class="nav-link" href="/order/dop"
|
||||
th:classappend="${#strings.equals(activeLink, '/order')} ? 'active' : ''">Доп задание</a>
|
||||
<a sec:authorize="hasRole('ROLE_ADMIN')" class="nav-link" href="/users"
|
||||
th:classappend="${#strings.equals(activeLink, '/users')} ? 'active' : ''">Пользователи</a>
|
||||
<a class="nav-link" href="/logout">
|
||||
Выход (<span th:text="${#authentication.name}"></span>)
|
||||
</a>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container-fluid">
|
||||
<div class="container container-padding" layout:fragment="content">
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<th:block layout:fragment="scripts">
|
||||
</th:block>
|
||||
</html>
|
14
demo/src/main/resources/templates/error.html
Normal file
14
demo/src/main/resources/templates/error.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
xmlns:th="http://www.thymeleaf.org"
|
||||
layout:decorate="~{default}">
|
||||
<body>
|
||||
<div class="container" layout:fragment="content">
|
||||
<div class="alert alert-danger">
|
||||
<span th:text="${error}"></span>
|
||||
</div>
|
||||
<a href="/">На главную</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
12
demo/src/main/resources/templates/index.html
Normal file
12
demo/src/main/resources/templates/index.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.thymeleaf.org"
|
||||
layout:decorate="~{default}">
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<div>It's works!</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
34
demo/src/main/resources/templates/login.html
Normal file
34
demo/src/main/resources/templates/login.html
Normal file
@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
xmlns:th="http://www.thymeleaf.org"
|
||||
layout:decorate="~{default}">
|
||||
>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<div th:if="${param.error}" class="alert alert-danger margin-bottom">
|
||||
Пользователь не найден или пароль указан не верно
|
||||
</div>
|
||||
<div th:if="${param.logout}" class="alert alert-success margin-bottom">
|
||||
Выход успешно произведен
|
||||
</div>
|
||||
<div th:if="${param.created}" class="alert alert-success margin-bottom">
|
||||
Пользователь '<span th:text="${param.created}"></span>' успешно создан
|
||||
</div>
|
||||
<form th:action="@{/login}" method="post" class="w-50 ms-2">
|
||||
<h2 class="py-3">Вход</h2>
|
||||
<h4>Логин</h4>
|
||||
<input class="form-control my-2" name="username" id="username" type="text" placeholder="Логин" required="true" autofocus="true"/>
|
||||
<h4>Пароль</h4>
|
||||
<input class="form-control my-2" name="password" id="password" type="password" placeholder="Пароль" required="true" />
|
||||
<div>
|
||||
<button class="btn btn-primary m-2" type="submit">Войти</button>
|
||||
<a href="/signup" style="margin-top: 1em; margin-left: 1em"
|
||||
>Зарегистрируйтесь, если нет аккаунта, здесь</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
37
demo/src/main/resources/templates/order-add.html
Normal file
37
demo/src/main/resources/templates/order-add.html
Normal file
@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.thymeleaf.org"
|
||||
layout:decorate="~{default}">
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<form action="#" th:action="@{/order/create}" th:object="${orderDto}" method="post">
|
||||
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn btn-primary btn-success">Создать</button>
|
||||
<a class="btn btn-primary btn-danger" th:href="@{/order}">
|
||||
Назад
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="supplier" class="form-label">Поставщик</label>
|
||||
<select id="supplier" class="form-select" th:field="*{supplierId}" th:name="${selectedSupplier}">
|
||||
<option th:each="value: ${suppliers}" th:selected="${selectedSupplier != null and selectedSupplier == value.id}" th:value="${value.id}" th:text=" ${value.name}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<p class="d-flex justify-content-between">
|
||||
<label>Продукты:</label>
|
||||
<div th:each="product : ${products}">
|
||||
<input type="checkbox" name="genres" th:text="${product.name} + '(Цена: ' + ${product.cost} + ') '"
|
||||
th:value="${product.id} "
|
||||
th:field="*{productsId}"
|
||||
/>
|
||||
</div>
|
||||
</p>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
61
demo/src/main/resources/templates/order-dop.html
Normal file
61
demo/src/main/resources/templates/order-dop.html
Normal file
@ -0,0 +1,61 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.thymeleaf.org"
|
||||
layout:decorate="~{default}">
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<form action="#" th:action="@{/order/dop}" th:object="${productDto}" method="get">
|
||||
<div class="mb-3">
|
||||
<label for="author" class="form-label">Продукт</label>
|
||||
|
||||
<select id="author" class="form-select" th:field="*{id}" th:name="${selectedProduct}">
|
||||
<option th:each="value: ${products}" th:selected="${selectedProduct != null and selectedProduct == value.id}" th:value="${value.id}" th:text="${value.name}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success button-fixed">Сформировать</button>
|
||||
</form>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">ID</th>
|
||||
<th scope="col">Название</th>
|
||||
<th scope="col">Лицензия</th>
|
||||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<tr th:each="supplier, iterator: ${suppliers}">
|
||||
<th scope="row" th:text="${iterator.index} + 1"/>
|
||||
<td th:text="${supplier.id}"/>
|
||||
<td th:text="${supplier.name}"/>
|
||||
<td th:text="${supplier.license}"/>
|
||||
<td style="width: 10%">
|
||||
<div class="btn-group" role="group" aria-label="Basic example">
|
||||
<a class="btn btn-warning button-fixed button-sm"
|
||||
th:href="@{/supplier/edit/{id}(id=${supplier.id})}">
|
||||
<i class="fa fa-pencil" aria-hidden="true"></i> Изменить
|
||||
</a>
|
||||
<button type="button" class="btn btn-danger button-fixed button-sm"
|
||||
th:attr="onclick=|confirm('Удалить запись?') && document.getElementById('remove-${supplier.id}').click()|">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i> Удалить
|
||||
</button>
|
||||
</div>
|
||||
<form th:action="@{/supplier/delete/{id}(id=${supplier.id})}" method="post">
|
||||
<button th:id="'remove-' + ${supplier.id}" type="submit" style="display: none">
|
||||
Удалить
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
41
demo/src/main/resources/templates/order.html
Normal file
41
demo/src/main/resources/templates/order.html
Normal file
@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.thymeleaf.org"
|
||||
layout:decorate="~{default}">
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<div>
|
||||
<a class="btn btn-success button-fixed"
|
||||
th:href="@{/order/add}">
|
||||
<i class="fa-solid fa-plus"></i> Создать заказ
|
||||
</a>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">ID</th>
|
||||
<th scope="col">Дата создания</th>
|
||||
<th scope="col">Поставщик</th>
|
||||
<th scope="col">Продукты</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="order, iterator: ${orders}">
|
||||
<th scope="row" th:text="${iterator.index} + 1"/>
|
||||
<td th:text="${order.id}"/>
|
||||
<td th:text="${order.dateOfOrder}" />
|
||||
<td th:text="${order.supplier.name}" />
|
||||
<td>
|
||||
<li th:each="product : ${order.products}" th:text="${product.name}"></li>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
30
demo/src/main/resources/templates/product-edit.html
Normal file
30
demo/src/main/resources/templates/product-edit.html
Normal file
@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.thymeleaf.org"
|
||||
layout:decorate="~{default}">
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<form action="#" th:action="@{/product/{id}(id=${id})}" th:object="${productDto}" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Название</label>
|
||||
<input type="text" class="form-control" id="name" th:field="${productDto.name}" required="true">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="cost" class="form-label">Цена</label>
|
||||
<input type="text" class="form-control" id="cost" th:field="${productDto.cost}" required="true">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn btn-primary button-fixed">
|
||||
<span th:if="${id == null}">Добавить</span>
|
||||
<span th:if="${id != null}">Обновить</span>
|
||||
</button>
|
||||
<a class="btn btn-secondary button-fixed" th:href="@{/product}">
|
||||
Назад
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
57
demo/src/main/resources/templates/product.html
Normal file
57
demo/src/main/resources/templates/product.html
Normal file
@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity6"
|
||||
xmlns:th="http://www.thymeleaf.org"
|
||||
layout:decorate="~{default}">
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<div>
|
||||
<a class="btn btn-success button-fixed"
|
||||
th:href="@{/product/edit/}">
|
||||
<i class="fa-solid fa-plus"></i> Добавить
|
||||
</a>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">ID</th>
|
||||
<th scope="col">Название</th>
|
||||
<th scope="col">Цена</th>
|
||||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="product, iterator: ${products}">
|
||||
<th scope="row" th:text="${iterator.index} + 1"/>
|
||||
<td th:text="${product.id}"/>
|
||||
<td th:text="${product.name}" />
|
||||
<td th:text="${product.cost}" />
|
||||
<td style="width: 10%">
|
||||
<div class="btn-group" role="group" aria-label="Basic example">
|
||||
<a class="btn btn-warning button-fixed button-sm"
|
||||
th:href="@{/product/edit/{id}(id=${product.id})}">
|
||||
<i class="fa fa-pencil" aria-hidden="true"></i> Изменить
|
||||
</a>
|
||||
<button type="button" class="btn btn-danger button-fixed button-sm"
|
||||
th:attr="onclick=|confirm('Удалить запись?') && document.getElementById('remove-${product.id}').click()|">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i> Удалить
|
||||
</button>
|
||||
</div>
|
||||
<form th:action="@{/product/delete/{id}(id=${product.id})}" method="post">
|
||||
<button th:id="'remove-' + ${product.id}" type="submit" style="display: none">
|
||||
Удалить
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
29
demo/src/main/resources/templates/signup.html
Normal file
29
demo/src/main/resources/templates/signup.html
Normal file
@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
xmlns:th="http://www.thymeleaf.org"
|
||||
layout:decorate="~{default}">
|
||||
<body>
|
||||
<div class="container container-padding" layout:fragment="content">
|
||||
<div th:if="${errors}" th:text="${errors}" class="margin-bottom alert alert-danger"></div>
|
||||
<form action="#" th:action="@{/signup}" th:object="${userDto}" method="post">
|
||||
<div class="mb-3">
|
||||
<input type="text" class="form-control" th:field="${userDto.login}"
|
||||
placeholder="Логин" required="true" autofocus="true" maxlength="64"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<input type="password" class="form-control" th:field="${userDto.password}"
|
||||
placeholder="Пароль" required="true" minlength="6" maxlength="64"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<input type="password" class="form-control" th:field="${userDto.passwordConfirm}"
|
||||
placeholder="Пароль (подтверждение)" required="true" minlength="6" maxlength="64"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn btn-success button-fixed">Создать</button>
|
||||
<a class="btn btn-primary button-fixed" href="/login">Назад</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
30
demo/src/main/resources/templates/supplier-edit.html
Normal file
30
demo/src/main/resources/templates/supplier-edit.html
Normal file
@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.thymeleaf.org"
|
||||
layout:decorate="~{default}">
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<form action="#" th:action="@{/supplier/{id}(id=${id})}" th:object="${supplierDto}" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Название</label>
|
||||
<input type="text" class="form-control" id="name" th:field="${supplierDto.name}" required="true">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="license" class="form-label">Лицензия</label>
|
||||
<input type="text" class="form-control" id="license" th:field="${supplierDto.license}" required="true">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn btn-primary button-fixed">
|
||||
<span th:if="${id == null}">Добавить</span>
|
||||
<span th:if="${id != null}">Обновить</span>
|
||||
</button>
|
||||
<a class="btn btn-secondary button-fixed" th:href="@{/supplier}">
|
||||
Назад
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
55
demo/src/main/resources/templates/supplier.html
Normal file
55
demo/src/main/resources/templates/supplier.html
Normal file
@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.thymeleaf.org"
|
||||
layout:decorate="~{default}">
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<div>
|
||||
<a class="btn btn-success button-fixed"
|
||||
th:href="@{/supplier/edit/}">
|
||||
<i class="fa-solid fa-plus"></i> Добавить
|
||||
</a>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">ID</th>
|
||||
<th scope="col">Название</th>
|
||||
<th scope="col">Лицензия</th>
|
||||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="supplier, iterator: ${suppliers}">
|
||||
<th scope="row" th:text="${iterator.index} + 1"/>
|
||||
<td th:text="${supplier.id}"/>
|
||||
<td th:text="${supplier.name}"/>
|
||||
<td th:text="${supplier.license}"/>
|
||||
<td style="width: 10%">
|
||||
<div class="btn-group" role="group" aria-label="Basic example">
|
||||
<a class="btn btn-warning button-fixed button-sm"
|
||||
th:href="@{/supplier/edit/{id}(id=${supplier.id})}">
|
||||
<i class="fa fa-pencil" aria-hidden="true"></i> Изменить
|
||||
</a>
|
||||
<button type="button" class="btn btn-danger button-fixed button-sm"
|
||||
th:attr="onclick=|confirm('Удалить запись?') && document.getElementById('remove-${supplier.id}').click()|">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i> Удалить
|
||||
</button>
|
||||
</div>
|
||||
<form th:action="@{/supplier/delete/{id}(id=${supplier.id})}" method="post">
|
||||
<button th:id="'remove-' + ${supplier.id}" type="submit" style="display: none">
|
||||
Удалить
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
38
demo/src/main/resources/templates/users.html
Normal file
38
demo/src/main/resources/templates/users.html
Normal file
@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
xmlns:th="http://www.thymeleaf.org"
|
||||
layout:decorate="~{default}">
|
||||
<body>
|
||||
<div class="container" layout:fragment="content">
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">ID</th>
|
||||
<th scope="col">Логин</th>
|
||||
<th scope="col">Роль</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="user, iterator: ${users}">
|
||||
<th scope="row" th:text="${iterator.index} + 1"></th>
|
||||
<td th:text="${user.id}"></td>
|
||||
<td th:text="${user.login}" style="width: 60%"></td>
|
||||
<td th:text="${user.role}" style="width: 20%"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div th:if="${totalPages > 0}" class="pagination">
|
||||
<span style="float: left; padding: 5px 5px;">Страницы:</span>
|
||||
<a th:each="page : ${pages}"
|
||||
th:href="@{/users(page=${page}, size=${users.size})}"
|
||||
th:text="${page}"
|
||||
th:class="${page == users.number + 1} ? active">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
7
demo/src/test/java/com/example/demo/Tests.java
Normal file
7
demo/src/test/java/com/example/demo/Tests.java
Normal file
@ -0,0 +1,7 @@
|
||||
package com.example.demo;
|
||||
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
public class Tests {
|
||||
}
|
53
demo/src/test/java/com/example/demo/TestsProduct.java
Normal file
53
demo/src/test/java/com/example/demo/TestsProduct.java
Normal file
@ -0,0 +1,53 @@
|
||||
package com.example.demo;
|
||||
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@SpringBootTest
|
||||
public class TestsProduct {
|
||||
// @Autowired
|
||||
// private ProductService productService;
|
||||
//
|
||||
//
|
||||
// @Test
|
||||
// void testProduct(){
|
||||
// productService.deleteAll();
|
||||
// final Product product = productService.addProduct("Huawei Band 3", 2000.13);
|
||||
// Assertions.assertNotNull(product.getId());
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testProductRead(){
|
||||
// productService.deleteAll();
|
||||
// final Product product = productService.addProduct("Huawei Band 3", 2000.13);
|
||||
// final Product findProduct = productService.findProduct(product.getId());
|
||||
// Assertions.assertEquals(product, findProduct);
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testProductReadNotFound(){
|
||||
// productService.deleteAll();
|
||||
// Assertions.assertThrows(EntityNotFoundException.class, () -> productService.findProduct(-1L));
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testProductReadAll(){
|
||||
// productService.deleteAll();
|
||||
// productService.addProduct("Samsung A3", 22000.4);
|
||||
// productService.addProduct("Huawei Band 3", 2000.13);
|
||||
// final List<Product> products = productService.findAllProducts();
|
||||
// Assertions.assertEquals(products.size(), 2);
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testProductReadAllEmpty(){
|
||||
// productService.deleteAll();
|
||||
// final List<Product> products = productService.findAllProducts();
|
||||
// Assertions.assertEquals(products.size(), 0);
|
||||
// }
|
||||
}
|
59
demo/src/test/java/com/example/demo/TestsSupplier.java
Normal file
59
demo/src/test/java/com/example/demo/TestsSupplier.java
Normal file
@ -0,0 +1,59 @@
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.demo.supply.Supplier.Supplier;
|
||||
import com.example.demo.supply.Order.OrderService;
|
||||
import com.example.demo.supply.Supplier.SupplierService;
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@SpringBootTest
|
||||
public class TestsSupplier {
|
||||
// @Autowired
|
||||
// private SupplierService supplierService;
|
||||
// @Autowired
|
||||
// private OrderService orderService;
|
||||
// @Autowired
|
||||
// private ProductService productService;
|
||||
//
|
||||
// @Test
|
||||
// void testSupplier(){
|
||||
// supplierService.deleteAll();
|
||||
// final Supplier supplier = supplierService.addSupplier("SuperSup", 359342);
|
||||
// Assertions.assertNotNull(supplier.getId());
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testSupplierRead(){
|
||||
// supplierService.deleteAll();
|
||||
// final Supplier supplier = supplierService.addSupplier("Huawei", 4357695);
|
||||
// final Supplier findSupplier = supplierService.findSupplier(supplier.getId());
|
||||
// Assertions.assertEquals(supplier, findSupplier);
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testSupplierReadNotFound(){
|
||||
// supplierService.deleteAll();
|
||||
// Assertions.assertThrows(EntityNotFoundException.class, () -> supplierService.findSupplier(-1L));
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testSupplierReadAll(){
|
||||
// supplierService.deleteAll();
|
||||
// supplierService.addSupplier("Samsung", 3485456);
|
||||
// supplierService.addSupplier("Huawei", 45736964);
|
||||
// final List<Supplier> suppliers = supplierService.findAllSuppliers();
|
||||
// Assertions.assertEquals(suppliers.size(), 2);
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testSupplierReadAllEmpty(){
|
||||
// supplierService.deleteAll();
|
||||
// final List<Supplier> suppliers = supplierService.findAllSuppliers();
|
||||
// Assertions.assertEquals(suppliers.size(), 0);
|
||||
// }
|
||||
}
|
23
front/.gitignore
vendored
Normal file
23
front/.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*
|
70
front/README.md
Normal file
70
front/README.md
Normal file
@ -0,0 +1,70 @@
|
||||
# Getting Started with Create React App
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
|
||||
|
||||
The page will reload when you make changes.\
|
||||
You may also see any lint errors in the console.
|
||||
|
||||
### `npm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.\
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder.\
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.\
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `npm run eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
|
||||
|
||||
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
|
||||
|
||||
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
|
||||
### Code Splitting
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
|
||||
|
||||
### Analyzing the Bundle Size
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
|
||||
|
||||
### Making a Progressive Web App
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
|
||||
|
||||
### Deployment
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
|
||||
|
||||
### `npm run build` fails to minify
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
|
17363
front/package-lock.json
generated
Normal file
17363
front/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
40
front/package.json
Normal file
40
front/package.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "front",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"axios": "^1.3.6",
|
||||
"bootstrap": "^5.2.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.10.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"web-vitals": "^2.1.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"
|
||||
]
|
||||
}
|
||||
}
|
13
front/public/index.html
Normal file
13
front/public/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe" crossorigin="anonymous"></script>
|
||||
|
||||
<title>Поставки</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
25
front/src/App.js
Normal file
25
front/src/App.js
Normal file
@ -0,0 +1,25 @@
|
||||
import { BrowserRouter, Route, Routes} from "react-router-dom";
|
||||
import CatalogProducts from "./Pages/CatalogProducts";
|
||||
import CatalogSuppliers from "./Pages/CatalogSuppliers";
|
||||
import OrderPage from "./Pages/OrdersPage";
|
||||
import CreateOrderPage from "./Pages/CreateOrderPage";
|
||||
import Header from "./general/Header";
|
||||
|
||||
function App() {
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Header/>
|
||||
<Routes>
|
||||
<Route path="/" Component={CatalogProducts}/>
|
||||
<Route path="/products" Component={CatalogProducts} />
|
||||
<Route path="/suppliers" Component={CatalogSuppliers} />
|
||||
<Route path="/orders" Component={OrderPage} />
|
||||
<Route path="/createOrder" Component={CreateOrderPage} />
|
||||
</Routes>
|
||||
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
87
front/src/DataService.js
Normal file
87
front/src/DataService.js
Normal file
@ -0,0 +1,87 @@
|
||||
import axios from 'axios';
|
||||
|
||||
function getFullUrl(url, data) {
|
||||
let currentUrl = new URL(url)
|
||||
//извлекаем поля
|
||||
const fields = Object.getOwnPropertyNames(data);
|
||||
//проходимся по каждому полю
|
||||
for (const field of fields) {
|
||||
|
||||
if (field === undefined) continue
|
||||
if (field === 'id') continue
|
||||
if (field === 'date') continue
|
||||
if (field === 'countProducts') continue
|
||||
if (field === 'products') continue
|
||||
if (field === 'supplierName') continue
|
||||
currentUrl.searchParams.append(field, data[field])
|
||||
}
|
||||
|
||||
return currentUrl;
|
||||
}
|
||||
|
||||
export default class DataService {
|
||||
static mainUrl = 'http://localhost:8080/';
|
||||
|
||||
static async readAll(url, transformer) {
|
||||
const response = await axios.get(this.mainUrl + url);
|
||||
return response.data.map(item => transformer(item));
|
||||
}
|
||||
|
||||
static async getOrders(url){
|
||||
const response = await fetch(this.mainUrl + url)
|
||||
const res = response.json()
|
||||
return res
|
||||
}
|
||||
|
||||
static async read(url, transformer) {
|
||||
const response = await axios.get(this.mainUrl + url);
|
||||
return transformer(response.data);
|
||||
}
|
||||
|
||||
static async create(url, data) {
|
||||
const response = await axios.post(getFullUrl(this.mainUrl + url, data))
|
||||
const res = response.data
|
||||
return res
|
||||
}
|
||||
|
||||
static async addProduct(url) {
|
||||
await fetch(url, {
|
||||
method: 'PATCH',
|
||||
}).catch(e => console.log(e))
|
||||
return true;
|
||||
}
|
||||
|
||||
static async getSomeSuppliers(){
|
||||
const arr =[
|
||||
{
|
||||
"id":104,
|
||||
"name":"prod3",
|
||||
"cost":3333
|
||||
},
|
||||
{
|
||||
"id":153,
|
||||
"name":"prod3",
|
||||
"cost":5555
|
||||
}
|
||||
]
|
||||
|
||||
//const resArr = JSON.stringify(arr)
|
||||
//console.log(resArr)
|
||||
const response = await axios.post(this.mainUrl + `order/someSuppliers/`, {
|
||||
body: {arr}
|
||||
});
|
||||
console.log(response)
|
||||
}
|
||||
|
||||
static async update(url, data) {
|
||||
await fetch(getFullUrl(this.mainUrl + url, data), {
|
||||
method: 'PATCH',
|
||||
}).catch(e => console.log(e))
|
||||
return true;
|
||||
}
|
||||
|
||||
static async delete(url) {
|
||||
const response = await axios.delete(this.mainUrl + url);
|
||||
return response.data.id;
|
||||
}
|
||||
}
|
112
front/src/Pages/CatalogOrders.jsx
Normal file
112
front/src/Pages/CatalogOrders.jsx
Normal file
@ -0,0 +1,112 @@
|
||||
import { React, useState, useEffect } from 'react';
|
||||
import Catalog from '../general/Catalog'
|
||||
import Order from '../models/Order';
|
||||
import Supplier from '../models/Supplier';
|
||||
import Product from '../models/Product';
|
||||
import DataService from '../DataService';
|
||||
import Modal from '../general/Modal';
|
||||
|
||||
export default function CatalogOrders(props) {
|
||||
const url = 'order/'
|
||||
const supplierUrl = 'supplier/'
|
||||
const productUrl = 'product/'
|
||||
|
||||
const transformer = (data) => {
|
||||
new Order(data)
|
||||
}
|
||||
|
||||
const catalogOrderHeaders = [
|
||||
{ name: 'date', label: 'Дата заказа' },
|
||||
{ name: 'supplier', label: 'Поставщик' },
|
||||
{ name: 'products', label: 'Продукт(ы)' }
|
||||
];
|
||||
|
||||
const [data, setData] = useState(new Order());
|
||||
const [suppliers, setSuppliers] = useState([]);
|
||||
const [products, setProducts] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
DataService.readAll(supplierUrl, (data) => new Supplier(data)).then(data => setSuppliers(data));
|
||||
DataService.readAll(productUrl, (data) => new Product(data)).then(data => setProducts(data));
|
||||
}, [])
|
||||
|
||||
const add = () =>{
|
||||
setData(new Order())
|
||||
console.log(data)
|
||||
}
|
||||
const edit = (data) => {
|
||||
console.log(data)
|
||||
setData(new Order(data))
|
||||
}
|
||||
|
||||
const addProduct = (data) => {
|
||||
setData(new Order(data))
|
||||
}
|
||||
|
||||
function handleFormChange(event) {
|
||||
console.log(data)
|
||||
setData({ ...data, [event.target.id]: event.target.value })
|
||||
console.log(data)
|
||||
}
|
||||
|
||||
const saveProducts = (event) => {
|
||||
console.log(products)
|
||||
setData({...products, [event.target.id]: event.target.value})
|
||||
console.log(products)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Catalog headers={catalogOrderHeaders}
|
||||
url={url}
|
||||
transformer={transformer}
|
||||
data={data}
|
||||
add={add}
|
||||
edit={edit}
|
||||
isOrder={true}
|
||||
addProduct={edit}
|
||||
modalAddProduct={
|
||||
<div className="mb-3">
|
||||
<label htmlFor="product" className="form-label">Поставщик</label>
|
||||
<select id="product" className="form-select" required
|
||||
value={data.product} onChange={saveProducts}>
|
||||
<option disabled value="">Укажите продукт</option>
|
||||
{
|
||||
products.map(product =>
|
||||
<option key={product.id} value={product.id}>{product.name}</option>
|
||||
)
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
}>
|
||||
|
||||
|
||||
<div className="mb-3">
|
||||
<label htmlFor="supplierId" className="form-label">Поставщик</label>
|
||||
<select id="supplierId" className="form-select" required
|
||||
value={data.supplierId} onChange={handleFormChange}>
|
||||
<option disabled value="">Укажите поставщика</option>
|
||||
{
|
||||
suppliers.map(supplier =>
|
||||
<option key={supplier.id} value={supplier.id}>{supplier.name}</option>
|
||||
)
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label htmlFor="product" className="form-label">Поставщик</label>
|
||||
<select id="product" className="form-select" required
|
||||
value={data.product} onChange={handleFormChange}>
|
||||
<option disabled value="">Укажите продукт</option>
|
||||
{
|
||||
products.map(product =>
|
||||
<option key={product.id} value={product.id}>{product.name}</option>
|
||||
)
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</Catalog>
|
||||
</>
|
||||
);
|
||||
}
|
47
front/src/Pages/CatalogProducts.jsx
Normal file
47
front/src/Pages/CatalogProducts.jsx
Normal file
@ -0,0 +1,47 @@
|
||||
import { useState } from 'react';
|
||||
import Catalog from '../general/Catalog'
|
||||
import Product from '../models/Product';
|
||||
|
||||
export default function CatalogProducts(props) {
|
||||
const url = 'product/'
|
||||
|
||||
const transformer = (data) => new Product(data)
|
||||
|
||||
const catalogProductHeaders = [
|
||||
{ name: 'name', label: 'Продукт' },
|
||||
{ name: 'cost', label: 'Цена' }
|
||||
];
|
||||
|
||||
const [data, setData] = useState(new Product());
|
||||
|
||||
const add = () => setData(new Product());
|
||||
const edit = (data) => setData(new Product(data))
|
||||
|
||||
|
||||
function handleFormChange(event) {
|
||||
setData({ ...data, [event.target.id]: event.target.value })
|
||||
}
|
||||
|
||||
return (
|
||||
<Catalog headers={catalogProductHeaders}
|
||||
getAllUrl={url}
|
||||
url={url}
|
||||
transformer={transformer}
|
||||
data={data}
|
||||
add={add}
|
||||
edit={edit}
|
||||
isOrder={false}>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="name" className="form-label">Наименование</label>
|
||||
<input type="text" id="name" className="form-control" required
|
||||
value={data.name} onChange={handleFormChange}/>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label htmlFor="cost" className="form-label">Цена</label>
|
||||
<input type="number" id="cost" className="form-control" required
|
||||
value={data.cost} onChange={handleFormChange}/>
|
||||
</div>
|
||||
</Catalog>
|
||||
);
|
||||
}
|
46
front/src/Pages/CatalogSuppliers.jsx
Normal file
46
front/src/Pages/CatalogSuppliers.jsx
Normal file
@ -0,0 +1,46 @@
|
||||
import { useState } from 'react';
|
||||
import Catalog from '../general/Catalog'
|
||||
import Supplier from '../models/Supplier';
|
||||
|
||||
export default function CatalogSuppliers(props) {
|
||||
const url = 'supplier/'
|
||||
|
||||
const transformer = (data) => new Supplier(data)
|
||||
|
||||
const catalogProductHeaders = [
|
||||
{ name: 'name', label: 'Поставщик' },
|
||||
{ name: 'license', label: 'Лицензия' }
|
||||
];
|
||||
|
||||
const [data, setData] = useState(new Supplier());
|
||||
|
||||
const add = () => setData(new Supplier());
|
||||
const edit = (data) => setData(new Supplier(data))
|
||||
|
||||
const handleFormChange = (event) => {
|
||||
setData({ ...data, [event.target.id]: event.target.value })
|
||||
}
|
||||
|
||||
return (
|
||||
<Catalog headers={catalogProductHeaders}
|
||||
getAllUrl={url}
|
||||
url={url}
|
||||
transformer={transformer}
|
||||
data={data}
|
||||
add={add}
|
||||
edit={edit}
|
||||
isOrder={false}>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="name" className="form-label">Поставщик</label>
|
||||
<input type="text" id="name" className="form-control" required
|
||||
value={data.name} onChange={handleFormChange}/>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label htmlFor="license" className="form-label">Лицензия</label>
|
||||
<input type="number" id="license" className="form-control" required
|
||||
value={data.license} onChange={handleFormChange}/>
|
||||
</div>
|
||||
</Catalog>
|
||||
);
|
||||
}
|
175
front/src/Pages/CreateOrderPage.jsx
Normal file
175
front/src/Pages/CreateOrderPage.jsx
Normal file
@ -0,0 +1,175 @@
|
||||
import { React, useState, useEffect } from "react";
|
||||
import { Link } from 'react-router-dom';
|
||||
import Supplier from "../models/Supplier";
|
||||
import DataService from "../DataService";
|
||||
import Order from "../models/Order";
|
||||
import Product from "../models/Product";
|
||||
import Table from "../general/Table";
|
||||
import Modal from "../general/Modal";
|
||||
|
||||
export default function CreateOrderPage(props){
|
||||
const url = 'order/'
|
||||
const supplierUrl = 'supplier/'
|
||||
const productUrl = 'product/'
|
||||
|
||||
let selectedItems = [];
|
||||
|
||||
const headers = [
|
||||
{ name: 'name', label: 'Продукт' },
|
||||
{ name: 'cost', label: 'Цена' }
|
||||
];
|
||||
|
||||
const transformer = (data) => new Order(data)
|
||||
const transformerSupplier = (data) => new Supplier(data)
|
||||
const transformerProduct = (data) => new Product(data)
|
||||
|
||||
const [suppliers, setSuppliers] = useState([])
|
||||
const [products, setProducts] = useState([])
|
||||
|
||||
const [order, setOrder] = useState(new Order())
|
||||
const [addsProduct, setAddsProduct] = useState(new Product())
|
||||
|
||||
const [modalHeader, setModalHeader] = useState('')
|
||||
const [modalConfirm, setModalConfirm] = useState('')
|
||||
const [modalVisible, setModalVisible] = useState(false)
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
loadItems()
|
||||
//eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const loadItems = () => {
|
||||
DataService.readAll(supplierUrl, transformerSupplier).then(data => setSuppliers(data))
|
||||
DataService.readAll(productUrl, transformerProduct).then(data => setProducts(data))
|
||||
}
|
||||
|
||||
const createOrder = () => {
|
||||
if(order.supplierId === ''){
|
||||
window.alert ('Заказ был заполнен неверно, попробуйте еще раз')
|
||||
return
|
||||
}
|
||||
DataService.create(url, order)
|
||||
.then(data => {
|
||||
order.products.map(product =>{
|
||||
DataService.addProduct(`http://localhost:8080/${url}addProduct/${data}?productId=${product.id}`)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const addProductInOrder = () => {
|
||||
DataService.read(`${productUrl}${addsProduct.id}`, transformerProduct)
|
||||
.then(data => {
|
||||
let contains = false
|
||||
order.products.map(product => {
|
||||
if(product.id === data.id) contains = true
|
||||
})
|
||||
if(!contains){
|
||||
order.products.push(data)
|
||||
setOrder({ ...order, products: order.products })
|
||||
}
|
||||
else
|
||||
window.alert ('Такой продукт уже был добавлен')
|
||||
})
|
||||
}
|
||||
|
||||
const removeProduct = () => {
|
||||
if (selectedItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.confirm('Удалить выбранные элементы?')) {
|
||||
const promises = [];
|
||||
selectedItems.forEach(item => {
|
||||
promises.push(DataService.delete(props.url + item));
|
||||
});
|
||||
Promise.all(promises).then((results) => {
|
||||
selectedItems.length = 0;
|
||||
loadItems();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const handleFormChange = (event) => {
|
||||
setOrder({ ...order, [event.target.id]: event.target.value })
|
||||
}
|
||||
|
||||
const handleAddProduct = (event) => {
|
||||
setAddsProduct({ ...addsProduct, [event.target.id]: event.target.value })
|
||||
}
|
||||
|
||||
const addProduct = () => {
|
||||
setAddsProduct(new Product())
|
||||
setModalHeader('Добавление продукта');
|
||||
setModalConfirm('Добавить');
|
||||
setModalVisible(true);
|
||||
}
|
||||
|
||||
const hideModal = () => setModalVisible(false)
|
||||
const ds = () => console.log("")
|
||||
|
||||
const handleTableClick = (tableSelectedItems) => selectedItems = tableSelectedItems;
|
||||
|
||||
return(
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<h1 className="display-6">Создание заказа</h1>
|
||||
</div>
|
||||
|
||||
<div className="row gx-5">
|
||||
<div className="btn-group" role="group" aria-label="Basic mixed styles example">
|
||||
<Link to="/orders">
|
||||
<button type="button" className="btn btn-success" onClick={createOrder}>Создать</button>
|
||||
<button type="button" className="btn btn-danger">Отмена</button>
|
||||
</Link>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<br></br>
|
||||
<div className="mb-3">
|
||||
<p className="h4" htmlFor="supplierId">Поставщик</p>
|
||||
<select id="supplierId" className="form-select " required
|
||||
value={order.supplierId} onChange={handleFormChange}>
|
||||
<option disabled value="">Укажите поставщика</option>
|
||||
{
|
||||
suppliers.map(supplier =>
|
||||
<option key={supplier.id} value={supplier.id}>{supplier.name}</option>
|
||||
)
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<p className="h4">Продукты</p>
|
||||
<div className="btn-group" role="group" aria-label="Basic mixed styles example">
|
||||
<button type="button" className="btn btn-success" onClick={addProduct}>Добавить продукт</button>
|
||||
<button type="button" className="btn btn-danger" onClick={removeProduct} >Удалить продукт</button>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
headers={headers}
|
||||
items={order.products}
|
||||
selectable={true}
|
||||
onClick={handleTableClick}
|
||||
onDblClick={ds}/>
|
||||
|
||||
<Modal
|
||||
header={modalHeader}
|
||||
confirm={modalConfirm}
|
||||
visible={modalVisible}
|
||||
onHide={hideModal}
|
||||
onDone={addProductInOrder}>
|
||||
<div className="mb-3">
|
||||
<p className="h4" htmlFor="id">Продукт</p>
|
||||
<select id="id" className="form-select " required
|
||||
value={addsProduct.id} onChange={handleAddProduct}>
|
||||
<option disabled value="">Укажите продукт</option>
|
||||
{
|
||||
products.map(product =>
|
||||
<option key={product.id} value={product.id}>{product.name}</option>
|
||||
)
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
79
front/src/Pages/OrdersPage.jsx
Normal file
79
front/src/Pages/OrdersPage.jsx
Normal file
@ -0,0 +1,79 @@
|
||||
import { React, useState, useEffect } from "react";
|
||||
import Table from "../general/Table";
|
||||
import ToolBar from "../general/ToolBar";
|
||||
import DataService from "../DataService";
|
||||
import Order from "../models/Order";
|
||||
|
||||
export default function OrderPage(){
|
||||
const url = 'order/'
|
||||
|
||||
const [orders, setOrders] = useState([])
|
||||
|
||||
const headers = [
|
||||
{ name: 'date', label: 'Дата заказа' },
|
||||
{ name: 'supplierName', label: 'Поставщик' },
|
||||
{ name: 'products', label: 'Продукт(ы)' }
|
||||
];
|
||||
|
||||
let selectedItems = [];
|
||||
|
||||
useEffect(() => {
|
||||
loadItems()
|
||||
//eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const loadItems = () => {
|
||||
DataService.getOrders(url).then(data => {
|
||||
setOrders([])
|
||||
data.map(order => {
|
||||
const date = new Date(order.dateOfOrder)
|
||||
order.dateOfOrder = `${date.getDate()}-${date.getMonth()}-${date.getFullYear()}`
|
||||
setOrders(prevState => [...prevState, new Order(order)])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const add = () => {
|
||||
console.log("add")
|
||||
loadItems()
|
||||
}
|
||||
const edit = () =>{}
|
||||
|
||||
const remove = () =>{
|
||||
if (selectedItems.length === 0)
|
||||
return
|
||||
|
||||
if (window.confirm('Удалить выбранные элементы?')) {
|
||||
const promises = [];
|
||||
selectedItems.forEach(item => {
|
||||
promises.push(DataService.delete(url + item));
|
||||
});
|
||||
Promise.all(promises).then(results => {
|
||||
selectedItems.length = 0;
|
||||
loadItems();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const handleTableClick = (tableSelectedItems) => {selectedItems = tableSelectedItems;}
|
||||
const handleTableDblClick = (tableSelectedItem) =>{
|
||||
DataService.getSomeSuppliers()
|
||||
}
|
||||
|
||||
return(
|
||||
<>
|
||||
<ToolBar
|
||||
add={add}
|
||||
edit={edit}
|
||||
remove={remove}
|
||||
addsVisible={true}/>
|
||||
|
||||
<Table
|
||||
headers={headers}
|
||||
items={orders}
|
||||
selectable={true}
|
||||
onClick={handleTableClick}
|
||||
onDblClick={handleTableDblClick}/>
|
||||
</>
|
||||
)
|
||||
}
|
147
front/src/general/Catalog.jsx
Normal file
147
front/src/general/Catalog.jsx
Normal file
@ -0,0 +1,147 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import ToolBar from "./ToolBar";
|
||||
import Modal from "./Modal";
|
||||
import Table from "./Table";
|
||||
import DataService from "../DataService";
|
||||
|
||||
function Catalog(props) {
|
||||
|
||||
const [items, setItems] = useState([]);
|
||||
const [modalHeader, setModalHeader] = useState('')
|
||||
const [modalConfirm, setModalConfirm] = useState('')
|
||||
const [modalVisible, setModalVisible] = useState(false)
|
||||
const [addProdVisible, setAddProdVisible] = useState(false)
|
||||
const [isEdit, setEdit] = useState(false)
|
||||
const [isAddProd, setIsAddProd] = useState(false)
|
||||
|
||||
|
||||
let selectedItems = [];
|
||||
|
||||
useEffect(() => {
|
||||
loadItems()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const loadItems = () => {
|
||||
DataService.readAll(props.url, props.transformer)
|
||||
.then(data => setItems(data))
|
||||
}
|
||||
|
||||
const saveItem = () => {
|
||||
if (!isEdit) {
|
||||
DataService.create(props.url, props.data).then(() => loadItems())
|
||||
} else
|
||||
DataService.update(props.url + props.data.id, props.data).then(() => loadItems())
|
||||
}
|
||||
|
||||
const add = () => {
|
||||
setEdit(false);
|
||||
setModalHeader('Добавление');
|
||||
setModalConfirm('Добавить');
|
||||
setModalVisible(true);
|
||||
props.add();
|
||||
}
|
||||
|
||||
const edit = () => {
|
||||
if (selectedItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
//editItem(selectedItems[0])
|
||||
|
||||
DataService.read(props.url + selectedItems[0], props.transformer)
|
||||
.then(data => {
|
||||
setEdit(true);
|
||||
setModalHeader('Редактирование элемента');
|
||||
setModalConfirm('Сохранить');
|
||||
setModalVisible(true);
|
||||
props.edit(data);
|
||||
});
|
||||
}
|
||||
|
||||
const editItem = (editedId) => {
|
||||
DataService.read(props.url + editedId, props.transformer)
|
||||
.then(data => {
|
||||
setEdit(true);
|
||||
setModalHeader('Редактирование элемента');
|
||||
setModalConfirm('Сохранить');
|
||||
setModalVisible(true);
|
||||
props.edit(data);
|
||||
});
|
||||
}
|
||||
|
||||
const remove = () => {
|
||||
if (selectedItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.confirm('Удалить выбранные элементы?')) {
|
||||
const promises = [];
|
||||
selectedItems.forEach(item => {
|
||||
promises.push(DataService.delete(props.url + item));
|
||||
});
|
||||
Promise.all(promises).then((results) => {
|
||||
selectedItems.length = 0;
|
||||
loadItems();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const addProduct = () => {
|
||||
if (selectedItems.length === 0)
|
||||
return
|
||||
|
||||
DataService.read(props.url + selectedItems[0], props.transformer)
|
||||
.then(data => {
|
||||
setEdit(false)
|
||||
setAddProdVisible(true)
|
||||
setModalHeader('Добавление продукта к заказу')
|
||||
setModalConfirm('Сохранить')
|
||||
setAddProdVisible(true)
|
||||
props.edit(data)
|
||||
});
|
||||
}
|
||||
|
||||
const handleTableClick = (tableSelectedItems) => selectedItems = tableSelectedItems;
|
||||
const handleTableDblClick = (tableSelectedItem) => editItem(tableSelectedItem);
|
||||
const hideModal = () => {
|
||||
setModalVisible(false)
|
||||
setAddProdVisible(false)
|
||||
}
|
||||
const modalDone = () => saveItem()
|
||||
|
||||
return (
|
||||
<>
|
||||
<ToolBar
|
||||
add={add}
|
||||
edit={edit}
|
||||
remove={remove}
|
||||
addProduct={addProduct}
|
||||
// removeProduct
|
||||
addsVisible={props.isOrder}/>
|
||||
<Table
|
||||
headers={props.headers}
|
||||
items={items}
|
||||
selectable={true}
|
||||
onClick={handleTableClick}
|
||||
onDblClick={handleTableDblClick}/>
|
||||
<Modal
|
||||
header={modalHeader}
|
||||
confirm={modalConfirm}
|
||||
visible={modalVisible}
|
||||
onHide={hideModal}
|
||||
onDone={modalDone}>
|
||||
{props.children}
|
||||
</Modal>
|
||||
<Modal
|
||||
header={modalHeader}
|
||||
confirm={modalConfirm}
|
||||
visible={addProdVisible}
|
||||
onHide={hideModal}
|
||||
onDone={modalDone}>
|
||||
{props.modalAddProduct}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Catalog;
|
35
front/src/general/Header.jsx
Normal file
35
front/src/general/Header.jsx
Normal file
@ -0,0 +1,35 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export default function Header() {
|
||||
return (
|
||||
<nav className="navbar navbar-expand-lg bg-light">
|
||||
<div className="container-fluid">
|
||||
<h1 className="navbar-brand">Поставки</h1>
|
||||
<button className="navbar-toggler" type="button"
|
||||
data-bs-toggle="collapse" data-bs-target="#navbarNav"
|
||||
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span className="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div className="collapse navbar-collapse" id="navbarNav">
|
||||
<ul className="navbar-nav">
|
||||
<li className="nav-item">
|
||||
<Link className="nav-link" to="/products">
|
||||
Продукты
|
||||
</Link>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<Link className="nav-link" to="/suppliers">
|
||||
Поставщики
|
||||
</Link>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<Link className="nav-link" to="/orders">
|
||||
Заказы
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav >
|
||||
);
|
||||
}
|
47
front/src/general/Modal.jsx
Normal file
47
front/src/general/Modal.jsx
Normal file
@ -0,0 +1,47 @@
|
||||
import React from "react";
|
||||
|
||||
function Modal(props) {
|
||||
const formRef = React.createRef();
|
||||
|
||||
const hide = () => {
|
||||
props.onHide();
|
||||
}
|
||||
|
||||
const done = (e) => {
|
||||
e.preventDefault();
|
||||
if (formRef.current.checkValidity()) {
|
||||
props.onDone();
|
||||
hide();
|
||||
} else {
|
||||
formRef.current.reportValidity();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="modal fade show" tabIndex="-1" aria-hidden="true"
|
||||
style={{ display: props.visible ? 'block' : 'none' }}>
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h1 className="modal-title fs-5" id="exampleModalLabel">{props.header}</h1>
|
||||
<button className="btn-close" type="button" aria-label="Close"
|
||||
onClick={hide}></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<form ref={formRef} onSubmit={done}>
|
||||
{props.children}
|
||||
</form>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button className="btn btn-secondary" type="button" onClick={hide}>Закрыть</button>
|
||||
<button className="btn btn-primary" type="button" onClick={done}>
|
||||
{props.confirm}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Modal;
|
83
front/src/general/Table.jsx
Normal file
83
front/src/general/Table.jsx
Normal file
@ -0,0 +1,83 @@
|
||||
import { useState } from 'react';
|
||||
import styles from './Table.module.css';
|
||||
|
||||
export default function Table(props) {
|
||||
const [tableUpdate, setTableUpdate] = useState(false);
|
||||
const [selectedItems, setSelectedItems] = useState([]);
|
||||
|
||||
const isSelected = (id) => {
|
||||
if (!props.selectable) {
|
||||
return false;
|
||||
}
|
||||
return selectedItems.includes(id);
|
||||
}
|
||||
|
||||
const click = (id) => {
|
||||
if (!props.selectable) {
|
||||
return;
|
||||
}
|
||||
if (isSelected(id)) {
|
||||
var index = selectedItems.indexOf(id);
|
||||
if (index !== -1) {
|
||||
selectedItems.splice(index, 1);
|
||||
setSelectedItems(selectedItems);
|
||||
setTableUpdate(!tableUpdate);
|
||||
}
|
||||
} else {
|
||||
selectedItems.push(id);
|
||||
setSelectedItems(selectedItems);
|
||||
setTableUpdate(!tableUpdate);
|
||||
}
|
||||
props.onClick(selectedItems);
|
||||
}
|
||||
|
||||
const dblClick = (id) => {
|
||||
if (!props.selectable) {
|
||||
return;
|
||||
}
|
||||
props.onDblClick(id);
|
||||
}
|
||||
|
||||
const view = (data, headName) =>{
|
||||
if(headName !== 'products')
|
||||
return data
|
||||
else{
|
||||
let prod = ''
|
||||
data.map(product => prod += ` <${product.name}> `)
|
||||
return prod
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<table className={`table table-hover ${styles.table} ${props.selectable ? styles.selectable : ''}`}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
{
|
||||
props.headers.map(header =>
|
||||
<th key={header.name} scope="col">
|
||||
{header.label}
|
||||
</th>
|
||||
)
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
props.items && props.items.map((item, index) =>
|
||||
<tr key={item.id}
|
||||
className={isSelected(item.id) ? styles.selected : ''}
|
||||
onClick={(e) => click(item.id, e)} onDoubleClick={(e) => dblClick(item.id, e)}>
|
||||
<th scope="row">{index + 1}</th>
|
||||
{
|
||||
props.headers.map(header =>
|
||||
<td key={item.id + header.name}>{view(item[header.name], header.name)}</td>
|
||||
)
|
||||
}
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
</tbody >
|
||||
</table >
|
||||
);
|
||||
}
|
12
front/src/general/Table.module.css
Normal file
12
front/src/general/Table.module.css
Normal file
@ -0,0 +1,12 @@
|
||||
.table tbody tr {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.selectable tbody tr:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: #0d6efd;
|
||||
opacity: 80%;
|
||||
}
|
36
front/src/general/ToolBar.jsx
Normal file
36
front/src/general/ToolBar.jsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import React from "react";
|
||||
import styles from './Toolbar.module.css';
|
||||
|
||||
//кнопочки
|
||||
function ToolBar(props) {
|
||||
|
||||
const add = () => props.add()
|
||||
const edit = () => props.edit()
|
||||
const remove = () => props.remove()
|
||||
|
||||
return (
|
||||
<div className="btn-group mt-2" role="group">
|
||||
<button type="button" className={`btn btn-success ${styles.btn}`} onClick={add}
|
||||
style={{ display: props.addsVisible ? 'none' : 'block' }}>
|
||||
Добавить
|
||||
</button>
|
||||
<Link to="/createOrder">
|
||||
<button type="button" className={`btn btn-success ${styles.btn}`} onClick={add}
|
||||
style={{ display: props.addsVisible ? 'block' : 'none' }}>
|
||||
Добавить
|
||||
</button>
|
||||
</Link>
|
||||
<button type="button" className={`btn btn-warning ${styles.btn}`} onClick={edit}
|
||||
style={{ display: props.addsVisible ? 'none' : 'block' }}>
|
||||
Изменить
|
||||
</button >
|
||||
<button type="button" className={`btn btn-danger ${styles.btn}`} onClick={remove}>
|
||||
Удалить
|
||||
</button >
|
||||
</div >
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export default ToolBar;
|
4
front/src/general/Toolbar.module.css
Normal file
4
front/src/general/Toolbar.module.css
Normal file
@ -0,0 +1,4 @@
|
||||
.btn {
|
||||
min-width: 140px;
|
||||
margin: 5px;
|
||||
}
|
10
front/src/index.js
Normal file
10
front/src/index.js
Normal file
@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
11
front/src/models/Order.js
Normal file
11
front/src/models/Order.js
Normal file
@ -0,0 +1,11 @@
|
||||
import Product from "./Product";
|
||||
|
||||
export default class Order {
|
||||
constructor(data) {
|
||||
this.id = data?.id;
|
||||
this.date = data?.dateOfOrder || '';
|
||||
this.supplierId = data?.supplier.id || '';
|
||||
this.supplierName = data?.supplier.name || '';
|
||||
this.products = data?.products || [];
|
||||
}
|
||||
}
|
7
front/src/models/Product.js
Normal file
7
front/src/models/Product.js
Normal file
@ -0,0 +1,7 @@
|
||||
export default class Product {
|
||||
constructor(data) {
|
||||
this.id = data?.id;
|
||||
this.name = data?.name || '';
|
||||
this.cost = data?.cost || '';
|
||||
}
|
||||
}
|
7
front/src/models/Supplier.js
Normal file
7
front/src/models/Supplier.js
Normal file
@ -0,0 +1,7 @@
|
||||
export default class Supplier {
|
||||
constructor(data) {
|
||||
this.id = data?.id;
|
||||
this.name = data?.name || '';
|
||||
this.license = data?.license || '';
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user