Compare commits
28 Commits
main
...
LabWork06_
Author | SHA1 | Date | |
---|---|---|---|
|
d1607d08d5 | ||
|
8e42ac159d | ||
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
|
34
demo/build.gradle
Normal file
34
demo/build.gradle
Normal file
@ -0,0 +1,34 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '3.0.2'
|
||||
id 'io.spring.dependency-management' version '1.1.0'
|
||||
}
|
||||
|
||||
group = 'com.example'
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
sourceCompatibility = '17'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
jar{
|
||||
enabled = false
|
||||
}
|
||||
|
||||
dependencies {
|
||||
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
implementation 'com.h2database:h2:2.1.210'
|
||||
implementation 'org.hibernate.validator:hibernate-validator'
|
||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
implementation 'com.auth0:java-jwt:4.4.0'
|
||||
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,28 @@
|
||||
package com.example.demo.supply.Configuration;
|
||||
|
||||
import com.example.demo.supply.Configuration.jwt.JwtFilter;
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class OpenAPI30Configuration {
|
||||
public static final String API_PREFIX = "/api/1.0";
|
||||
|
||||
@Bean
|
||||
public OpenAPI customizeOpenAPI() {
|
||||
final String securitySchemeName = JwtFilter.TOKEN_BEGIN_STR;
|
||||
return new OpenAPI()
|
||||
.addSecurityItem(new SecurityRequirement()
|
||||
.addList(securitySchemeName))
|
||||
.components(new Components()
|
||||
.addSecuritySchemes(securitySchemeName, new SecurityScheme()
|
||||
.name(securitySchemeName)
|
||||
.type(SecurityScheme.Type.HTTP)
|
||||
.scheme("bearer")
|
||||
.bearerFormat("JWT")));
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package com.example.demo.supply.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,96 @@
|
||||
package com.example.demo.supply.Configuration;
|
||||
|
||||
import com.example.demo.supply.Configuration.jwt.JwtFilter;
|
||||
import com.example.demo.supply.User.controller.UserController;
|
||||
import com.example.demo.supply.User.model.UserRole;
|
||||
import com.example.demo.supply.User.service.UserService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
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.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableGlobalMethodSecurity(securedEnabled = true)
|
||||
public class SecurityConfiguration {
|
||||
private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class);
|
||||
|
||||
public static final String SPA_URL_MASK = "/{path:[^\\.]*}/";
|
||||
|
||||
private final UserService userService;
|
||||
private final JwtFilter jwtFilter;
|
||||
|
||||
public SecurityConfiguration(UserService userService) {
|
||||
this.userService = userService;
|
||||
this.jwtFilter = new JwtFilter(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);
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
log.info("Creating security configuration");
|
||||
http.cors()
|
||||
.and()
|
||||
.csrf().disable()
|
||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
.and()
|
||||
.authorizeHttpRequests()
|
||||
.requestMatchers("/", SPA_URL_MASK).permitAll()
|
||||
.requestMatchers(HttpMethod.POST, UserController.URL_SIGNUP).permitAll()
|
||||
.requestMatchers(HttpMethod.POST, UserController.URL_LOGIN).permitAll()
|
||||
.requestMatchers(HttpMethod.GET, "/users/*").permitAll()
|
||||
.requestMatchers(HttpMethod.GET, "/h2-console").permitAll()
|
||||
.anyRequest()
|
||||
.authenticated()
|
||||
.and()
|
||||
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
|
||||
.anonymous();
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(HttpSecurity http, PasswordEncoderConfiguration bCryptPasswordEncoder)
|
||||
throws Exception {
|
||||
return http.getSharedObject(AuthenticationManagerBuilder.class)
|
||||
.userDetailsService(userService)
|
||||
.passwordEncoder(bCryptPasswordEncoder.createPasswordEncoder())
|
||||
.and()
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebSecurityCustomizer webSecurityCustomizer() {
|
||||
return (web) -> web.ignoring()
|
||||
.requestMatchers(HttpMethod.OPTIONS, "/**")
|
||||
.requestMatchers("/*.js")
|
||||
.requestMatchers("/*.png")
|
||||
.requestMatchers("/*.jpg")
|
||||
.requestMatchers("/*.html")
|
||||
.requestMatchers("/*.css")
|
||||
.requestMatchers("/assets/**")
|
||||
.requestMatchers("/favicon.ico")
|
||||
.requestMatchers("/.js", "/.css")
|
||||
.requestMatchers("/swagger-ui/index.html")
|
||||
.requestMatchers("/webjars/**")
|
||||
.requestMatchers("/swagger-resources/**")
|
||||
.requestMatchers("/v3/api-docs/**");
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package com.example.demo.supply.Configuration;
|
||||
|
||||
import org.springframework.boot.web.server.ErrorPage;
|
||||
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
||||
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.servlet.config.annotation.*;
|
||||
|
||||
@Configuration
|
||||
public class WebConfiguration implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry){
|
||||
registry.addMapping("/**").allowedMethods("*");
|
||||
}
|
||||
@Override
|
||||
public void addViewControllers(ViewControllerRegistry registry) {
|
||||
registry.addViewController(SecurityConfiguration.SPA_URL_MASK).setViewName("forward:/");
|
||||
registry.addViewController("/notFound").setViewName("forward:/");
|
||||
ViewControllerRegistration registration = registry.addViewController("/notFound");
|
||||
registration.setViewName("forward:/index.html");
|
||||
registration.setStatusCode(HttpStatus.OK);
|
||||
}
|
||||
//@Override
|
||||
//public void addViewControllers(ViewControllerRegistry registry) {
|
||||
// WebMvcConfigurer.super.addViewControllers(registry);
|
||||
// registry.addViewController("rest-test");
|
||||
//}
|
||||
@Bean
|
||||
public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> containerCustomizer() {
|
||||
return container -> {
|
||||
container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/notFound"));
|
||||
};
|
||||
}
|
||||
@Override
|
||||
public void addResourceHandlers(
|
||||
ResourceHandlerRegistry registry) {
|
||||
registry.addResourceHandler("/public/**")
|
||||
.addResourceLocations("/WEB-INF/view/react/build/public/");
|
||||
registry.addResourceHandler("/*.js")
|
||||
.addResourceLocations("/WEB-INF/view/react/build/");
|
||||
registry.addResourceHandler("/*.json")
|
||||
.addResourceLocations("/WEB-INF/view/react/build/");
|
||||
registry.addResourceHandler("/*.ico")
|
||||
.addResourceLocations("/WEB-INF/view/react/build/");
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.example.demo.supply.Configuration.jwt;
|
||||
|
||||
public class JwtException extends RuntimeException {
|
||||
public JwtException(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
|
||||
public JwtException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package com.example.demo.supply.Configuration.jwt;
|
||||
|
||||
import com.example.demo.supply.User.service.UserService;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.GenericFilterBean;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class JwtFilter extends GenericFilterBean {
|
||||
private static final String AUTHORIZATION = "Authorization";
|
||||
public static final String TOKEN_BEGIN_STR = "Bearer ";
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
public JwtFilter(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
private String getTokenFromRequest(HttpServletRequest request) {
|
||||
String bearer = request.getHeader(AUTHORIZATION);
|
||||
if (StringUtils.hasText(bearer) && bearer.startsWith(TOKEN_BEGIN_STR)) {
|
||||
return bearer.substring(TOKEN_BEGIN_STR.length());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void raiseException(ServletResponse response, int status, String message) throws IOException {
|
||||
if (response instanceof final HttpServletResponse httpResponse) {
|
||||
httpResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
httpResponse.setStatus(status);
|
||||
final byte[] body = new ObjectMapper().writeValueAsBytes(message);
|
||||
response.getOutputStream().write(body);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request,
|
||||
ServletResponse response,
|
||||
FilterChain chain) throws IOException, ServletException {
|
||||
if (request instanceof final HttpServletRequest httpRequest) {
|
||||
final String token = getTokenFromRequest(httpRequest);
|
||||
if (StringUtils.hasText(token)) {
|
||||
try {
|
||||
final UserDetails user = userService.loadUserByToken(token);
|
||||
final UsernamePasswordAuthenticationToken auth =
|
||||
new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
|
||||
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||
} catch (JwtException e) {
|
||||
raiseException(response, HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
raiseException(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
|
||||
String.format("Internal error: %s", e.getMessage()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package com.example.demo.supply.Configuration.jwt;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "jwt", ignoreInvalidFields = true)
|
||||
public class JwtProperties {
|
||||
private String devToken = "";
|
||||
private Boolean isDev = true;
|
||||
|
||||
public String getDevToken() {
|
||||
return devToken;
|
||||
}
|
||||
|
||||
public void setDevToken(String devToken) {
|
||||
this.devToken = devToken;
|
||||
}
|
||||
|
||||
public Boolean isDev() {
|
||||
return isDev;
|
||||
}
|
||||
|
||||
public void setDev(Boolean dev) {
|
||||
isDev = dev;
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
package com.example.demo.supply.Configuration.jwt;
|
||||
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
import com.auth0.jwt.exceptions.JWTVerificationException;
|
||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
import com.auth0.jwt.interfaces.JWTVerifier;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Component
|
||||
public class JwtProvider {
|
||||
private final static Logger LOG = LoggerFactory.getLogger(JwtProvider.class);
|
||||
|
||||
private final static byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
|
||||
private final static String ISSUER = "auth0";
|
||||
|
||||
private final Algorithm algorithm;
|
||||
private final JWTVerifier verifier;
|
||||
|
||||
public JwtProvider(JwtProperties jwtProperties) {
|
||||
if (!jwtProperties.isDev()) {
|
||||
LOG.info("Generate new JWT key for prod");
|
||||
try {
|
||||
final MessageDigest salt = MessageDigest.getInstance("SHA-256");
|
||||
salt.update(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8));
|
||||
LOG.info("Use generated JWT key for prod \n{}", bytesToHex(salt.digest()));
|
||||
algorithm = Algorithm.HMAC256(bytesToHex(salt.digest()));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new JwtException(e);
|
||||
}
|
||||
} else {
|
||||
LOG.info("Use default JWT key for dev \n{}", jwtProperties.getDevToken());
|
||||
algorithm = Algorithm.HMAC256(jwtProperties.getDevToken());
|
||||
}
|
||||
verifier = JWT.require(algorithm)
|
||||
.withIssuer(ISSUER)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static String bytesToHex(byte[] bytes) {
|
||||
byte[] hexChars = new byte[bytes.length * 2];
|
||||
for (int j = 0; j < bytes.length; j++) {
|
||||
int v = bytes[j] & 0xFF;
|
||||
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
|
||||
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
|
||||
}
|
||||
return new String(hexChars, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public String generateToken(String login) {
|
||||
final Date issueDate = Date.from(LocalDate.now()
|
||||
.atStartOfDay(ZoneId.systemDefault())
|
||||
.toInstant());
|
||||
final Date expireDate = Date.from(LocalDate.now()
|
||||
.plusDays(15)
|
||||
.atStartOfDay(ZoneId.systemDefault())
|
||||
.toInstant());
|
||||
return JWT.create()
|
||||
.withIssuer(ISSUER)
|
||||
.withIssuedAt(issueDate)
|
||||
.withExpiresAt(expireDate)
|
||||
.withSubject(login)
|
||||
.sign(algorithm);
|
||||
}
|
||||
|
||||
private DecodedJWT validateToken(String token) {
|
||||
try {
|
||||
return verifier.verify(token);
|
||||
} catch (JWTVerificationException e) {
|
||||
throw new JwtException(String.format("Token verification error: %s", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isTokenValid(String token) {
|
||||
if (!StringUtils.hasText(token)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
validateToken(token);
|
||||
return true;
|
||||
} catch (JwtException e) {
|
||||
LOG.error(e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<String> getLoginFromToken(String token) {
|
||||
try {
|
||||
return Optional.ofNullable(validateToken(token).getSubject());
|
||||
} catch (JwtException e) {
|
||||
LOG.error(e.getMessage());
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
106
demo/src/main/java/com/example/demo/supply/Order/Order.java
Normal file
106
demo/src/main/java/com/example/demo/supply/Order/Order.java
Normal file
@ -0,0 +1,106 @@
|
||||
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 jakarta.persistence.*;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
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("/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,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 jakarta.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,49 @@
|
||||
package com.example.demo.supply.Product;
|
||||
|
||||
import com.example.demo.supply.User.model.UserRole;
|
||||
import org.springframework.security.access.annotation.Secured;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/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("/")
|
||||
@Secured({UserRole.AsString.ADMIN})
|
||||
public ProductDto createProduct(@RequestParam() String name,
|
||||
@RequestParam() double cost) {
|
||||
return new ProductDto(productService.addProduct(name, cost));
|
||||
}
|
||||
|
||||
@PatchMapping("/{id}")
|
||||
@Secured({UserRole.AsString.ADMIN})
|
||||
public ProductDto updateProduct(@PathVariable Long id,
|
||||
@RequestParam String name,
|
||||
@RequestParam double cost) {
|
||||
return new ProductDto(productService.updateProduct(id, name, cost));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@Secured({UserRole.AsString.ADMIN})
|
||||
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,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,56 @@
|
||||
package com.example.demo.supply.Product;
|
||||
|
||||
import com.example.demo.supply.User.service.UserService;
|
||||
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;
|
||||
private final UserService userService;
|
||||
|
||||
public ProductService(ProductRepository productRepository, UserService userService){
|
||||
this.productRepository = productRepository;
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@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 jakarta.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("/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,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();
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package com.example.demo.supply.User.controller;
|
||||
|
||||
import com.example.demo.supply.Configuration.OpenAPI30Configuration;
|
||||
import com.example.demo.supply.User.model.User;
|
||||
import com.example.demo.supply.User.model.UserRole;
|
||||
import com.example.demo.supply.User.service.UserService;
|
||||
import com.example.demo.supply.util.validation.ValidationException;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.util.Pair;
|
||||
import org.springframework.security.access.annotation.Secured;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
|
||||
@RestController
|
||||
public class UserController {
|
||||
public static final String URL_LOGIN = "/jwt/login";
|
||||
public static final String URL_SIGNUP = "/signup";
|
||||
public static final String URL_MAIN = "/users";
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
public UserController(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
@GetMapping(OpenAPI30Configuration.API_PREFIX + URL_MAIN)
|
||||
@Secured({UserRole.AsString.ADMIN})
|
||||
public Pair<Page<UserDto>, List<Integer>> getUsers(@RequestParam(defaultValue = "1") int page,
|
||||
@RequestParam(defaultValue = "5") int size){
|
||||
final Page<UserDto> users = userService.findAllPages(page, size).map(UserDto::new);
|
||||
final int totalPages = users.getTotalPages();
|
||||
final List<Integer> pageNumbers = IntStream.rangeClosed(1, totalPages)
|
||||
.boxed()
|
||||
.toList();
|
||||
return Pair.of(users, pageNumbers);
|
||||
}
|
||||
@PostMapping(URL_LOGIN)
|
||||
public UserInfoDto login(@RequestBody @Valid UserLoginDto userLoginDto) {
|
||||
return userService.loginAndGetToken(userLoginDto);
|
||||
}
|
||||
@PostMapping(URL_SIGNUP)
|
||||
public String signup(@RequestBody @Valid UserSignupDto userSignupDto){
|
||||
try {
|
||||
User user = userService.createUser(userSignupDto.getLogin(), userSignupDto.getPassword(), userSignupDto.getPasswordConfirm());
|
||||
return user.getLogin() + "was created";
|
||||
}
|
||||
catch(ValidationException e){
|
||||
return e.getMessage();
|
||||
}
|
||||
}
|
||||
@GetMapping(URL_MAIN + "/{login}")
|
||||
public UserDetails getCurrentUser(@PathVariable String login){
|
||||
try{
|
||||
return userService.loadUserByUsername(login);
|
||||
}
|
||||
catch(Exception e){
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package com.example.demo.supply.User.controller;
|
||||
|
||||
|
||||
import com.example.demo.supply.User.model.User;
|
||||
import com.example.demo.supply.User.model.UserRole;
|
||||
|
||||
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,27 @@
|
||||
package com.example.demo.supply.User.controller;
|
||||
|
||||
|
||||
import com.example.demo.supply.User.model.UserRole;
|
||||
|
||||
public class UserInfoDto {
|
||||
private final String token;
|
||||
private final String login;
|
||||
private final UserRole role;
|
||||
public UserInfoDto(String token, String login, UserRole role) {
|
||||
this.token = token;
|
||||
this.login = login;
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public String getLogin() {
|
||||
return login;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public UserRole getRole() {
|
||||
return role;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.example.demo.supply.User.controller;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
public class UserLoginDto {
|
||||
@NotBlank
|
||||
private String login;
|
||||
@NotBlank
|
||||
private String password;
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package com.example.demo.supply.User.controller;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.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,83 @@
|
||||
package com.example.demo.supply.User.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.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 = 6, 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "User{" +
|
||||
"id=" + id +
|
||||
", login='" + login + '\'' +
|
||||
", password='" + password + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.example.demo.supply.User.model;
|
||||
|
||||
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,8 @@
|
||||
package com.example.demo.supply.User.repository;
|
||||
|
||||
import com.example.demo.supply.User.model.User;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
User findOneByLoginIgnoreCase(String login);
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.example.demo.supply.User.service;
|
||||
|
||||
public class UserExistsException extends RuntimeException {
|
||||
public UserExistsException(String login) {
|
||||
super(String.format("User '%s' already exists", login));
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.example.demo.supply.User.service;
|
||||
|
||||
public class UserNotFoundException extends RuntimeException {
|
||||
public UserNotFoundException(String login) {
|
||||
super(String.format("User not found '%s'", login));
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package com.example.demo.supply.User.service;
|
||||
|
||||
|
||||
import com.example.demo.supply.Configuration.jwt.JwtException;
|
||||
import com.example.demo.supply.Configuration.jwt.JwtProvider;
|
||||
import com.example.demo.supply.User.controller.UserInfoDto;
|
||||
import com.example.demo.supply.User.controller.UserLoginDto;
|
||||
import com.example.demo.supply.User.model.User;
|
||||
import com.example.demo.supply.User.model.UserRole;
|
||||
import com.example.demo.supply.User.repository.UserRepository;
|
||||
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;
|
||||
private final JwtProvider jwtProvider;
|
||||
|
||||
public UserService(UserRepository userRepository,
|
||||
PasswordEncoder passwordEncoder,
|
||||
ValidatorUtil validatorUtil,
|
||||
JwtProvider jwtProvider) {
|
||||
this.userRepository = userRepository;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.validatorUtil = validatorUtil;
|
||||
this.jwtProvider = jwtProvider;
|
||||
}
|
||||
|
||||
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 UserExistsException(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);
|
||||
}
|
||||
|
||||
public UserInfoDto loginAndGetToken(UserLoginDto userDto) {
|
||||
final User user = findByLogin(userDto.getLogin());
|
||||
if (user == null) {
|
||||
throw new UserNotFoundException(userDto.getLogin());
|
||||
}
|
||||
if (!passwordEncoder.matches(userDto.getPassword(), user.getPassword())) {
|
||||
throw new UserNotFoundException(user.getLogin());
|
||||
}
|
||||
return new UserInfoDto(jwtProvider.generateToken(user.getLogin()), user.getLogin(), user.getRole());
|
||||
}
|
||||
|
||||
public UserDetails loadUserByToken(String token) throws UsernameNotFoundException {
|
||||
if (!jwtProvider.isTokenValid(token)) {
|
||||
throw new JwtException("Bad token");
|
||||
}
|
||||
final String userLogin = jwtProvider.getLoginFromToken(token)
|
||||
.orElseThrow(() -> new JwtException("Token is not contain Login"));
|
||||
return loadUserByUsername(userLogin);
|
||||
}
|
||||
|
||||
@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,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 jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.Validation;
|
||||
import jakarta.validation.Validator;
|
||||
import jakarta.validation.ValidatorFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
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()));
|
||||
}
|
||||
}
|
||||
}
|
14
demo/src/main/resources/application.properties
Normal file
14
demo/src/main/resources/application.properties
Normal file
@ -0,0 +1,14 @@
|
||||
spring.main.banner-mode=off
|
||||
server.port=8080
|
||||
server.tomcat.relaxed-query-chars=|,{,},[,]
|
||||
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
|
||||
jwt.dev-token=my-secret-jwt
|
||||
jwt.dev=true
|
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 |
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",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.4.4",
|
||||
"axios": "^1.1.3",
|
||||
"bootstrap": "^5.2.2",
|
||||
"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>
|
39
front/src/App.js
Normal file
39
front/src/App.js
Normal file
@ -0,0 +1,39 @@
|
||||
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";
|
||||
import SignUpPage from "./Pages/SignUpPage";
|
||||
import LoginPage from "./Pages/LoginPage";
|
||||
import PrivateRoutes from './utils/PrivateRoutes';
|
||||
import UsersPage from "./Pages/UsersPage";
|
||||
import GetSomeSuppliers from "./Pages/GetSomeSuppliers";
|
||||
|
||||
function App() {
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Header/>
|
||||
<div>
|
||||
<Routes>
|
||||
<Route element={<PrivateRoutes role={"USER"}/>}>
|
||||
<Route path="/" Component={CatalogProducts}/>
|
||||
<Route path="/products" Component={CatalogProducts} />
|
||||
<Route path="/suppliers" Component={CatalogSuppliers} />
|
||||
<Route path="/orders" Component={OrderPage} />
|
||||
<Route path="/createOrder" Component={CreateOrderPage} />
|
||||
<Route path="/getSupl" Component={GetSomeSuppliers} />
|
||||
</Route>
|
||||
<Route element={<PrivateRoutes role={"ADMIN"}/>}>
|
||||
<Route path="/Users" Component={UsersPage}/>
|
||||
</Route>
|
||||
<Route path="/Login" Component={LoginPage}/>
|
||||
<Route path="/Signup" Component={SignUpPage}/>
|
||||
</Routes>
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
36
front/src/AuthService.js
Normal file
36
front/src/AuthService.js
Normal file
@ -0,0 +1,36 @@
|
||||
import axios from "axios";
|
||||
import UserSignUp from "./models/UserSignUp";
|
||||
import UserLogin from "./models/UserLogin";
|
||||
|
||||
const API_URL = "http://localhost:8080";
|
||||
|
||||
const register = (username, password, passwordConfirm) => {
|
||||
return axios.post(API_URL + "/signup", new UserSignUp({login: username,
|
||||
password: password,
|
||||
passwordConfirm: passwordConfirm}));
|
||||
};
|
||||
|
||||
const login = (username, password) => {
|
||||
return axios
|
||||
.post(API_URL + "/jwt/login", new UserLogin({login: username, password: password}))
|
||||
.then((response) => {
|
||||
if(response.status === 200){
|
||||
localStorage.setItem("token", response.data.token);
|
||||
localStorage.setItem("role", response.data.role);
|
||||
localStorage.setItem("login", response.data.login);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
localStorage.removeItem("token");
|
||||
localStorage.removeItem("role");
|
||||
localStorage.removeItem("login");
|
||||
};
|
||||
const AuthService = {
|
||||
register,
|
||||
login,
|
||||
logout,
|
||||
};
|
||||
|
||||
export default AuthService;
|
86
front/src/DataService.js
Normal file
86
front/src/DataService.js
Normal file
@ -0,0 +1,86 @@
|
||||
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 readUsersPage(dataUrlPrefix, url, page) {
|
||||
const response = await axios.get(dataUrlPrefix + url + `?page=${page}`,{
|
||||
headers:{
|
||||
"Authorization": "Bearer " + localStorage.getItem("token")
|
||||
}
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
static async readUser(dataUrlPrefix, url, login){
|
||||
const response = await axios.get(dataUrlPrefix + url + `/${login}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
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(url){
|
||||
const response = await fetch(this.mainUrl + url, {
|
||||
method: 'GET',
|
||||
}).catch(e => console.log(e))
|
||||
const res = response.json()
|
||||
return res
|
||||
}
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
97
front/src/Pages/GetSomeSuppliers.jsx
Normal file
97
front/src/Pages/GetSomeSuppliers.jsx
Normal file
@ -0,0 +1,97 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Modal from "../general/Modal";
|
||||
import Table from "../general/Table";
|
||||
import DataService from "../DataService";
|
||||
import Product from "../models/Product";
|
||||
import Supplier from "../models/Supplier";
|
||||
|
||||
export default function GetSomeSuppliers(props) {
|
||||
const url = 'order/someSuppliers/'
|
||||
const productUrl = 'product/'
|
||||
|
||||
const headers = [
|
||||
{ name: 'name', label: 'Поставщик' },
|
||||
{ name: 'license', label: 'Лицензия' }
|
||||
];
|
||||
|
||||
const transformer = (data) => new Supplier(data)
|
||||
const transformerProduct = (data) => new Product(data)
|
||||
|
||||
const [suppliers, setSuppliers] = useState([])
|
||||
const [products, setProducts] = useState([])
|
||||
const [product, setProduct] = 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(productUrl, transformerProduct).then(data =>{
|
||||
console.log(data)
|
||||
setProducts(data)
|
||||
})
|
||||
}
|
||||
|
||||
const chooseProduct = () => {
|
||||
setModalHeader('Добавление')
|
||||
setModalConfirm('Добавить')
|
||||
setModalVisible(true)
|
||||
}
|
||||
|
||||
const save = () => {
|
||||
console.log(product)
|
||||
DataService.getSomeSuppliers(`${url}${product.id}`)
|
||||
.then(data => setSuppliers(data))
|
||||
}
|
||||
|
||||
const handleChooseProduct = (event) => {
|
||||
// console.log(event.target.id)
|
||||
// console.log(event.target.value)
|
||||
setProduct({ ...product, [event.target.id]: event.target.value })
|
||||
}
|
||||
|
||||
const handleTableClick = (tableSelectedItems) => console.log("")
|
||||
const handleTableDblClick = (tableSelectedItem) => console.log("")
|
||||
|
||||
const hideModal = () => {
|
||||
setModalVisible(false)
|
||||
}
|
||||
const modalDone = () => save()
|
||||
|
||||
return (
|
||||
<>
|
||||
<button type="button" className="btn btn-success" onClick={chooseProduct}>Выбрать продукт</button>
|
||||
|
||||
<Table
|
||||
headers={headers}
|
||||
items={suppliers}
|
||||
selectable={false}
|
||||
onClick={handleTableClick}
|
||||
onDblClick={handleTableDblClick}/>
|
||||
<Modal
|
||||
header={modalHeader}
|
||||
confirm={modalConfirm}
|
||||
visible={modalVisible}
|
||||
onHide={hideModal}
|
||||
onDone={modalDone}>
|
||||
<div className="mb-3">
|
||||
<p className="h4" htmlFor="id">Продукт</p>
|
||||
<select id="id" className="form-select" required
|
||||
value={product.id} onChange={handleChooseProduct}>
|
||||
<option disabled value="">Укажите продукт</option>
|
||||
{
|
||||
products.map(product =>
|
||||
<option key={product.id} value={product.id}>{product.name}</option>
|
||||
)
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
72
front/src/Pages/LoginPage.jsx
Normal file
72
front/src/Pages/LoginPage.jsx
Normal file
@ -0,0 +1,72 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useState } from "react";
|
||||
import AuthService from "../AuthService"
|
||||
|
||||
export default function LoginPage(props) {
|
||||
const formRef = React.createRef();
|
||||
const navigate = useNavigate();
|
||||
const [message, setMessage] = useState("");
|
||||
const [state, setState] = useState({
|
||||
login: "",
|
||||
password: ""
|
||||
});
|
||||
const handleChange = (event) => {
|
||||
setState({ ...state, [event.target.name]: event.target.value});
|
||||
};
|
||||
const handleSubmit = (event) =>{
|
||||
event.preventDefault();
|
||||
setMessage("");
|
||||
AuthService.login(state.login, state.password).then(
|
||||
() => {
|
||||
navigate("/");
|
||||
window.location.reload();
|
||||
},
|
||||
(error) => {
|
||||
const resMessage =
|
||||
(error.response &&
|
||||
error.response.data &&
|
||||
error.response.data.message) ||
|
||||
error.message ||
|
||||
error.toString();
|
||||
|
||||
setMessage(resMessage);
|
||||
}
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="flex-shrink-0" style={{backgroundColor : 'rgb(255,255,255)'}}>
|
||||
<div className="d-flex justify-content-center">
|
||||
<form ref={formRef} className="w-50 ms-2" onSubmit={(e)=>handleSubmit(e)}>
|
||||
<h2 className="py-3">Вход</h2>
|
||||
<h4>Логин</h4>
|
||||
<input className="form-control my-2" name="login" value={state.login} onChange={(e)=>handleChange(e)} type="text" required />
|
||||
<h4>Пароль</h4>
|
||||
<input
|
||||
className="form-control my-2"
|
||||
type="password"
|
||||
name="password"
|
||||
value={state.password}
|
||||
onChange={(e)=>handleChange(e)}
|
||||
required
|
||||
/>
|
||||
<div>
|
||||
<button className="btn btn-primary m-2" type="submit">
|
||||
Войти
|
||||
</button>
|
||||
<a href="/Signup" style={{marginTop: 1, marginLeft: 1}}>
|
||||
Зарегистрируйтесь, если нет аккаунта, здесь
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
{message && (
|
||||
<div className="form-group">
|
||||
<div className="alert alert-danger" role="alert">
|
||||
{message}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</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}/>
|
||||
</>
|
||||
)
|
||||
}
|
82
front/src/Pages/SignUpPage.jsx
Normal file
82
front/src/Pages/SignUpPage.jsx
Normal file
@ -0,0 +1,82 @@
|
||||
import React, { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import AuthService from "../AuthService"
|
||||
|
||||
export default function SignUpPage(props) {
|
||||
const formRef = React.createRef();
|
||||
const navigate = useNavigate();
|
||||
const [message, setMessage] = useState("");
|
||||
const [state, setState] = useState({
|
||||
login: "",
|
||||
password: "",
|
||||
passwordConfirm: ""
|
||||
});
|
||||
const handleChange = (event) => {
|
||||
setState({ ...state, [event.target.name]: event.target.value});
|
||||
};
|
||||
const handleSubmit = (event) =>{
|
||||
event.preventDefault();
|
||||
setMessage("");
|
||||
AuthService.register(state.login, state.password, state.passwordConfirm).then(
|
||||
() => {
|
||||
navigate("/Login");
|
||||
window.location.reload();
|
||||
},
|
||||
(error) => {
|
||||
const resMessage =
|
||||
(error.response &&
|
||||
error.response.data &&
|
||||
error.response.data.message) ||
|
||||
error.message ||
|
||||
error.toString();
|
||||
|
||||
setMessage(resMessage);
|
||||
}
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="flex-shrink-0" style={{backgroundColor : 'rgb(255,255,255)'}}>
|
||||
<div className="d-flex justify-content-center">
|
||||
<form ref={formRef} className="w-50 ms-2" onSubmit={(e)=>handleSubmit(e)}>
|
||||
<h2 className="py-3">Вход</h2>
|
||||
<h4>Логин</h4>
|
||||
<input className="form-control my-2" name="login" value={state.login} onChange={(e)=>handleChange(e)} type="text" required />
|
||||
<h4>Пароль</h4>
|
||||
<input
|
||||
className="form-control my-2"
|
||||
type="password"
|
||||
name="password"
|
||||
value={state.password}
|
||||
onChange={(e)=>handleChange(e)}
|
||||
required
|
||||
/>
|
||||
<h4>Подтверждение пароля</h4>
|
||||
<input
|
||||
className="form-control my-2"
|
||||
type="password"
|
||||
name="passwordConfirm"
|
||||
value={state.passwordConfirm}
|
||||
onChange={(e)=>handleChange(e)}
|
||||
required
|
||||
/>
|
||||
<div className="d-flex flex-row">
|
||||
<button className="btn btn-primary m-2" type="submit">
|
||||
Создать аккаунт
|
||||
</button>
|
||||
<Link className="nav-link" to={"/Login"}>
|
||||
Назад
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
{message && (
|
||||
<div className="form-group">
|
||||
<div className="alert alert-danger" role="alert">
|
||||
{message}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
63
front/src/Pages/UsersPage.jsx
Normal file
63
front/src/Pages/UsersPage.jsx
Normal file
@ -0,0 +1,63 @@
|
||||
import DataService from "../DataService";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
export default function UsersPage() {
|
||||
const [users, setUsers] = useState([]);
|
||||
const [pageNumbers, setPageNumbers] = useState([]);
|
||||
const [pageNumber, setPageNumber] = useState();
|
||||
const usersUrl = "/users";
|
||||
const host = "http://localhost:8080/api/1.0";
|
||||
|
||||
useEffect(() => {
|
||||
DataService.readUsersPage(host,usersUrl, 1).then((data)=>{
|
||||
setUsers(data.first.content);
|
||||
setPageNumbers(data.second);
|
||||
setPageNumber(1);
|
||||
});
|
||||
}, []);
|
||||
const pageButtonOnClick = function (page) {
|
||||
DataService.readUsersPage(host,usersUrl, page).then((data)=>{
|
||||
setUsers(data.first.content);
|
||||
setPageNumber(page);
|
||||
});
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div className="table-shell mb-3">
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ width: "15%" }} scope="col">ID</th>
|
||||
<th style={{ width: "30%" }} scope="col">Логин</th>
|
||||
<th style={{ width: "15%" }} scope="col">Роль</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map((user, index) => (
|
||||
<tr>
|
||||
<td style={{ width: "15%" }}>{user.id}</td>
|
||||
<td style={{ width: "30%" }}>{user.login}</td>
|
||||
<td style={{ width: "15%" }}>{user.role}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
Pages:
|
||||
</p>
|
||||
<nav>
|
||||
<ul className="pagination">
|
||||
{pageNumbers.map((number) => (
|
||||
<li className={`page-item ${number === pageNumber + 1 ? "active" : ""}`}
|
||||
onClick={() => pageButtonOnClick(number)}>
|
||||
<a className="page-link" href="#">{number}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
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;
|
67
front/src/general/Header.jsx
Normal file
67
front/src/general/Header.jsx
Normal file
@ -0,0 +1,67 @@
|
||||
import { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import AuthService from "../AuthService";
|
||||
|
||||
export default function Header() {
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleLogout = () =>{
|
||||
AuthService.logout();
|
||||
navigate("/Login");
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
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>
|
||||
|
||||
<li className="nav-item">
|
||||
<Link className="nav-link" to="/getSupl">
|
||||
Доп
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
<li className="nav-item">
|
||||
<Link className="nav-link" to="/Login">
|
||||
Вход
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<span className="col text-end">
|
||||
{typeof localStorage.getItem("role") === 'string' &&
|
||||
<Link className="nav-link" onClick={handleLogout} to={""}>
|
||||
{"Выход(" + localStorage.getItem("login") + ")"}
|
||||
</Link>
|
||||
}
|
||||
</span>
|
||||
</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 || '';
|
||||
}
|
||||
}
|
6
front/src/models/UserLogin.js
Normal file
6
front/src/models/UserLogin.js
Normal file
@ -0,0 +1,6 @@
|
||||
export default class UserLogin {
|
||||
constructor(data) {
|
||||
this.login = data?.login;
|
||||
this.password = data?.password;
|
||||
}
|
||||
}
|
7
front/src/models/UserSignUp.js
Normal file
7
front/src/models/UserSignUp.js
Normal file
@ -0,0 +1,7 @@
|
||||
export default class UserSignUp {
|
||||
constructor(data) {
|
||||
this.login = data?.login;
|
||||
this.password = data?.password;
|
||||
this.passwordConfirm = data?.passwordConfirm;
|
||||
}
|
||||
}
|
36
front/src/utils/PrivateRoutes.jsx
Normal file
36
front/src/utils/PrivateRoutes.jsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Outlet, Navigate, useLocation } from 'react-router-dom'
|
||||
import DataService from '../DataService';
|
||||
|
||||
const PrivateRoutes = (props) => {
|
||||
let location = useLocation()
|
||||
useEffect(()=>{
|
||||
try{
|
||||
DataService.readUser("http://localhost:8080","/users",localStorage.getItem("login")).then((data)=>{
|
||||
if(data.authorities[0] !== localStorage.getItem("role")){
|
||||
throw new SyntaxError("Данные не совпадают")
|
||||
}
|
||||
}).catch((e) => {
|
||||
localStorage.clear();
|
||||
window.location.reload();
|
||||
console.log(e)
|
||||
})
|
||||
}
|
||||
catch(e){
|
||||
localStorage.clear();
|
||||
window.location.reload();
|
||||
console.log(e)
|
||||
}
|
||||
},[location])
|
||||
let getPermission = false;
|
||||
let userToken = localStorage.getItem("token");
|
||||
let userRole = localStorage.getItem("role");
|
||||
if(userToken && (props.role === userRole || userRole === "ADMIN")){
|
||||
getPermission = true;
|
||||
}
|
||||
return(
|
||||
getPermission ? <Outlet/> : <Navigate to="/Login"/>
|
||||
)
|
||||
}
|
||||
|
||||
export default PrivateRoutes
|
Loading…
Reference in New Issue
Block a user