Compare commits

...

5 Commits

Author SHA1 Message Date
Maksim Skobaro
c2d19a7724 improved, more featured, fixed
Exceptions and Errors are better
Files structure is better
New ComponentContext.ts
New DataTable.tsx tables.ts
Massive components refactoring
New Group.java
New LoggingRequestFilter.java LoggingSessionListener.java
New NotificationStore.ts SysInfoStore.ts
New reactiveValue.ts ReactiveControls.tsx
New dependencies
And much more
2025-02-07 07:05:15 +03:00
Maksim Skobaro
96ffb3ad41 implement login/quit, PendingStore.ts 2025-02-02 09:44:29 +03:00
Maksim Skobaro
f80db06adf refactoring 2025-02-02 09:43:34 +03:00
Maksim Skobaro
181dc824a1 Implemented UserProfile.tsx, without editing
* added font awesome
* replaced GitHub logo in Footer.tsx with one provided by FontAwesome
* added loader, when userStore fetching data from server
* allow circular dependencies, since this is no problem
* fix default (e.g. prod) profile
* fix a problem, when no authenticated person calls /api/v1/user/current endpoint
2024-10-21 00:55:58 +03:00
Maksim Skobaro
212249becd moved from vite to webpack + babel, since vite '**unusable**' 2024-10-20 14:43:39 +03:00
142 changed files with 8426 additions and 3533 deletions

1
.gitignore vendored
View File

@ -30,3 +30,4 @@ build/
### VS Code ### ### VS Code ###
.vscode/ .vscode/
/logs/app.log

View File

@ -1,19 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
wrapperVersion=3.3.2
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip

View File

@ -1,11 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Build &amp; 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">
<option name="Maven.BeforeRunTask" enabled="true" file="$PROJECT_DIR$/pom.xml" goal="package -Dmaven.test.skip=true" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Start RDBMS" run_configuration_type="docker-deploy" />
</method>
</configuration>
</component>

View File

@ -1,11 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Clean, Build &amp; 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">
<option name="Maven.BeforeRunTask" enabled="true" file="$PROJECT_DIR$/pom.xml" goal="clean package -Dmaven.test.skip=true" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Start RDBMS" run_configuration_type="docker-deploy" />
</method>
</configuration>
</component>

View File

@ -1,8 +0,0 @@
<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" />
</configuration>
</component>

View File

@ -1,11 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Start RDBMS" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
<deployment type="docker-compose.yml">
<settings>
<option name="envFilePath" value="" />
<option name="sourceFilePath" value="server/docker-compose.yml" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

View File

@ -2,8 +2,8 @@ services:
db: db:
image: postgres:16.2-alpine3.19 image: postgres:16.2-alpine3.19
environment: environment:
- "POSTGRES_DB=db" - "POSTGRES_DB=tdms"
- "POSTGRES_PASSWORD=root" - "POSTGRES_PASSWORD=root"
- "POSTGRES_USER=root" - "POSTGRES_USER=root"
ports: ports:
- "5432:5432" - "5400:5432"

259
mvnw vendored
View File

@ -1,259 +0,0 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.3.2
#
# Optional ENV vars
# -----------------
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
# MVNW_REPOURL - repo url base for downloading maven distribution
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
set -euf
[ "${MVNW_VERBOSE-}" != debug ] || set -x
# OS specific support.
native_path() { printf %s\\n "$1"; }
case "$(uname)" in
CYGWIN* | MINGW*)
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
native_path() { cygpath --path --windows "$1"; }
;;
esac
# set JAVACMD and JAVACCMD
set_java_home() {
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
if [ -n "${JAVA_HOME-}" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACCMD="$JAVA_HOME/jre/sh/javac"
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACCMD="$JAVA_HOME/bin/javac"
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
return 1
fi
fi
else
JAVACMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v java
)" || :
JAVACCMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v javac
)" || :
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
return 1
fi
fi
}
# hash string like Java String::hashCode
hash_string() {
str="${1:-}" h=0
while [ -n "$str" ]; do
char="${str%"${str#?}"}"
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
str="${str#?}"
done
printf %x\\n $h
}
verbose() { :; }
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
die() {
printf %s\\n "$1" >&2
exit 1
}
trim() {
# MWRAPPER-139:
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
# Needed for removing poorly interpreted newline sequences when running in more
# exotic environments such as mingw bash on Windows.
printf "%s" "${1}" | tr -d '[:space:]'
}
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
while IFS="=" read -r key value; do
case "${key-}" in
distributionUrl) distributionUrl=$(trim "${value-}") ;;
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
esac
done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
case "${distributionUrl##*/}" in
maven-mvnd-*bin.*)
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
*)
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
distributionPlatform=linux-amd64
;;
esac
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
;;
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
esac
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
distributionUrlName="${distributionUrl##*/}"
distributionUrlNameMain="${distributionUrlName%.*}"
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
exec_maven() {
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
}
if [ -d "$MAVEN_HOME" ]; then
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
exec_maven "$@"
fi
case "${distributionUrl-}" in
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
esac
# prepare tmp dir
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
trap clean HUP INT TERM EXIT
else
die "cannot create temp dir"
fi
mkdir -p -- "${MAVEN_HOME%/*}"
# Download and Install Apache Maven
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
verbose "Downloading from: $distributionUrl"
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
# select .zip or .tar.gz
if ! command -v unzip >/dev/null; then
distributionUrl="${distributionUrl%.zip}.tar.gz"
distributionUrlName="${distributionUrl##*/}"
fi
# verbose opt
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
# normalize http auth
case "${MVNW_PASSWORD:+has-password}" in
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
esac
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
verbose "Found wget ... using wget"
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
verbose "Found curl ... using curl"
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
elif set_java_home; then
verbose "Falling back to use Java to download"
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
cat >"$javaSource" <<-END
public class Downloader extends java.net.Authenticator
{
protected java.net.PasswordAuthentication getPasswordAuthentication()
{
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
}
public static void main( String[] args ) throws Exception
{
setDefault( new Downloader() );
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
}
}
END
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
verbose " - Compiling Downloader.java ..."
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
verbose " - Running Downloader.java ..."
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
fi
# If specified, validate the SHA-256 sum of the Maven distribution zip file
if [ -n "${distributionSha256Sum-}" ]; then
distributionSha256Result=false
if [ "$MVN_CMD" = mvnd.sh ]; then
echo "Checksum validation is not supported for maven-mvnd." >&2
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
elif command -v sha256sum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
elif command -v shasum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
if [ $distributionSha256Result = false ]; then
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
exit 1
fi
fi
# unzip and move
if command -v unzip >/dev/null; then
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
else
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
clean || :
exec_maven "$@"

149
mvnw.cmd vendored
View File

@ -1,149 +0,0 @@
<# : batch portion
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.3.2
@REM
@REM Optional ENV vars
@REM MVNW_REPOURL - repo url base for downloading maven distribution
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
@SET __MVNW_CMD__=
@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-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%
@SET __MVNW_PSMODULEP_SAVE=
@SET __MVNW_ARG0_NAME__=
@SET MVNW_USERNAME=
@SET MVNW_PASSWORD=
@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
@echo Cannot start maven from wrapper >&2 && exit /b 1
@GOTO :EOF
: end batch / begin powershell #>
$ErrorActionPreference = "Stop"
if ($env:MVNW_VERBOSE -eq "true") {
$VerbosePreference = "Continue"
}
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
$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"
}
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
"maven-mvnd-*" {
$USE_MVND = $true
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
$MVN_CMD = "mvnd.cmd"
break
}
default {
$USE_MVND = $false
$MVN_CMD = $script -replace '^mvnw','mvn'
break
}
}
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
if ($env:MVNW_REPOURL) {
$MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
}
$distributionUrlName = $distributionUrl -replace '^.*/',''
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
if ($env:MAVEN_USER_HOME) {
$MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
}
$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
exit $?
}
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
}
# prepare tmp dir
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
trap {
if ($TMP_DOWNLOAD_DIR.Exists) {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
}
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
# Download and Install Apache Maven
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
Write-Verbose "Downloading from: $distributionUrl"
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
$webclient = New-Object System.Net.WebClient
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
# If specified, validate the SHA-256 sum of the Maven distribution zip file
$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."
}
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
}
}
# unzip and move
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
try {
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
} catch {
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
Write-Error "fail to move MAVEN_HOME"
}
} finally {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"

3
server/.gitignore vendored
View File

@ -1 +1,2 @@
/target /target
/logs/app.log

View File

@ -88,6 +88,16 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId> <artifactId>spring-boot-starter-actuator</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -11,8 +11,6 @@ import org.springframework.context.ConfigurableApplicationContext;
@Slf4j @Slf4j
public class TdmsApplication { public class TdmsApplication {
public static void main(String[] args) { public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(TdmsApplication.class, args); SpringApplication.run(TdmsApplication.class, args);
String staticLocation = context.getEnvironment().getProperty("spring.web.resources.static-locations");
log.info("Static location: {}", staticLocation);
} }
} }

View File

@ -1,24 +1,22 @@
package ru.tubryansk.tdms.config; package ru.tubryansk.tdms.config;
import jakarta.servlet.http.HttpSessionEvent; import lombok.extern.slf4j.Slf4j;
import jakarta.servlet.http.HttpSessionListener; import org.apache.commons.lang3.StringUtils;
import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager; import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy; 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.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
@ -26,78 +24,88 @@ import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.CorsConfigurationSource;
import static org.springframework.security.web.context.HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY; import java.time.Duration;
import java.util.List;
@Configuration @Configuration
@Slf4j
public class SecurityConfiguration { public class SecurityConfiguration {
@Bean @Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity, AuthenticationManager authenticationManager) throws Exception { public SecurityFilterChain securityFilterChain(
HttpSecurity httpSecurity,
AuthenticationManager authenticationManager,
@Qualifier("corsConfig") CorsConfigurationSource cors
) throws Exception {
return httpSecurity return httpSecurity
.authorizeHttpRequests(this::configureHttpAuthorization) .authorizeHttpRequests(this::configureHttpAuthorization)
.csrf(AbstractHttpConfigurer::disable) .csrf(AbstractHttpConfigurer::disable) /* todo: настроить csrf */
.cors(a -> a.configurationSource(corsConfiguration())) .cors(a -> a.configurationSource(cors))
.authenticationManager(authenticationManager) .authenticationManager(authenticationManager)
.sessionManagement(this::configureSessionManagement) .sessionManagement(cfg -> {
.build(); cfg.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
cfg.maximumSessions(1);
})
.build();
} }
@Bean @Bean
public CorsConfigurationSource corsConfiguration() { @Qualifier("corsConfig")
public CorsConfigurationSource corsConfigurationProd(
@Value("${application.domain}") String domain,
@Value("${application.port}") String port,
@Value("${application.protocol}") String protocol,
Environment environment
) {
return request -> { return request -> {
String url = StringUtils.join(protocol, "://", domain, ":", port);
CorsConfiguration corsConfiguration = new CorsConfiguration(); CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.applyPermitDefaultValues(); corsConfiguration.setMaxAge(Duration.ofDays(1));
corsConfiguration.addAllowedMethod("DELETE"); corsConfiguration.addAllowedOrigin(url);
corsConfiguration.addAllowedMethod("PUT"); if (environment.matchesProfiles("dev")) {
corsConfiguration.addAllowedMethod("PATCH"); corsConfiguration.addAllowedOrigin("http://localhost:8081");
}
corsConfiguration.setAllowedMethods(List.of(HttpMethod.GET.name(), HttpMethod.POST.name(), HttpMethod.OPTIONS.name()));
corsConfiguration.setAllowedHeaders(List.of("Authorization", "Content-Type"));
corsConfiguration.setAllowCredentials(true);
return corsConfiguration; return corsConfiguration;
}; };
} }
private void configureHttpAuthorization(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry httpAuthorization) {
/* API ROUTES */
httpAuthorization.requestMatchers("/api/v1/diploma-topic/**").permitAll();
httpAuthorization.requestMatchers("/api/v1/user/**").permitAll();
httpAuthorization.requestMatchers("/api/**").denyAll();
/* STATIC ROUTES */
httpAuthorization.requestMatchers("/**").permitAll();
/* OTHER */
httpAuthorization.anyRequest().denyAll();
}
@Bean @Bean
public AuthenticationManager authenticationManager(UserDetailsService userDetailsService) { public AuthenticationManager authenticationManager(UserDetailsService userDetailsService) {
return new ProviderManager(authenticationProvider(userDetailsService)); return new ProviderManager(authenticationProvider(userDetailsService));
} }
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
private void configureHttpAuthorization(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry httpAuthorization) {
/* SysInfoController */
httpAuthorization.requestMatchers("/api/v1/sysinfo/**").permitAll();
/* UserController */
httpAuthorization.requestMatchers("/api/v1/user/logout").authenticated();
httpAuthorization.requestMatchers("/api/v1/user/login").anonymous();
httpAuthorization.requestMatchers("/api/v1/user/current").permitAll();
httpAuthorization.requestMatchers("/api/v1/user/get-all").hasAuthority("ROLE_ADMINISTRATOR");
httpAuthorization.requestMatchers("/api/v1/user/register").hasAuthority("ROLE_ADMINISTRATOR");
httpAuthorization.requestMatchers("/api/v1/user/validate-registration").hasAuthority("ROLE_ADMINISTRATOR");
/* StudentController */
httpAuthorization.requestMatchers("/api/v1/student/current").permitAll();
/* GroupController */
httpAuthorization.requestMatchers("/api/v1/group/get-all").permitAll();
/* deny all other api requests */
httpAuthorization.requestMatchers("/api/**").denyAll();
/* since api already blocked, all other requests are static resources */
httpAuthorization.requestMatchers("/**").permitAll();
}
private AuthenticationProvider authenticationProvider(UserDetailsService userDetailsService) { private AuthenticationProvider authenticationProvider(UserDetailsService userDetailsService) {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider(passwordEncoder()); DaoAuthenticationProvider provider = new DaoAuthenticationProvider(passwordEncoder());
provider.setUserDetailsService(userDetailsService); provider.setUserDetailsService(userDetailsService);
return provider; return provider;
} }
private PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Bean
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("izrailev_v_ya_1", "1");
Authentication authenticated = authenticationManager.authenticate(authentication);
context.setAuthentication(authenticated);
SecurityContextHolder.setContext(context);
se.getSession().setAttribute(SPRING_SECURITY_CONTEXT_KEY, context);
}
};
}
// todo: remove when login/logout is implemented, since we do not need automatically created session with no authentication
private void configureSessionManagement(SessionManagementConfigurer<HttpSecurity> sessionManagement) {
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
}
} }

