initial commit

This commit is contained in:
Kayashov.SM
2026-01-29 00:19:41 +04:00
commit f34a7eced5
18 changed files with 669 additions and 0 deletions

116
pom.xml Normal file
View File

@@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>ru.kayashov</groupId>
<artifactId>karthall</artifactId>
<version>4.3.0</version>
<name>KartHall</name>
<packaging>jar</packaging>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.5.2.Final</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.3</version>
</dependency>
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots</artifactId>
<version>6.4.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jakarta-xmlbind-annotations</artifactId>
<version>2.18.2</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.kafka</groupId>-->
<!-- <artifactId>spring-kafka</artifactId>-->
<!-- <version>3.2.4</version>-->
<!-- </dependency>-->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,13 @@
package ru.kayashov.karthall;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class KartHallApplication {
public static void main(String[] args) {
SpringApplication.run(KartHallApplication.class, args);
}
}

View File

@@ -0,0 +1,23 @@
package ru.kayashov.karthall.model.entity;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import java.util.List;
@Entity
@Getter
@Setter
public class Championship {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "championship")
private List<Event> events;
}

View File

@@ -0,0 +1,31 @@
package ru.kayashov.karthall.model.entity;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
@Entity
@Getter
@Setter
public class Event {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private LocalDate date;
private LocalTime startTime;
private LocalTime endTime;
@ManyToOne
private Championship championship;
}

View File

@@ -0,0 +1,20 @@
package ru.kayashov.karthall.model.entity;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
@Getter
@Setter
public class Place {
@Id
@GeneratedValue
private Long id;
private String name;
private String address;
private String photo;
}

View File

@@ -0,0 +1,65 @@
package ru.kayashov.karthall.model.entity;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import java.time.LocalDateTime;
import java.util.Collection;
@Entity
@Getter
@Setter
public class Player implements UserDetails {
@Id
private Long id;
private String name;
private String lastName;
@Enumerated(EnumType.STRING)
private UserRole role;
private Integer age;
private String email;
private String phone;
private String image;
private String password;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return role.getAuthorities();
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return id.toString();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}

View File

@@ -0,0 +1,28 @@
package ru.kayashov.karthall.model.entity;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import java.util.Set;
import java.util.stream.Collectors;
@Getter
@RequiredArgsConstructor
public enum UserRole {
RACER(Set.of("racer")), //стандартный участник
MODERATOR(Set.of("racer", "event_admin")), //модератор события
ADMIN(Set.of("racer", "event_admin", "championship_admin")), //модератор чемпионата
OWNER(Set.of("racer", "event_admin", "championship_admin", "place_admin")), //модератор места проведения
SUPERADMIN(Set.of("racer", "event_admin", "championship_admin", "place_admin", "admin")); //супер админ
private final Set<String> authorities;
public Set<GrantedAuthority> getAuthorities() {
return authorities.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toSet());
}
}

View File

@@ -0,0 +1,7 @@
package ru.kayashov.karthall.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import ru.kayashov.karthall.model.entity.Championship;
public interface ChampionshipRepository extends JpaRepository<Championship, Long> {
}

View File

@@ -0,0 +1,7 @@
package ru.kayashov.karthall.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import ru.kayashov.karthall.model.entity.Event;
public interface EventRepository extends JpaRepository<Event, Long> {
}

View File

@@ -0,0 +1,7 @@
package ru.kayashov.karthall.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import ru.kayashov.karthall.model.entity.Place;
public interface PlaceRepository extends JpaRepository<Place, Long> {
}

View File

@@ -0,0 +1,10 @@
package ru.kayashov.karthall.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import ru.kayashov.karthall.model.entity.Player;
import java.util.Optional;
public interface PlayerRepository extends JpaRepository<Player, Long> {
Optional<Player> findByPhoneOrderByEmail(String login);
}

View File

@@ -0,0 +1,37 @@
package ru.kayashov.karthall.security;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import ru.kayashov.karthall.model.entity.UserRole;
import ru.kayashov.karthall.repository.PlayerRepository;
import java.util.Set;
@Component
@RequiredArgsConstructor
public class CustomUserDetailService implements UserDetailsService {
private final PlayerRepository repository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return repository.findById(Long.parseLong(username)).orElseThrow(() -> new RuntimeException("Пользователь с email " + username + " не найден!"));
}
@Transactional
public Set<GrantedAuthority> getAuthorities(long userId) {
// Visitor visitor = repository.findById(userId).orElseThrow(RuntimeException::new);
return UserRole.ADMIN.getAuthorities();
// return visitor.getResidents().stream()
// .filter(BarResident::getActive)
// .map(BarResident::getRole)
// .map(UserRole::getAuthorities)
// .flatMap(Collection::stream)
// .collect(Collectors.toSet());
}
}

View File

@@ -0,0 +1,62 @@
package ru.kayashov.karthall.security;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Component
@RequiredArgsConstructor
public class JwtTokenFilter extends GenericFilterBean {
public static final String BEARER_PREFIX = "Bearer ";
public static final String HEADER_NAME = "Authorization";
private final JwtTokenProvider jwtTokenProvider;
private final CustomUserDetailService detailService;
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain filterChain) throws IOException, ServletException {
var authHeader = ((HttpServletRequest) request).getHeader(HEADER_NAME);
if (authHeader == null || !authHeader.startsWith(BEARER_PREFIX)) {
filterChain.doFilter(request, response);
return;
}
// Обрезаем префикс и получаем имя пользователя из токена
var jwt = authHeader.substring(BEARER_PREFIX.length());
var username = jwtTokenProvider.extractUserName(jwt);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = detailService.loadUserByUsername(username);
// Если токен валиден, то аутентифицируем пользователя
if (jwtTokenProvider.isTokenValid(jwt, userDetails)) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
userDetails,
null,
detailService.getAuthorities(Long.parseLong(username))
);
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails((HttpServletRequest) request));
context.setAuthentication(authToken);
SecurityContextHolder.setContext(context);
}
}
filterChain.doFilter(request, response);
}
}

