Compare commits

...

28 Commits

Author SHA1 Message Date
Inohara
d1607d08d5 fix 2023-05-15 14:59:20 +04:00
Inohara
8e42ac159d вроде работаетЭ 2023-05-15 02:06:02 +04:00
Ino
5bfc441e59 fix 2023-05-10 16:36:34 +04:00
Ino
a1b1f97ee6 что то работает 2023-05-10 16:14:38 +04:00
Ino
12ac235025 работает, невероятное 2023-05-10 14:49:58 +04:00
Inohara
cf46765fb9 устала -_- 2023-05-02 14:47:10 +04:00
Inohara
44d9f2b935 // 2023-05-02 10:47:18 +04:00
Inohara
0a3a5aedbb как всегда ничего не работает (」°ロ°)」 2023-05-01 21:59:28 +04:00
Ino
c6889698bf de 2023-04-28 16:51:14 +04:00
Ino
4f93068cce пыталась сделать доп из 3 лабы...... 2023-04-28 15:15:53 +04:00
Ino
a9112f5e06 оказывается дата уже была в миллисекундах (╯°□°)╯ 2023-04-28 13:06:01 +04:00
Ino
2b40c29f36 исключила добавление повторяющихся 2023-04-28 11:16:11 +04:00
Inohara
5364651da2 Неужели спустя 500 тысяч часов кодинга это работает невероятное 2023-04-27 22:52:14 +04:00
Ino
816d2d1284 |*_*| 2023-04-27 16:58:16 +04:00
Ino
ac0427a16e ~_~ 2023-04-26 12:50:39 +04:00
Ino
09dd2f7ae2 #_# 2023-04-25 16:56:17 +04:00
Inohara
2babc111b9 &_& 2023-04-25 11:23:28 +04:00
Ino
07bd46c0ac -\" 2023-04-19 16:59:53 +04:00
Ino
7a1916bff3 -\(оо)/- 2023-04-19 16:57:15 +04:00
Inohara
6c9581c820 -_- 2023-04-18 21:06:26 +04:00
Ino
b91f23a39e реакт +_+ помогити 2023-04-12 16:13:25 +04:00
Ino
11173e07c4 что то 2023-04-04 13:18:53 +04:00
Inohara
89fcb3cbca 1 2023-04-03 13:43:27 +04:00
Inohara
9876a17856 начало 4 лабы 2023-04-03 13:35:16 +04:00
Inohara
128fd2ff2b рабочий доп 2023-04-02 12:11:48 +04:00
Inohara
1e958a8b94 Доработки 2023-03-26 10:40:42 +04:00
Inohara
ca7afdf94e Вроде работает 2023-03-21 00:01:18 +04:00
Ino
ba48211be0 начало 3 лабы 2023-03-16 16:36:43 +04:00
87 changed files with 21269 additions and 0 deletions

39
demo/.gitignore vendored Normal file
View 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
View 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

Binary file not shown.

View 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
View 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
View 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
View File

@ -0,0 +1 @@
rootProject.name = 'demo'

View 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);
}
}

View File

@ -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")));
}
}

View File

@ -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();
}
}

View File

@ -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/**");
}
}

View File

@ -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/");
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}
}

View 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 + '\'' +
'}';
}
}

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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 + '\'' +
'}';
}
}

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -0,0 +1,6 @@
package com.example.demo.supply.Product;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProductRepository extends JpaRepository<Product, Long> {
}

View File

@ -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();
}
}

View File

@ -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 + '\'' +
'}';
}
}

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -0,0 +1,6 @@
package com.example.demo.supply.Supplier;
import org.springframework.data.jpa.repository.JpaRepository;
public interface SupplierRepository extends JpaRepository<Supplier, Long> {
}

View File

@ -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();
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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 + '\'' +
'}';
}
}

View File

@ -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";
}
}

View File

@ -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);
}

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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()));
}
}

View File

@ -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";
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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()));
}
}
}

View 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

View File

@ -0,0 +1,15 @@
.container-padding {
padding: 10px;
}
.margin-bottom {
margin-bottom: 10px;
}
.button-fixed {
min-width: 120px;
}
.button-sm {
padding: 1px;
}

View 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

View File

@ -0,0 +1,7 @@
package com.example.demo;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class Tests {
}

View 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);
// }
}

View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

40
front/package.json Normal file
View 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
View 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
View 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
View 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
View 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;
}
}

View 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>
</>
);
}

View 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>
);
}

View 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>
);
}

View 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>
)
}

View 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>
</>
);
}

View 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>
);
}

View 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}/>
</>
)
}

View 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>
);
}

View 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>
</>
);
}

View 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;

View 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 >
);
}

View 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;

View 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 >
);
}

View File

@ -0,0 +1,12 @@
.table tbody tr {
user-select: none;
}
.selectable tbody tr:hover {
cursor: pointer;
}
.selected {
background-color: #0d6efd;
opacity: 80%;
}

View 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;

View File

@ -0,0 +1,4 @@
.btn {
min-width: 140px;
margin: 5px;
}

10
front/src/index.js Normal file
View 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
View 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 || [];
}
}

View File

@ -0,0 +1,7 @@
export default class Product {
constructor(data) {
this.id = data?.id;
this.name = data?.name || '';
this.cost = data?.cost || '';
}
}

View File

@ -0,0 +1,7 @@
export default class Supplier {
constructor(data) {
this.id = data?.id;
this.name = data?.name || '';
this.license = data?.license || '';
}
}

View File

@ -0,0 +1,6 @@
export default class UserLogin {
constructor(data) {
this.login = data?.login;
this.password = data?.password;
}
}

View File

@ -0,0 +1,7 @@
export default class UserSignUp {
constructor(data) {
this.login = data?.login;
this.password = data?.password;
this.passwordConfirm = data?.passwordConfirm;
}
}

View 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