View File

@ -1,33 +1,13 @@
package ru.tubryansk.tdms.controller; package ru.tubryansk.tdms.controller;
import jakarta.validation.constraints.PositiveOrZero;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import ru.tubryansk.tdms.dto.DiplomaTopicDTO;
import ru.tubryansk.tdms.service.DiplomaTopicService;
import java.util.List;
@RestController @RestController
@RequestMapping("/api/v1/diploma-topic/") @RequestMapping("/api/v1/diploma-topic/")
@Validated @Validated
public class DiplomaTopicController { public class DiplomaTopicController {
@Autowired
private DiplomaTopicService diplomaTopicService;
@GetMapping("/get-all")
public List<DiplomaTopicDTO> getAll() {
return diplomaTopicService.getAll();
}
@GetMapping("/get-by-id/{id:[\\-+]?\\d+}")
public DiplomaTopicDTO getById(@PathVariable @PositiveOrZero Integer id) {
return diplomaTopicService.getById(id);
}
} }

View File

@ -0,0 +1,20 @@
package ru.tubryansk.tdms.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import ru.tubryansk.tdms.controller.payload.GroupDTO;
import ru.tubryansk.tdms.service.GroupService;
import java.util.Collection;
@RestController("/api/v1/group/")
public class GroupController {
@Autowired
private GroupService groupService;
@GetMapping("/get-all-groups")
public Collection<GroupDTO> getAllGroups() {
return groupService.getAllGroups();
}
}

View File

@ -0,0 +1,20 @@
package ru.tubryansk.tdms.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import ru.tubryansk.tdms.controller.payload.StudentDTO;
import ru.tubryansk.tdms.service.StudentService;
@RestController
@RequestMapping("/api/v1/student/")
public class StudentController {
@Autowired
private StudentService studentService;
@GetMapping("/current")
public StudentDTO getCurrentStudent() {
return studentService.getCallerStudentDTO();
}
}

View File

@ -0,0 +1,21 @@
package ru.tubryansk.tdms.controller;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import ru.tubryansk.tdms.service.SysInfoService;
@RestController
@RequestMapping("/api/v1/sysinfo")
public class SysInfoController {
@Autowired
private SysInfoService sysInfoService;
@SneakyThrows
@GetMapping("/version")
public String getVersion() {
return sysInfoService.getVersion();
}
}

View File

@ -1,32 +1,51 @@
package ru.tubryansk.tdms.controller; package ru.tubryansk.tdms.controller;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping; import ru.tubryansk.tdms.controller.payload.LoginDTO;
import org.springframework.web.bind.annotation.PostMapping; import ru.tubryansk.tdms.controller.payload.RegistrationDTO;
import org.springframework.web.bind.annotation.RequestMapping; import ru.tubryansk.tdms.controller.payload.UserDTO;
import org.springframework.web.bind.annotation.RestController; import ru.tubryansk.tdms.service.AuthenticationService;
import ru.tubryansk.tdms.dto.UserDTO; import ru.tubryansk.tdms.service.CallerService;
import ru.tubryansk.tdms.entity.User;
import ru.tubryansk.tdms.service.UserService; import ru.tubryansk.tdms.service.UserService;
import java.util.List;
@RestController @RestController
@Validated
@RequestMapping("/api/v1/user") @RequestMapping("/api/v1/user")
@Slf4j @Slf4j
public class UserController { public class UserController {
@Autowired
private AuthenticationService authenticationService;
@Autowired
private CallerService callerService;
@Autowired @Autowired
private UserService userService; private UserService userService;
@GetMapping("/current") @GetMapping("/current")
public UserDTO getCurrentUser() { public UserDTO getCurrentUser() {
User principal = userService.getCallerPrincipal(); return callerService.getCallerUserDTO();
return principal != null ? UserDTO.from(principal, true) : UserDTO.fromUnauthenticated();
} }
@PostMapping("/logout") @PostMapping("/logout")
public void logout() { public void logout() {
userService.logout(); authenticationService.logout();
}
@PostMapping("/login")
public void login(@RequestBody @Valid LoginDTO loginDTO) {
authenticationService.login(loginDTO.getUsername(), loginDTO.getPassword());
}
@PostMapping("/register")
public void post(@RequestBody @Valid RegistrationDTO registrationDTO) {
userService.registerUser(registrationDTO);
}
@GetMapping("/get-all")
public List<UserDTO> getAllUsers() {
return userService.getAllUsers();
} }
} }

View File

@ -1,4 +1,4 @@
package ru.tubryansk.tdms.dto; package ru.tubryansk.tdms.controller.payload;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -8,11 +8,11 @@ public record ErrorResponse(String message, ErrorCode errorCode) {
@RequiredArgsConstructor @RequiredArgsConstructor
@Getter @Getter
public enum ErrorCode { public enum ErrorCode {
BAD_REQUEST(HttpStatus.BAD_REQUEST), BUSINESS_ERROR(HttpStatus.BAD_REQUEST),
VALIDATION_ERROR(HttpStatus.BAD_REQUEST), VALIDATION_ERROR(HttpStatus.BAD_REQUEST),
INTERNAL_ERROR(HttpStatus.INTERNAL_SERVER_ERROR), INTERNAL_ERROR(HttpStatus.INTERNAL_SERVER_ERROR),
NOT_FOUND(HttpStatus.NOT_FOUND), NOT_FOUND(HttpStatus.NOT_FOUND),
ACCESS_DENIED(HttpStatus.FORBIDDEN) ACCESS_DENIED(HttpStatus.FORBIDDEN),
; ;
private final HttpStatus httpStatus; private final HttpStatus httpStatus;

View File

@ -0,0 +1,14 @@
package ru.tubryansk.tdms.controller.payload;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class GroupDTO {
private String name;
private String principalName;
private Boolean isMePrincipal;
}

View File

@ -0,0 +1,12 @@
package ru.tubryansk.tdms.controller.payload;
import jakarta.validation.constraints.NotEmpty;
import lombok.Getter;
@Getter
public class LoginDTO {
@NotEmpty(message = "Логин не может быть пустым")
private String username;
@NotEmpty(message = "Пароль не может быть пустым")
private String password;
}

View File

@ -0,0 +1,34 @@
package ru.tubryansk.tdms.controller.payload;
import jakarta.validation.constraints.*;
import lombok.Getter;
import org.hibernate.validator.constraints.Length;
@Getter
public class RegistrationDTO {
@NotEmpty(message = "Логин не может быть пустым")
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "Логин должен содержать только латинские буквы, цифры и знак подчеркивания")
@Size(min = 5, message = "Логин должен содержать минимум 5 символов")
@Size(max = 32, message = "Логин должен содержать максимум 32 символов")
private String login;
@NotEmpty(message = "Пароль не может быть пустым")
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{8,}$", message = "Пароль должен содержать хотя бы одну цифру, одну заглавную и одну строчную букву, минимум 8 символов")
private String password;
@NotEmpty(message = "Имя не может быть пустым")
@Length(min = 3, message = "Имя должно содержать минимум 3 символа")
@Pattern(regexp = "^[a-zA-Zа-яА-ЯёЁ\\s]+$", message = "Имя должно содержать только буквы английского или русского алфавита и пробелы")
private String fullName;
@NotNull(message = "Почта не может быть пустой")
@Email(message = "Почта должна быть валидным адресом электронной почты")
private String email;
@NotNull(message = "Номер телефона не может быть пустым")
@Pattern(regexp = "^\\+[1-9]\\d{6,14}$", message = "Номер телефона должен начинаться с '+' и содержать от 7 до 15 цифр")
private String numberPhone;
private StudentRegistrationDTO studentData;
@Getter
public static class StudentRegistrationDTO {
@NotNull(message = "Группа не может быть пустой")
private Long groupId;
}
}

View File

@ -0,0 +1,19 @@
package ru.tubryansk.tdms.controller.payload;
import ru.tubryansk.tdms.entity.Role;
import ru.tubryansk.tdms.entity.User;
import java.util.List;
public record RoleDTO(String name, String authority) {
public static RoleDTO from(Role role) {
return new RoleDTO(role.getName(), role.getAuthority());
}
public static List<RoleDTO> from(User user) {
return user.getRoles().stream().map(RoleDTO::from).toList();
}
}

View File

@ -0,0 +1,50 @@
package ru.tubryansk.tdms.controller.payload;
import lombok.Data;
import ru.tubryansk.tdms.entity.Student;
@Data
public class StudentDTO {
// private Boolean form;
// private Integer protectionOrder;
// private String magistracy;
// private Boolean digitalFormatPresent;
// private Integer markComment;
// private Integer markPractice;
// private String predefenceComment;
// private String normalControl;
// private Integer antiPlagiarism;
// private String note;
// private Boolean recordBookReturned;
// private String work;
// private UserDTO user;
// private String diplomaTopic;
// private UserDTO mentorUser;
// private GroupDTO group;
public static StudentDTO from(Student student) {
StudentDTO studentDTO = new StudentDTO();
// studentDTO.setForm(student.getForm());
// return studentDTO;
// student.getForm(),
// student.getProtectionOrder(),
// student.getMagistracy(),
// student.getDigitalFormatPresent(),
// student.getMarkComment(),
// student.getMarkPractice(),
// student.getPredefenceComment(),
// student.getNormalControl(),
// student.getAntiPlagiarism(),
// student.getNote(),
// student.getRecordBookReturned(),
// student.getWork(),
// UserDTO.from(student.getUser()),
// student.getDiplomaTopic().getName(),
// UserDTO.from(student.getMentorUser()),
// GroupDTO.from(student.getGroup())
// );
return studentDTO;
}
}

View File

@ -0,0 +1,42 @@
package ru.tubryansk.tdms.controller.payload;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Builder;
import ru.tubryansk.tdms.entity.User;
import java.time.ZonedDateTime;
import java.util.List;
@Builder
@JsonInclude(JsonInclude.Include.NON_ABSENT)
public record UserDTO(
boolean authenticated,
String login,
String fullName,
String email,
String phone,
ZonedDateTime createdAt,
ZonedDateTime updatedAt,
List<RoleDTO> authorities) {
public static UserDTO unauthenticated() {
return UserDTO.builder()
.authenticated(false)
.build();
}
public static UserDTO from(User user) {
return UserDTO.builder()
.authenticated(true)
.login(user.getLogin())
.fullName(user.getFullName())
.email(user.getEmail())
.phone(user.getNumberPhone())
.createdAt(user.getCreatedAt())
.updatedAt(user.getUpdatedAt())
.authorities(RoleDTO.from(user))
.build();
}
}

View File

@ -1,16 +0,0 @@
package ru.tubryansk.tdms.dto;
import lombok.Builder;
import ru.tubryansk.tdms.entity.DiplomaTopic;
@Builder
public record DiplomaTopicDTO(Integer id, String name) {
public static DiplomaTopicDTO fromEntity(DiplomaTopic diplomaTopic) {
return DiplomaTopicDTO.builder()
.id(diplomaTopic.getId())
.name(diplomaTopic.getName())
.build();
}
}

View File

@ -1,15 +0,0 @@
package ru.tubryansk.tdms.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RoleDTO {
private Integer id;
private String name;
}

View File

@ -1,30 +0,0 @@
package ru.tubryansk.tdms.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class StudentDTO {
private Integer id;
private Boolean form;
private Integer protectionOrder;
private String magistracy;
private Boolean digitalFormatPresent;
private Integer markComment;
private Integer markPractice;
private String predefenceComment;
private String normalControl;
private Integer antiPlagiarism;
private String note;
private Boolean recordBookReturned;
private String work;
private Integer userId;
private Integer diplomaTopicId;
private Integer mentorUserId;
private Integer groupId;
}

View File

@ -1,43 +0,0 @@
package ru.tubryansk.tdms.dto;
import lombok.Builder;
import ru.tubryansk.tdms.entity.Role;
import ru.tubryansk.tdms.entity.User;
import java.time.ZonedDateTime;
import java.util.List;
@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.getCreatedAt())
.updatedAt(user.getUpdatedAt())
.authorities(user.getRoles().stream().map(Role::getAuthority).toList())
.build();
}
}

View File

@ -2,23 +2,22 @@ package ru.tubryansk.tdms.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import lombok.ToString;
@Entity
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @ToString
@AllArgsConstructor @Entity
@Table(name = "diploma_topic", schema = "vkr") @Table(name = "diploma_topic")
public class DiplomaTopic { public class DiplomaTopic {
@Id @Id
@Column(name = "id") @Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)//Макс из-за SEQUENCE прога лежит, если хочешь, можешь менять я не понял что не нравиться при сборке @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id; private Long id;
@Column(name = "name", nullable = false) @Column(name = "name")
private String name; private String name;
} }

View File

