вроде работаетЭ

This commit is contained in:
Inohara 2023-05-15 02:06:02 +04:00
parent 5bfc441e59
commit 8e42ac159d
59 changed files with 914 additions and 951 deletions

View File

@ -1,7 +1,7 @@
plugins {
id 'java'
id 'org.springframework.boot' version '2.6.3'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'org.springframework.boot' version '3.0.2'
id 'io.spring.dependency-management' version '1.1.0'
}
group = 'com.example'
@ -17,24 +17,15 @@ jar{
}
dependencies {
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-devtools'
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
implementation 'org.webjars:bootstrap:5.1.3'
implementation 'org.webjars:jquery:3.6.0'
implementation 'org.webjars:font-awesome:6.1.0'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation '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-ui:1.6.5'
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'
}

View File

@ -1,67 +0,0 @@
package com.example.demo.configuration;
import com.example.demo.supply.User.UserRole;
import com.example.demo.supply.User.UserService;
import com.example.demo.supply.User.UserSignupMvcController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class);
private static final String LOGIN_URL = "/login";
private final UserService userService;
public SecurityConfiguration(UserService userService) {
this.userService = userService;
createAdminOnStartup();
}
private void createAdminOnStartup() {
final String admin = "admin";
if (userService.findByLogin(admin) == null) {
log.info("Admin user successfully created");
userService.createUser(admin, admin, admin, UserRole.ADMIN);
}
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().sameOrigin().and()
.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers(UserSignupMvcController.SIGNUP_URL).permitAll()
.antMatchers(HttpMethod.GET, LOGIN_URL).permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage(LOGIN_URL).permitAll()
.and()
.logout().permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
@Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers("/css/**")
.antMatchers("/js/**")
.antMatchers("/templates/**")
.antMatchers("/webjars/**");
}
}

View File

@ -1,24 +0,0 @@
package com.example.demo.configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
public static final String REST_API = "/api";
@Override
public void addViewControllers(ViewControllerRegistry registry) {
WebMvcConfigurer.super.addViewControllers(registry);
registry.addViewController("rest-test");
registry.addViewController("login");
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("*");
}
}

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

@ -1,4 +1,4 @@
package com.example.demo.configuration;
package com.example.demo.supply.Configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

@ -3,15 +3,11 @@ 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 javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.util.Objects;
import javax.persistence.*;
import java.util.Date;
import java.util.ArrayList;
import java.util.List;

View File

@ -11,7 +11,7 @@ import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("/api/order")
@RequestMapping("/order")
public class OrderController {
private OrderService orderService;

View File

@ -1,79 +0,0 @@
package com.example.demo.supply.Order;
import com.example.demo.supply.Product.ProductDto;
import com.example.demo.supply.Product.ProductService;
import com.example.demo.supply.Supplier.SupplierDto;
import com.example.demo.supply.Supplier.SupplierService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@Controller
@RequestMapping("/order")
public class OrderMvcController {
private OrderService orderService;
private ProductService productService;
private SupplierService supplierService;
public OrderMvcController(OrderService orderService,
ProductService productService,
SupplierService supplierService){
this.orderService = orderService;
this.productService = productService;
this.supplierService = supplierService;
}
@GetMapping
public String getOrders(Model model) {
model.addAttribute("orders",
orderService.findAllOrders().stream()
.map(OrderDto::new)
.toList());
return "order";
}
@GetMapping("/dop")
public String getSuppliers(Model model,
@RequestParam(required = false) Long id) {
if(id == null || id <= 0) {
model.addAttribute("productDto", new ProductDto());
model.addAttribute("suppliers", null);
model.addAttribute("selectedProduct", null);
}
else{
ProductDto product = new ProductDto(productService.findProduct(id));
model.addAttribute("productDto", product);
model.addAttribute("selectedProduct", null);
model.addAttribute("suppliers", orderService.suppliers(id).stream().map(SupplierDto::new).toList());
}
model.addAttribute("products", productService.findAllProducts().stream().map(ProductDto::new).toList());
// model.addAttribute("orderDto", new OrderDto(orderService.findOrder(id)));
return "order-dop";
}
@GetMapping("/add")
public String addOrder(Model model) {
model.addAttribute("orderDto", new OrderDtoForCreate());
model.addAttribute("selectedSupplier", null);
model.addAttribute("suppliers", supplierService.findAllSuppliers().stream().map(SupplierDto::new).toList());
model.addAttribute("products", productService.findAllProducts().stream().map(ProductDto::new).toList());
return "order-add";
}
@PostMapping("/create")
public String saveOrder(Model model,
@ModelAttribute("orderDto") @Valid OrderDtoForCreate order,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
return "order-add";
}
Order newOrder = orderService.addOrder(supplierService.findSupplier(order.getSupplierId()).getId());
for(Long obj: order.getProductsId())
orderService.addProduct(newOrder.getId(), obj);
return "redirect:/order";
}
}

