ladder start x2.

This commit is contained in:
Michael Wain 2024-06-08 01:14:06 +03:00
parent 42fe4ef3ac
commit 5c8778ca96
23 changed files with 633 additions and 18 deletions

18
pom.xml
View File

@ -72,6 +72,24 @@
<artifactId>reflections</artifactId>
<version>0.10.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
<!-- Temporary explicit version to fix Thymeleaf bug -->
<version>3.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -3,8 +3,10 @@ package com.alterdekim.hearthhack;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@SpringBootApplication
@EnableScheduling

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,71 @@
package com.alterdekim.hearthhack.component;
import com.alterdekim.hearthhack.entity.RoomPlayer;
import com.alterdekim.hearthhack.entity.User;
import com.alterdekim.hearthhack.service.RoomPlayerService;
import com.alterdekim.hearthhack.service.RoomService;
import com.alterdekim.hearthhack.service.UserService;
import com.alterdekim.hearthhack.util.Hash;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Component
public class GamePool {
@Autowired
private UserService userService;
@Autowired
private RoomService roomService;
@Autowired
private RoomPlayerService roomPlayerService;
private ConcurrentHashMap<Long, GameRoom> games = new ConcurrentHashMap<>();
private static final int PLAYERS_COUNT = 2;
@Scheduled(fixedRate = 1000)
private void refreshRooms() {
roomService.getAll()
.forEach(r -> {
if( roomPlayerService.findByRoomId(r.getId()).size() != PLAYERS_COUNT ) return;
List<RoomPlayer> players = roomPlayerService.findByRoomId(r.getId());
roomPlayerService.removeByRoomId(r.getId());
roomService.removeRoom(r.getId());
games.put(r.getId(), new GameRoom(players, userService));
});
}
public Boolean containsPlayer(Long userId) {
return games.keySet()
.stream()
.anyMatch(k -> games.get(k)
.getPlayers()
.stream()
.anyMatch(p -> p.getId().longValue() == userId.longValue()
)
);
}
public Optional<Long> getGameIdByPlayerId(Long userId) {
return games.keySet()
.stream()
.filter(k -> games.get(k)
.getPlayers()
.stream()
.anyMatch(p -> p.getId().longValue() == userId.longValue()
)
)
.findFirst();
}
}

View File

@ -0,0 +1,19 @@
package com.alterdekim.hearthhack.component;
import com.alterdekim.hearthhack.entity.RoomPlayer;
import com.alterdekim.hearthhack.entity.User;
import com.alterdekim.hearthhack.service.UserService;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
@AllArgsConstructor
public class GameRoom {
@Getter
private List<RoomPlayer> players;
private UserService userService;
}

View File

@ -46,7 +46,7 @@ public class GameServer {
}
}
public void removeConnection(TcpConnection c) {
public void removeConnection(GameConnection c) {
connections.remove(c);
}
}

View File

@ -23,14 +23,12 @@ public class GameMasterProcessor extends Processor {
// FindGameRequest FindGameResponse game_master
// GameType
Protocol.FindGameRequest gameRequest = Protocol.FindGameRequest.parseFrom(packet.getBody());
log.info("FindGameRequest: {}", gameRequest);
Protocol.Attribute a = gameRequest.getProperties().getCreationAttributesList().stream()
.filter(p -> p.hasName() && p.getName().equals("type"))
.findFirst()
.get();
GameType gameType = GameType.parseFromInt((int) a.getValue().getIntValue());
log.info("YUUUP {}", gameType);
if( gameType != GameType.GT_CASUAL ) return;
Protocol.FindGameResponse response = Protocol.FindGameResponse.newBuilder()
.setQueued(true)

View File

@ -40,7 +40,6 @@ public class GetDeck extends ClientRequestParser {
Protocol.GetDeckContentsResponse.Builder deckContentsResponse = Protocol.GetDeckContentsResponse.newBuilder();
for( long id : request.getDeckIdList() ) {
log.info("DeckId: {}", id);
deckContentsResponse.addDecks(
Protocol.DeckContents.newBuilder()
.setSuccess(true)

View File

@ -0,0 +1,26 @@
package com.alterdekim.hearthhack.dto;
import jakarta.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO {
private Long id;
@NotEmpty(message = "Username should not be empty")
private String username;
@NotEmpty(message = "Password should not be empty")
private String password;
@NotEmpty(message = "Invite code should not be empty")
private String invite_code;
private Boolean terms_and_conditions_accept;
private String lang;
}

View File

@ -0,0 +1,27 @@
package com.alterdekim.hearthhack.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.List;
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name="roles")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable=false, unique=true)
private String name;
@ManyToMany(mappedBy="roles")
private List<User> users;
}

View File

@ -0,0 +1,23 @@
package com.alterdekim.hearthhack.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "room")
public class Room {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String roomPassword;
}