@ -2,26 +2,24 @@ package ru.tubryansk.tdms.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import lombok.ToString;
@Entity
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @ToString
@AllArgsConstructor @Entity
@Table(name = "group", schema = "vkr") @Table(name = "group")
public class Group { public class Group {
@Id @Id
@Column(name = "id") @Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id; private Long id;
@Column(name = "name", nullable = false) @Column(name = "name")
private String name; private String name;
@ManyToOne() @ManyToOne
@JoinColumn(name = "principal_user_id", nullable = false) @JoinColumn(name = "curator_user_id")
private User principalUser; private User groupCurator;
} }

View File

@ -1,28 +1,25 @@
package ru.tubryansk.tdms.entity; package ru.tubryansk.tdms.entity;
import jakarta.persistence.*; import jakarta.persistence.Column;
import lombok.AllArgsConstructor; import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.ToString;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
@Entity
@Getter @Getter
@Setter @ToString
@NoArgsConstructor @Entity
@AllArgsConstructor @Table(name = "`role`")
@Table(name = "role", schema = "vkr")
public class Role implements GrantedAuthority { public class Role implements GrantedAuthority {
@Id @Id
@Column(name = "id") @Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
private Integer id; @Column(name = "name")
@Column(name = "name", nullable = false)
private String name; private String name;
@Column(name = "authority", nullable = false) @Column(name = "authority")
private String authority; private String authority;
} }

View File