View File

@ -2,8 +2,8 @@ package com.example.demo.supply.Product;
import com.example.demo.supply.Order.Order;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import javax.persistence.*;
import java.util.Objects;
import java.util.ArrayList;

View File

@ -1,12 +1,13 @@
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
@CrossOrigin
@RequestMapping("/api/product")
@RequestMapping("/product")
public class ProductController {
private final ProductService productService;

View File

@ -1,60 +0,0 @@
package com.example.demo.supply.Product;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@Controller
@RequestMapping("/product")
public class ProductMvcController {
private final ProductService productService;
public ProductMvcController(ProductService productService){ this.productService = productService;}
@GetMapping
public String getProducts(Model model) {
model.addAttribute("products",
productService.findAllProducts().stream()
.map(ProductDto::new)
.toList());
return "product";
}
@GetMapping(value = {"/edit", "/edit/{id}"})
public String editProduct(@PathVariable(required = false) Long id,
Model model) {
if (id == null || id <= 0) {
model.addAttribute("productDto", new ProductDto());
} else {
model.addAttribute("productId", id);
model.addAttribute("productDto", new ProductDto(productService.findProduct(id)));
}
return "product-edit";
}
@PostMapping(value = {"", "/{id}"})
public String saveProduct(@PathVariable(required = false) Long id,
@ModelAttribute @Valid ProductDto productDto,
BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
return "product-edit";
}
if (id == null || id <= 0) {
productService.addProduct(productDto.getName(), productDto.getCost());
} else {
productService.updateProduct(id, productDto.getName(), productDto.getCost());
}
return "redirect:/product";
}
@PostMapping("/delete/{id}")
public String deleteProduct(@PathVariable Long id) {
productService.deleteProduct(id);
return "redirect:/product";
}
}

View File

@ -1,5 +1,6 @@
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;
@ -9,8 +10,11 @@ import java.util.Optional;
@Service
public class ProductService {
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository){
private final UserService userService;
public ProductService(ProductRepository productRepository, UserService userService){
this.productRepository = productRepository;
this.userService = userService;
}
@Transactional

View File

@ -2,8 +2,8 @@ package com.example.demo.supply.Supplier;
import com.example.demo.supply.Order.Order;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import javax.persistence.*;
import java.util.Objects;
import java.util.ArrayList;

View File

@ -6,7 +6,7 @@ import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("/api/supplier")
@RequestMapping("/supplier")
public class SupplierController {
private final SupplierService supplierService;

View File

@ -1,60 +0,0 @@
package com.example.demo.supply.Supplier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@Controller
@RequestMapping("/supplier")
public class SupplierMvcController {
private final SupplierService supplierService;
public SupplierMvcController(SupplierService supplierService){ this.supplierService = supplierService;}
@GetMapping
public String getSuppliers(Model model) {
model.addAttribute("suppliers",
supplierService.findAllSuppliers().stream()
.map(SupplierDto::new)
.toList());
return "supplier";
}
@GetMapping(value = {"/edit", "/edit/{id}"})
public String editSuppliers(@PathVariable(required = false) Long id,
Model model) {
if (id == null || id <= 0) {
model.addAttribute("supplierDto", new SupplierDto());
} else {
model.addAttribute("supplierId", id);
model.addAttribute("supplierDto", new SupplierDto(supplierService.findSupplier(id)));
}
return "supplier-edit";
}
@PostMapping(value = {"", "/{id}"})
public String saveSuppliers(@PathVariable(required = false) Long id,
@ModelAttribute @Valid SupplierDto supplierDto,
BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
return "supplier-edit";
}
if (id == null || id <= 0) {
supplierService.addSupplier(supplierDto.getName(), supplierDto.getLicense());
} else {
supplierService.updateSupplier(id, supplierDto.getName(), supplierDto.getLicense());
}
return "redirect:/supplier";
}
@PostMapping("/delete/{id}")
public String deleteSuppliers(@PathVariable Long id) {
supplierService.deleteSupplier(id);
return "redirect:/supplier";
}
}

View File

@ -1,40 +0,0 @@
package com.example.demo.supply.User;
import org.springframework.data.domain.Page;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
import java.util.stream.IntStream;
@Controller
@RequestMapping("/users")
public class UserMvcController {
private final UserService userService;
public UserMvcController(UserService userService) {
this.userService = userService;
}
@GetMapping
@Secured({UserRole.AsString.ADMIN})
public String getUsers(@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "5") int size,
Model model) {
final Page<UserDto> users = userService.findAllPages(page, size)
.map(UserDto::new);
model.addAttribute("users", users);
final int totalPages = users.getTotalPages();
final List<Integer> pageNumbers = IntStream.rangeClosed(1, totalPages)
.boxed()
.toList();
model.addAttribute("pages", pageNumbers);
model.addAttribute("totalPages", totalPages);
return "users";
}
}