View File

@@ -0,0 +1,88 @@
package ru.kayashov.karthall.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import ru.kayashov.karthall.model.entity.Player;
import javax.annotation.PostConstruct;
import javax.crypto.SecretKey;
import java.security.Key;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtTokenProvider {
@Value("${token.signing.key}")
private String jwtSecretKey;
@PostConstruct
protected void init() {
jwtSecretKey = Base64.getEncoder().encodeToString(jwtSecretKey.getBytes());
}
public String extractUserName(String token) {
return extractClaim(token, Claims::getSubject);
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
if (userDetails instanceof Player customUserDetails) {
claims.put("id", customUserDetails.getId());
// claims.put("role", customUserDetails.getRole());
}
return generateToken(claims, userDetails);
}
public boolean isTokenValid(String token, UserDetails userDetails) {
final String userName = extractUserName(token);
return (userName.equals(userDetails.getUsername())) && !isTokenExpired(token);
}
private <T> T extractClaim(String token, Function<Claims, T> claimsResolvers) {
final Claims claims = extractAllClaims(token);
return claimsResolvers.apply(claims);
}
private String generateToken(Map<String, Object> extraClaims, UserDetails userDetails) {
return Jwts.builder()
.claims(extraClaims)
.subject(userDetails.getUsername())
.issuedAt(new Date())
.expiration(new Date(Instant.now().plus(24, ChronoUnit.HOURS).toEpochMilli()))
.signWith(getSigningKey())
.compact();
}
private boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
private Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public Claims extractAllClaims(String token) {
return Jwts.parser()
.verifyWith((SecretKey) getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload();
}
private Key getSigningKey() {
String keyForSigning = jwtSecretKey;
byte[] bytes = Decoders.BASE64.decode(keyForSigning);
return Keys.hmacShaKeyFor(bytes);
}
}

View File

@@ -0,0 +1,93 @@
package ru.kayashov.karthall.security;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
import org.springframework.security.config.annotation.web.configurers.CorsConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import java.util.List;
import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtTokenFilter jwtTokenFilter;
private final CustomUserDetailService userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.cors(this::corsConfiguration)
.authorizeHttpRequests(this::authorizeConfiguration)
.sessionManagement(manager -> manager.sessionCreationPolicy(STATELESS))
.authenticationProvider(authenticationProvider())
.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
private void authorizeConfiguration(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry request) {
request
.antMatchers()
.permitAll();
// Можно указать конкретный путь, * - 1 уровень вложенности, ** - любое количество уровней вложенности
// .antMatchers("/api/auth/**").permitAll()
// .antMatchers("/api/cocktail/menu").permitAll()
// .antMatchers("/api/cocktail/drink/**").permitAll()
// .antMatchers("/api/cocktail/modal").permitAll()
// .antMatchers("/api/category").permitAll()
// .antMatchers("/api/glass").permitAll()
// .antMatchers("/api/ingredient/simple").permitAll()
// .anyRequest()
// .authenticated();
}
private void corsConfiguration(CorsConfigurer<HttpSecurity> cors) {
cors.configurationSource(request -> {
var corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOriginPatterns(List.of("*"));
corsConfiguration.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
corsConfiguration.setAllowedHeaders(List.of("*"));
corsConfiguration.setAllowCredentials(true);
return corsConfiguration;
});
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
}

View File

@@ -0,0 +1,21 @@
package ru.kayashov.karthall.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import ru.kayashov.karthall.model.entity.Event;
import java.util.concurrent.ExecutorService;
@Slf4j
@Service
@RequiredArgsConstructor
public class EventService {
private final ExecutorService executorService;
// public void createEvent(Event event) {
// executorService.submit(() -> {eventRepository.save(event);});
// }
}

View File

@@ -0,0 +1,23 @@
package ru.kayashov.karthall.service;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import ru.kayashov.karthall.model.entity.Player;
import ru.kayashov.karthall.repository.PlayerRepository;
@Service
@RequiredArgsConstructor
public class PlayerService {
private final PlayerRepository playerRepository;
public Player getCurrentVisitor() {
Long id = ((Player) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getId();
return findById(id);
}
public Player findById(Long id) {
return playerRepository.findById(id).orElseThrow(() -> new RuntimeException("пользователь не найден. id: " + id));
}
}

View File

@@ -0,0 +1,18 @@
spring.application.name=myBar
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=${DB_URL:jdbc:postgresql://localhost:5433/kart_hall}
spring.datasource.username=${DB_NAME:kart_hall}
spring.datasource.password=${DB_PASSWORD:pgkarthallpass}
spring.datasource.hikari.minimum-idle=15
spring.datasource.hikari.maximum-pool-size=50
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.max-lifetime=600000
spring.jpa.generate-ddl=true
token.signing.key=${SIGNING_KEY:ThisIsKartHallSecretKey-1.0.0Version}
spring.jpa.show-sql=false
#server.port=8081