@ -2,22 +2,26 @@ package ru.tubryansk.tdms.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Data; import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Data @Getter
@Setter
@ToString
@Entity @Entity
@Table(name = "student", schema = "vkr") @Table(name = "student")
public class Student { public class Student {
@Id @Id
@Column(name = "id") @Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id; private Long id;
@Column(name = "form") @Column(name = "form")
private Boolean form; private Boolean form;
@Column(name = "protection_order" , nullable = false) @Column(name = "protection_order")
private Integer protectionOrder; private Integer protectionOrder;
@Column(name = "magistracy" ) @Column(name = "magistracy")
private String magistracy; private String magistracy;
@Column(name = "digital_format_present") @Column(name = "digital_format_present")
private Boolean digitalFormatPresent; private Boolean digitalFormatPresent;
@ -36,18 +40,17 @@ public class Student {
@Column(name = "record_book_returned") @Column(name = "record_book_returned")
private Boolean recordBookReturned; private Boolean recordBookReturned;
@Column(name = "work") @Column(name = "work")
private String work; private String work;
@OneToOne @OneToOne
@JoinColumn(name = "user_id", nullable = false) @JoinColumn(name = "user_id")
private User user; private User user;
@ManyToOne @ManyToOne
@JoinColumn(name = "diploma_topic_id", nullable = false) @JoinColumn(name = "diploma_topic_id")
private DiplomaTopic diplomaTopic; private DiplomaTopic diplomaTopic;
@ManyToOne @ManyToOne
@JoinColumn(name = "mentor_user_id", nullable = false) @JoinColumn(name = "mentor_user_id")
private User mentorUser; private User mentorUser;
@ManyToOne @ManyToOne
@JoinColumn(name = "group_id", nullable = false) @JoinColumn(name = "group_id")
private Group group; private Group group;
} }

View File

@ -2,10 +2,11 @@ package ru.tubryansk.tdms.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import lombok.ToString;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
@ -15,40 +16,45 @@ import java.util.Collection;
import java.util.List; import java.util.List;
@Entity
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @ToString
@AllArgsConstructor @Entity
@Table(name = "user", schema = "vkr") @Table(name = "`user`")
public class User implements UserDetails { public class User implements UserDetails {
@Id @Id
@Column(name = "id") @Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id; private Long id;
@Column(name = "login", nullable = false, unique = true) @Column(name = "login")
private String login; private String login;
@Column(name = "password", nullable = false) @Column(name = "password")
private String password; private String password;
@Column(name = "full_name", nullable = false) @Column(name = "full_name")
private String fullName; private String fullName;
@Column(name = "mail", nullable = false, unique = true) @Column(name = "email")
private String mail; private String email;
@Column(name = "number_phone", nullable = false, unique = true) @Column(name = "number_phone")
private String numberPhone; private String numberPhone;
@Column(name = "created_at", nullable = false) @Column(name = "created_at")
@CreationTimestamp
private ZonedDateTime createdAt; private ZonedDateTime createdAt;
@Column(name = "updated_at") @Column(name = "updated_at")
@UpdateTimestamp
private ZonedDateTime updatedAt; private ZonedDateTime updatedAt;
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) @ManyToMany
@JoinTable(name = "user_role",schema = "vkr", @JoinTable(
joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), name = "user_role",
inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id")) joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))
private List<Role> roles; private List<Role> roles;
@Override @Override
public Collection<? extends GrantedAuthority> getAuthorities() { public Collection<? extends GrantedAuthority> getAuthorities() {
return roles.stream().map(Role::getAuthority).map(SimpleGrantedAuthority::new).toList(); return roles.stream()
.map(Role::getAuthority)
.map(SimpleGrantedAuthority::new)
.toList();
} }
@Override @Override

View File

@ -0,0 +1,4 @@
package ru.tubryansk.tdms.entity.repository;
public interface DefenceRepository {
}

View File

@ -1,9 +1,13 @@
package ru.tubryansk.tdms.repository; package ru.tubryansk.tdms.entity.repository;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import ru.tubryansk.tdms.entity.DiplomaTopic; import ru.tubryansk.tdms.entity.DiplomaTopic;
import ru.tubryansk.tdms.exception.NotFoundException;
@Repository @Repository
public interface DiplomaTopicRepository extends JpaRepository<DiplomaTopic, Integer> { public interface DiplomaTopicRepository extends JpaRepository<DiplomaTopic, Integer> {
default DiplomaTopic findByIdThrow(Integer id) {
return this.findById(id).orElseThrow(() -> new NotFoundException(DiplomaTopic.class, id));
}
} }

View File

@ -0,0 +1,13 @@
package ru.tubryansk.tdms.entity.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import ru.tubryansk.tdms.entity.Group;
import ru.tubryansk.tdms.exception.NotFoundException;
@Repository
public interface GroupRepository extends JpaRepository<Group, Long> {
default Group findByIdThrow(Long id) {
return this.findById(id).orElseThrow(() -> new NotFoundException(Group.class, id));
}
}

View File

@ -0,0 +1,9 @@
package ru.tubryansk.tdms.entity.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import ru.tubryansk.tdms.entity.Role;
@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
}

View File

@ -0,0 +1,18 @@
package ru.tubryansk.tdms.entity.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import ru.tubryansk.tdms.entity.Student;
import ru.tubryansk.tdms.entity.User;
import ru.tubryansk.tdms.exception.NotFoundException;
import java.util.Optional;
@Repository
public interface StudentRepository extends JpaRepository<Student, Integer> {
default Student findByIdThrow(Integer id) {
return this.findById(id).orElseThrow(() -> new NotFoundException(Student.class, id));
}
Optional<Student> findByUser(User user);
}

View File

@ -1,7 +1,6 @@
package ru.tubryansk.tdms.repository; package ru.tubryansk.tdms.entity.repository;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.security.core.userdetails.UserDetails;
import ru.tubryansk.tdms.entity.User; import ru.tubryansk.tdms.entity.User;
import java.util.Optional; import java.util.Optional;

View File

@ -1,6 +1,6 @@
package ru.tubryansk.tdms.exception; package ru.tubryansk.tdms.exception;
import ru.tubryansk.tdms.dto.ErrorResponse; import ru.tubryansk.tdms.controller.payload.ErrorResponse;
public class AccessDeniedException extends BusinessException { public class AccessDeniedException extends BusinessException {
public AccessDeniedException() { public AccessDeniedException() {

View File

@ -1,6 +1,6 @@
package ru.tubryansk.tdms.exception; package ru.tubryansk.tdms.exception;
import ru.tubryansk.tdms.dto.ErrorResponse; import ru.tubryansk.tdms.controller.payload.ErrorResponse;
public class BusinessException extends RuntimeException { public class BusinessException extends RuntimeException {
public BusinessException(String message) { public BusinessException(String message) {
@ -8,6 +8,6 @@ public class BusinessException extends RuntimeException {
} }
public ErrorResponse.ErrorCode getErrorCode() { public ErrorResponse.ErrorCode getErrorCode() {
return ErrorResponse.ErrorCode.INTERNAL_ERROR; return ErrorResponse.ErrorCode.BUSINESS_ERROR;
} }
} }

View File

@ -2,42 +2,55 @@ package ru.tubryansk.tdms.exception;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component; import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.resource.NoResourceFoundException; import org.springframework.web.servlet.resource.NoResourceFoundException;
import ru.tubryansk.tdms.dto.ErrorResponse; import ru.tubryansk.tdms.controller.payload.ErrorResponse;
import java.util.stream.Collectors;
@RestControllerAdvice @RestControllerAdvice
@Slf4j @Slf4j
public class GlobalExceptionHandler { public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class) @ExceptionHandler(BindException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { public ErrorResponse handleMethodArgumentNotValidException(BindException e) {
// todo: make a better error message log.debug("Validation error: {}", e.getMessage());
return new ErrorResponse(e.getMessage(), ErrorResponse.ErrorCode.VALIDATION_ERROR); String validationErrors = e.getAllErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.joining(", "));
return new ErrorResponse(validationErrors, ErrorResponse.ErrorCode.VALIDATION_ERROR);
} }
@ExceptionHandler(BusinessException.class) @ExceptionHandler(BusinessException.class)
public ErrorResponse handleBusinessException(BusinessException e, HttpServletResponse response) { public ErrorResponse handleBusinessException(BusinessException e, HttpServletResponse response) {
log.info("Business error", e);
response.setStatus(e.getErrorCode().getHttpStatus().value()); response.setStatus(e.getErrorCode().getHttpStatus().value());
return new ErrorResponse(e.getMessage(), e.getErrorCode()); return new ErrorResponse(e.getMessage(), e.getErrorCode());
} }
@ExceptionHandler(org.springframework.security.access.AccessDeniedException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public ErrorResponse handleAccessDeniedException(AccessDeniedException e) {
log.debug("Access denied", e);
return new ErrorResponse("", ErrorResponse.ErrorCode.ACCESS_DENIED);
}
@ExceptionHandler(NoResourceFoundException.class) @ExceptionHandler(NoResourceFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND) @ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleNoResourceFoundException(NoResourceFoundException e) { public ErrorResponse handleNoResourceFoundException(NoResourceFoundException e) {
// todo: make error page log.error("Resource not found", e);
return new ErrorResponse(e.getMessage(), ErrorResponse.ErrorCode.NOT_FOUND); return new ErrorResponse(e.getMessage(), ErrorResponse.ErrorCode.NOT_FOUND);
} }
@ExceptionHandler(Exception.class) @ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleUnexpectedException(Exception e) { public ErrorResponse handleUnexpectedException(Exception e) {
// todo: make error page
log.error("Unexpected exception.", e); log.error("Unexpected exception.", e);
return new ErrorResponse(e.getMessage(), ErrorResponse.ErrorCode.INTERNAL_ERROR); return new ErrorResponse(e.getMessage(), ErrorResponse.ErrorCode.INTERNAL_ERROR);
} }

View File

@ -1,10 +1,10 @@
package ru.tubryansk.tdms.exception; package ru.tubryansk.tdms.exception;
import ru.tubryansk.tdms.dto.ErrorResponse; import ru.tubryansk.tdms.controller.payload.ErrorResponse;
public class NotFoundException extends BusinessException { public class NotFoundException extends BusinessException {
public NotFoundException(Class<?> entityClass, Integer id) { public NotFoundException(Class<?> entityClass, Object id) {
super(entityClass.getSimpleName() + " with id " + id + " not found"); super(entityClass.getSimpleName() + " с идентификатором " + id + " не наеден");
} }
@Override @Override

View File

@ -0,0 +1,51 @@
package ru.tubryansk.tdms.service;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.tubryansk.tdms.entity.User;
import static org.springframework.security.web.context.HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;
@Service
@Slf4j
public class AuthenticationService {
@Autowired
private HttpServletRequest request;
@Autowired
private AuthenticationManager authenticationManager;
public boolean authenticated() {
var authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication.isAuthenticated() && (authentication.getPrincipal() instanceof User);
}
public void logout() {
HttpSession session = request.getSession(false);
if(session != null) {
session.invalidate();
}
}
@Transactional
public void login(String username, String password) {
try {
var context = SecurityContextHolder.createEmptyContext();
var token = new UsernamePasswordAuthenticationToken(username, password);
var authenticated = authenticationManager.authenticate(token);
context.setAuthentication(authenticated);
SecurityContextHolder.setContext(context);
request.getSession(true).setAttribute(SPRING_SECURITY_CONTEXT_KEY, context);
} catch (Exception e) {
log.error("Failed to log in user: {}", username, e);
throw e;
}
log.debug("User {} logged in", username);
}
}

View File

@ -0,0 +1,26 @@
package ru.tubryansk.tdms.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import ru.tubryansk.tdms.controller.payload.UserDTO;
import ru.tubryansk.tdms.entity.User;
import java.util.Optional;
@Service
public class CallerService {
@Autowired
private AuthenticationService authenticationService;
public Optional<User> getCallerUser() {
if(authenticationService.authenticated()) {
return Optional.of((User) SecurityContextHolder.getContext().getAuthentication().getPrincipal());
}
return Optional.empty();
}
public UserDTO getCallerUserDTO() {
return getCallerUser().map(UserDTO::from).orElse(UserDTO.unauthenticated());
}
}

View File

@ -1,31 +1,9 @@
package ru.tubryansk.tdms.service; package ru.tubryansk.tdms.service;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.tubryansk.tdms.entity.DiplomaTopic;
import ru.tubryansk.tdms.exception.NotFoundException;
import ru.tubryansk.tdms.repository.DiplomaTopicRepository;
import ru.tubryansk.tdms.dto.DiplomaTopicDTO;
import java.util.List;
import java.util.stream.Collectors;
@Service @Service
@Transactional @Transactional
public class DiplomaTopicService { public class DiplomaTopicService {
@Autowired
private DiplomaTopicRepository diplomaTopicRepository;
public List<DiplomaTopicDTO> getAll() {
return diplomaTopicRepository.findAll()
.stream()
.map(DiplomaTopicDTO::fromEntity)
.collect(Collectors.toList());
}
public DiplomaTopicDTO getById(Integer id) {
return DiplomaTopicDTO.fromEntity(diplomaTopicRepository.findById(id)
.orElseThrow(() -> new NotFoundException(DiplomaTopic.class, id)));
}
} }

View File

@ -0,0 +1,15 @@
package ru.tubryansk.tdms.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.tubryansk.tdms.controller.payload.GroupDTO;
import java.util.Collection;
@Service
@Transactional
public class GroupService {
public Collection<GroupDTO> getAllGroups() {
return null;
}
}

View File

@ -0,0 +1,17 @@
package ru.tubryansk.tdms.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.ContextStartedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class LifeCycleService {
@EventListener(ContextStartedEvent.class)
public void onStartup(ContextStartedEvent event) {
ApplicationContext applicationContext = event.getApplicationContext();
log.info("Static files location: {}", applicationContext.getEnvironment().getProperty("spring.web.resources.static-locations"));
}
}

View File

@ -0,0 +1,38 @@
package ru.tubryansk.tdms.service;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.tubryansk.tdms.entity.Role;
import ru.tubryansk.tdms.entity.repository.RoleRepository;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class RoleService {
public enum Authority {
ROLE_ADMINISTRATOR,
ROLE_COMMISSION_MEMBER,
ROLE_TEACHER,
ROLE_SECRETARY,
ROLE_STUDENT,
}
public transient Map<String, Role> roles;
@Autowired
private RoleRepository roleRepository;
@PostConstruct
@Transactional
public void init() {
roles = new ConcurrentHashMap<>();
roleRepository.findAll().forEach(role -> roles.put(role.getAuthority(), role));
}
public Role getRoleByAuthority(Authority authority) {
return roles.get(authority.name());
}
}

View File

@ -0,0 +1,56 @@
package ru.tubryansk.tdms.service;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import ru.tubryansk.tdms.controller.payload.StudentDTO;
import ru.tubryansk.tdms.entity.DiplomaTopic;
import ru.tubryansk.tdms.entity.Student;
import ru.tubryansk.tdms.entity.repository.DiplomaTopicRepository;
import ru.tubryansk.tdms.entity.repository.StudentRepository;
import ru.tubryansk.tdms.exception.AccessDeniedException;
import java.util.Map;
import java.util.Optional;
@Service
@Transactional
public class StudentService {
@Autowired
private StudentRepository studentRepository;
@Autowired
private DiplomaTopicRepository diplomaTopicRepository;
@Autowired
private Optional<Student> student;
@Autowired
private CallerService callerService;
/** @param studentToDiplomaTopic Map of @{@link Student} id and @{@link DiplomaTopic} id */
public void changeDiplomaTopic(Map<Integer, Integer> studentToDiplomaTopic) {
studentToDiplomaTopic.forEach(this::changeDiplomaTopic);
}
public void changeDiplomaTopic(Integer studentId, Integer diplomaTopicId) {
Student student = studentRepository.findByIdThrow(studentId);
DiplomaTopic diplomaTopic = diplomaTopicRepository.findByIdThrow(diplomaTopicId);
student.setDiplomaTopic(diplomaTopic);
}
public void changeCallerDiplomaTopic(Integer diplomaTopicId) {
DiplomaTopic diplomaTopic = diplomaTopicRepository.findByIdThrow(diplomaTopicId);
student.ifPresentOrElse(s -> s.setDiplomaTopic(diplomaTopic), () -> {throw new AccessDeniedException();});
}
public Optional<Student> getCallerStudent() {
return studentRepository.findByUser(callerService.getCallerUser().orElse(null));
}
public StudentDTO getCallerStudentDTO() {
Student callerStudent = getCallerStudent().orElse(null);
if (callerStudent == null) {
return null;
}
return StudentDTO.from(callerStudent);
}
}

View File

@ -0,0 +1,14 @@
package ru.tubryansk.tdms.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class SysInfoService {
@Value("${application.version}")
private String version;
public String getVersion() {
return version;
}
}

View File

@ -1,19 +1,23 @@
package ru.tubryansk.tdms.service; package ru.tubryansk.tdms.service;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; 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.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.tubryansk.tdms.controller.payload.RegistrationDTO;
import ru.tubryansk.tdms.controller.payload.UserDTO;
import ru.tubryansk.tdms.entity.Role;
import ru.tubryansk.tdms.entity.Student;
import ru.tubryansk.tdms.entity.User; import ru.tubryansk.tdms.entity.User;
import ru.tubryansk.tdms.repository.UserRepository; import ru.tubryansk.tdms.entity.repository.GroupRepository;
import ru.tubryansk.tdms.entity.repository.StudentRepository;
import ru.tubryansk.tdms.entity.repository.UserRepository;
import static org.springframework.security.web.context.HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY; import java.util.ArrayList;
import java.util.List;
@Service @Service
@Transactional @Transactional
@ -22,32 +26,74 @@ public class UserService implements UserDetailsService {
@Autowired @Autowired
private UserRepository userRepository; private UserRepository userRepository;
@Autowired @Autowired
private HttpServletRequest httpServletRequest; private GroupRepository groupRepository;
@Autowired
public User getCallerPrincipal() { private StudentRepository studentRepository;
if(!authenticated()) { @Autowired
return null; private RoleService roleService;
} @Autowired
private PasswordEncoder passwordEncoder;
return (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
public boolean authenticated() {
var authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication.isAuthenticated() && !(authentication instanceof AnonymousAuthenticationToken);
}
@Override @Override
public User loadUserByUsername(String username) throws UsernameNotFoundException { public User loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findUserByLogin(username) log.info("Loading user with username: {}", username);
.orElseThrow(() -> new UsernameNotFoundException("User not found")); User user = userRepository.findUserByLogin(username).orElseThrow(() -> {
log.info("User with login {} not found", username);
return new UsernameNotFoundException("User with login " + username + " not found");
});
log.info("User with login {} loaded", username);
return user;
} }
public void logout() { public List<UserDTO> getAllUsers() {
HttpSession session = httpServletRequest.getSession(true); log.info("Loading all users");
// if(session != null) { List<UserDTO> users = userRepository.findAll().stream()
// session.invalidate(); .map(UserDTO::from)
// } .toList();
session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, null); log.info("{} users loaded", users.size());
return users;
}
public void registerUser(RegistrationDTO registrationDTO) {
log.info("Registering new user with login: {}", registrationDTO.getLogin());
User user = transientUser(registrationDTO);
Student student = transientStudent(registrationDTO.getStudentData());
fillRoles(user, registrationDTO);
log.info("Saving new user: {}", user);
userRepository.save(user);
if (student != null) {
student.setUser(user);
log.info("User is student, saving student: {}", student);
studentRepository.save(student);
}
}
private User transientUser(RegistrationDTO registrationDTO) {
User user = new User();
user.setLogin(registrationDTO.getLogin());
user.setPassword(passwordEncoder.encode(registrationDTO.getPassword()));
user.setFullName(registrationDTO.getFullName());
user.setEmail(registrationDTO.getEmail());
user.setNumberPhone(registrationDTO.getNumberPhone());
return user;
}
private Student transientStudent(RegistrationDTO.StudentRegistrationDTO studentData) {
if (studentData == null) {
return null;
}
Student student = new Student();
student.setGroup(groupRepository.findByIdThrow(studentData.getGroupId()));
return student;
}
private void fillRoles(User user, RegistrationDTO registrationDTO) {
List<Role> roles = new ArrayList<>();
if (registrationDTO.getStudentData() != null) {
roles.add(roleService.getRoleByAuthority(RoleService.Authority.ROLE_STUDENT));
}
user.setRoles(roles);
} }
} }

View File

@ -0,0 +1,29 @@
package ru.tubryansk.tdms.web;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
@Slf4j
public class LoggingRequestFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
long startTime = System.currentTimeMillis();
log.info("Making request: {}. user: {}, session: {}, remote ip: {}",
request.getRequestURI(), request.getRemoteUser(),
request.getSession().getId(), request.getRemoteAddr());
try {
filterChain.doFilter(request, response);
} finally {
long duration = System.currentTimeMillis() - startTime;
log.info("Request finished with {} status. duration: {} ms", response.getStatus(), duration);
}
}
}

View File

@ -0,0 +1,23 @@
package ru.tubryansk.tdms.web;
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class LoggingSessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
log.debug("Session created: {}, user {}",
se.getSession().getId(), SecurityContextHolder.getContext().getAuthentication().getName());
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
log.debug("Session destroyed: {}, user: {}",
se.getSession().getId(), SecurityContextHolder.getContext().getAuthentication().getName());
}
}

View File

@ -4,8 +4,6 @@ application:
domain: localhost domain: localhost
protocol: http protocol: http
spring: spring:
flyway:
out-of-order: true
web: web:
resources: resources:
static-locations: file:///${user.dir}/web/dist/ static-locations: file:///${user.dir}/web/dist/

View File

@ -1,19 +1,19 @@
db: db:
url: jdbc:postgresql://localhost:5432/db url: jdbc:postgresql://localhost:5400/tdms
schema: public
user: root user: root
password: root password: root
schema: vkr
application: application:
name: @name@ name: @name@
version: @version@ version: @version@
type: production type: production
port: 80 port: 80
domain: tdms.tu-bryansk.ru domain: tdms.tu-bryansk.ru
protocol: https protocol: http
spring: spring:
application: application:
name: tdms name: ${application.name}
datasource: datasource:
url: ${db.url} url: ${db.url}
username: ${db.user} username: ${db.user}
@ -28,7 +28,8 @@ spring:
user: ${db.user} user: ${db.user}
password: ${db.password} password: ${db.password}
schemas: ${db.schema} schemas: ${db.schema}
locations: db.migration main:
banner-mode: off
server: server:
port: ${application.port} port: ${application.port}
address: ${application.domain} address: ${application.domain}

View File

@ -1,6 +0,0 @@
create table vkr.role
(
id integer primary key generated by default as identity,
name text not null unique,
authority text not null unique
)

View File

@ -1,12 +0,0 @@
create table vkr.user
(
id integer primary key generated by default as identity,
login text not null unique,
password text not null,
full_name text not null,
mail text not null unique,
number_phone text not null unique,
created_at timestamp(6) with time zone not null,
updated_at timestamp(6) with time zone
)

View File

@ -1,9 +0,0 @@
create table vkr.user_role
(
user_id integer not null,
role_id integer not null,
foreign key (user_id) references vkr.user (id)
on delete cascade,
foreign key (role_id) references vkr.role (id)
on delete cascade
)

View File

@ -1,5 +0,0 @@
create table vkr.diploma_topic
(
id integer primary key generated by default as identity,
name text not null unique
)

View File

@ -1,9 +0,0 @@
create table vkr.group
(
id integer primary key generated by default as identity,
name text not null unique,
principal_user_id integer not null,
foreign key (principal_user_id) references vkr.user (id)
on delete cascade
)

View File

@ -1,24 +0,0 @@
create table vkr.student
(
id integer primary key generated by default as identity,
form boolean,
protection_order integer not null,
magistracy text,
digital_format_present boolean,
mark_comment integer,
mark_practice integer,
predefence_comment text,
normal_control text,
anti_plagiarism int,
note text,
record_book_returned boolean,
work text,
user_id integer not null,
diploma_topic_id integer not null,
mentor_user_id integer not null,
group_id integer not null,
foreign key (user_id) references vkr.user (id) on delete cascade,
foreign key (diploma_topic_id) references vkr.diploma_topic (id) on delete cascade,
foreign key (mentor_user_id) references vkr.user (id) on delete cascade,
foreign key (group_id) references vkr.group (id) on delete cascade
)

View File

@ -1,96 +0,0 @@
INSERT INTO vkr.user (login, password, full_name, mail, number_phone, created_at, updated_at)
VALUES ('akulenko_mikhail', 'password123', 'Акуленко Михаил Вячеславович', 'akulenko.mikhail@example.com',
'+79110000001', NOW(), NOW()),
('borovikov_artem', 'password123', 'Боровиков Артём Викторович', 'borovikov.artem@example.com', '+79110000002',
NOW(), NOW()),
('bykonya_alexey', 'password123', 'Быконя Алексей Николаевич', 'bykonya.alexey@example.com', '+79110000003',
NOW(), NOW()),
('ermakov_alexander', 'password123', 'Ермаков Александр Сергеевич', 'ermakov.alexander@example.com',
'+79110000004', NOW(), NOW()),
('zgursky_evgeny', 'password123', 'Згурский Евгений Олегович', 'zgursky.evgeny@example.com', '+79110000005',
NOW(), NOW()),
('ibishov_tural', 'password123', 'Ибишов Турал Садай оглы', 'ibishov.tural@example.com', '+79110000006', NOW(),
NOW()),
('ignatenko_vladimir', 'password123', 'Игнатенко Владимир Алексеевич', 'ignatenko.vladimir@example.com',
'+79110000007', NOW(), NOW()),
('lazukin_danila', 'password123', 'Лазукин Данила Дмитриевич', 'lazukin.danila@example.com', '+79110000008',
NOW(), NOW()),
('mitiaev_danila', 'password123', 'Митяев Данила Алексеевич', 'mitiaev.danila@example.com', '+79110000009',
NOW(), NOW()),
('neshkov_daniil', 'password123', 'Нешков Даниил Владимирович', 'neshkov.daniil@example.com', '+79110000010',
NOW(), NOW()),
('petrov_pavel', 'password123', 'Петров Павел Сергеевич', 'petrov.pavel@example.com', '+79110000011', NOW(),
NOW()),
('sazonov_andrey', 'password123', 'Сазонов Андрей Андреевич', 'sazonov.andrey@example.com', '+79110000012',
NOW(), NOW()),
('solokhin_maxim', 'password123', 'Солохин Максим Николаевич', 'solokhin.maxim@example.com', '+79110000013',
NOW(), NOW()),
('sochinsky_artem', 'password123', 'Сочинский Артем Александрович', 'sochinsky.artem@example.com',
'+79110000014', NOW(), NOW()),
('trisvyatsky_kirill', 'password123', 'Трисвятский Кирилл Андреевич', 'trisvyatsky.kirill@example.com',
'+79110000015', NOW(), NOW()),
('turov_alexander', 'password123', 'Туров Александр Сергеевич', 'turov.alexander@example.com', '+79110000016',
NOW(), NOW()),
('shevtsova_alexandra', 'password123', 'Шевцова Александра Валерьевна', 'shevtsova.alexandra@example.com',
'+79110000017', NOW(), NOW()),
('kibalyuk_artem', 'password123', 'Кибалюк Артем Сергеевич', 'kibalyuk.artem@example.com', '+79110000018', NOW(),
NOW()),
('shulindin_artem', 'password123', 'Шулындин Артём Андреевич', 'shulindin.artem@example.com', '+79110000019',
NOW(), NOW()),
('belyaev_egor', 'password123', 'Беляев Егор Андреевич', 'belyaev.egor@example.com', '+79110000020', NOW(),
NOW()),
('berezhnoy_igor', 'password123', 'Бережной Игорь Александрович', 'berezhnoy.igor@example.com', '+79110000021',
NOW(), NOW()),
('bogun_pavel', 'password123', 'Богун Павел Сергеевич', 'bogun.pavel@example.com', '+79110000022', NOW(), NOW()),
('vaseykin_nikita', 'password123', 'Васейкин Никита Павлович', 'vaseykin.nikita@example.com', '+79110000023',
NOW(), NOW()),
('gomonov_nikita', 'password123', 'Гомонов Никита Алексеевич', 'gomonov.nikita@example.com', '+79110000024',
NOW(), NOW()),
('druyan_oleg', 'password123', 'Друян Олег Викторович', 'druyan.oleg@example.com', '+79110000025', NOW(), NOW()),
('ivanov_kirill', 'password123', 'Иванов Кирилл Эдуардович', 'ivanov.kirill@example.com', '+79110000026', NOW(),
NOW()),
('ivanova_veronika', 'password123', 'Иванова Вероника Евгеньевна', 'ivanova.veronika@example.com',
'+79110000027', NOW(), NOW()),
('izotov_ivan', 'password123', 'Изотов Иван Алексеевич', 'izotov.ivan@example.com', '+79110000028', NOW(),
NOW()),
('isakov_zahar', 'password123', 'Исаков Захар Александрович', 'isakov.zahar@example.com', '+79110000029', NOW(),
NOW()),
('iskritsky_daniil', 'password123', 'Искрицкий Даниил Павлович', 'iskritsky.daniil@example.com', '+79110000030',
NOW(), NOW()),
('linko_daria', 'password123', 'Линько Дарья Андреевна', 'linko.daria@example.com', '+79110000031', NOW(),
NOW()),
('logutov_kirill', 'password123', 'Логутов Кирилл Александрович', 'logutov.kirill@example.com', '+79110000032',
NOW(), NOW()),
('nekrassov_sergey', 'password123', 'Некрасов Сергей Игоревич', 'nekrassov.sergey@example.com', '+79110000033',
NOW(), NOW()),
('sinyagin_ilya', 'password123', 'Синягин Илья Александрович', 'sinyagin.ilya@example.com', '+79110000034',
NOW(), NOW()),
('sopriko_daniil', 'password123', 'Соприко Даниил Сергеевич', 'sopriko.daniil@example.com', '+79110000035',
NOW(), NOW()),
('turovsky_ivan', 'password123', 'Туровский Иван Алексеевич', 'turovsky.ivan@example.com', '+79110000036', NOW(),
NOW()),
('frantsev_sergey', 'password123', 'Францев Сергей Дмитриевич', 'frantsev.sergey@example.com', '+79110000037',
NOW(), NOW()),
('chepurnoy_maxim', 'password123', 'Чепурной Максим Романович', 'chepurnoy.maxim@example.com', '+79110000038',
NOW(), NOW()),
('schemelinin_dmitry', 'password123', 'Щемелинин Дмитрий Михайлович', 'schemelinin.dmitry@example.com',
'+79110000039', NOW(), NOW()),
('bulatitsky_d_i_1', 'password123', 'Булатицкий Д. И.', 'bulatitsky.d.i.1@example.com', '+79110000040', NOW(),
NOW()),
('kopeliovich_d_i_1', 'password123', 'Копелиович Д. И.', 'kopeliovich.d.i.1@example.com', '+79110000041', NOW(),
NOW()),
('dergachev_k_v', 'password123', 'Дергачев К. В.', 'dergachev.k.v@example.com', '+79110000042', NOW(), NOW()),
('trubakov_e_o', 'password123', 'Трубаков Е. О.', 'trubakov.e.o@example.com', '+79110000043', NOW(), NOW()),
('radchenko_a_o', 'password123', 'Радченко А. О.', 'radchenko.a.o@example.com', '+79110000044', NOW(), NOW()),
('zimin_s_n_1', 'password123', 'Зимин С. Н.', 'zimin.s.n.1@example.com', '+79110000045', NOW(), NOW()),
('koptenok_e_v', 'password123', 'Коптенок Е. В.', 'koptenok.e.v@example.com', '+79110000046', NOW(), NOW()),
('mikhaleva_o_a', 'password123', 'Михалева О. А.', 'mikhaleva.o.a@example.com', '+79110000047', NOW(), NOW()),
('gulakov_k_v', 'password123', 'Гулаков К. В.', 'gulakov.k.v@example.com', '+79110000048', NOW(), NOW()),
('titarev_d_v', 'password123', 'Титарёв Д. В.', 'titarev.d.v@example.com', '+79110000049', NOW(), NOW()),
('izrailev_v_ya_1', 'password123', 'Израилев В. Я.', 'izrailev.v.ya.1@example.com', '+79110000050', NOW(),
NOW()),
('podvesovsky_a_g_1', 'password123', 'Подвесовский А. Г.', 'podvesovsky.a.g.1@example.com', '+79110000051',
NOW(), NOW()),
('trubakov_a_o', 'password123', 'Трубаков А. О.', 'trubakov.a.o@example.com', '+79110000059',
NOW(), NOW()),
('lageriev_d_g', 'password123', 'Лагерев Д. Г.', 'lageriev.d.g@example.com', '+79110000052', NOW(), NOW());

View File

@ -1 +0,0 @@
UPDATE vkr.user SET password = '{noop}1';

View File

@ -1,2 +0,0 @@
INSERT INTO vkr.group (name ,principal_user_id)
VALUES('ИВТ-1',40),('ИВТ-2',40);

View File

@ -1,2 +0,0 @@
INSERT INTO vkr.role (name,authority)
VALUES('Руководитель','ROLE_DIRECTOR'),('Куратор','ROLE_TUTOR'),('Студент','ROLE_STUDENT');

View File

@ -1,73 +0,0 @@
INSERT INTO vkr.student (form,
protection_order,
magistracy,
digital_format_present,
mark_comment,
mark_practice,
predefence_comment,
normal_control,
anti_plagiarism,
note,
record_book_returned,
work,
user_id,
diploma_topic_id,
mentor_user_id,
group_id)
VALUES (true, 1100, 'ПРИ, ИВТ или другой вуз', true, 5, 5, 'ок', 'Подписано', 7862, 'Акт о внедрении', true, 'нет', 1,
1, 40, 1),
(true, 1110, 'Да, но не уверен, в БГТУ ли', true, 5, 5, 'ок', 'Подписано', 8196, 'Заявка, Акт о внедрении', true,
'ООО "ЦИРОБЗ"', 2, 2, 40, 1),
(true, 3500, 'Нет', true, 3, 3, 'Критически низкий уровень. Допущен под ответственность руководителя ВКР',
'Подписано', 7141, 'Иниц', true, 'нет', 3, 3, 41, 1),
(true, 1800, 'Да, но не уверен, в БГТУ ли', true, 4, 5, 'Усилить работу', 'Подписано', 5381, 'Иниц', true, 'нет',
4, 4, 42, 1),
(true, 2100, 'ИВТ, ПРИ', true, 5, 5, 'ок', 'Подписано', 8146, 'Заявка, Акт о внедрении', true, 'нет', 5, 5, 43,
1),
(true, 1100, 'ИВТ, ПРИ', true, 5, 5, 'ок', 'Подписано', 7965, 'Иниц', true, 'нет', 6, 6, 41, 1),
(true, 1200, 'нет', true, 5, 4, 'Усилить работу', 'Подписано', 8583, Null, true, 'Газ Энерго Комплект (ГЭК)', 7,
7, 44, 1),
(true, 1700, 'Нет', true, 5, 5, 'Критически низкий уровень. Допущен под ответственность руководителя ВКР',
'Подписано', 6647, Null, true, 'нет', 8, 8, 45, 1),
(true, 3300, 'ИВТ, ПРИ или другой вуз', true, 5, 5, 'Усилить работу', 'Подписано', 6741,
'Заявка, Акт о внедрении', true, 'нет', 9, 9, 46, 1),
(true, 1200, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 7645, 'Заявка, Акт о внедрении', true, 'нет', 10, 10, 40, 1),
(true, 3700, 'нет', true, 3, 3, 'Критически низкий уровень. Допущен под ответственность руководителя ВКР', '1',
3093, 'Иниц', true, 'нет', 11, 11, 45, 1),
(true, 1900, 'Нет', true, 5, 5, 'ок', 'Подписано', 8175, 'Заявка, Акт о внедрении', true, 'нет', 12, 12, 42, 1),
(true, 3200, 'ИВТ', true, 5, 5, 'Усилить работу', 'Подписано', 7805, 'Акт', true, 'нет', 13, 13, 46, 1),
(true, 1200, 'ИВТ, ПРИ', true, 4, 4, 'ок', 'рек', 7590, 'Иниц', true, 'нет', 14, 14, 45, 1),
(true, 3900, 'нет', true, 5, 5, 'Усилить работу', 'Подписано', 6463, 'Заявка, Акт о внедрении', true, 'нет', 15,
15, 40, 1),
(true, 2200, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 7441, Null, true, 'ООО "ЦИРОБЗ"', 16, 16, 44, 1),
(true, 1130, 'Нет', true, 5, 4, 'Критически низкий уровень. Допущен под ответственность руководителя ВКР',
'Подписано', 7319, 'Заявка, Акт о внедрении', true, 'нет', 17, 17, 40, 1),
(true, 2400, 'ИВТ', true, 4, 5, 'ок', 'Подписано', 6436, Null, true, 'нет', 18, 18, 45, 1),
(true, 1600, 'ИВТ', true, 5, 5, 'Усилить работу', 'Подписано', 6227, 'Исслед', true, 'АО "БЭМЗ"', 19, 19, 47, 1),
(true, 1400, 'Нет', true, 5, 5, 'ок', 'Подписано', 8935, 'Иниц', true, 'ООО "ЦИРОБЗ"', 20, 20, 44, 2),
(true, 2900, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 7971, 'Заявка, Акт о внедрении', true, 'нет', 21, 21, 40, 2),
(true, 2600, 'ИВТ, ПРИ', true, 3, 3, 'ок', 'Подписано', 7284, Null, true, 'нет', 22, 22, 48, 2),
(true, 3100, 'ИВТ, ПРИ', true, 5, 5, 'Усилить работу', 'Подписано', 4966, 'Заявка, Акт о внедрении', true, 'нет',
23, 23, 45, 2),
(true, 3110, 'Нет', true, 5, 5, 'ок', 'Подписано', 7174, Null, true, 'нет', 24, 24, 40, 2),
(true, 3800, 'Нет', true, 5, 5, 'ок', 'Подписано', 7233, Null, true, 'нет', 25, 25, 45, 2),
(true, 3300, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 7133, Null, true, 'нет', 26, 26, 43, 2),
(true, 3130, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 8083, 'Заявка, Акт о внедрении', true, 'нет', 27, 27, 49, 2),
(true, 3400, 'Нет', true, 5, 4, 'Критически низкий уровень. Допущен под ответственность руководителя ВКР',
'Подписано', 7968, 'Иниц', true, 'нет', 28, 28, 50, 2),
(true, 3120, 'ИВТ или ПРИ', true, 5, 5, 'ок', 'Подписано', 7940, 'Исслед', true, 'ООО "ЦИРОБЗ"', 29, 29, 40, 2),
(true, 2800, 'Нет (возможно)', true, 4, 4, 'Усилить работу', 'рек', 6775, 'Заявка, Акт о внедрении', true, 'нет',
30, 30, 40, 2),
(true, 2100, 'Нет (возможно)', true, 5, 5, 'ок', 'Подписано', 7637, 'Заявка, Акт о внедрении', true, 'нет', 31,
31, 40, 2),
(true, 2700, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 8544, 'Заявка, Акт о внедрении', true, 'нет', 32, 32, 45, 2),
(true, 2130, 'ИВТ, ПРИ', true, 5, 5, 'ок', 'Подписано', 7166, 'Заявка, Акт о внедрении', true, 'нет', 33, 33, 51,
2),
(true, 2110, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 6075, 'Заявка, Акт о внедрении', true, 'нет', 34, 34, 52, 2),
(true, 3100, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 7057, 'Заявка, Акт о внедрении', true, 'нет', 35, 35, 50, 2),
(true, 2120, 'В БГТУ на другой кафедре (38.04.01 Экономика или 27.04.05. Инноватика)', true, 5, 5, 'ок',
'Подписано', 7057, 'Заявка, Акт о внедрении', true, 'нет', 36, 36, 51, 2),
(true, 2500, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 6583, 'Заявка, Акт о внедрении', true, 'нет', 37, 37, 53, 2),
(true, 1300, 'ИВТ, ПРИ', true, 5, 5, 'ок', 'Подписано', 8444, 'Заявка, Акт о внедрении', true, 'нет', 38, 38, 44,
2),
(true, 3600, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 7631, 'Заявка, Акт о внедрении', true, 'нет', 39, 39, 45, 2);

View File

@ -1,55 +0,0 @@
INSERT INTO vkr.user_role (user_id, role_id)
VALUES (1, 3),
(2, 3),
(3, 3),
(4, 3),
(5, 3),
(6, 3),
(7, 3),
(8, 3),
(9, 3),
(10, 3),
(11, 3),
(12, 3),
(13, 3),
(14, 3),
(15, 3),
(16, 3),
(17, 3),
(18, 3),
(19, 3),
(20, 3),
(21, 3),
(22, 3),
(23, 3),
(24, 3),
(25, 3),
(26, 3),
(27, 3),
(28, 3),
(29, 3),
(30, 3),
(31, 3),
(32, 3),
(33, 3),
(34, 3),
(35, 3),
(36, 3),
(37, 3),
(38, 3),
(39, 3),
(40, 1),
(41, 1),
(42, 1),
(43, 1),
(44, 1),
(45, 1),
(46, 1),
(47, 1),
(48, 1),
(49, 1),
(50, 1),
(51, 1),
(52, 1),
(53, 1),
(40, 2);

View File

@ -0,0 +1,13 @@
create table role
(
id bigint primary key,
name text not null unique,
authority text not null unique
);
-- COMMENTS
comment on table role is 'Таблица ролей пользователей';
comment on column role.name is 'Человекочитаемое имя роли';
comment on column role.authority is 'Имя роли в системе';

View File

@ -0,0 +1,22 @@
create table "user"
(
id bigserial primary key,
login text not null unique,
password text not null,
full_name text not null,
email text not null unique,
number_phone text not null unique,
created_at timestamptz not null,
updated_at timestamptz
);
-- COMMENTS
comment on table "user" is 'Таблица пользователей';
comment on column "user".login is 'Логин пользователя';
comment on column "user".password is 'Пароль пользователя';
comment on column "user".full_name is 'Полное имя пользователя в формате Фамилия Имя Отчество';
comment on column "user".email is 'Почта пользователя';
comment on column "user".number_phone is 'Номер телефона пользователя';

View File

@ -0,0 +1,21 @@
create table user_role
(
id bigserial primary key,
user_id bigint not null,
role_id bigint not null
);
-- FOREIGN KEY
alter table user_role
add constraint fk_user_role_user_id
foreign key (user_id) references "user" (id);
alter table user_role
add constraint fk_user_role_role_id
foreign key (role_id) references role (id);
-- COMMENTS
comment on table user_role is 'Таблица связи пользователей и ролей';
comment on column user_role.user_id is 'Идентификатор пользователя';
comment on column user_role.role_id is 'Идентификатор роли';

View File

@ -0,0 +1,11 @@
create table diploma_topic
(
id bigserial primary key,
name text not null
);
-- COMMENTS
comment on table diploma_topic is 'Таблица тем дипломных работ';
comment on column diploma_topic.name is 'Название темы дипломной работы';

View File

@ -0,0 +1,22 @@
create table "group"
(
id bigserial primary key,
name text not null unique,
curator_user_id bigint,
created_at timestamptz not null,
updated_at timestamptz
);
-- FOREIGN KEY
alter table "group"
add constraint fk_group_curator_user_id
foreign key (curator_user_id) references "user" (id)
on delete set null on update cascade;
-- COMMENTS
comment on table "group" is 'Таблица групп студентов';
comment on column "group".name is 'Название группы';
comment on column "group".curator_user_id is 'Идентификатор куратора группы';

View File

@ -0,0 +1,65 @@
create table student
(
id bigserial primary key,
user_id bigint not null,
diploma_topic_id bigint not null,
mentor_user_id bigint not null,
group_id bigint not null,
form boolean,
protection_day int,
protection_order int,
magistracy text,
digital_format_present boolean,
mark_comment int,
mark_practice int,
predefence_comment text,
normal_control text,
anti_plagiarism int,
note text,
record_book_returned boolean,
work text,
created_at timestamptz not null,
updated_at timestamptz
);
-- FOREIGN KEY
alter table student
add constraint fk_student_user_id
foreign key (user_id) references "user" (id)
on delete cascade on update cascade;
alter table student
add constraint fk_student_diploma_topic_id
foreign key (diploma_topic_id) references diploma_topic (id)
on delete set null on update cascade;
alter table student
add constraint fk_student_mentor_user_id
foreign key (mentor_user_id) references "user" (id)
on delete set null on update cascade;
alter table student
add constraint fk_student_group_id
foreign key (group_id) references "group" (id)
on delete set null on update cascade;
-- COMMENTS
comment on table student is 'Таблица студентов';
comment on column student.user_id is 'Идентификатор пользователя';
comment on column student.diploma_topic_id is 'Идентификатор темы дипломной работы';
comment on column student.mentor_user_id is 'Идентификатор научного руководителя';
comment on column student.group_id is 'Идентификатор группы';
comment on column student.form is 'Форма обучения';
comment on column student.protection_day is 'День защиты';
comment on column student.protection_order is 'Порядок защиты';
comment on column student.magistracy is 'Магистратура';
comment on column student.digital_format_present is 'Предоставлен в электронном виде';
comment on column student.mark_comment is 'Комментарий к оценке';
comment on column student.mark_practice is 'Оценка практики';
comment on column student.predefence_comment is 'Комментарий к защите';
comment on column student.normal_control is 'Обычный контроль';
comment on column student.anti_plagiarism is 'Антиплагиат';
comment on column student.note is 'Примечание';
comment on column student.record_book_returned is 'Ведомость возвращена';
comment on column student.work is 'Работа';

View File

@ -0,0 +1,6 @@
insert into role (id, name, authority)
values (1, 'Преподаватель', 'ROLE_TEACHER'),
(2, 'Студент', 'ROLE_STUDENT'),
(3, 'Член комиссии ГЭК', 'ROLE_COMMISSION_MEMBER'),
(4, 'Администратор', 'ROLE_ADMINISTRATOR'),
(5, 'Секретарь', 'ROLE_SECRETARY');

View File

@ -0,0 +1,8 @@
insert into "user" (id, login, password, full_name, email, number_phone, created_at)
values (1, 'admin', '{noop}admin', 'Администратор', 'admin@tdms.tu-byransk.ru', '', now());
insert into user_role (id, user_id, role_id)
values (1, 1, 4);
select setval('user_id_seq', (select max(id) from "user"));
select setval('user_role_id_seq', (select max(id) from user_role));

View File

@ -0,0 +1,7 @@
create table defence
(
id bigserial primary key,
defence_date timestamptz,
created_at timestamptz not null,
updated_at timestamptz
);

View File

@ -1,4 +1,4 @@
INSERT INTO vkr.diploma_topic (name) INSERT INTO diploma_topic (name)
VALUES ('Мобильное приложение для заказа автозапчастей на платформе ABCP'), VALUES ('Мобильное приложение для заказа автозапчастей на платформе ABCP'),
('Подсистема уведомления пользователей для программного комплекса "РискПроф. Учебный центр"'), ('Подсистема уведомления пользователей для программного комплекса "РискПроф. Учебный центр"'),
('Веб-приложение "Таск-менеджер"'), ('Веб-приложение "Таск-менеджер"'),

View File

@ -0,0 +1,3 @@
INSERT INTO "group" (name, principal_user_id)
VALUES ('ИВТ-1', 40),
('ИВТ-2', 40);

View File

@ -0,0 +1,98 @@
do
$$
begin
INSERT INTO student (form,
protection_order,
magistracy,
digital_format_present,
mark_comment,
mark_practice,
predefence_comment,
normal_control,
anti_plagiarism,
note,
record_book_returned,
work,
user_id,
diploma_topic_id,
mentor_user_id,
group_id,
created_at)
VALUES (true, 1100, 'ПРИ, ИВТ или другой вуз', true, 5, 5, 'ок', 'Подписано', 7862, 'Акт о внедрении', true,
'нет', 1, 1, 40, 3, now()),
(true, 1110, 'Да, но не уверен, в БГТУ ли', true, 5, 5, 'ок', 'Подписано', 8196,
'Заявка, Акт о внедрении', true, 'ООО "ЦИРОБЗ"', 2, 2, 40, 3, now()),
(true, 3500, 'Нет', true, 3, 3,
'Критически низкий уровень. Допущен под ответственность руководителя ВКР', 'Подписано', 7141, 'Иниц',
true, 'нет', 3, 3, 41, 3, now()),
(true, 1800, 'Да, но не уверен, в БГТУ ли', true, 4, 5, 'Усилить работу', 'Подписано', 5381, 'Иниц',
true, 'нет', 4, 4, 42, 3, now()),
(true, 2100, 'ИВТ, ПРИ', true, 5, 5, 'ок', 'Подписано', 8146, 'Заявка, Акт о внедрении', true, 'нет', 5,
5, 43, 3, now()),
(true, 1100, 'ИВТ, ПРИ', true, 5, 5, 'ок', 'Подписано', 7965, 'Иниц', true, 'нет', 6, 6, 41, 3, now()),
(true, 1200, 'нет', true, 5, 4, 'Усилить работу', 'Подписано', 8583, Null, true,
'Газ Энерго Комплект (ГЭК)', 7, 7, 44, 3, now()),
(true, 1700, 'Нет', true, 5, 5,
'Критически низкий уровень. Допущен под ответственность руководителя ВКР', 'Подписано', 6647, Null,
true, 'нет', 8, 8, 45, 3, now()),
(true, 3300, 'ИВТ, ПРИ или другой вуз', true, 5, 5, 'Усилить работу', 'Подписано', 6741,
'Заявка, Акт о внедрении', true, 'нет', 9, 9, 46, 3, now()),
(true, 1200, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 7645, 'Заявка, Акт о внедрении', true, 'нет', 10, 10,
40, 3, now()),
(true, 3700, 'нет', true, 3, 3,
'Критически низкий уровень. Допущен под ответственность руководителя ВКР', '1', 3093, 'Иниц', true,
'нет', 11, 11, 45, 3, now()),
(true, 1900, 'Нет', true, 5, 5, 'ок', 'Подписано', 8175, 'Заявка, Акт о внедрении', true, 'нет', 12, 12,
42, 3, now()),
(true, 3200, 'ИВТ', true, 5, 5, 'Усилить работу', 'Подписано', 7805, 'Акт', true, 'нет', 13, 13, 46, 3,
now()),
(true, 1200, 'ИВТ, ПРИ', true, 4, 4, 'ок', 'рек', 7590, 'Иниц', true, 'нет', 14, 14, 45, 3, now()),
(true, 3900, 'нет', true, 5, 5, 'Усилить работу', 'Подписано', 6463, 'Заявка, Акт о внедрении', true,
'нет', 15, 15, 40, 3, now()),
(true, 2200, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 7441, Null, true, 'ООО "ЦИРОБЗ"', 16, 16, 44, 3,
now()),
(true, 1130, 'Нет', true, 5, 4,
'Критически низкий уровень. Допущен под ответственность руководителя ВКР', 'Подписано', 7319,
'Заявка, Акт о внедрении', true, 'нет', 17, 17, 40, 3, now()),
(true, 2400, 'ИВТ', true, 4, 5, 'ок', 'Подписано', 6436, Null, true, 'нет', 18, 18, 45, 3, now()),
(true, 1600, 'ИВТ', true, 5, 5, 'Усилить работу', 'Подписано', 6227, 'Исслед', true, 'АО "БЭМЗ"', 19, 19,
47, 3, now()),
(true, 1400, 'Нет', true, 5, 5, 'ок', 'Подписано', 8935, 'Иниц', true, 'ООО "ЦИРОБЗ"', 20, 20, 44, 4,
now()),
(true, 2900, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 7971, 'Заявка, Акт о внедрении', true, 'нет', 21, 21,
40, 4, now()),
(true, 2600, 'ИВТ, ПРИ', true, 3, 3, 'ок', 'Подписано', 7284, Null, true, 'нет', 22, 22, 48, 4, now()),
(true, 3100, 'ИВТ, ПРИ', true, 5, 5, 'Усилить работу', 'Подписано', 4966, 'Заявка, Акт о внедрении',
true, 'нет', 23, 23, 45, 4, now()),
(true, 3110, 'Нет', true, 5, 5, 'ок', 'Подписано', 7174, Null, true, 'нет', 24, 24, 40, 4, now()),
(true, 3800, 'Нет', true, 5, 5, 'ок', 'Подписано', 7233, Null, true, 'нет', 25, 25, 45, 4, now()),
(true, 3300, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 7133, Null, true, 'нет', 26, 26, 43, 4, now()),
(true, 3130, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 8083, 'Заявка, Акт о внедрении', true, 'нет', 27, 27,
49, 4, now()),
(true, 3400, 'Нет', true, 5, 4,
'Критически низкий уровень. Допущен под ответственность руководителя ВКР', 'Подписано', 7968, 'Иниц',
true, 'нет', 28, 28, 50, 4, now()),
(true, 3120, 'ИВТ или ПРИ', true, 5, 5, 'ок', 'Подписано', 7940, 'Исслед', true, 'ООО "ЦИРОБЗ"', 29, 29,
40, 4, now()),
(true, 2800, 'Нет (возможно)', true, 4, 4, 'Усилить работу', 'рек', 6775, 'Заявка, Акт о внедрении',
true, 'нет', 30, 30, 40, 4, now()),
(true, 2100, 'Нет (возможно)', true, 5, 5, 'ок', 'Подписано', 7637, 'Заявка, Акт о внедрении', true,
'нет', 31, 31, 40, 4, now()),
(true, 2700, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 8544, 'Заявка, Акт о внедрении', true, 'нет', 32, 32,
45, 4, now()),
(true, 2130, 'ИВТ, ПРИ', true, 5, 5, 'ок', 'Подписано', 7166, 'Заявка, Акт о внедрении', true, 'нет', 33,
33, 51, 4, now()),
(true, 2110, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 6075, 'Заявка, Акт о внедрении', true, 'нет', 34, 34,
52, 4, now()),
(true, 3100, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 7057, 'Заявка, Акт о внедрении', true, 'нет', 35, 35,
50, 4, now()),
(true, 2120, 'В БГТУ на другой кафедре (38.04.01 Экономика или 27.04.05. Инноватика)', true, 5, 5, 'ок',
'Подписано', 7057, 'Заявка, Акт о внедрении', true, 'нет', 36, 36, 51, 4, now()),
(true, 2500, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 6583, 'Заявка, Акт о внедрении', true, 'нет', 37, 37,
53, 4, now()),
(true, 1300, 'ИВТ, ПРИ', true, 5, 5, 'ок', 'Подписано', 8444, 'Заявка, Акт о внедрении', true, 'нет', 38,
38, 44, 4, now()),
(true, 3600, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 7631, 'Заявка, Акт о внедрении', true, 'нет', 39, 39,
45, 4, now());
end
$$;

View File

@ -0,0 +1,82 @@
INSERT INTO "user" (login, password, full_name, mail, number_phone, created_at)
VALUES ('akulenko_mikhail', '{noop}1', 'Акуленко Михаил Вячеславович', 'akulenko.mikhail@example.com',
'+79110000001', NOW()),
('borovikov_artem', '{noop}1', 'Боровиков Артём Викторович', 'borovikov.artem@example.com', '+79110000002',
NOW()),
('bykonya_alexey', '{noop}1', 'Быконя Алексей Николаевич', 'bykonya.alexey@example.com', '+79110000003',
NOW()),
('ermakov_alexander', '{noop}1', 'Ермаков Александр Сергеевич', 'ermakov.alexander@example.com',
'+79110000004', NOW()),
('zgursky_evgeny', '{noop}1', 'Згурский Евгений Олегович', 'zgursky.evgeny@example.com', '+79110000005',
NOW()),
('ibishov_tural', '{noop}1', 'Ибишов Турал Садай оглы', 'ibishov.tural@example.com', '+79110000006', NOW()),
('ignatenko_vladimir', '{noop}1', 'Игнатенко Владимир Алексеевич', 'ignatenko.vladimir@example.com',
'+79110000007', NOW()),
('lazukin_danila', '{noop}1', 'Лазукин Данила Дмитриевич', 'lazukin.danila@example.com', '+79110000008',
NOW()),
('mitiaev_danila', '{noop}1', 'Митяев Данила Алексеевич', 'mitiaev.danila@example.com', '+79110000009',
NOW()),
('neshkov_daniil', '{noop}1', 'Нешков Даниил Владимирович', 'neshkov.daniil@example.com', '+79110000010',
NOW()),
('petrov_pavel', '{noop}1', 'Петров Павел Сергеевич', 'petrov.pavel@example.com', '+79110000011', NOW()),
('sazonov_andrey', '{noop}1', 'Сазонов Андрей Андреевич', 'sazonov.andrey@example.com', '+79110000012',
NOW()),
('solokhin_maxim', '{noop}1', 'Солохин Максим Николаевич', 'solokhin.maxim@example.com', '+79110000013',
NOW()),
('sochinsky_artem', '{noop}1', 'Сочинский Артем Александрович', 'sochinsky.artem@example.com',
'+79110000014', NOW()),
('trisvyatsky_kirill', '{noop}1', 'Трисвятский Кирилл Андреевич', 'trisvyatsky.kirill@example.com',
'+79110000015', NOW()),
('turov_alexander', '{noop}1', 'Туров Александр Сергеевич', 'turov.alexander@example.com', '+79110000016',
NOW()),
('shevtsova_alexandra', '{noop}1', 'Шевцова Александра Валерьевна', 'shevtsova.alexandra@example.com',
'+79110000017', NOW()),
('kibalyuk_artem', '{noop}1', 'Кибалюк Артем Сергеевич', 'kibalyuk.artem@example.com', '+79110000018', NOW()),
('shulindin_artem', '{noop}1', 'Шулындин Артём Андреевич', 'shulindin.artem@example.com', '+79110000019',
NOW()),
('belyaev_egor', '{noop}1', 'Беляев Егор Андреевич', 'belyaev.egor@example.com', '+79110000020', NOW()),
('berezhnoy_igor', '{noop}1', 'Бережной Игорь Александрович', 'berezhnoy.igor@example.com', '+79110000021',
NOW()),
('bogun_pavel', '{noop}1', 'Богун Павел Сергеевич', 'bogun.pavel@example.com', '+79110000022', NOW()),
('vaseykin_nikita', '{noop}1', 'Васейкин Никита Павлович', 'vaseykin.nikita@example.com', '+79110000023',
NOW()),
('gomonov_nikita', '{noop}1', 'Гомонов Никита Алексеевич', 'gomonov.nikita@example.com', '+79110000024',
NOW()),
('druyan_oleg', '{noop}1', 'Друян Олег Викторович', 'druyan.oleg@example.com', '+79110000025', NOW()),
('ivanov_kirill', '{noop}1', 'Иванов Кирилл Эдуардович', 'ivanov.kirill@example.com', '+79110000026', NOW()),
('ivanova_veronika', '{noop}1', 'Иванова Вероника Евгеньевна', 'ivanova.veronika@example.com',
'+79110000027', NOW()),
('izotov_ivan', '{noop}1', 'Изотов Иван Алексеевич', 'izotov.ivan@example.com', '+79110000028', NOW()),
('isakov_zahar', '{noop}1', 'Исаков Захар Александрович', 'isakov.zahar@example.com', '+79110000029', NOW()),
('iskritsky_daniil', '{noop}1', 'Искрицкий Даниил Павлович', 'iskritsky.daniil@example.com', '+79110000030',
NOW()),
('linko_daria', '{noop}1', 'Линько Дарья Андреевна', 'linko.daria@example.com', '+79110000031', NOW()),
('logutov_kirill', '{noop}1', 'Логутов Кирилл Александрович', 'logutov.kirill@example.com', '+79110000032',
NOW()),
('nekrassov_sergey', '{noop}1', 'Некрасов Сергей Игоревич', 'nekrassov.sergey@example.com', '+79110000033',
NOW()),
('sinyagin_ilya', '{noop}1', 'Синягин Илья Александрович', 'sinyagin.ilya@example.com', '+79110000034',
NOW()),
('sopriko_daniil', '{noop}1', 'Соприко Даниил Сергеевич', 'sopriko.daniil@example.com', '+79110000035',
NOW()),
('turovsky_ivan', '{noop}1', 'Туровский Иван Алексеевич', 'turovsky.ivan@example.com', '+79110000036', NOW()),
('frantsev_sergey', '{noop}1', 'Францев Сергей Дмитриевич', 'frantsev.sergey@example.com', '+79110000037',
NOW()),
('chepurnoy_maxim', '{noop}1', 'Чепурной Максим Романович', 'chepurnoy.maxim@example.com', '+79110000038',
NOW()),
('schemelinin_dmitry', '{noop}1', 'Щемелинин Дмитрий Михайлович', 'schemelinin.dmitry@example.com',
'+79110000039', NOW()),
('bulatitsky_d_i_1', '{noop}1', 'Булатицкий Д. И.', 'bulatitsky.d.i.1@example.com', '+79110000040', NOW()),
('kopeliovich_d_i_1', '{noop}1', 'Копелиович Д. И.', 'kopeliovich.d.i.1@example.com', '+79110000041', NOW()),
('dergachev_k_v', '{noop}1', 'Дергачев К. В.', 'dergachev.k.v@example.com', '+79110000042', NOW()),
('trubakov_e_o', '{noop}1', 'Трубаков Е. О.', 'trubakov.e.o@example.com', '+79110000043', NOW()),
('radchenko_a_o', '{noop}1', 'Радченко А. О.', 'radchenko.a.o@example.com', '+79110000044', NOW()),
('zimin_s_n_1', '{noop}1', 'Зимин С. Н.', 'zimin.s.n.1@example.com', '+79110000045', NOW()),
('koptenok_e_v', '{noop}1', 'Коптенок Е. В.', 'koptenok.e.v@example.com', '+79110000046', NOW()),
('mikhaleva_o_a', '{noop}1', 'Михалева О. А.', 'mikhaleva.o.a@example.com', '+79110000047', NOW()),
('gulakov_k_v', '{noop}1', 'Гулаков К. В.', 'gulakov.k.v@example.com', '+79110000048', NOW()),
('titarev_d_v', '{noop}1', 'Титарёв Д. В.', 'titarev.d.v@example.com', '+79110000049', NOW()),
('izrailev_v_ya_1', '{noop}1', 'Израилев В. Я.', 'izrailev.v.ya.1@example.com', '+79110000050', NOW()),
('podvesovsky_a_g_1', '{noop}1', 'Подвесовский А. Г.', 'podvesovsky.a.g.1@example.com', '+79110000051', NOW()),
('trubakov_a_o', '{noop}1', 'Трубаков А. О.', 'trubakov.a.o@example.com', '+79110000059', NOW()),
('lageriev_d_g', '{noop}1', 'Лагерев Д. Г.', 'lageriev.d.g@example.com', '+79110000052', NOW());

View File

@ -0,0 +1,58 @@
do
$$
declare
teacher_id bigint := 1;
student_id bigint := 2;
commission_member_id bigint := 3;
administrator_id bigint := 4;
secretary_id bigint := 5;
begin
INSERT INTO user_role (user_id, role_id)
VALUES (1, student_id),
(2, student_id),
(3, student_id),
(4, student_id),
(5, student_id),
(6, student_id),
(7, student_id),
(8, student_id),
(9, student_id),
(10, student_id),
(11, student_id),
(12, student_id),
(13, student_id),
(14, student_id),
(15, student_id),
(16, student_id),
(17, student_id),
(18, student_id),
(19, student_id),
(20, student_id),
(21, student_id),
(22, student_id),
(23, student_id),
(24, student_id),
(25, student_id),
(26, student_id),
(27, student_id),
(28, student_id),
(29, student_id),
(30, student_id),
(31, student_id),
(32, student_id),
(33, student_id),
(34, student_id),
(35, student_id),
(36, student_id),
(37, teacher_id),
(37, administrator_id),
(38, commission_member_id),
(39, teacher_id),
(40, teacher_id),
(41, teacher_id),
(42, teacher_id),
(43, teacher_id),
(44, secretary_id),
(45, secretary_id);
end
$$;

View File

@ -0,0 +1,22 @@
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>logs/app.log</file>
<append>false</append>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="ru.tubryansk.tdms" level="debug" />
<root level="warn">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</configuration>

View File

@ -1,18 +0,0 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}

12
web/babel.config.json Normal file
View File

@ -0,0 +1,12 @@
{
"presets": [
["@babel/preset-env", {"modules": false}],
"@babel/preset-react",
"@babel/preset-typescript"
],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties"],
["@babel/plugin-transform-typescript"]
]
}

7294
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,35 +1,43 @@
{ {
"name": "web", "name": "web",
"version": "1.0.0",
"private": true, "private": true,
"version": "0.0.1",
"type": "module",
"scripts": { "scripts": {
"dev": "vite", "build": "webpack --mode production",
"build": "tsc -b && vite build", "dev": "webpack-dev-server --mode development"
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@react-buddy/ide-toolbox": "^2.4.0", "@fortawesome/fontawesome-svg-core": "^6.6.0",
"@fortawesome/free-brands-svg-icons": "^6.6.0",
"@fortawesome/free-regular-svg-icons": "^6.6.0",
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/react-fontawesome": "^0.2.2",
"@types/lodash": "^4.17.15",
"axios": "^1.7.7", "axios": "^1.7.7",
"bootstrap": "^5.3.3", "bootstrap": "^5.3.3",
"lodash": "^4.17.21",
"mobx": "^6.13.1", "mobx": "^6.13.1",
"mobx-react": "^9.1.1", "mobx-react": "^9.1.1",
"mobx-state-router": "^6.0.1", "mobx-state-router": "^6.0.1",
"react": "^18.3.1", "react": "^18.2.0",
"react-bootstrap": "^2.10.4", "react-bootstrap": "^2.10.4",
"react-dom": "^18.3.1" "react-dom": "^18.2.0",
"uuid": "^11.0.5"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.3.3", "@babel/plugin-proposal-decorators": "^7.25.7",
"@types/react-dom": "^18.3.0", "@babel/preset-env": "^7.25.8",
"@typescript-eslint/eslint-plugin": "^7.15.0", "@babel/preset-react": "^7.25.7",
"@typescript-eslint/parser": "^7.15.0", "@types/react": "^18.2.0",
"@vitejs/plugin-react": "^4.3.1", "@types/react-dom": "^18.2.0",
"eslint": "^8.57.0", "@types/webpack": "^5.28.5",
"eslint-plugin-react-hooks": "^4.6.2", "css-loader": "^7.1.2",
"eslint-plugin-react-refresh": "^0.4.7", "html-webpack-plugin": "^5.6.2",
"typescript": "^5.2.2", "style-loader": "^4.0.0",
"vite": "^5.3.4" "ts-loader": "^9.5.1",
"typescript": "^5.6.3",
"webpack": "^5.95.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.1.0"
} }
} }

View File

@ -1,20 +1,17 @@
import React from "react";
import ReactDOM from 'react-dom/client' import ReactDOM from 'react-dom/client'
import './index.css' import './index.css'
import 'bootstrap/dist/css/bootstrap.min.css'; import 'bootstrap/dist/css/bootstrap.min.css';
import {RouterContext, RouterView} from "mobx-state-router"; import {RouterContext, RouterView} from "mobx-state-router";
import {initApp} from "./utils/init.ts"; import {initApp} from "./utils/init";
import {MyRouterStore} from "./store/MyRouterStore.ts"; import {RootStoreContext} from './store/RootStoreContext';
import { RootStoreContext } from "./store/RootStore.tsx"; import {viewMap} from "./router/viewMap";
const rootStore = initApp(); const rootStore = initApp();
ReactDOM.createRoot(document.getElementById('root')!).render( ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode> <RootStoreContext.Provider value={rootStore}>
<RootStoreContext.Provider value={rootStore}> <RouterContext.Provider value={rootStore.routerStore}>
<RouterContext.Provider value={rootStore.routerStore}> <RouterView viewMap={viewMap}/>
<RouterView viewMap={MyRouterStore.makeViewMap()} /> </RouterContext.Provider>
</RouterContext.Provider> </RootStoreContext.Provider>
</RootStoreContext.Provider>
</React.StrictMode>
); );

View File

@ -0,0 +1,89 @@
import {ComponentContext} from "../utils/ComponentContext";
import {observer} from "mobx-react";
import {Notification, NotificationType} from "../store/NotificationStore";
import {Card, CardBody, CardHeader, CardText, CardTitle, Col, Row} from "react-bootstrap";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {action, makeObservable} from "mobx";
@observer
export class NotificationContainer extends ComponentContext {
forEachNotificationRender(notifications: Notification[], type: NotificationType) {
return notifications.map(notification => (
<NotificationPopup key={notification.uuid} notification={notification} type={type}/>
));
}
render() {
return <div style={{position: 'fixed', left: '50%', transform: 'translateX(-50%)', zIndex: 1000}}>
{this.forEachNotificationRender(this.notificationStore.errors, NotificationType.ERROR)}
{this.forEachNotificationRender(this.notificationStore.successes, NotificationType.SUCCESS)}
{this.forEachNotificationRender(this.notificationStore.warnings, NotificationType.WARNING)}
{this.forEachNotificationRender(this.notificationStore.infos, NotificationType.INFO)}
</div>
}
}
@observer
class NotificationPopup extends ComponentContext<{ notification: Notification, type: NotificationType }> {
constructor(props: { notification: Notification, type: NotificationType }) {
super(props);
makeObservable(this);
}
@action.bound
close() {
this.notificationStore.close(this.props.notification.uuid);
}
get cardClassName() {
switch (this.props.type) {
case NotificationType.ERROR:
return 'text-bg-danger';
case NotificationType.WARNING:
return 'text-bg-warning';
case NotificationType.INFO:
return 'text-bg-info';
case NotificationType.SUCCESS:
return 'text-bg-success';
}
}
render() {
const hasTitle = !!this.props.notification.title && this.props.notification.title.length > 0;
const closeIcon = <FontAwesomeIcon icon={'close'} onClick={this.close}/>;
return <Card className={`position-relative mt-3 opacity-75 ${this.cardClassName}`}>
{
hasTitle &&
<CardHeader>
<CardTitle>
<Row>
<Col sm={11}>
{this.props.notification.title}
</Col>
<Col className={'text-end'}>
{closeIcon}
</Col>
</Row>
</CardTitle>
</CardHeader>
}
<CardBody>
<CardText>
<Row>
<Col sm={11}>
{this.props.notification.message}
</Col>
{
!hasTitle &&
<Col className={'text-end'}>
{closeIcon}
</Col>
}
</Row>
</CardText>
</CardBody>
</Card>
}
}

View File

@ -1,19 +0,0 @@
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/>
</>
}
}