View File

@ -1,48 +0,0 @@
package com.example.demo.supply.User;
import com.example.demo.supply.util.validation.ValidationException;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.validation.Valid;
@Controller
@RequestMapping(UserSignupMvcController.SIGNUP_URL)
public class UserSignupMvcController {
public static final String SIGNUP_URL = "/signup";
private final UserService userService;
public UserSignupMvcController(UserService userService) {
this.userService = userService;
}
@GetMapping
public String showSignupForm(Model model) {
model.addAttribute("userDto", new UserSignupDto());
return "signup";
}
@PostMapping
public String signup(@ModelAttribute("userDto") @Valid UserSignupDto userSignupDto,
BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
return "signup";
}
try {
final User user = userService.createUser(
userSignupDto.getLogin(), userSignupDto.getPassword(), userSignupDto.getPasswordConfirm());
return "redirect:/login?created=" + user.getLogin();
} catch (ValidationException e) {
model.addAttribute("errors", e.getMessage());
return "signup";
}
}
}

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

@ -1,4 +1,8 @@
package com.example.demo.supply.User;
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;

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

@ -1,7 +1,7 @@
package com.example.demo.supply.User;
package com.example.demo.supply.User.controller;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public class UserSignupDto {
@NotBlank

View File

@ -1,8 +1,9 @@
package com.example.demo.supply.User;
package com.example.demo.supply.User.model;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.Objects;
@Entity
@ -17,7 +18,7 @@ public class User {
private String login;
@Column(nullable = false, length = 64)
@NotBlank
@Size(min = 4, max = 64)
@Size(min = 6, max = 64)
private String password;
private UserRole role;
@ -63,11 +64,20 @@ public class User {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(id, user.id) && Objects.equals(login, user.login);
return Objects.equals(id, user.id);
}
@Override
public int hashCode() {
return Objects.hash(id, login);
return Objects.hash(id);
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", login='" + login + '\'' +
", password='" + password + '\'' +
'}';
}
}

View File

@ -1,4 +1,4 @@
package com.example.demo.supply.User;
package com.example.demo.supply.User.model;
import org.springframework.security.core.GrantedAuthority;

View File

@ -1,5 +1,6 @@
package com.example.demo.supply.User;
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> {

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

@ -1,5 +1,13 @@
package com.example.demo.supply.User;
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;
@ -11,7 +19,6 @@ 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;
@ -20,13 +27,16 @@ 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) {
ValidatorUtil validatorUtil,
JwtProvider jwtProvider) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.validatorUtil = validatorUtil;
this.jwtProvider = jwtProvider;
}
public Page<User> findAllPages(int page, int size) {
@ -43,7 +53,7 @@ public class UserService implements UserDetailsService {
public User createUser(String login, String password, String passwordConfirm, UserRole role) {
if (findByLogin(login) != null) {
throw new ValidationException(String.format("User '%s' already exists", login));
throw new UserExistsException(login);
}
final User user = new User(login, passwordEncoder.encode(password), role);
validatorUtil.validate(user);
@ -53,6 +63,26 @@ public class UserService implements UserDetailsService {
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);

View File

@ -1,11 +1,11 @@
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 javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.Set;
import java.util.stream.Collectors;

View File

@ -1,5 +1,6 @@
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
@ -9,3 +10,5 @@ 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

@ -1,10 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
</html>

View File

@ -1,56 +0,0 @@
<!DOCTYPE html>
<html lang="ru"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity6"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="UTF-8"/>
<title>Поставки</title>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="icon" href="/favicon.svg">
<script type="text/javascript" src="/webjars/bootstrap/5.1.3/js/bootstrap.bundle.min.js"></script>
<link rel="stylesheet" href="/webjars/bootstrap/5.1.3/css/bootstrap.min.css"/>
<link rel="stylesheet" href="/webjars/font-awesome/6.1.0/css/all.min.css"/>
<link rel="stylesheet" href="/css/style.css"/>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="/">
<i class="fa-solid fa-font-awesome"></i>
Поставки
</a>
<button class="navbar-toggler" type="button"
data-bs-toggle="collapse" data-bs-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav" th:with="activeLink=${#request.requestURI}" sec:authorize="isAuthenticated()">
<a class="nav-link" href="/"
th:classappend="${#strings.equals(activeLink, '/')} ? 'active' : ''">Главная</a>
<a class="nav-link" href="/product"
th:classappend="${#strings.equals(activeLink, '/product')} ? 'active' : ''">Продукты</a>
<a class="nav-link" href="/supplier"
th:classappend="${#strings.equals(activeLink, '/supplier')} ? 'active' : ''">Поставщики</a>
<a class="nav-link" href="/order"
th:classappend="${#strings.equals(activeLink, '/order')} ? 'active' : ''">Заказы</a>
<a class="nav-link" href="/order/dop"
th:classappend="${#strings.equals(activeLink, '/order')} ? 'active' : ''">Доп задание</a>
<a sec:authorize="hasRole('ROLE_ADMIN')" class="nav-link" href="/users"
th:classappend="${#strings.equals(activeLink, '/users')} ? 'active' : ''">Пользователи</a>
<a class="nav-link" href="/logout">
Выход (<span th:text="${#authentication.name}"></span>)
</a>
</ul>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="container container-padding" layout:fragment="content">
</div>
</div>
</body>
<th:block layout:fragment="scripts">
</th:block>
</html>

View File

@ -1,12 +0,0 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.thymeleaf.org"
layout:decorate="~{default}">
<head>
</head>
<body>
<div layout:fragment="content">
<div>It's works!</div>
</div>
</body>
</html>

View File

@ -1,34 +0,0 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:th="http://www.thymeleaf.org"
layout:decorate="~{default}">
>
<head>
</head>
<body>
<div layout:fragment="content">
<div th:if="${param.error}" class="alert alert-danger margin-bottom">
Пользователь не найден или пароль указан не верно
</div>
<div th:if="${param.logout}" class="alert alert-success margin-bottom">
Выход успешно произведен
</div>
<div th:if="${param.created}" class="alert alert-success margin-bottom">
Пользователь '<span th:text="${param.created}"></span>' успешно создан
</div>
<form th:action="@{/login}" method="post" class="w-50 ms-2">
<h2 class="py-3">Вход</h2>
<h4>Логин</h4>
<input class="form-control my-2" name="username" id="username" type="text" placeholder="Логин" required="true" autofocus="true"/>
<h4>Пароль</h4>
<input class="form-control my-2" name="password" id="password" type="password" placeholder="Пароль" required="true" />
<div>
<button class="btn btn-primary m-2" type="submit">Войти</button>
<a href="/signup" style="margin-top: 1em; margin-left: 1em"
>Зарегистрируйтесь, если нет аккаунта, здесь</a>
</div>
</form>
</div>
</body>
</html>

View File

@ -1,37 +0,0 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.thymeleaf.org"
layout:decorate="~{default}">
<head>
</head>
<body>
<div layout:fragment="content">
<form action="#" th:action="@{/order/create}" th:object="${orderDto}" method="post">
<div class="mb-3">
<button type="submit" class="btn btn-primary btn-success">Создать</button>
<a class="btn btn-primary btn-danger" th:href="@{/order}">
Назад
</a>
</div>
<div class="mb-3">
<label for="supplier" class="form-label">Поставщик</label>
<select id="supplier" class="form-select" th:field="*{supplierId}" th:name="${selectedSupplier}">
<option th:each="value: ${suppliers}" th:selected="${selectedSupplier != null and selectedSupplier == value.id}" th:value="${value.id}" th:text=" ${value.name}"></option>
</select>
</div>
<p class="d-flex justify-content-between">
<label>Продукты:</label>
<div th:each="product : ${products}">
<input type="checkbox" name="genres" th:text="${product.name} + '(Цена: ' + ${product.cost} + ') '"
th:value="${product.id} "
th:field="*{productsId}"
/>
</div>
</p>
</form>
</div>
</body>
</html>

View File

@ -1,61 +0,0 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.thymeleaf.org"
layout:decorate="~{default}">
<head>
</head>
<body>
<div layout:fragment="content">
<form action="#" th:action="@{/order/dop}" th:object="${productDto}" method="get">
<div class="mb-3">
<label for="author" class="form-label">Продукт</label>
<select id="author" class="form-select" th:field="*{id}" th:name="${selectedProduct}">
<option th:each="value: ${products}" th:selected="${selectedProduct != null and selectedProduct == value.id}" th:value="${value.id}" th:text="${value.name}"></option>
</select>
</div>
<button type="submit" class="btn btn-success button-fixed">Сформировать</button>
</form>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">ID</th>
<th scope="col">Название</th>
<th scope="col">Лицензия</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr th:each="supplier, iterator: ${suppliers}">
<th scope="row" th:text="${iterator.index} + 1"/>
<td th:text="${supplier.id}"/>
<td th:text="${supplier.name}"/>
<td th:text="${supplier.license}"/>
<td style="width: 10%">
<div class="btn-group" role="group" aria-label="Basic example">
<a class="btn btn-warning button-fixed button-sm"
th:href="@{/supplier/edit/{id}(id=${supplier.id})}">
<i class="fa fa-pencil" aria-hidden="true"></i> Изменить
</a>
<button type="button" class="btn btn-danger button-fixed button-sm"
th:attr="onclick=|confirm('Удалить запись?') && document.getElementById('remove-${supplier.id}').click()|">
<i class="fa fa-trash" aria-hidden="true"></i> Удалить
</button>
</div>
<form th:action="@{/supplier/delete/{id}(id=${supplier.id})}" method="post">
<button th:id="'remove-' + ${supplier.id}" type="submit" style="display: none">
Удалить
</button>
</form>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>

View File

@ -1,41 +0,0 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.thymeleaf.org"
layout:decorate="~{default}">
<head>
</head>
<body>
<div layout:fragment="content">
<div>
<a class="btn btn-success button-fixed"
th:href="@{/order/add}">
<i class="fa-solid fa-plus"></i> Создать заказ
</a>
</div>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">ID</th>
<th scope="col">Дата создания</th>
<th scope="col">Поставщик</th>
<th scope="col">Продукты</th>
</tr>
</thead>
<tbody>
<tr th:each="order, iterator: ${orders}">
<th scope="row" th:text="${iterator.index} + 1"/>
<td th:text="${order.id}"/>
<td th:text="${order.dateOfOrder}" />
<td th:text="${order.supplier.name}" />
<td>
<li th:each="product : ${order.products}" th:text="${product.name}"></li>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>

View File

@ -1,30 +0,0 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.thymeleaf.org"
layout:decorate="~{default}">
<head>
</head>
<body>
<div layout:fragment="content">
<form action="#" th:action="@{/product/{id}(id=${id})}" th:object="${productDto}" method="post">
<div class="mb-3">
<label for="name" class="form-label">Название</label>
<input type="text" class="form-control" id="name" th:field="${productDto.name}" required="true">
</div>
<div class="mb-3">
<label for="cost" class="form-label">Цена</label>
<input type="text" class="form-control" id="cost" th:field="${productDto.cost}" required="true">
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary button-fixed">
<span th:if="${id == null}">Добавить</span>
<span th:if="${id != null}">Обновить</span>
</button>
<a class="btn btn-secondary button-fixed" th:href="@{/product}">
Назад
</a>
</div>
</form>
</div>
</body>
</html>

View File

@ -1,55 +0,0 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.thymeleaf.org"
layout:decorate="~{default}">
<head>
</head>
<body>
<div layout:fragment="content">
<div>
<a class="btn btn-success button-fixed"
th:href="@{/product/edit/}">
<i class="fa-solid fa-plus"></i> Добавить
</a>
</div>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">ID</th>
<th scope="col">Название</th>
<th scope="col">Цена</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr th:each="product, iterator: ${products}">
<th scope="row" th:text="${iterator.index} + 1"/>
<td th:text="${product.id}"/>
<td th:text="${product.name}" />
<td th:text="${product.cost}" />
<td style="width: 10%">
<div class="btn-group" role="group" aria-label="Basic example">
<a class="btn btn-warning button-fixed button-sm"
th:href="@{/product/edit/{id}(id=${product.id})}">
<i class="fa fa-pencil" aria-hidden="true"></i> Изменить
</a>
<button type="button" class="btn btn-danger button-fixed button-sm"
th:attr="onclick=|confirm('Удалить запись?') && document.getElementById('remove-${product.id}').click()|">
<i class="fa fa-trash" aria-hidden="true"></i> Удалить
</button>
</div>
<form th:action="@{/product/delete/{id}(id=${product.id})}" method="post">
<button th:id="'remove-' + ${product.id}" type="submit" style="display: none">
Удалить
</button>
</form>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>

View File

@ -1,29 +0,0 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:th="http://www.thymeleaf.org"
layout:decorate="~{default}">
<body>
<div class="container container-padding" layout:fragment="content">
<div th:if="${errors}" th:text="${errors}" class="margin-bottom alert alert-danger"></div>
<form action="#" th:action="@{/signup}" th:object="${userDto}" method="post">
<div class="mb-3">
<input type="text" class="form-control" th:field="${userDto.login}"
placeholder="Логин" required="true" autofocus="true" maxlength="64"/>
</div>
<div class="mb-3">
<input type="password" class="form-control" th:field="${userDto.password}"
placeholder="Пароль" required="true" minlength="6" maxlength="64"/>
</div>
<div class="mb-3">
<input type="password" class="form-control" th:field="${userDto.passwordConfirm}"
placeholder="Пароль (подтверждение)" required="true" minlength="6" maxlength="64"/>
</div>
<div class="mb-3">
<button type="submit" class="btn btn-success button-fixed">Создать</button>
<a class="btn btn-primary button-fixed" href="/login">Назад</a>
</div>
</form>
</div>
</body>
</html>

View File

@ -1,30 +0,0 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.thymeleaf.org"
layout:decorate="~{default}">
<head>
</head>
<body>
<div layout:fragment="content">
<form action="#" th:action="@{/supplier/{id}(id=${id})}" th:object="${supplierDto}" method="post">
<div class="mb-3">
<label for="name" class="form-label">Название</label>
<input type="text" class="form-control" id="name" th:field="${supplierDto.name}" required="true">
</div>
<div class="mb-3">
<label for="license" class="form-label">Лицензия</label>
<input type="text" class="form-control" id="license" th:field="${supplierDto.license}" required="true">
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary button-fixed">
<span th:if="${id == null}">Добавить</span>
<span th:if="${id != null}">Обновить</span>
</button>
<a class="btn btn-secondary button-fixed" th:href="@{/supplier}">
Назад
</a>
</div>
</form>
</div>
</body>
</html>

View File

@ -1,55 +0,0 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.thymeleaf.org"
layout:decorate="~{default}">
<head>
</head>
<body>
<div layout:fragment="content">
<div>
<a class="btn btn-success button-fixed"
th:href="@{/supplier/edit/}">
<i class="fa-solid fa-plus"></i> Добавить
</a>
</div>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">ID</th>
<th scope="col">Название</th>
<th scope="col">Лицензия</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr th:each="supplier, iterator: ${suppliers}">
<th scope="row" th:text="${iterator.index} + 1"/>
<td th:text="${supplier.id}"/>
<td th:text="${supplier.name}"/>
<td th:text="${supplier.license}"/>
<td style="width: 10%">
<div class="btn-group" role="group" aria-label="Basic example">
<a class="btn btn-warning button-fixed button-sm"
th:href="@{/supplier/edit/{id}(id=${supplier.id})}">
<i class="fa fa-pencil" aria-hidden="true"></i> Изменить
</a>
<button type="button" class="btn btn-danger button-fixed button-sm"
th:attr="onclick=|confirm('Удалить запись?') && document.getElementById('remove-${supplier.id}').click()|">
<i class="fa fa-trash" aria-hidden="true"></i> Удалить
</button>
</div>
<form th:action="@{/supplier/delete/{id}(id=${supplier.id})}" method="post">
<button th:id="'remove-' + ${supplier.id}" type="submit" style="display: none">
Удалить
</button>
</form>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>

View File

@ -1,38 +0,0 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:th="http://www.thymeleaf.org"
layout:decorate="~{default}">
<body>
<div class="container" layout:fragment="content">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">ID</th>
<th scope="col">Логин</th>
<th scope="col">Роль</th>
</tr>
</thead>
<tbody>
<tr th:each="user, iterator: ${users}">
<th scope="row" th:text="${iterator.index} + 1"></th>
<td th:text="${user.id}"></td>
<td th:text="${user.login}" style="width: 60%"></td>
<td th:text="${user.role}" style="width: 20%"></td>
</tr>
</tbody>
</table>
</div>
<div th:if="${totalPages > 0}" class="pagination">
<span style="float: left; padding: 5px 5px;">Страницы:</span>
<a th:each="page : ${pages}"
th:href="@{/users(page=${page}, size=${users.size})}"
th:text="${page}"
th:class="${page == users.number + 1} ? active">
</a>
</div>
</div>
</body>
</html>

View File

@ -5,11 +5,11 @@
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.3.6",
"bootstrap": "^5.2.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.10.0",
"react-router-dom": "^6.4.4",
"axios": "^1.1.3",
"bootstrap": "^5.2.2",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},

View File

@ -4,20 +4,32 @@ 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';
function App() {
return (
<BrowserRouter>
<Header/>
<Routes>
<Route path="/" Component={CatalogProducts}/>
<Route path="/products" Component={CatalogProducts} />
<Route path="/suppliers" Component={CatalogSuppliers} />
<Route path="/orders" Component={OrderPage} />
<Route path="/createOrder" Component={CreateOrderPage} />
</Routes>
<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>
<Route element={<PrivateRoutes role={"ADMIN"}/>}>
{/* <Route element={<UsersPage/>} path="/Users"/> */}
</Route>
<Route path="/Login" Component={LoginPage}/>
<Route path="/Signup" Component={SignUpPage}/>
</Routes>
</div>
</BrowserRouter>
);
}

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;

View File

@ -38,6 +38,20 @@ export default class DataService {
return transformer(response.data);
}
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 create(url, data) {
const response = await axios.post(getFullUrl(this.mainUrl + url, data))
const res = response.data
@ -51,28 +65,6 @@ export default class DataService {
return true;
}
static async getSomeSuppliers(){
const arr =[
{
"id":104,
"name":"prod3",
"cost":3333
},
{
"id":153,
"name":"prod3",
"cost":5555
}
]
//const resArr = JSON.stringify(arr)
//console.log(resArr)
const response = await axios.post(this.mainUrl + `order/someSuppliers/`, {
body: {arr}
});
console.log(response)
}
static async update(url, data) {
await fetch(getFullUrl(this.mainUrl + url, data), {
method: 'PATCH',

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

@ -1,6 +1,18 @@
import { Link } from 'react-router-dom';
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">
@ -27,9 +39,23 @@ export default function Header() {
Заказы
</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,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