login/signup commit
This commit is contained in:
parent
8386db6502
commit
aecd4fd77a
103
pom.xml
Normal file
103
pom.xml
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.alterdekim.game</groupId>
|
||||||
|
<artifactId>NoDice</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.0.0</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>1.8</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>log4j</groupId>
|
||||||
|
<artifactId>log4j</artifactId>
|
||||||
|
<version>1.2.17</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-devtools</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</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>
|
||||||
|
<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-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
|
<version>8.0.33</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
<version>1.18.28</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>repackage</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<excludes>
|
||||||
|
<exclude>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
@ -1,2 +1,24 @@
|
|||||||
package com.alterdekim.game;public class Application {
|
package com.alterdekim.game;
|
||||||
|
|
||||||
|
import com.alterdekim.game.storage.StorageService;
|
||||||
|
import org.springframework.boot.CommandLineRunner;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@EnableScheduling
|
||||||
|
public class Application {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(Application.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
CommandLineRunner init(StorageService storageService) {
|
||||||
|
return (args) -> {
|
||||||
|
storageService.init();
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
package com.alterdekim.game.controller;
|
||||||
|
|
||||||
|
import com.alterdekim.game.dto.UserDTO;
|
||||||
|
import com.alterdekim.game.entities.Invite;
|
||||||
|
import com.alterdekim.game.entities.User;
|
||||||
|
import com.alterdekim.game.service.InviteService;
|
||||||
|
import com.alterdekim.game.service.UserService;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
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;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Controller
|
||||||
|
public class AuthController {
|
||||||
|
|
||||||
|
private final String base_title = " | Nosedive";
|
||||||
|
|
||||||
|
private final UserService userService;
|
||||||
|
private final InviteService inviteService;
|
||||||
|
|
||||||
|
public AuthController(UserService userService, InviteService inviteService) {
|
||||||
|
this.inviteService = inviteService;
|
||||||
|
this.userService = userService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/game")
|
||||||
|
public String gamePage(Model model) {
|
||||||
|
return "game";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/login")
|
||||||
|
public String loginPage(Model model) {
|
||||||
|
model.addAttribute("title", "Login" + base_title);
|
||||||
|
return "login";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/access-denied")
|
||||||
|
public String accessDenied(Model model) {
|
||||||
|
model.addAttribute("title", "Access denied");
|
||||||
|
return "access-denied";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/signup")
|
||||||
|
public String showRegistrationForm(Model model) {
|
||||||
|
UserDTO userDto = new UserDTO();
|
||||||
|
model.addAttribute("user", userDto);
|
||||||
|
return "signup";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/signup")
|
||||||
|
public String registration(
|
||||||
|
@ModelAttribute("user") @Valid UserDTO userDto,
|
||||||
|
BindingResult result,
|
||||||
|
Model model) {
|
||||||
|
User existingUser = userService.findByUsername(userDto.getUsername());
|
||||||
|
Invite existingInvite = inviteService.findById(1);
|
||||||
|
|
||||||
|
if(existingUser != null && existingUser.getUsername() != null && !existingUser.getUsername().isEmpty() ){
|
||||||
|
result.rejectValue("username", null,
|
||||||
|
"There is already an account registered with the same username");
|
||||||
|
return "redirect:/signup?error=1";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!existingInvite.getInvite_code().equals(userDto.getInvite_code())) {
|
||||||
|
result.rejectValue("invite_code", null, "Incorrect invite code.");
|
||||||
|
return "redirect:/signup?error=1";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(result.hasErrors()) {
|
||||||
|
model.addAttribute("user", new UserDTO());
|
||||||
|
return "redirect:/signup?error=1";
|
||||||
|
}
|
||||||
|
|
||||||
|
userService.saveUser(userDto);
|
||||||
|
return "redirect:/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
|||||||
|
package com.alterdekim.game.controller;
|
||||||
|
|
||||||
|
import com.alterdekim.game.storage.StorageFileNotFoundException;
|
||||||
|
import com.alterdekim.game.storage.StorageProperties;
|
||||||
|
import com.alterdekim.game.storage.StorageService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@EnableConfigurationProperties(StorageProperties.class)
|
||||||
|
public class FileServerController {
|
||||||
|
|
||||||
|
private final StorageService storageService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public FileServerController(StorageService storageService) {
|
||||||
|
this.storageService = storageService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/static/{assetspath}/{filename:.+}")
|
||||||
|
@ResponseBody
|
||||||
|
public ResponseEntity<Resource> serveFile(@PathVariable String filename, @PathVariable String assetspath) {
|
||||||
|
Resource file = storageService.loadAsResource("static/" + assetspath + "/" + filename);
|
||||||
|
if( assetspath.equals("images") ) {
|
||||||
|
return ResponseEntity.ok().contentType(MediaType.IMAGE_PNG).header(HttpHeaders.CONTENT_DISPOSITION,
|
||||||
|
"attachment; filename=\"" + file.getFilename() + "\"").body(file);
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok().contentType(new MediaType("text", assetspath)).header(HttpHeaders.CONTENT_DISPOSITION,
|
||||||
|
"attachment; filename=\"" + file.getFilename() + "\"").body(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(StorageFileNotFoundException.class)
|
||||||
|
public ResponseEntity<?> handleStorageFileNotFound(StorageFileNotFoundException exc) {
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
24
src/main/java/com/alterdekim/game/dto/UserDTO.java
Normal file
24
src/main/java/com/alterdekim/game/dto/UserDTO.java
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package com.alterdekim.game.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 String lang;
|
||||||
|
}
|
||||||
|
|
23
src/main/java/com/alterdekim/game/entities/Invite.java
Normal file
23
src/main/java/com/alterdekim/game/entities/Invite.java
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package com.alterdekim.game.entities;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Entity
|
||||||
|
@Table(name = "invite_codes")
|
||||||
|
public class Invite {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(nullable=false)
|
||||||
|
private String invite_code;
|
||||||
|
}
|
||||||
|
|
29
src/main/java/com/alterdekim/game/entities/Role.java
Normal file
29
src/main/java/com/alterdekim/game/entities/Role.java
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package com.alterdekim.game.entities;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
38
src/main/java/com/alterdekim/game/entities/User.java
Normal file
38
src/main/java/com/alterdekim/game/entities/User.java
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package com.alterdekim.game.entities;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(nullable=false, unique=true)
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@Column(nullable=false)
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@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<>();
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.alterdekim.game.handler;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
|
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
|
||||||
|
@Override
|
||||||
|
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
|
||||||
|
response.sendRedirect("/access-denied");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
|||||||
|
package com.alterdekim.game.repository;
|
||||||
|
|
||||||
|
import com.alterdekim.game.entities.Invite;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface InviteRepository extends JpaRepository<Invite, Integer> {
|
||||||
|
Optional<Invite> findById(Integer id);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.alterdekim.game.repository;
|
||||||
|
|
||||||
|
import com.alterdekim.game.entities.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();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.alterdekim.game.repository;
|
||||||
|
|
||||||
|
import com.alterdekim.game.entities.User;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface UserRepository extends JpaRepository<User, Integer> {
|
||||||
|
User findByUsername(String username);
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
package com.alterdekim.game.security;
|
||||||
|
|
||||||
|
import com.alterdekim.game.handler.CustomAccessDeniedHandler;
|
||||||
|
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.access.AccessDeniedHandler;
|
||||||
|
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("/game").permitAll()
|
||||||
|
.requestMatchers("/games").permitAll()
|
||||||
|
.requestMatchers("/static/**").permitAll()
|
||||||
|
.requestMatchers("/access-denied").permitAll()
|
||||||
|
.requestMatchers("/signup").permitAll()
|
||||||
|
.requestMatchers("/favicon.ico").permitAll()
|
||||||
|
.requestMatchers("/signup/**").permitAll()
|
||||||
|
.requestMatchers("/").permitAll()
|
||||||
|
).formLogin(
|
||||||
|
form -> form
|
||||||
|
.loginPage("/login")
|
||||||
|
.loginProcessingUrl("/login")
|
||||||
|
.failureForwardUrl("/")
|
||||||
|
.defaultSuccessUrl("/game")
|
||||||
|
.permitAll()
|
||||||
|
)
|
||||||
|
.logout(
|
||||||
|
logout -> logout
|
||||||
|
.logoutUrl("/logout")
|
||||||
|
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
|
||||||
|
.permitAll()
|
||||||
|
)
|
||||||
|
.requestCache((cache) -> cache
|
||||||
|
.requestCache(requestCache)
|
||||||
|
)
|
||||||
|
.exceptionHandling((exc) -> exc
|
||||||
|
.accessDeniedHandler(accessDeniedHandler())
|
||||||
|
.accessDeniedPage("/access-denied"));
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AccessDeniedHandler accessDeniedHandler() {
|
||||||
|
return new CustomAccessDeniedHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
||||||
|
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public static PasswordEncoder passwordEncoder(){
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.alterdekim.game.service;
|
||||||
|
|
||||||
|
import com.alterdekim.game.entities.Invite;
|
||||||
|
|
||||||
|
public interface InviteService {
|
||||||
|
Invite findById(Integer id);
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.alterdekim.game.service;
|
||||||
|
|
||||||
|
import com.alterdekim.game.entities.Invite;
|
||||||
|
import com.alterdekim.game.repository.InviteRepository;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class InviteServiceImpl implements InviteService {
|
||||||
|
|
||||||
|
private final InviteRepository repository;
|
||||||
|
|
||||||
|
public InviteServiceImpl(InviteRepository repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Invite findById(Integer id) {
|
||||||
|
return repository.findById(id).orElse(null);
|
||||||
|
}
|
||||||
|
}
|
15
src/main/java/com/alterdekim/game/service/UserService.java
Normal file
15
src/main/java/com/alterdekim/game/service/UserService.java
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package com.alterdekim.game.service;
|
||||||
|
|
||||||
|
import com.alterdekim.game.dto.UserDTO;
|
||||||
|
import com.alterdekim.game.entities.User;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface UserService {
|
||||||
|
void saveUser(UserDTO userDto);
|
||||||
|
|
||||||
|
User findByUsername(String usernane);
|
||||||
|
|
||||||
|
List<UserDTO> findAllUsers();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
|||||||
|
package com.alterdekim.game.service;
|
||||||
|
|
||||||
|
import com.alterdekim.game.dto.UserDTO;
|
||||||
|
import com.alterdekim.game.entities.Role;
|
||||||
|
import com.alterdekim.game.entities.User;
|
||||||
|
import com.alterdekim.game.repository.RoleRepository;
|
||||||
|
import com.alterdekim.game.repository.UserRepository;
|
||||||
|
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;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class UserServiceImpl implements UserService {
|
||||||
|
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
private final RoleRepository roleRepository;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
public UserServiceImpl(UserRepository userRepository,
|
||||||
|
RoleRepository roleRepository,
|
||||||
|
PasswordEncoder passwordEncoder) {
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
this.roleRepository = roleRepository;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveUser(UserDTO userDto) {
|
||||||
|
User user = new User();
|
||||||
|
user.setUsername(userDto.getUsername());
|
||||||
|
|
||||||
|
user.setPassword(passwordEncoder.encode(userDto.getPassword()));
|
||||||
|
Role role = roleRepository.findByName("ROLE_ADMIN");
|
||||||
|
if(role == null){
|
||||||
|
role = checkRoleExist();
|
||||||
|
}
|
||||||
|
user.setRoles(Collections.singletonList(role));
|
||||||
|
userRepository.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User findByUsername(String username) {
|
||||||
|
return userRepository.findByUsername(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserDTO> findAllUsers() {
|
||||||
|
List<User> users = userRepository.findAll();
|
||||||
|
return users.stream().map(this::convertEntityToDto)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
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_ADMIN");
|
||||||
|
return roleRepository.save(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,2 +1,106 @@
|
|||||||
package com.alterdekim.game.storage;public class FileSystemStorageService {
|
package com.alterdekim.game.storage;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.FileSystemUtils;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class FileSystemStorageService implements StorageService {
|
||||||
|
|
||||||
|
private final Path rootLocation;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public FileSystemStorageService(StorageProperties properties) {
|
||||||
|
|
||||||
|
if(properties.getLocation().trim().isEmpty()){
|
||||||
|
throw new StorageException("File upload location can not be Empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rootLocation = Paths.get(new ClassPathResource(properties.getLocation()).getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void store(MultipartFile file) {
|
||||||
|
try {
|
||||||
|
if (file.isEmpty()) {
|
||||||
|
throw new StorageException("Failed to store empty file.");
|
||||||
|
}
|
||||||
|
Path destinationFile = this.rootLocation.resolve(
|
||||||
|
Paths.get(file.getOriginalFilename()))
|
||||||
|
.normalize().toAbsolutePath();
|
||||||
|
if (!destinationFile.getParent().equals(this.rootLocation.toAbsolutePath())) {
|
||||||
|
throw new StorageException(
|
||||||
|
"Cannot store file outside current directory.");
|
||||||
|
}
|
||||||
|
try (InputStream inputStream = file.getInputStream()) {
|
||||||
|
Files.copy(inputStream, destinationFile,
|
||||||
|
StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new StorageException("Failed to store file.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<Path> loadAll() {
|
||||||
|
try {
|
||||||
|
return Files.walk(this.rootLocation, 1)
|
||||||
|
.filter(path -> !path.equals(this.rootLocation))
|
||||||
|
.map(this.rootLocation::relativize);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new StorageException("Failed to read stored files", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Path load(String filename) {
|
||||||
|
return rootLocation.resolve(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Resource loadAsResource(String filename) {
|
||||||
|
try {
|
||||||
|
Resource resource = new ClassPathResource(filename);
|
||||||
|
if (resource.exists() || resource.isReadable()) {
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new StorageFileNotFoundException(
|
||||||
|
"Could not read file: " + filename);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
throw new StorageFileNotFoundException("Could not read file: " + filename, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteAll() {
|
||||||
|
FileSystemUtils.deleteRecursively(rootLocation.toFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() {
|
||||||
|
try {
|
||||||
|
Files.createDirectories(rootLocation);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new StorageException("Could not initialize storage", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,12 @@
|
|||||||
package com.alterdekim.game.storage;public class StorageException {
|
package com.alterdekim.game.storage;
|
||||||
|
|
||||||
|
public class StorageException extends RuntimeException {
|
||||||
|
|
||||||
|
public StorageException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StorageException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,12 @@
|
|||||||
package com.alterdekim.game.storage;public class StorageFileNotFoundException {
|
package com.alterdekim.game.storage;
|
||||||
|
|
||||||
|
public class StorageFileNotFoundException extends StorageException {
|
||||||
|
|
||||||
|
public StorageFileNotFoundException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StorageFileNotFoundException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,2 +1,12 @@
|
|||||||
package com.alterdekim.game.storage;public class StorageProperties {
|
package com.alterdekim.game.storage;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
@Getter
|
||||||
|
@ConfigurationProperties("storage")
|
||||||
|
public class StorageProperties {
|
||||||
|
private String location = "static";
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,24 @@
|
|||||||
package com.alterdekim.game.storage;public class StorageService {
|
package com.alterdekim.game.storage;
|
||||||
|
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public interface StorageService {
|
||||||
|
|
||||||
|
void init();
|
||||||
|
|
||||||
|
void store(MultipartFile file);
|
||||||
|
|
||||||
|
Stream<Path> loadAll();
|
||||||
|
|
||||||
|
Path load(String filename);
|
||||||
|
|
||||||
|
Resource loadAsResource(String filename);
|
||||||
|
|
||||||
|
void deleteAll();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
506
src/main/resources/static/css/game.css
Normal file
506
src/main/resources/static/css/game.css
Normal file
@ -0,0 +1,506 @@
|
|||||||
|
body {
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: 'Open Sans',tahoma,arial,sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
._mbtn {
|
||||||
|
text-align: center;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropbox {
|
||||||
|
width: 17%;
|
||||||
|
position: absolute;
|
||||||
|
margin-top: 2%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropbox div {
|
||||||
|
background-color: white;
|
||||||
|
color: grey;
|
||||||
|
font-size: 16pt;
|
||||||
|
padding-top: 5px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stars {
|
||||||
|
position: fixed;
|
||||||
|
width: 55px;
|
||||||
|
text-align: center;
|
||||||
|
display: inline-flex;
|
||||||
|
margin: auto;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
._star {
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: -10px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hstars {
|
||||||
|
margin-left: -40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._hstar {
|
||||||
|
margin-top: -50px;
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
._vstar {
|
||||||
|
margin-top: -110px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._hhstar {
|
||||||
|
margin-left: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
position: fixed;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #262c2f;
|
||||||
|
overflow: hidden!important;
|
||||||
|
}
|
||||||
|
#chatinput {
|
||||||
|
width: 511px;
|
||||||
|
height: 24px;
|
||||||
|
background-color:rgba(0, 0, 0, 0.1);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
outline:none;
|
||||||
|
font-size:16px;
|
||||||
|
}
|
||||||
|
#log {
|
||||||
|
position: fixed;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow: scroll;
|
||||||
|
width: 511px;
|
||||||
|
height: 480px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
#loading {
|
||||||
|
position: fixed;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #262c2f;
|
||||||
|
overflow: hidden!important;
|
||||||
|
z-index: 99;
|
||||||
|
}
|
||||||
|
.table {
|
||||||
|
display: grid;
|
||||||
|
grid-column-gap: 2px;
|
||||||
|
grid-row-gap: 2px;
|
||||||
|
}
|
||||||
|
.corner {
|
||||||
|
background-color: white;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
.fv {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
width: 100px;
|
||||||
|
height: 55px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.fh {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
width: 55px;
|
||||||
|
height: 100px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.cost {
|
||||||
|
background-color: white;
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* .cost.red {
|
||||||
|
background-color: #CC0000;
|
||||||
|
color:#FFFFFF
|
||||||
|
}
|
||||||
|
|
||||||
|
.cost.green {
|
||||||
|
background-color: #00CC00;
|
||||||
|
color:#FFFFFF
|
||||||
|
} */
|
||||||
|
|
||||||
|
.up {
|
||||||
|
margin-top: 18px;
|
||||||
|
}
|
||||||
|
.iconH {
|
||||||
|
width: 55px;
|
||||||
|
height: 100px;
|
||||||
|
position:relative;
|
||||||
|
}
|
||||||
|
.iconV {
|
||||||
|
width: 100px;
|
||||||
|
height: 55px;
|
||||||
|
position:relative;
|
||||||
|
/* background-image: url("http://localhost/monopoly/fields/land_rover.png"); */
|
||||||
|
}
|
||||||
|
|
||||||
|
.horImg {
|
||||||
|
/* width:55px;
|
||||||
|
height:55px; */
|
||||||
|
width:55px;
|
||||||
|
position:absolute;
|
||||||
|
left:25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertImg {
|
||||||
|
/* width:55px;
|
||||||
|
height:55px; */
|
||||||
|
width:55px;
|
||||||
|
-webkit-transform:rotate(-90deg);
|
||||||
|
-moz-transform: rotate(-90deg);
|
||||||
|
-ms-transform: rotate(-90deg);
|
||||||
|
-o-transform: rotate(-90deg);
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
position:absolute;
|
||||||
|
top:25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rcost {
|
||||||
|
float: right;
|
||||||
|
background-color: white;
|
||||||
|
width: 55px;
|
||||||
|
height: 18px;
|
||||||
|
transform: rotate(90deg);
|
||||||
|
margin-top: 18px;
|
||||||
|
margin-left: 82px;
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.lcost {
|
||||||
|
float: left;
|
||||||
|
background-color: white;
|
||||||
|
width: 55px;
|
||||||
|
height: 18px;
|
||||||
|
transform: rotate(90deg);
|
||||||
|
margin-top: 18px;
|
||||||
|
margin-left: -38px;
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.table_center {
|
||||||
|
grid-row-start: 2;
|
||||||
|
grid-column-start: 2;
|
||||||
|
grid-column-end: 11;
|
||||||
|
grid-row-end: 11;
|
||||||
|
}
|
||||||
|
.players {
|
||||||
|
margin-right: 80px;
|
||||||
|
}
|
||||||
|
.player {
|
||||||
|
margin-top: 10px;
|
||||||
|
background-color: #15191a;
|
||||||
|
width: 173px;
|
||||||
|
height: 130px;
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 22px;
|
||||||
|
padding: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.player-turn {
|
||||||
|
width: 193px;
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
.game {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
chip colors:
|
||||||
|
red - #f2454a
|
||||||
|
blue - #00adef
|
||||||
|
green - #73d249
|
||||||
|
purple - #c874ed
|
||||||
|
orange - #e19d22
|
||||||
|
*/
|
||||||
|
|
||||||
|
.font-red { color: #f2454a; }
|
||||||
|
.font-blue { color: #00adef; }
|
||||||
|
.font-green { color: #73d249; }
|
||||||
|
.font-purple { color: #c874ed; }
|
||||||
|
.font-orange { color: #e19d22; }
|
||||||
|
|
||||||
|
.red { background-color: #f2454a; }
|
||||||
|
.blue { background-color: #00adef; }
|
||||||
|
.green { background-color: #73d249; }
|
||||||
|
.purple { background-color: #c874ed; }
|
||||||
|
.orange { background-color: #e19d22; }
|
||||||
|
|
||||||
|
|
||||||
|
.chips {
|
||||||
|
/* background-color:#ff0000; */
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
left:173px;
|
||||||
|
top:0px;
|
||||||
|
position:absolute;
|
||||||
|
/* border:1px solid green; */
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.chip {
|
||||||
|
box-shadow: 0 0 10px rgba(0,0,0,1);
|
||||||
|
border-radius: 50px;
|
||||||
|
position: absolute;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
z-index: 98;
|
||||||
|
grid-row-start: 1;
|
||||||
|
grid-row-end: 11;
|
||||||
|
grid-column-start: 1;
|
||||||
|
grid-column-end: 11;
|
||||||
|
margin-top: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chipMask {
|
||||||
|
border-radius: 50px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
z-index: 99;
|
||||||
|
background-color: black;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn_string {
|
||||||
|
display: inline-flex;
|
||||||
|
padding-left:5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#cell_descr {
|
||||||
|
z-index:100;
|
||||||
|
border-radius:10px;
|
||||||
|
width: 202px;
|
||||||
|
background-color:#ffffff;
|
||||||
|
position:absolute;
|
||||||
|
box-shadow: 0px -5px 10px rgba(10,10,10,0.3);
|
||||||
|
background-color:#ffffff;
|
||||||
|
padding-bottom:10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell_descr_top {
|
||||||
|
background-color:#646d79;
|
||||||
|
width:202px;
|
||||||
|
height:57px;
|
||||||
|
border-radius:10px 10px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rent_table {
|
||||||
|
color:#646d79;
|
||||||
|
font-size:11px;
|
||||||
|
background-color:#ffffff;
|
||||||
|
margin-left:10px;
|
||||||
|
margin-right:10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.currency {
|
||||||
|
color:#adb3bb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.star {
|
||||||
|
font-size:14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.star_big {
|
||||||
|
color:#fdbd00;
|
||||||
|
font-size:18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell_descr_title {
|
||||||
|
color:#ffffff;
|
||||||
|
font-size:16px;
|
||||||
|
width:202px;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell_descr_subtitle {
|
||||||
|
color:#e0e0e0;
|
||||||
|
font-size:9px;
|
||||||
|
width:202px;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-top: 0px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dialog {
|
||||||
|
position: fixed;
|
||||||
|
width: 491px;
|
||||||
|
/* height: 200px;*/
|
||||||
|
background-color: white;
|
||||||
|
margin-left:10px;
|
||||||
|
margin-right:10px;
|
||||||
|
margin-top:10px;
|
||||||
|
padding-bottom:10px;
|
||||||
|
border-radius:10px;
|
||||||
|
color:#646d79;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dialog_title {
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
#dialog_desc {
|
||||||
|
font-size: 12px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#contract {
|
||||||
|
width: 511px;
|
||||||
|
height: 511px;
|
||||||
|
background-color: white;
|
||||||
|
position: fixed;
|
||||||
|
color: #646d79;
|
||||||
|
font-size: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: white;
|
||||||
|
width: 220px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 6px 4px;
|
||||||
|
margin: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn_long {
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: white;
|
||||||
|
width: 460px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 6px 4px;
|
||||||
|
margin: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
[contenteditable="true"].single-line {
|
||||||
|
white-space: nowrap;
|
||||||
|
width:100px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
[contenteditable="true"].single-line br {
|
||||||
|
display:none;
|
||||||
|
|
||||||
|
}
|
||||||
|
[contenteditable="true"].single-line * {
|
||||||
|
display:inline;
|
||||||
|
white-space:nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.green, .btn_long.green {
|
||||||
|
background-color: #00c29e;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.grey, .btn_long.grey {
|
||||||
|
background-color: #eceef1;
|
||||||
|
color:#646d79;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.contract-player {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contract_money {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
appearance: textfield;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #646d79;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: white;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin-top: 50%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diceFace {
|
||||||
|
position:absolute;
|
||||||
|
width:200px;
|
||||||
|
height:200px;
|
||||||
|
text-align:center;
|
||||||
|
line-height:200px;
|
||||||
|
font-size:45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dice_view {
|
||||||
|
position: absolute;
|
||||||
|
width:200px;
|
||||||
|
height:200px;
|
||||||
|
perspective:600px;
|
||||||
|
transform: scale(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dice {
|
||||||
|
width:200px;
|
||||||
|
height:200px;
|
||||||
|
position:relative;
|
||||||
|
transform-style:preserve-3d;
|
||||||
|
transition: transform 1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dice_front {
|
||||||
|
background-image: url("monopoly/cube/1.jpg");
|
||||||
|
background-size: 200px;
|
||||||
|
transform:rotateY(0deg) translateZ(100px);
|
||||||
|
}
|
||||||
|
.dice_right {
|
||||||
|
background-image: url("monopoly/cube/2.jpg");
|
||||||
|
background-size: 200px;
|
||||||
|
transform:rotateY(90deg) translateZ(100px);
|
||||||
|
}
|
||||||
|
.dice_back {
|
||||||
|
background-image: url("monopoly/cube/3.jpg");
|
||||||
|
background-size: 200px;
|
||||||
|
transform:rotateY(180deg) translateZ(100px);
|
||||||
|
}
|
||||||
|
.dice_left {
|
||||||
|
background-image: url("monopoly/cube/4.jpg");
|
||||||
|
background-size: 200px;
|
||||||
|
transform:rotateY(-90deg) translateZ(100px);
|
||||||
|
}
|
||||||
|
.dice_top {
|
||||||
|
background-image: url("monopoly/cube/5.jpg");
|
||||||
|
background-size: 200px;
|
||||||
|
transform:rotateX(90deg) translateZ(100px);
|
||||||
|
}
|
||||||
|
.dice_bottom {
|
||||||
|
background-image: url("monopoly/cube/6.jpg");
|
||||||
|
background-size: 200px;
|
||||||
|
transform:rotateX(-90deg) translateZ(100px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeout {
|
||||||
|
position: absolute;
|
||||||
|
left: 150px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
52
src/main/resources/static/css/style.css
Normal file
52
src/main/resources/static/css/style.css
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
.navbar {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
background-color: #f4f4f5;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #e6e9ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-primary {
|
||||||
|
border-color: transparent;
|
||||||
|
color: #5b5d67;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #37bc9d;
|
||||||
|
color: #fff;
|
||||||
|
border-color: #37bc9d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: rgba(55,188,157,0.85);
|
||||||
|
border-color: rgba(55,188,157,0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-primary:hover {
|
||||||
|
background-color: rgba(55,188,157,0.85);
|
||||||
|
border-color: rgba(55,188,157,0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.2em;
|
||||||
|
}
|
8
src/main/resources/templates/fragments/essentials.html
Normal file
8
src/main/resources/templates/fragments/essentials.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<th:block th:fragment="essentials">
|
||||||
|
<script type="module" src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
|
||||||
|
<script>
|
||||||
|
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
||||||
|
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
|
||||||
|
</script>
|
||||||
|
</th:block>
|
24
src/main/resources/templates/fragments/footer.html
Normal file
24
src/main/resources/templates/fragments/footer.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<th:block th:fragment="footer">
|
||||||
|
<footer class="bg-light text-center text-lg-start">
|
||||||
|
<div class="container p-4">
|
||||||
|
<div class="row d-flex justify-content-center">
|
||||||
|
<div class="col-lg-4 col-md-12 mb-4 mb-md-0">
|
||||||
|
<h6 class="text-uppercase">Nosedive</h6>
|
||||||
|
<p>
|
||||||
|
Nosedive is a free online game.
|
||||||
|
All brands and trademarks on this page belong to their respective owners and are advertised.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="mx-auto col-lg-6 col-md-12 mb-4 mb-md-0">
|
||||||
|
<h6 class="text-uppercase">Social</h6>
|
||||||
|
<p>
|
||||||
|
Follow Monopoly on social networks to stay up to date with game updates.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center p-3">
|
||||||
|
© 2024 Copyright: alterdekim
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</th:block>
|
8
src/main/resources/templates/fragments/head.html
Normal file
8
src/main/resources/templates/fragments/head.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<th:block th:fragment="head">
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title th:text="${title} ? ${title} : 'Nosedive'"></title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,500,800" rel="stylesheet">
|
||||||
|
<link href="/static/css/style.css" rel="stylesheet">
|
||||||
|
</th:block>
|
32
src/main/resources/templates/fragments/navbar.html
Normal file
32
src/main/resources/templates/fragments/navbar.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<th:block th:fragment="navbar">
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="collapse navbar-collapse justify-content-center" id="navbarNav">
|
||||||
|
<a class="navbar-brand" href="/">Nosedive</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>
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<button class="btn btn-primary navbar-btn">
|
||||||
|
<ion-icon name="game-controller-outline"></ion-icon>
|
||||||
|
<span>Games</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<button class="btn btn-outline-primary navbar-btn">
|
||||||
|
<ion-icon name="people-outline"></ion-icon>
|
||||||
|
<span>Friends</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<button class="btn btn-outline-primary navbar-btn">
|
||||||
|
<ion-icon name="briefcase-outline"></ion-icon>
|
||||||
|
<span>Inventory</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</th:block>
|
272
src/main/resources/templates/game.html
Normal file
272
src/main/resources/templates/game.html
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html xmlns:th="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"></meta>
|
||||||
|
<link rel="stylesheet" href="/static/css/game.css">
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
|
||||||
|
<title>Монополия онлайн</title>
|
||||||
|
<link rel="shortcut icon" type="image/png" href="../static/images/favicon.ico"/>
|
||||||
|
<div id="contract_field" style="display: none">
|
||||||
|
<div style="display: inline-flex;">
|
||||||
|
<div class="contract_image" style="margin-left: 10px"><img src="../static/images/mitsubishi.png" style="width: 55px;"></div>
|
||||||
|
<div class="contract_info" style="margin-left: 10px; display: inline-block; text-align: left">
|
||||||
|
<div class="contract_name">Mitsubishi</div>
|
||||||
|
<div class="contract_cost"style="font-size: 14px">3.000$</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!--<div id="loading" style="width: 100%; height: 100%;">
|
||||||
|
<div style="text-align: center">
|
||||||
|
<p id="loading_status" style="color: white">Идёт загрузка игры...</p>
|
||||||
|
<div id="returnBtn" onclick="window.location = '/games?gtype=monopoly'" class="btn green" style="margin-top: 5px; display: none; margin-bottom: auto; margin-left: auto; margin-right: auto;">Назад</div>
|
||||||
|
</div>
|
||||||
|
</div>-->
|
||||||
|
<div class="wrapper" style="width: 100%; height: 100%">
|
||||||
|
<div class="game">
|
||||||
|
<div class="players">
|
||||||
|
<th:block th:each="p: ${players}">
|
||||||
|
<div class="player" data-pid="${p.id}" onClick="drop(this)">
|
||||||
|
<p class="timeout"></p>
|
||||||
|
<p class="nickname" th:text="${p.name}"></p>
|
||||||
|
<p class="money" th:text="${p.money}"></p>
|
||||||
|
<div class="dropbox" style="display: none"></div> <!-- margin-top: -35px; -->
|
||||||
|
</div>
|
||||||
|
</th:block>
|
||||||
|
</div>
|
||||||
|
<div class="table">
|
||||||
|
<div class="table_center">
|
||||||
|
|
||||||
|
<!--<div id="log" style="position: fixed; padding: 5px; overflow: scroll;">
|
||||||
|
<div style="display: inline-flex;"><p class="font-red">Name: </p><p> lol</p></div>
|
||||||
|
<div style="display: inline-flex;"><p class="font-red">Name: </p><p> lol</p></div>
|
||||||
|
<div style="display: inline-flex;"><p class="font-red">Name: </p><p> lol</p></div>
|
||||||
|
<div style="display: inline-flex;"><p class="font-red">Name: </p><p> lol</p></div>
|
||||||
|
</div> -->
|
||||||
|
<div id="log">
|
||||||
|
<!-- <div>Игрок <span style="color: red">lol</span> ходит</div> -->
|
||||||
|
</div>
|
||||||
|
<div style="position: absolute; margin-top: 485px" >
|
||||||
|
<input placeholder="Напишите в чат" type="text" id="chatinput"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="dice_cubes" style="margin-top: 100px; display: none;">
|
||||||
|
<div class="dice_view" style="margin-left: 100px;">
|
||||||
|
<div class="dice" id="first_dice">
|
||||||
|
<div class="diceFace dice_front"></div>
|
||||||
|
<div class="diceFace dice_right"></div>
|
||||||
|
<div class="diceFace dice_back"></div>
|
||||||
|
<div class="diceFace dice_left"></div>
|
||||||
|
<div class="diceFace dice_top"></div>
|
||||||
|
<div class="diceFace dice_bottom"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dice_view" style="margin-left: 225px; margin-top: 125px">
|
||||||
|
<div class="dice" id="second_dice">
|
||||||
|
<div class="diceFace dice_front"></div>
|
||||||
|
<div class="diceFace dice_right"></div>
|
||||||
|
<div class="diceFace dice_back"></div>
|
||||||
|
<div class="diceFace dice_left"></div>
|
||||||
|
<div class="diceFace dice_top"></div>
|
||||||
|
<div class="diceFace dice_bottom"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="contract" style="display: none;">
|
||||||
|
<p>Договор</p>
|
||||||
|
<div style="display: inline-flex; width: 511px">
|
||||||
|
<p style="font-size: 16px; text-align: left; margin-left: 10px" class="font-red contract-player">Вы: </p>
|
||||||
|
<p style="font-size: 16px; text-align: left; margin-left: 230px" class="font-green contract-player">User: </p>
|
||||||
|
</div>
|
||||||
|
<div id="my_contract_fields" style="position: absolute; display: grid">
|
||||||
|
<div style="display: inline-flex;">
|
||||||
|
<div style="margin-left: 10px"><img src="../static/images/money.png" style="width: 55px;"></div>
|
||||||
|
<div style="margin-left: 10px; display: inline-block; text-align: left">
|
||||||
|
<input type="number" id="self_money" class="contract_money single-line" value="0" />
|
||||||
|
<div style="font-size: 14px">Наличные</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--<div style="display: inline-flex;">
|
||||||
|
<div style="margin-left: 10"><img src="images/mitsubishi.png" style="width: 55px;"></div>
|
||||||
|
<div style="margin-left: 10; display: inline-block; text-align: left">
|
||||||
|
<div>Mitsubishi</div>
|
||||||
|
<div style="font-size: 14">3.000$</div>
|
||||||
|
</div>
|
||||||
|
</div>-->
|
||||||
|
</div>
|
||||||
|
<div id="contract_fields" style="position: absolute; display: grid; margin-left: 260px">
|
||||||
|
<div style="display: inline-flex;">
|
||||||
|
<div style="margin-left: 10px"><img src="../static/images/money.png" style="width: 55px;"></div>
|
||||||
|
<div style="margin-left: 10px; display: inline-block; text-align: left">
|
||||||
|
<input type="number" id="other_money" class="contract_money single-line" value="0" />
|
||||||
|
<div style="font-size: 14px">Наличные</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr width="1" size="320" style="height: 60%">
|
||||||
|
<div style="display: flex; width: 100%">
|
||||||
|
<div style="width: 33.3%">
|
||||||
|
<div id="allSelfMoney" style="font-size: 16px;">0$</div>
|
||||||
|
</div>
|
||||||
|
<div style="width: 33.3%">
|
||||||
|
<div style="font-size: 16px;">Общая сумма</div>
|
||||||
|
</div>
|
||||||
|
<div style="width: 33.3%">
|
||||||
|
<div id="allOtherMoney" style="font-size: 16px;">0$</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="btn_string">
|
||||||
|
<div id="contractAgree" onClick="contractAgree()" class="btn green" style="width: 100px;">Принять</div><div id="contractDeny" onClick="contractDeny()" class="btn red" style="width: 100px;">Отказать</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: none;" id="dialog" class="dialogturn">
|
||||||
|
<p id="dialog_title">Покупаем?</p>
|
||||||
|
<p id="dialog_desc">Lorem ipsum dolor sit amet...</p></br>
|
||||||
|
<div class="btn_string" id="twobtns">
|
||||||
|
<div id="buybtn" onClick="leftBtnClicked(this)" class="btn green">Купить за 1,000k</div><div id="ignorebtn" onClick="rightBtnClicked()" class="btn grey">На аукцион</div>
|
||||||
|
</div>
|
||||||
|
<div class="btn_string" id="onebtn">
|
||||||
|
<div class="btn_long green" onClick="centerBtnClicked()" id="ogbtn">Купить за 1,000k</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- style="top:270px;left:400px" -->
|
||||||
|
<div style="display: none;" id="cell_descr">
|
||||||
|
<div class="cell_descr_top">
|
||||||
|
<div class="cell_descr_title">Apple</div>
|
||||||
|
<div class="cell_descr_subtitle"><!--Электроника--></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="rent_table">
|
||||||
|
<tr>
|
||||||
|
<div id="buyOrSell" style="display: none;" >
|
||||||
|
<div style="width: 100%; display: inline-flex;">
|
||||||
|
<div onClick="bnf(this)" id="tbbb" class="btn green" style="width: 100%">Купить</div><div onClick="sfof(this)" id="tbsb" style="width: 100%" class="btn red">Продать</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="onlySell" style="display: none;">
|
||||||
|
<div style="width: 100%; display: inline-flex;">
|
||||||
|
<div class="btn_long red" onClick="sfof(this)">Продать</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">Стройте филиалы, чтобы увеличить ренту</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Базовая рента</td>
|
||||||
|
<td align="right"><b id="baserent">350</b><span class="currency">k</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="star">★</span></td>
|
||||||
|
<td align="right"><b id="firstrent">1,750</b><span class="currency">k</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="star">★ ★</span></td>
|
||||||
|
<td align="right"><b id="secondrent">5,000</b><span class="currency">k</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="star">★ ★ ★</span></td>
|
||||||
|
<td align="right"><b id="thirdrent">11,050</b><span class="currency">k</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="star">★ ★ ★ ★</span></td>
|
||||||
|
<td align="right"><b id="fourthrent">13,000</b><span class="currency">k</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="star_big">★</span></td>
|
||||||
|
<td align="right"><b id="fithrent">15,000</b><span class="currency">k</span></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding-top:10px;">Стоимость поля</td>
|
||||||
|
<td style="padding-top:10px;" align="right"><b id="buyprice">3,500</b><span class="currency">k</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr style="visibility: hidden;">
|
||||||
|
<td>Залог поля</td>
|
||||||
|
<td align="right"><b id="depositprice">1,750</b><span class="currency">k</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr style="visibility: hidden;">
|
||||||
|
<td>Выкуп поля</td>
|
||||||
|
<td align="right"><b id="redeemprice">2,100</b><span class="currency">k</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Покупка филиала</td>
|
||||||
|
<td align="right"><b id="fprice">2,000</b><span class="currency">k</span></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- <div class="chips"> -->
|
||||||
|
<!-- {262,55} {318,55} {375,55} -->
|
||||||
|
<div class="chip red" data-trow=0 data-tcol=0></div>
|
||||||
|
<div class="chip green" data-trow=0 data-tcol=0></div>
|
||||||
|
<div class="chip blue" data-trow=0 data-tcol=0></div>
|
||||||
|
<div class="chip purple" data-trow=0 data-tcol=0></div>
|
||||||
|
<div class="chip orange" data-trow=0 data-tcol=0></div>
|
||||||
|
<!-- </div> -->
|
||||||
|
<div class="up"><div class="board_field corner"><img src="../static/images/start.png" style="width: 100%; height: 100%;" /></div></div>
|
||||||
|
|
||||||
|
<th:block th:each="f: ${fields_up}">
|
||||||
|
<div th:data-fid="${f.uid}" class="board_field" style="grid-column: @{${f.id}}"> <!-- 2 + -->
|
||||||
|
<div class="cost" th:text="${f.cost}"></div>
|
||||||
|
<div class="fh">
|
||||||
|
<div class="iconH">
|
||||||
|
<img th:src="${f.img}" class="vertImg">
|
||||||
|
</div>
|
||||||
|
<div class="stars">
|
||||||
|
<span class="_star" th:text="${f.stars}"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
|
<div class="up" style="grid-column: 11"><div class="board_field corner"><img src="../static/images/injail.png" style="width: 100%; height: 100%;" /></div></div>
|
||||||
|
<div class="board_field" style="grid-column: 11"><div class="rcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/lacoste.png" class="horImg"></div><div class="stars hstars"><span class="_star _hstar"></span></div></div></div>
|
||||||
|
<div class="board_field" style="grid-column: 11"><div class="rcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/mitsubishi.png" class="horImg"></div><div class="stars hstars"><span class="_star _hstar"></span></div></div></div>
|
||||||
|
<div class="board_field" style="grid-column: 11"><div class="rcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/vw.png" class="horImg"></div></div><div class="stars hstars"><span class="_star _hstar"></span></div></div>
|
||||||
|
<div class="board_field" style="grid-column: 11"><div class="rcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/mcdonalds.svg" class="horImg"></div><div class="stars hstars"><span class="_star _hstar"></span></div></div></div>
|
||||||
|
<div class="board_field" style="grid-column: 11"><div class="rcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/apple.png" class="horImg"></div><div class="stars hstars"><span class="_star _hstar"></span></div></div></div>
|
||||||
|
<div class="board_field" style="grid-column: 11"><div class="rcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/vw.png" class="horImg"></div><div class="stars hstars"><span class="_star _hstar"></span></div></div></div>
|
||||||
|
<div class="board_field" style="grid-column: 11"><div class="rcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/macdonalds.png" class="horImg"></div><div class="stars hstars"><span class="_star _hstar"></span></div></div></div>
|
||||||
|
<div class="board_field" style="grid-column: 11"><div class="rcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/nike.png" class="horImg"></div><div class="stars hstars"><span class="_star _hstar"></span></div></div></div>
|
||||||
|
<div class="board_field" style="grid-column: 11"><div class="rcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/pepsi.png" class="horImg"></div><div class="stars hstars"><span class="_star _hstar"></span></div></div></div>
|
||||||
|
<div class="board_field corner" style="grid-column: 11"><img src="../static/images/parking.png" style="width:100%; height:100%;" /></div>
|
||||||
|
<div class="board_field" style="grid-column: 2; grid-row: 11;"><div class="fh"><div class="iconH"><img src="../static/images/nike.png" class="vertImg"></div><div class="stars"><span class="_star _vstar"></span></div></div><div class="cost">0</div></div>
|
||||||
|
<div class="board_field" style="grid-column: 3; grid-row: 11;"><div class="fh"><div class="iconH"><img src="../static/images/mitsubishi.png" class="vertImg"></div><div class="stars"><span class="_star _vstar"></span></div></div><div class="cost">0</div></div>
|
||||||
|
<div class="board_field" style="grid-column: 4; grid-row: 11;"><div class="fh"><div class="iconH"><img src="../static/images/peugeot.png" class="vertImg"></div><div class="stars"><span class="_star _vstar"></span></div></div><div class="cost">0</div></div>
|
||||||
|
<div class="board_field" style="grid-column: 5; grid-row: 11;"><div class="fh"><div class="iconH"><img src="../static/images/vw.png" class="vertImg"></div><div class="stars"><span class="_star _vstar"></span></div></div><div class="cost">0</div></div>
|
||||||
|
<div class="board_field" style="grid-column: 6; grid-row: 11;"><div class="fh"><div class="iconH"><img src="../static/images/linux.png" class="vertImg"></div><div class="stars"><span class="_star _vstar"></span></div></div><div class="cost">0</div></div>
|
||||||
|
<div class="board_field" style="grid-column: 7; grid-row: 11;"><div class="fh"><div class="iconH"><img src="../static/images/apple.png" class="vertImg"></div><div class="stars"><span class="_star _vstar"></span></div></div><div class="cost">0</div></div>
|
||||||
|
<div class="board_field" style="grid-column: 8; grid-row: 11;"><div class="fh"><div class="iconH"><img src="../static/images/burger_king.svg" class="vertImg"></div><div class="stars"><span class="_star _vstar"></span></div></div><div class="cost">0</div></div>
|
||||||
|
<div class="board_field" style="grid-column: 9; grid-row: 11;"><div class="fh"><div class="iconH"><img src="../static/images/lacoste.png" class="vertImg"></div><div class="stars"><span class="_star _vstar"></span></div></div><div class="cost">0</div></div>
|
||||||
|
<div class="board_field" style="grid-column: 10; grid-row: 11;"><div class="fh"><div class="iconH"><img src="../static/images/pepsi.png" class="vertImg"></div><div class="stars"><span class="_star _vstar"></span></div></div><div class="cost">0</div></div>
|
||||||
|
<div class="board_field corner" style="grid-column: 1; grid-row: 11"><img src="../static/images/gotojail.png" style="width: 100%; height: 100%;" /></div>
|
||||||
|
<div class="board_field" style="grid-column: 1; grid-row: 10;"><div class="lcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/peugeot.png" class="horImg"></div><div class="stars hstars _hhstar"><span class="_star _hstar"></span></div></div></div>
|
||||||
|
<div class="board_field" style="grid-column: 1; grid-row: 9;"><div class="lcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/apple.png" class="horImg"></div><div class="stars hstars _hhstar"><span class="_star _hstar"></span></div></div></div>
|
||||||
|
<div class="board_field" style="grid-column: 1; grid-row: 8;"><div class="lcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/linux.png" class="horImg"></div><div class="stars hstars _hhstar"><span class="_star _hstar"></span></div></div></div>
|
||||||
|
<div class="board_field" style="grid-column: 1; grid-row: 7;"><div class="lcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/macdonalds.png" class="horImg"></div><div class="stars hstars _hhstar"><span class="_star _hstar"></span></div></div></div>
|
||||||
|
<div class="board_field" style="grid-column: 1; grid-row: 6;"><div class="lcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/pepsi.png" class="horImg"></div><div class="stars hstars _hhstar"><span class="_star _hstar"></span></div></div></div>
|
||||||
|
<div class="board_field" style="grid-column: 1; grid-row: 5;"><div class="lcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/burger_king.svg" class="horImg"></div><div class="stars hstars _hhstar"><span class="_star _hstar"></span></div></div></div>
|
||||||
|
<div class="board_field" style="grid-column: 1; grid-row: 4;"><div class="lcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/mitsubishi.png" class="horImg"></div><div class="stars hstars _hhstar"><span class="_star _hstar"></span></div></div></div>
|
||||||
|
<div class="board_field" style="grid-column: 1; grid-row: 3;"><div class="lcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/adidas.png" class="horImg"></div><div class="stars hstars _hhstar"><span class="_star _hstar"></span></div></div></div>
|
||||||
|
<div class="board_field" style="grid-column: 1; grid-row: 2;"><div class="lcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/lacoste.png" class="horImg"></div><div class="stars hstars _hhstar"><span class="_star _hstar"></span></div></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<footer>
|
||||||
|
<script type="text/javascript" src="/games/cookie.js"></script>
|
||||||
|
<script type="text/javascript" src="/games/db.js"></script>
|
||||||
|
<script type="text/javascript" src="/games/methods.js"></script>
|
||||||
|
<script type="text/javascript" src="game.js"></script>
|
||||||
|
<script type="text/javascript" src="/static/javascript/scale.js"></script>
|
||||||
|
</footer>
|
||||||
|
</html>
|
36
src/main/resources/templates/login.html
Normal file
36
src/main/resources/templates/login.html
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<th:block th:insert="~{fragments/head}"></th:block>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<th:block th:insert="~{fragments/navbar}"></th:block>
|
||||||
|
|
||||||
|
<div class="container mt-5 min-vh-100" style="max-width: 330px;">
|
||||||
|
<h4 class="text-center">Login</h4>
|
||||||
|
<th:block th:if="${param.error}">
|
||||||
|
<div class="alert alert-danger" role="alert" th:text="#{login_error}"></div>
|
||||||
|
</th:block>
|
||||||
|
<th:block th:if="${error}">
|
||||||
|
<div class="alert alert-danger" role="alert" th:text="#{login_error}"></div>
|
||||||
|
</th:block>
|
||||||
|
<form method="post" th:action="@{/login}">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="username" class="form-label"><ion-icon name="at-outline"></ion-icon> Nickname</label>
|
||||||
|
<input type="text" name="username" class="form-control" id="username">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password" class="form-label"><ion-icon name="key-outline"></ion-icon> Password</label>
|
||||||
|
<input type="password" name="password" class="form-control" id="password">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<p>Don't have an account? <a href="/signup">Sign up</a></p>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Login</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<th:block th:insert="~{fragments/footer}"></th:block>
|
||||||
|
<th:block th:insert="~{fragments/essentials}"></th:block>
|
||||||
|
</body>
|
||||||
|
</html>
|
48
src/main/resources/templates/signup.html
Normal file
48
src/main/resources/templates/signup.html
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<th:block th:insert="~{fragments/head}"></th:block>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<th:block th:insert="~{fragments/navbar}"></th:block>
|
||||||
|
|
||||||
|
<div class="container mt-5 min-vh-100" style="max-width: 330px;">
|
||||||
|
<h4 class="text-center">Sign up</h4>
|
||||||
|
<th:block th:if="${param.error}">
|
||||||
|
<div class="alert alert-danger" role="alert" th:text="#{login_error}"></div>
|
||||||
|
</th:block>
|
||||||
|
<th:block th:if="${error}">
|
||||||
|
<div class="alert alert-danger" role="alert" th:text="#{login_error}"></div>
|
||||||
|
</th:block>
|
||||||
|
<form method="post" th:action="@{/signup}" th:object="${user}">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="username" class="form-label" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="This nickname will see other users and they will be able tag you like: @username"><ion-icon name="at-outline"></ion-icon> Nickname</label>
|
||||||
|
<input type="text" name="username" class="form-control" id="username">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password" class="form-label" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="Password must have at least 7 symbols, include letters, digits, any of special symbols (#$%:;~|&^!_\/-+*)"><ion-icon name="key-outline"></ion-icon> Password</label>
|
||||||
|
<input type="password" name="password" class="form-control" id="password">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="invite_input">Invite code</label>
|
||||||
|
<input type="text" name="invite_code" class="form-control" id="invite_input">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" value="" id="flexCheckDefault">
|
||||||
|
<label class="form-check-label" for="flexCheckDefault">
|
||||||
|
I have read the terms and conditions
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Sign up</button>
|
||||||
|
<div class="mb-3">
|
||||||
|
<p>Already have an account? <a href="/login">Login</a></p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<th:block th:insert="~{fragments/footer}"></th:block>
|
||||||
|
<th:block th:insert="~{fragments/essentials}"></th:block>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
x
Reference in New Issue
Block a user