View File

@ -1,21 +0,0 @@
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 &copy;</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;

View File

@ -1,49 +0,0 @@
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;

View File

@ -1,7 +0,0 @@
import {DefaultPage} from "./DefaultPage.tsx";
export default class Root extends DefaultPage {
get page() {
return <h1>Home</h1>
}
}

View File

@ -1,7 +0,0 @@
import {DefaultPage} from "./DefaultPage.tsx";
export default class UserProfile extends DefaultPage {
get page() {
return <h1>User Profile</h1>
}
}

View File

@ -0,0 +1,147 @@
import {ComponentContext} from "../../utils/ComponentContext";
import {TableDescriptor} from "../../utils/tables";
import {observer} from "mobx-react";
import {action, makeObservable} from "mobx";
import {FormSelect, Pagination, Table} from "react-bootstrap";
export interface DataTableProps<T> {
tableDescriptor: TableDescriptor<T>;
}
@observer
export class DataTable<T> extends ComponentContext<DataTableProps<T>> {
constructor(props: DataTableProps<T>) {
super(props);
makeObservable(this);
}
header() {
return <tr>
{this.props.tableDescriptor.columns.map(column => <th className={'text-center'}
key={column.key}>{column.title}</th>)}
</tr>
}
body() {
const firstColumnKey = this.props.tableDescriptor.columns[0].key;
return this.props.tableDescriptor.data.map(row => {
const rowAny = row as any;
return <tr key={rowAny[firstColumnKey]}>
{
this.props.tableDescriptor.columns.map(column => {
return <td
className={'text-center'}
key={column.key}>
{column.format(rowAny[column.key])}
</td>
})
}
</tr>
});
}
isFirstPage() {
if (typeof this.props.tableDescriptor.page === 'undefined') {
return true;
}
return this.props.tableDescriptor.page === 0;
}
isLastPage() {
if (typeof this.props.tableDescriptor.page === 'undefined' || typeof this.props.tableDescriptor.pageSize === 'undefined') {
return true;
}
return this.props.tableDescriptor.page === (this.props.tableDescriptor.data.length / this.props.tableDescriptor.pageSize);
}
@action.bound
goFirstPage() {
if (typeof this.props.tableDescriptor.page === 'undefined') {
return;
}
this.props.tableDescriptor.page = 0;
}
@action.bound
goLastPage() {
if (typeof this.props.tableDescriptor.page === 'undefined' || typeof this.props.tableDescriptor.pageSize === 'undefined') {
return;
}
this.props.tableDescriptor.page = this.props.tableDescriptor.data.length / this.props.tableDescriptor.pageSize;
}
@action.bound
goNextPage() {
if (typeof this.props.tableDescriptor.page === 'undefined' || typeof this.props.tableDescriptor.pageSize === 'undefined') {
return;
}
this.props.tableDescriptor.page++;
}
@action.bound
goPrevPage() {
if (typeof this.props.tableDescriptor.page === 'undefined') {
return;
}
this.props.tableDescriptor.page--;
}
@action.bound
changePageSize(e: any) {
this.props.tableDescriptor.pageSize = parseInt(e.target.value);
}
footer() {
const table = this.props.tableDescriptor;
if (typeof table.page === 'undefined' || typeof table.pageSize === 'undefined') {
return null;
}
return <tr className={'text-center'}>
<td colSpan={table.columns.length}>
<div className={'d-flex justify-content-between'}>
<div/>
<Pagination className={'mb-0'}>
<Pagination.First onClick={this.goFirstPage} disabled={this.isFirstPage()}/>
<Pagination.Ellipsis disabled={this.isFirstPage()}/>
<Pagination.Prev onClick={this.goPrevPage} disabled={this.isFirstPage()}/>
<Pagination.Item active>{this.props.tableDescriptor.page}</Pagination.Item>
<Pagination.Next onClick={this.goNextPage} disabled={!this.isLastPage()}/>
<Pagination.Ellipsis disabled={!this.isLastPage()}/>
<Pagination.Last onClick={this.goLastPage} disabled={!this.isLastPage()}/>
</Pagination>
<FormSelect className={'w-auto'} onChange={this.changePageSize}>
<option>10</option>
<option>20</option>
<option>50</option>
<option>100</option>
</FormSelect>
</div>
</td>
</tr>
}
render() {
const table = this.props.tableDescriptor;
return <Table hover striped>
<thead>
{this.header()}
</thead>
<tbody>
{this.body()}
</tbody>
{
table.pageable &&
<tfoot>
{this.footer()}
</tfoot>
}
</Table>
}
}