View File

@ -0,0 +1,31 @@
package com.alterdekim.hearthhack.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "room_player")
public class RoomPlayer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long roomId;
@Column(nullable = false)
private Long userId;
public RoomPlayer(Long roomId, Long userId) {
this.roomId = roomId;
this.userId = userId;
}
}

View File

@ -0,0 +1,40 @@
package com.alterdekim.hearthhack.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name="users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable=false, unique=true)
private String username;
@Column(nullable=false)
private String password;
@Column(nullable = false)
private String displayName;
@ManyToMany(fetch = FetchType.EAGER, cascade=CascadeType.ALL)
@JoinTable(
name="users_roles",
joinColumns={@JoinColumn(name="USER_ID", referencedColumnName="ID")},
inverseJoinColumns={@JoinColumn(name="ROLE_ID", referencedColumnName="ID")})
private List<Role> roles = new ArrayList<>();
}

View File

@ -0,0 +1,13 @@
package com.alterdekim.hearthhack.repository;
import com.alterdekim.hearthhack.entity.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface RoleRepository extends JpaRepository<Role, Long> {
Role findByName(String name);
List<Role> findAll();
}

View File

@ -0,0 +1,32 @@
package com.alterdekim.hearthhack.repository;
import com.alterdekim.hearthhack.entity.RoomPlayer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Repository
public interface RoomPlayerRepository extends JpaRepository<RoomPlayer, Long> {
@Query(value = "SELECT new RoomPlayer(r.id, r.roomId, r.userId) FROM RoomPlayer r WHERE r.roomId = :roomId ORDER BY r.userId ASC")
List<RoomPlayer> findByRoomId(@Param("roomId") Long roomId);
@Transactional
@Modifying
@Query(value = "DELETE FROM RoomPlayer r WHERE r.userId = :userId")
void deleteAllByUserId(@Param("userId") Long userId);
@Transactional
@Modifying
@Query(value = "DELETE FROM RoomPlayer r WHERE r.roomId = :roomId")
void removeByRoomId(@Param("roomId") Long roomId);
@Query(value = "SELECT r.roomId FROM RoomPlayer r WHERE r.userId = :userId ORDER BY r.roomId ASC LIMIT 1")
Long hasUserId(@Param("userId") Long userId);
}

View File

@ -0,0 +1,20 @@
package com.alterdekim.hearthhack.repository;
import com.alterdekim.hearthhack.entity.Room;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Repository
public interface RoomRepository extends JpaRepository<Room, Long> {
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Modifying
@Query(value = "DELETE FROM room WHERE room.id NOT IN (SELECT t.room_id FROM (SELECT COUNT(id) as UID, room_id FROM room_player GROUP BY room_id) t)", nativeQuery = true)
void clearEmptyRooms();
}

View File

@ -0,0 +1,10 @@
package com.alterdekim.hearthhack.repository;
import com.alterdekim.hearthhack.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}

View File

@ -0,0 +1,60 @@
package com.alterdekim.hearthhack.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
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.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity
public class SpringSecurity {
@Autowired
private UserDetailsService userDetailsService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
requestCache.setMatchingRequestParameterName(null);
http.csrf().disable()
.authorizeHttpRequests((authorize) ->
authorize
.requestMatchers("/profile/**").hasAnyAuthority("ROLE_USER")
.requestMatchers("/").permitAll()
).formLogin(
form -> form
.loginPage("/login")
.loginProcessingUrl("/login")
.failureForwardUrl("/")
.defaultSuccessUrl("/games")
.permitAll()
)
.logout(
logout -> logout
.logoutUrl("/logout")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.permitAll()
)
.requestCache((cache) -> cache
.requestCache(requestCache)
);
return http.build();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public static PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}

View File

