basic js: store/context, domain models, utils, router
This commit is contained in:
parent
a05e250888
commit
9191e6bf16
@ -1,5 +1,6 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Build & Run" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot">
|
||||
<option name="ACTIVE_PROFILES" value="dev" />
|
||||
<module name="server" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="ru.tubryansk.tdms.TdmsApplication" />
|
||||
<method v="2">
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Clean, Build & Run" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot">
|
||||
<option name="ACTIVE_PROFILES" value="dev" />
|
||||
<module name="server" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="ru.tubryansk.tdms.TdmsApplication" />
|
||||
<method v="2">
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Run" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot">
|
||||
<option name="ACTIVE_PROFILES" value="dev" />
|
||||
<module name="server" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="ru.tubryansk.tdms.TdmsApplication" />
|
||||
<method v="2" />
|
||||
|
||||
6
mvnw.cmd
vendored
6
mvnw.cmd
vendored
@ -32,7 +32,7 @@
|
||||
@SET __MVNW_ERROR__=
|
||||
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
|
||||
@SET PSModulePath=
|
||||
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
|
||||
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-PageContent -Raw '%~f0'))) -NoNewScope}"`) DO @(
|
||||
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
|
||||
)
|
||||
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
|
||||
@ -51,7 +51,7 @@ if ($env:MVNW_VERBOSE -eq "true") {
|
||||
}
|
||||
|
||||
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
|
||||
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
|
||||
$distributionUrl = (Get-PageContent -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
|
||||
if (!$distributionUrl) {
|
||||
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||
}
|
||||
@ -121,7 +121,7 @@ if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
|
||||
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
|
||||
|
||||
# If specified, validate the SHA-256 sum of the Maven distribution zip file
|
||||
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
|
||||
$distributionSha256Sum = (Get-PageContent -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
|
||||
if ($distributionSha256Sum) {
|
||||
if ($USE_MVND) {
|
||||
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
|
||||
|
||||
12
pom.xml
12
pom.xml
@ -3,6 +3,12 @@
|
||||
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>
|
||||
|
||||
<properties>
|
||||
<name>TDMS</name>
|
||||
<description>Thesis-Defense-Management-System</description>
|
||||
<version>0.0.1</version>
|
||||
</properties>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
@ -12,11 +18,11 @@
|
||||
|
||||
<groupId>ru.tubryansk</groupId>
|
||||
<artifactId>tdms</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<version>0.0.1</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>TDMS</name>
|
||||
<description>Thesis-Defense-Management-System</description>
|
||||
<name>${name}</name>
|
||||
<description>${description}</description>
|
||||
|
||||
<modules>
|
||||
<module>web</module>
|
||||
|
||||
@ -7,12 +7,12 @@
|
||||
<parent>
|
||||
<groupId>ru.tubryansk</groupId>
|
||||
<artifactId>tdms</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<version>0.0.1</version>
|
||||
</parent>
|
||||
|
||||
<groupId>ru.tubryansk.tdms</groupId>
|
||||
<artifactId>server</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<version>0.0.1</version>
|
||||
|
||||
<name>TDMS :: SERVER</name>
|
||||
|
||||
|
||||
@ -1,15 +1,18 @@
|
||||
package ru.tubryansk.tdms;
|
||||
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
|
||||
@SpringBootApplication
|
||||
@Slf4j
|
||||
public class TdmsApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(TdmsApplication.class, args);
|
||||
ConfigurableApplicationContext context = SpringApplication.run(TdmsApplication.class, args);
|
||||
String staticLocation = context.getEnvironment().getProperty("spring.web.resources.static-locations");
|
||||
log.info("Static location: {}", staticLocation);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package ru.tubryansk.tdms.config;
|
||||
|
||||
import jakarta.servlet.http.HttpSessionEvent;
|
||||
import jakarta.servlet.http.HttpSessionListener;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
@ -18,12 +19,12 @@ import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
|
||||
import static org.springframework.security.web.context.HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;
|
||||
|
||||
@ -31,19 +32,32 @@ import static org.springframework.security.web.context.HttpSessionSecurityContex
|
||||
@Configuration
|
||||
public class SecurityConfiguration {
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity, AuthenticationManager authenticationManager) throws Exception {
|
||||
return httpSecurity
|
||||
.authorizeHttpRequests(this::configureHttpAuthorization)
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.cors(AbstractHttpConfigurer::disable)
|
||||
.authenticationManager(authenticationManager())
|
||||
.cors(a -> a.configurationSource(corsConfiguration()))
|
||||
.authenticationManager(authenticationManager)
|
||||
.sessionManagement(this::configureSessionManagement)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfiguration() {
|
||||
return request -> {
|
||||
CorsConfiguration corsConfiguration = new CorsConfiguration();
|
||||
corsConfiguration.applyPermitDefaultValues();
|
||||
corsConfiguration.addAllowedMethod("DELETE");
|
||||
corsConfiguration.addAllowedMethod("PUT");
|
||||
corsConfiguration.addAllowedMethod("PATCH");
|
||||
return corsConfiguration;
|
||||
};
|
||||
}
|
||||
|
||||
private void configureHttpAuthorization(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry httpAuthorization) {
|
||||
/* API ROUTES */
|
||||
httpAuthorization.requestMatchers("/api/diploma-topic/**").permitAll();
|
||||
httpAuthorization.requestMatchers("/api/v1/diploma-topic/**").permitAll();
|
||||
httpAuthorization.requestMatchers("/api/v1/user/**").permitAll();
|
||||
httpAuthorization.requestMatchers("/api/**").denyAll();
|
||||
/* STATIC ROUTES */
|
||||
httpAuthorization.requestMatchers("/**").permitAll();
|
||||
@ -52,38 +66,29 @@ public class SecurityConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager() {
|
||||
|
||||
return new ProviderManager(authenticationProvider());
|
||||
public AuthenticationManager authenticationManager(UserDetailsService userDetailsService) {
|
||||
return new ProviderManager(authenticationProvider(userDetailsService));
|
||||
}
|
||||
|
||||
private AuthenticationProvider authenticationProvider() {
|
||||
private AuthenticationProvider authenticationProvider(UserDetailsService userDetailsService) {
|
||||
DaoAuthenticationProvider provider = new DaoAuthenticationProvider(passwordEncoder());
|
||||
provider.setUserDetailsService(userDetailsService());
|
||||
provider.setUserDetailsService(userDetailsService);
|
||||
return provider;
|
||||
}
|
||||
|
||||
private UserDetailsService userDetailsService() {
|
||||
return new InMemoryUserDetailsManager(User.builder()
|
||||
.username("admin")
|
||||
.password("{noop}admin")
|
||||
.authorities("ROLE_STUDENT", "ROLE_TEACHER", "ROLE_CURATOR")
|
||||
.build());
|
||||
}
|
||||
|
||||
private PasswordEncoder passwordEncoder() {
|
||||
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
// todo: remove when login/logout is implemented
|
||||
public HttpSessionListener autoAuthenticateUnderAdmin() {
|
||||
public HttpSessionListener autoAuthenticateUnderAdmin(AuthenticationManager authenticationManager) {
|
||||
return new HttpSessionListener() {
|
||||
@Override
|
||||
public void sessionCreated(HttpSessionEvent se) {
|
||||
LoggerFactory.getLogger(this.getClass()).info("Session created {}. Authenticated, as izrailev_v_ya_1", se.getSession().getId());
|
||||
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken("admin", "admin");
|
||||
Authentication authenticated = authenticationManager().authenticate(authentication);
|
||||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken("izrailev_v_ya_1", "1");
|
||||
Authentication authenticated = authenticationManager.authenticate(authentication);
|
||||
context.setAuthentication(authenticated);
|
||||
SecurityContextHolder.setContext(context);
|
||||
se.getSession().setAttribute(SPRING_SECURITY_CONTEXT_KEY, context);
|
||||
|
||||
@ -15,9 +15,9 @@ import java.util.List;
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/diploma-topic/")
|
||||
@RequestMapping("/api/v1/diploma-topic/")
|
||||
@Validated
|
||||
public class DiplomaTopicRestController {
|
||||
public class DiplomaTopicController {
|
||||
@Autowired
|
||||
private DiplomaTopicService diplomaTopicService;
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
package ru.tubryansk.tdms.controller;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import ru.tubryansk.tdms.dto.UserDTO;
|
||||
import ru.tubryansk.tdms.entity.User;
|
||||
import ru.tubryansk.tdms.service.UserService;
|
||||
|
||||
@RestController
|
||||
@Validated
|
||||
@RequestMapping("/api/v1/user")
|
||||
@Slf4j
|
||||
public class UserController {
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@GetMapping("/current")
|
||||
public UserDTO getCurrentUser() {
|
||||
User principal = userService.getCallerPrincipal();
|
||||
return principal != null ? UserDTO.from(principal, true) : UserDTO.fromUnauthenticated();
|
||||
}
|
||||
|
||||
@PostMapping("/logout")
|
||||
public void logout() {
|
||||
userService.logout();
|
||||
}
|
||||
}
|
||||
@ -1,26 +1,43 @@
|
||||
package ru.tubryansk.tdms.dto;
|
||||
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import ru.tubryansk.tdms.entity.Role;
|
||||
import ru.tubryansk.tdms.entity.User;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class UserDTO {
|
||||
private Integer id;
|
||||
private String login;
|
||||
private String password;
|
||||
private String fullName;
|
||||
private String mail;
|
||||
private String numberPhone;
|
||||
private ZonedDateTime createAt;
|
||||
private ZonedDateTime updateAt;
|
||||
List<Integer> roleId;
|
||||
@Builder
|
||||
public record UserDTO(
|
||||
boolean authenticated,
|
||||
String login,
|
||||
String password,
|
||||
String fullName,
|
||||
String email,
|
||||
String phoneNumber,
|
||||
ZonedDateTime createdAt,
|
||||
ZonedDateTime updatedAt,
|
||||
List<String> authorities) {
|
||||
|
||||
public static UserDTO fromUnauthenticated() {
|
||||
return UserDTO.builder()
|
||||
.authenticated(false)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static UserDTO from(User user, boolean anonymize) {
|
||||
return UserDTO.builder()
|
||||
.authenticated(true)
|
||||
.login(user.getLogin())
|
||||
.password(anonymize ? "" : user.getPassword())
|
||||
.fullName(user.getFullName())
|
||||
.email(user.getMail())
|
||||
.phoneNumber(user.getNumberPhone())
|
||||
.createdAt(user.getCreateAt())
|
||||
.updatedAt(user.getUpdateAt())
|
||||
.authorities(user.getRoles().stream().map(Role::getAuthority).toList())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
|
||||
@Entity
|
||||
@ -14,11 +15,16 @@ import lombok.Setter;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table(name = "role", schema = "vkr")
|
||||
public class Role {
|
||||
public class Role implements GrantedAuthority {
|
||||
@Id
|
||||
@Column(name = "id")
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Integer id;
|
||||
@Column(name = "name", nullable = false)
|
||||
private String name;
|
||||
|
||||
@Override
|
||||
public String getAuthority() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,8 +6,12 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@ -17,7 +21,7 @@ import java.util.List;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table(name = "user", schema = "vkr")
|
||||
public class User {
|
||||
public class User implements UserDetails {
|
||||
@Id
|
||||
@Column(name = "id")
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@ -36,10 +40,19 @@ public class User {
|
||||
private ZonedDateTime createAt;
|
||||
@Column(name = "update_at")
|
||||
private ZonedDateTime updateAt;
|
||||
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||
@JoinTable(name = "user_role",schema = "vkr",
|
||||
joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
|
||||
inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))
|
||||
private List<Role> userLoyaltyLevels;
|
||||
private List<Role> roles;
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return roles.stream().map(Role::getAuthority).map(SimpleGrantedAuthority::new).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return login;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
package ru.tubryansk.tdms.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import ru.tubryansk.tdms.entity.User;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Integer> {
|
||||
Optional<User> findUserByLogin(String login);
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
package ru.tubryansk.tdms.service;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import jakarta.transaction.Transactional;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.tubryansk.tdms.entity.User;
|
||||
import ru.tubryansk.tdms.repository.UserRepository;
|
||||
|
||||
import static org.springframework.security.web.context.HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
@Slf4j
|
||||
public class UserService implements UserDetailsService {
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
@Autowired
|
||||
private HttpServletRequest httpServletRequest;
|
||||
|
||||
public User getCallerPrincipal() {
|
||||
if(!authenticated()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
}
|
||||
|
||||
public boolean authenticated() {
|
||||
var authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
return authentication.isAuthenticated() && !(authentication instanceof AnonymousAuthenticationToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public User loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
return userRepository.findUserByLogin(username)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||
}
|
||||
|
||||
public void logout() {
|
||||
HttpSession session = httpServletRequest.getSession(true);
|
||||
// if(session != null) {
|
||||
// session.invalidate();
|
||||
// }
|
||||
session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, null);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"name": "db.url",
|
||||
"type": "java.lang.String",
|
||||
"description": "Database url."
|
||||
},
|
||||
{
|
||||
"name": "db.user",
|
||||
"type": "java.lang.String",
|
||||
"description": "Database user."
|
||||
},
|
||||
{
|
||||
"name": "db.password",
|
||||
"type": "java.lang.String",
|
||||
"description": "Database password."
|
||||
},
|
||||
{
|
||||
"name": "db.schema",
|
||||
"type": "java.lang.String",
|
||||
"description": "Database schema."
|
||||
},
|
||||
{
|
||||
"name": "application.name",
|
||||
"type": "java.lang.String",
|
||||
"description": "Service name."
|
||||
},
|
||||
{
|
||||
"name": "application.version",
|
||||
"type": "java.lang.String",
|
||||
"description": "Service version."
|
||||
},
|
||||
{
|
||||
"name": "application.type",
|
||||
"type": "java.lang.String",
|
||||
"description": "Service build type."
|
||||
},
|
||||
{
|
||||
"name": "application.domain",
|
||||
"type": "java.lang.String",
|
||||
"description": "Service domain."
|
||||
},
|
||||
{
|
||||
"name": "application.port",
|
||||
"type": "java.lang.Integer",
|
||||
"description": "Service port."
|
||||
},
|
||||
{
|
||||
"name": "application.protocol",
|
||||
"type": "java.lang.String",
|
||||
"description": "Service protocol."
|
||||
}
|
||||
] }
|
||||
17
server/src/main/resources/application-dev.yml
Normal file
17
server/src/main/resources/application-dev.yml
Normal file
@ -0,0 +1,17 @@
|
||||
application:
|
||||
type: development
|
||||
port: 8080
|
||||
domain: localhost
|
||||
protocol: http
|
||||
spring:
|
||||
flyway:
|
||||
out-of-order: true
|
||||
web:
|
||||
resources:
|
||||
static-locations: file://${user.dir}/web/dist/
|
||||
chain:
|
||||
cache: false
|
||||
compressed: false
|
||||
server:
|
||||
compression:
|
||||
enabled: false
|
||||
@ -1,23 +1,36 @@
|
||||
db:
|
||||
url: jdbc:postgresql://localhost:5432/db
|
||||
user: root
|
||||
password: root
|
||||
schema: vkr
|
||||
application:
|
||||
name: @name@
|
||||
version: @version@
|
||||
type: production
|
||||
port: 80
|
||||
domain: tdms.tu-bryansk.ru
|
||||
protocol: https
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: tdms
|
||||
datasource:
|
||||
url: jdbc:postgresql://localhost:5432/db
|
||||
username: root
|
||||
password: root
|
||||
driver-class-name: org.postgresql.Driver
|
||||
url: ${db.url}
|
||||
username: ${db.user}
|
||||
password: ${db.password}
|
||||
hikari:
|
||||
schema: ${db.schema}
|
||||
jpa:
|
||||
open-in-view: false
|
||||
show-sql: true
|
||||
hibernate.ddl-auto: validate
|
||||
database: postgresql
|
||||
properties.hibernate:
|
||||
pretty_print: true
|
||||
format_sql: true
|
||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||
flyway:
|
||||
user: root
|
||||
password: root
|
||||
schemas: vkr
|
||||
url: ${db.url}
|
||||
user: ${db.user}
|
||||
password: ${db.password}
|
||||
schemas: ${db.schema}
|
||||
locations: db.migration
|
||||
url: jdbc:postgresql://localhost:5432/db
|
||||
server:
|
||||
port: ${application.port}
|
||||
address: ${application.domain}
|
||||
compression:
|
||||
enabled: true
|
||||
|
||||
@ -0,0 +1 @@
|
||||
UPDATE vkr.user SET password = '{noop}1';
|
||||
@ -7,6 +7,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
<script type="module" src="/src/Application.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
245
web/package-lock.json
generated
245
web/package-lock.json
generated
@ -9,9 +9,11 @@
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@react-buddy/ide-toolbox": "^2.4.0",
|
||||
"axios": "^1.7.7",
|
||||
"bootstrap": "^5.3.3",
|
||||
"mobx": "^6.13.1",
|
||||
"mobx-react": "^9.1.1",
|
||||
"mobx-state-router": "^6.0.1",
|
||||
"react": "^18.3.1",
|
||||
"react-bootstrap": "^2.10.4",
|
||||
"react-dom": "^18.3.1"
|
||||
@ -104,12 +106,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz",
|
||||
"integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==",
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz",
|
||||
"integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.25.0",
|
||||
"@babel/types": "^7.25.6",
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"jsesc": "^2.5.1"
|
||||
@ -252,12 +254,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.25.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz",
|
||||
"integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==",
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz",
|
||||
"integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.25.2"
|
||||
"@babel/types": "^7.25.6"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
@ -322,16 +324,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.25.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz",
|
||||
"integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==",
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz",
|
||||
"integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.24.7",
|
||||
"@babel/generator": "^7.25.0",
|
||||
"@babel/parser": "^7.25.3",
|
||||
"@babel/generator": "^7.25.6",
|
||||
"@babel/parser": "^7.25.6",
|
||||
"@babel/template": "^7.25.0",
|
||||
"@babel/types": "^7.25.2",
|
||||
"@babel/types": "^7.25.6",
|
||||
"debug": "^4.3.1",
|
||||
"globals": "^11.1.0"
|
||||
},
|
||||
@ -340,9 +342,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz",
|
||||
"integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==",
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz",
|
||||
"integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.24.8",
|
||||
@ -985,6 +987,14 @@
|
||||
"react": "^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-force/utils": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-force/utils/-/utils-2.3.0.tgz",
|
||||
"integrity": "sha512-HdGqlVbNifHN0DL0kybtfuz3oXKk5B8nTkiZwMtMZGrYPOjw0WmHSLT0ptlyXkPUzIrJoRnazL//L3jHZghaCQ==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@restart/hooks": {
|
||||
"version": "0.4.16",
|
||||
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz",
|
||||
@ -1606,6 +1616,21 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.7.7",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
|
||||
"integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@ -1746,6 +1771,17 @@
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@ -1781,7 +1817,6 @@
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
|
||||
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
@ -1794,12 +1829,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/decode-uri-component": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
|
||||
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-is": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dequal": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||
@ -2264,6 +2315,14 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/filter-obj": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
|
||||
"integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
||||
@ -2300,6 +2359,38 @@
|
||||
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
@ -2428,6 +2519,19 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/history": {
|
||||
"version": "4.10.1",
|
||||
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
|
||||
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"loose-envify": "^1.2.0",
|
||||
"resolve-pathname": "^3.0.0",
|
||||
"tiny-invariant": "^1.0.2",
|
||||
"tiny-warning": "^1.0.0",
|
||||
"value-equal": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
|
||||
@ -2676,6 +2780,25 @@
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
@ -2748,11 +2871,30 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/mobx-state-router": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mobx-state-router/-/mobx-state-router-6.0.1.tgz",
|
||||
"integrity": "sha512-tb2FCzb4Owxh6MQ+FkFC6ntJfM0TcOy2UG/3YBcRm/6hLhr42XG+ijOoQydf9C66DY7ihYVzf8LhSHvr8ie4fA==",
|
||||
"dependencies": {
|
||||
"@react-force/utils": "^2.3.0",
|
||||
"debug": "^4.3.3",
|
||||
"history": "^4.10.1",
|
||||
"path-to-regexp": "^6.2.0",
|
||||
"query-string": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"mobx": ">=6",
|
||||
"mobx-react-lite": ">=3",
|
||||
"react": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.7",
|
||||
@ -2887,6 +3029,11 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
|
||||
"integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="
|
||||
},
|
||||
"node_modules/path-type": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||
@ -2973,6 +3120,11 @@
|
||||
"react": ">=0.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
@ -2982,6 +3134,23 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/query-string": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz",
|
||||
"integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==",
|
||||
"dependencies": {
|
||||
"decode-uri-component": "^0.2.2",
|
||||
"filter-obj": "^1.1.0",
|
||||
"split-on-first": "^1.0.0",
|
||||
"strict-uri-encode": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
@ -3102,6 +3271,11 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-pathname": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
|
||||
"integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
|
||||
},
|
||||
"node_modules/reusify": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
|
||||
@ -3245,6 +3419,22 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/split-on-first": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
|
||||
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/strict-uri-encode": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
|
||||
"integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
@ -3287,6 +3477,16 @@
|
||||
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/tiny-invariant": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
|
||||
},
|
||||
"node_modules/tiny-warning": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
|
||||
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
|
||||
},
|
||||
"node_modules/to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
@ -3423,6 +3623,11 @@
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/value-equal": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
|
||||
"integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.3.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz",
|
||||
|
||||
@ -10,13 +10,15 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-buddy/ide-toolbox": "^2.4.0",
|
||||
"axios": "^1.7.7",
|
||||
"bootstrap": "^5.3.3",
|
||||
"mobx": "^6.13.1",
|
||||
"mobx-react": "^9.1.1",
|
||||
"mobx-state-router": "^6.0.1",
|
||||
"react": "^18.3.1",
|
||||
"react-bootstrap": "^2.10.4",
|
||||
"react-dom": "^18.3.1",
|
||||
"@react-buddy/ide-toolbox": "^2.4.0"
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.3.3",
|
||||
|
||||
@ -6,12 +6,12 @@
|
||||
<parent>
|
||||
<groupId>ru.tubryansk</groupId>
|
||||
<artifactId>tdms</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<version>0.0.1</version>
|
||||
</parent>
|
||||
|
||||
<groupId>ru.tubryansk.tdms</groupId>
|
||||
<artifactId>web</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<version>0.0.1</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>TDMS :: WEB</name>
|
||||
|
||||
20
web/src/Application.tsx
Normal file
20
web/src/Application.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import React from "react";
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import './index.css'
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import {RouterContext, RouterView} from "mobx-state-router";
|
||||
import {initApp} from "./utils/init.ts";
|
||||
import {MyRouterStore} from "./store/MyRouterStore.ts";
|
||||
import { RootStoreContext } from "./store/RootStore.tsx";
|
||||
|
||||
const rootStore = initApp();
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<RootStoreContext.Provider value={rootStore}>
|
||||
<RouterContext.Provider value={rootStore.routerStore}>
|
||||
<RouterView viewMap={MyRouterStore.makeViewMap()} />
|
||||
</RouterContext.Provider>
|
||||
</RootStoreContext.Provider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
@ -1,60 +0,0 @@
|
||||
import {GitHubLogo} from './Svgs.tsx';
|
||||
import {Container, Nav, Navbar, NavDropdown} from "react-bootstrap";
|
||||
|
||||
const Header = () =>
|
||||
<header>
|
||||
<Navbar className="bg-body-tertiary" fixed="top">
|
||||
<Container>
|
||||
<Navbar.Brand className="" href="#">TDMS</Navbar.Brand>
|
||||
<Nav>
|
||||
<NavDropdown title="Группы">
|
||||
<NavDropdown.Item href="#">Список</NavDropdown.Item>
|
||||
<NavDropdown.Item href="#">Редактировать</NavDropdown.Item>
|
||||
</NavDropdown>
|
||||
</Nav>
|
||||
|
||||
<Nav className="ms-auto">
|
||||
<Navbar.Text>Пользователь:</Navbar.Text>
|
||||
<NavDropdown title="Фамилия И. О." id="basic-nav-dropdown">
|
||||
<NavDropdown.Item href="#">Моя страница</NavDropdown.Item>
|
||||
<NavDropdown.Divider/>
|
||||
<NavDropdown.Item href="#">Выйти</NavDropdown.Item>
|
||||
</NavDropdown>
|
||||
</Nav>
|
||||
</Container>
|
||||
</Navbar>
|
||||
</header>
|
||||
|
||||
const Footer = () => {
|
||||
return (
|
||||
<footer>
|
||||
<Navbar className="bg-body-tertiary">
|
||||
<Container>
|
||||
<Navbar.Text>Thesis Defence Management System ©</Navbar.Text>
|
||||
<Nav>
|
||||
<Nav.Link href="https://github.com/Velixeor/Thesis-Defense-Management-System">
|
||||
<GitHubLogo width={32} height={32}/>
|
||||
</Nav.Link>
|
||||
</Nav>
|
||||
</Container>
|
||||
</Navbar>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
const Content = () =>
|
||||
<article>
|
||||
<div className="text-center mt-5">There you can see some dashboards in future :)</div>
|
||||
</article>
|
||||
|
||||
function MainPage() {
|
||||
return (
|
||||
<>
|
||||
<Header/>
|
||||
<Content/>
|
||||
<Footer/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default MainPage;
|
||||
19
web/src/components/Page/DefaultPage.tsx
Normal file
19
web/src/components/Page/DefaultPage.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import {Component, ReactNode} from "react";
|
||||
import Header from "./Header.tsx";
|
||||
import {Container} from "react-bootstrap";
|
||||
import Footer from "./Footer.tsx";
|
||||
|
||||
export abstract class DefaultPage extends Component {
|
||||
abstract get page(): ReactNode;
|
||||
// declare context: ContextType<typeof RootStoreContext>
|
||||
|
||||
render() {
|
||||
return <>
|
||||
<Header/>
|
||||
<Container className={"mt-5 mb-5"}>
|
||||
{this.page}
|
||||
</Container>
|
||||
<Footer/>
|
||||
</>
|
||||
}
|
||||
}
|
||||
7
web/src/components/Page/Error.tsx
Normal file
7
web/src/components/Page/Error.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import {DefaultPage} from "./DefaultPage.tsx";
|
||||
|
||||
export default class Error extends DefaultPage {
|
||||
get page() {
|
||||
return <h1>Error</h1>
|
||||
}
|
||||
}
|
||||
21
web/src/components/Page/Footer.tsx
Normal file
21
web/src/components/Page/Footer.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import {Container, Nav, Navbar} from "react-bootstrap";
|
||||
import {GitHubLogo} from "../../utils/svg.tsx";
|
||||
|
||||
const Footer = () => {
|
||||
return (
|
||||
<footer>
|
||||
<Navbar className="bg-body-tertiary">
|
||||
<Container>
|
||||
<Navbar.Text>Thesis Defence Management System ©</Navbar.Text>
|
||||
<Nav>
|
||||
<Nav.Link href="https://github.com/Velixeor/Thesis-Defense-Management-System">
|
||||
<GitHubLogo width={32} height={32}/>
|
||||
</Nav.Link>
|
||||
</Nav>
|
||||
</Container>
|
||||
</Navbar>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
export default Footer;
|
||||
49
web/src/components/Page/Header.tsx
Normal file
49
web/src/components/Page/Header.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import {Container, Nav, Navbar, NavDropdown} from "react-bootstrap";
|
||||
import {FC} from "react";
|
||||
import {RouterLink} from "mobx-state-router";
|
||||
import {useRootStore} from "../../store/RootStore.tsx";
|
||||
import {IAuthenticated} from "../../models/user.ts";
|
||||
import {observer} from "mobx-react";
|
||||
|
||||
export const Header: FC = observer(() => {
|
||||
const store = useRootStore();
|
||||
const user = store.userStore.user;
|
||||
|
||||
return <header>
|
||||
<Navbar className="bg-body-tertiary" fixed="top">
|
||||
<Container>
|
||||
<Navbar.Brand>
|
||||
<Nav.Link as={RouterLink} routeName='root'>TDMS</Nav.Link>
|
||||
</Navbar.Brand>
|
||||
<Nav>
|
||||
<NavDropdown title="Группы">
|
||||
<NavDropdown.Item>Список</NavDropdown.Item>
|
||||
<NavDropdown.Item>Редактировать</NavDropdown.Item>
|
||||
</NavDropdown>
|
||||
</Nav>
|
||||
|
||||
<Nav className="ms-auto">
|
||||
{
|
||||
user.authenticated &&
|
||||
<>
|
||||
<Navbar.Text>Пользователь:</Navbar.Text>
|
||||
<NavDropdown
|
||||
title={(user as IAuthenticated).fullName}>
|
||||
<NavDropdown.Item>Моя страница</NavDropdown.Item>
|
||||
<NavDropdown.Divider/>
|
||||
<NavDropdown.Item onClick={() => store.userStore.logout()}>Выйти</NavDropdown.Item>
|
||||
</NavDropdown>
|
||||
</>
|
||||
}
|
||||
|
||||
{
|
||||
!user.authenticated &&
|
||||
<Nav.Link as={RouterLink} routeName='login'>Войти</Nav.Link>
|
||||
}
|
||||
</Nav>
|
||||
</Container>
|
||||
</Navbar>
|
||||
</header>
|
||||
});
|
||||
|
||||
export default Header;
|
||||
7
web/src/components/Page/Root.tsx
Normal file
7
web/src/components/Page/Root.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import {DefaultPage} from "./DefaultPage.tsx";
|
||||
|
||||
export default class Root extends DefaultPage {
|
||||
get page() {
|
||||
return <h1>Home</h1>
|
||||
}
|
||||
}
|
||||
7
web/src/components/Page/UserProfile.tsx
Normal file
7
web/src/components/Page/UserProfile.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import {DefaultPage} from "./DefaultPage.tsx";
|
||||
|
||||
export default class UserProfile extends DefaultPage {
|
||||
get page() {
|
||||
return <h1>User Profile</h1>
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,4 @@
|
||||
/* Reset css */
|
||||
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
import React from "react";
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import MainPage from './components/MainPage.tsx'
|
||||
import './index.css'
|
||||
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<MainPage/>
|
||||
</React.StrictMode>,
|
||||
)
|
||||
13
web/src/models/user.ts
Normal file
13
web/src/models/user.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export interface IAuthenticated {
|
||||
authenticated: true,
|
||||
login: string,
|
||||
password: string,
|
||||
fullName: string,
|
||||
email: string,
|
||||
phone: string,
|
||||
createdAt: string,
|
||||
updatedAt: string,
|
||||
authorities: string[],
|
||||
}
|
||||
|
||||
export declare type IUser = {authenticated: false} | IAuthenticated;
|
||||
24
web/src/routes.tsx
Normal file
24
web/src/routes.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import UserProfile from "./components/Page/UserProfile.tsx";
|
||||
import React from "react";
|
||||
import Root from "./components/Page/Root.tsx";
|
||||
import Error from "./components/Page/Error.tsx";
|
||||
|
||||
interface Route {
|
||||
name: string;
|
||||
pattern: string;
|
||||
view: React.ReactElement;
|
||||
}
|
||||
|
||||
export const routes: Route[] = [{
|
||||
name: 'root',
|
||||
pattern: '/',
|
||||
view: <Root/>,
|
||||
}, {
|
||||
name: 'profile',
|
||||
pattern: '/profile',
|
||||
view: <UserProfile/>,
|
||||
}, {
|
||||
name: 'error',
|
||||
pattern: '/error',
|
||||
view: <Error/>,
|
||||
}];
|
||||
3
web/src/services/UserService.ts
Normal file
3
web/src/services/UserService.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export default class UserService {
|
||||
|
||||
}
|
||||
30
web/src/store/MyRouterStore.ts
Normal file
30
web/src/store/MyRouterStore.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {browserHistory, createRouterState, HistoryAdapter, RouterStore} from "mobx-state-router";
|
||||
import {routes} from "../routes.tsx";
|
||||
import {RootStore} from "./RootStore.tsx";
|
||||
|
||||
|
||||
export class MyRouterStore extends RouterStore {
|
||||
constructor(rootStore: RootStore) {
|
||||
super(MyRouterStore.makeRoutesMap(),
|
||||
createRouterState('error', {notFound: true}),
|
||||
{rootStore: rootStore});
|
||||
}
|
||||
|
||||
static makeViewMap() {
|
||||
return routes.reduce<any>((map, route) => {
|
||||
map[route.name] = route.view;
|
||||
return map;
|
||||
}, {});
|
||||
}
|
||||
|
||||
static makeRoutesMap() {
|
||||
return routes.map(route => {
|
||||
return {...route, view: undefined};
|
||||
});
|
||||
}
|
||||
|
||||
init() {
|
||||
const historyAdapter = new HistoryAdapter(this, browserHistory);
|
||||
historyAdapter.observeRouterStateChanges();
|
||||
}
|
||||
}
|
||||
31
web/src/store/RootStore.tsx
Normal file
31
web/src/store/RootStore.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import {UserStore} from "./UserStore.ts";
|
||||
import {MyRouterStore} from "./MyRouterStore.ts";
|
||||
import {createContext, useContext} from "react";
|
||||
|
||||
export class RootStore {
|
||||
userStore = new UserStore(this);
|
||||
routerStore = new MyRouterStore(this);
|
||||
|
||||
constructor() {
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.userStore.init();
|
||||
this.routerStore.init();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export const RootStoreContext = createContext<RootStore | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
export function useRootStore(): RootStore {
|
||||
const rootStore = useContext(RootStoreContext);
|
||||
if (rootStore === undefined) {
|
||||
throw new Error('useRootStore must be used within a RootStoreProvider');
|
||||
}
|
||||
|
||||
return rootStore;
|
||||
}
|
||||
30
web/src/store/UserStore.ts
Normal file
30
web/src/store/UserStore.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {get, post} from "../utils/request.tsx";
|
||||
import {makeObservable, observable, runInAction} from "mobx";
|
||||
import {RootStore} from "./RootStore.ts";
|
||||
import {IUser} from "../models/user.ts";
|
||||
|
||||
export class UserStore {
|
||||
rootStore: RootStore;
|
||||
user: IUser = {authenticated: false};
|
||||
|
||||
constructor(rootStore: RootStore) {
|
||||
makeObservable(this, {
|
||||
user: observable,
|
||||
});
|
||||
this.rootStore = rootStore;
|
||||
}
|
||||
|
||||
init() {
|
||||
get<IUser>('/user/current').then((response) => {
|
||||
runInAction(() => {
|
||||
this.user = response;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
logout() {
|
||||
post('/user/logout').then(() => {
|
||||
this.init();
|
||||
});
|
||||
}
|
||||
}
|
||||
12
web/src/utils/init.ts
Normal file
12
web/src/utils/init.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import {configure} from "mobx";
|
||||
import {RootStore} from "../store/RootStore.tsx";
|
||||
|
||||
|
||||
export const initMobX = () => {
|
||||
configure({enforceActions: 'observed'});
|
||||
}
|
||||
|
||||
export const initApp = () => {
|
||||
initMobX();
|
||||
return new RootStore();
|
||||
}
|
||||
32
web/src/utils/request.tsx
Normal file
32
web/src/utils/request.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import axios, {AxiosRequestConfig} from "axios";
|
||||
|
||||
export const apiUrl = "http://localhost:8080/api/v1/";
|
||||
|
||||
export const get = async <T extends unknown> (url: string, data?: T) => {
|
||||
return await request({
|
||||
url: url,
|
||||
method: 'GET',
|
||||
data: data,
|
||||
}) as T;
|
||||
}
|
||||
|
||||
export const post = async <T extends unknown> (url: string, data?: T) => {
|
||||
return request({
|
||||
url: url,
|
||||
method: 'POST',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
export const request = async <T extends unknown> (config: AxiosRequestConfig<T>) => {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
console.debug(`${config.method} ${config.url} request: ${config.method === 'GET' ? JSON.stringify(config.params) : JSON.stringify(config.data)}`);
|
||||
axios.request({...config, baseURL: apiUrl}).then((response) => {
|
||||
console.debug(`${config.method} ${config.url} response: ${JSON.stringify(response.data)}`);
|
||||
resolve(response.data);
|
||||
}).catch((error) => {
|
||||
console.error(`${config.method} ${config.url} error: ${error}`);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -22,7 +22,7 @@
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"allowSyntheticDefaultImports": true
|
||||
"allowSyntheticDefaultImports": true,
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
@ -2,5 +2,14 @@ import {defineConfig} from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
esbuild: {
|
||||
tsconfigRaw: {
|
||||
compilerOptions: {
|
||||
experimentalDecorators: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
react()
|
||||
],
|
||||
})
|
||||
|
||||
Loading…
Reference in New Issue
Block a user