View File

@ -0,0 +1,3 @@
.l-no-bg label::after {
background-color: rgba(0, 0, 0, 0) !important;
}

View File

@ -0,0 +1,116 @@
import React from "react";
import {ReactiveValue} from "../../../utils/reactive/reactiveValue";
import {observer} from "mobx-react";
import {action, makeObservable, observable} from "mobx";
import {Button, ButtonGroup, FloatingLabel, FormControl, FormText, ToggleButton} from "react-bootstrap";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import './ReactiveControls.css';
export interface ReactiveInputProps<T> {
value: ReactiveValue<T>;
label?: string;
disabled?: boolean;
className?: string;
}
@observer
export class StringInput extends React.Component<ReactiveInputProps<string>> {
constructor(props: any) {
super(props);
makeObservable(this);
if (this.props.value.value === undefined) {
this.props.value.setAuto('');
}
this.props.value.setField(this.props.label);
}
@action.bound
onChange(event: React.ChangeEvent<HTMLInputElement>) {
this.props.value.set(event.currentTarget.value);
}
render() {
return <div className={'mb-1 l-no-bg'}>
{/*todo: disable background-color for label*/}
<FloatingLabel label={this.props.label} className={`${this.props.className} mt-0 mb-0`}>
<FormControl type='text' placeholder={this.props.label} disabled={this.props.disabled}
onChange={this.onChange} value={this.props.value.value}
className={`${this.props.value.invalid ? 'bg-danger' : this.props.value.touched ? 'bg-success' : ''} bg-opacity-10`}/>
</FloatingLabel>
<FormText children={this.props.value.firstError} className={`text-danger mt-0 mb-0 d-block`}/>
</div>
}
}
@observer
export class PasswordInput extends React.Component<ReactiveInputProps<string>> {
@observable showPassword = false;
constructor(props: any) {
super(props);
makeObservable(this);
if (this.props.value.value === undefined) {
this.props.value.setAuto('');
}
this.props.value.setField(this.props.label);
}
@action.bound
onChange(event: React.ChangeEvent<HTMLInputElement>) {
this.props.value.set(event.currentTarget.value);
}
@action.bound
toggleShowPassword() {
this.showPassword = !this.showPassword;
}
render() {
return <div className={'mb-1 l-no-bg'}>
<div className={'d-flex justify-content-between align-items-center'}>
<FloatingLabel label={this.props.label} className={`${this.props.className} w-100`}>
<FormControl type={`${this.showPassword ? 'text' : 'password'}`} placeholder={this.props.label}
disabled={this.props.disabled}
className={`${this.props.value.invalid ? 'bg-danger' : this.props.value.touched ? 'bg-success' : ''} bg-opacity-10`}
onChange={this.onChange} value={this.props.value.value}/>
</FloatingLabel>
<Button onClick={this.toggleShowPassword} variant={"outline-secondary"}>
<FontAwesomeIcon icon={this.showPassword ? 'eye-slash' : 'eye'}/>
</Button>
</div>
<FormText children={this.props.value.firstError} className={'text-danger d-block mt-0 mb-0'}/>
</div>
}
}
@observer
export class SelectButtonInput extends React.Component<ReactiveInputProps<string>> {
constructor(props: any) {
super(props);
makeObservable(this);
if (this.props.value.value === undefined) {
this.props.value.setAuto('');
}
this.props.value.setField(this.props.label);
}
@action.bound
onChange(event: React.ChangeEvent<HTMLInputElement>) {
this.props.value.set(event.currentTarget.value);
}
render() {
return <>
<ButtonGroup className={'d-block l-no-bg'}>
<ToggleButton key={'admin'} value={'admin'} id={`radio-admin`} type="radio"
variant={'outline-primary'} children={'Администратор'}
checked={this.props.value.value === 'admin'} onChange={this.onChange}/>
<ToggleButton key={'student'} id={`radio-student`} type="radio" value={'student'}
variant={'outline-primary'}
checked={this.props.value.value === 'student'} onChange={this.onChange}
children={'Студент'}/>
</ButtonGroup>
<FormText children={this.props.value.firstError} className={'text-danger d-block'}/>
</>
}
}