@ -0,0 +1,40 @@
package com.alterdekim.hearthhack.service;
import com.alterdekim.hearthhack.entity.RoomPlayer;
import com.alterdekim.hearthhack.repository.RoomPlayerRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class RoomPlayerService {
private final RoomPlayerRepository repository;
public List<RoomPlayer> getAll() {
return repository.findAll();
}
public List<RoomPlayer> findByRoomId(Long roomId) {
return repository.findByRoomId(roomId);
}
public void joinRoom(Long id, Long userId) {
repository.save(new RoomPlayer(id, userId));
}
public void leaveByUserId(Long userId) {
repository.deleteAllByUserId(userId);
}
public Long hasUserId(Long userId) {
return repository.hasUserId(userId);
}
public void removeByRoomId(Long roomId) {
repository.removeByRoomId(roomId);
}
}

View File

@ -0,0 +1,35 @@
package com.alterdekim.hearthhack.service;
import com.alterdekim.hearthhack.entity.Room;
import com.alterdekim.hearthhack.repository.RoomRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@RequiredArgsConstructor
@Service
public class RoomService {
private final RoomRepository roomRepository;
public List<Room> getAll() {
return roomRepository.findAll();
}
public Optional<Room> findById(Long id) {
return roomRepository.findById(id);
}
public Long createRoom(Room room) {
return roomRepository.save(room).getId();
}
public void clearEmptyRooms() {
roomRepository.clearEmptyRooms();
}
public void removeRoom(Long roomId) {
roomRepository.deleteById(roomId);
}
}

View File

@ -0,0 +1,67 @@
package com.alterdekim.hearthhack.service;
import com.alterdekim.hearthhack.dto.UserDTO;
import com.alterdekim.hearthhack.entity.Role;
import com.alterdekim.hearthhack.entity.User;
import com.alterdekim.hearthhack.repository.RoleRepository;
import com.alterdekim.hearthhack.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
private final RoleRepository roleRepository;
private final PasswordEncoder passwordEncoder;
public void saveUser(UserDTO userDto) {
User user = new User();
user.setUsername(userDto.getUsername());
user.setDisplayName(userDto.getUsername());
user.setPassword(passwordEncoder.encode(userDto.getPassword()));
Role role = roleRepository.findByName("ROLE_USER");
if(role == null){
role = checkRoleExist();
}
user.setRoles(Collections.singletonList(role));
userRepository.save(user);
}
public User findByUsername(String username) {
return userRepository.findByUsername(username);
}
public List<UserDTO> findAllUsers() {
List<User> users = userRepository.findAll();
return users.stream().map(this::convertEntityToDto)
.collect(Collectors.toList());
}
public User findById(Long id) {
return userRepository.findById(id).orElse(null);
}
private UserDTO convertEntityToDto(User user){
UserDTO userDto = new UserDTO();
userDto.setUsername(user.getUsername());
return userDto;
}
private Role checkRoleExist() {
Role role = new Role();
role.setName("ROLE_USER");
return roleRepository.save(role);
}
}

View File

@ -0,0 +1,35 @@
package com.alterdekim.hearthhack.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class Hash {
public static String sha256( byte[] b ) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
return bytesToHex(digest.digest(b));
}
private static String bytesToHex(byte[] hash) {
StringBuilder hexString = new StringBuilder(2 * hash.length);
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
public static String rnd() {
return IntStream.generate(() -> new Random().nextInt(0, 62))
.limit(32)
.boxed()
.map(i -> "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".charAt(i)+"")
.collect(Collectors.joining());
}
}

View File

@ -37,6 +37,13 @@ public class PegasusPacket {
this.body = body;
}
public PegasusPacket(int type, int context, byte[] body) {
this.type = type;
this.context = context;
this.size = body.length;
this.body = body;
}
public PegasusPacket(int type, int context, int size, Object body) {
this.type = type;
this.context = context;
@ -44,11 +51,6 @@ public class PegasusPacket {
this.body = body;
}
public Object GetBody() {
return this.body;
}
public boolean IsLoaded() {
return this.body != null;
}
@ -97,7 +99,12 @@ public class PegasusPacket {
System.arraycopy(protoBuf.toByteArray(), 0, array, 8, this.size);
return array;
}
return null;
this.size = ((byte[]) this.body).length;
byte[] array = new byte[this.size + 4 + 4];
System.arraycopy(getBytes(this.type), 0, array, 0, 4);
System.arraycopy(getBytes(this.size), 0, array, 4, 4);
System.arraycopy(this.body, 0, array, 8, this.size);
return array;
}
private int toInt32_2(byte[] bytes, int index) {