View File

@ -0,0 +1,40 @@
import {ReactNode} from "react";
import {Container} from "react-bootstrap";
import Header from "./Header";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {ComponentContext} from "../../utils/ComponentContext";
import {NotificationContainer} from "../NotificationContainer";
import {Footer} from "./Footer";
export abstract class DefaultPage extends ComponentContext {
get page(): ReactNode {
throw new Error('This is not abstract method, ' +
'because mobx cant handle abstract methods. ' +
'Please override this method in child class. ' +
'Do not call it directly.');
}
render() {
const thinking = this.thinkStore.isThinking();
return <>
<Header/>
<Container className={"mt-5 mb-5"}>
{
thinking &&
<div id='fullscreen-loader'>
<FontAwesomeIcon icon='gear' size="4x" spin/>
</div>
}
{
!thinking &&
<>
<NotificationContainer/>
{this.page}
</>
}
</Container>
<Footer/>
</>
}
}

View File

@ -1,5 +1,7 @@
import {DefaultPage} from "./DefaultPage.tsx"; import {observer} from "mobx-react";
import {DefaultPage} from "./DefaultPage";
@observer
export default class Error extends DefaultPage { export default class Error extends DefaultPage {
get page() { get page() {
return <h1>Error</h1> return <h1>Error</h1>

Some files were not shown because too many files have changed in this diff Show More